基本信息
> ASP.NET 3.5与ASP.NET兼容模式下的WCF服务
>将jQuery与this service proxy用于AJAX请求
>自定义IErrorHandler和IServiceBehavior实现来捕获异常并提供Faults,将其序列化为JSON
我正在本地使用Cassini进行测试(我看到一些线程讨论了在本地调试时出现的问题,但在生产环境中工作正常)。
我遇到的问题是,当我的WCF服务抛出异常时,$ .ajax调用的成功处理程序被触发。响应为空,状态文本为“成功”,响应代码为202 / Accepted。
IErrorHandler实现确实被使用,因为我可以通过它来查看创建的FaultMessage。最后发生的是,成功回调会引发错误,因为当期望JSON字符串时,响应文本为空。错误回调从不触发。
提供一点洞察的一件事是从端点行为中删除enableWebScript选项。当我这样做时,发生了两件事情:
>回复不再包裹(即没有{d:“result”},只是“结果”)。
>错误回调被触发,但响应只是来自IIS的400 / Bad Request黄屏死机的HTML,而不是我的序列化故障。
我尝试了十大结果中显示的更多内容,或者来自Google的关于随机组合关键字“jquery ajax asp.net wcf faultcontract json”的更多内容,所以如果你计划在谷歌搜索一个答案,不要打扰。我希望有人在这个问题上遇到过这个问题。
最终我想要实现的是:
>能够在WCF方法中抛出任何类型的异常
>使用FaultContact
>捕获ShipmentServiceErrorHandler中的例外
>将序列化的ShipmentServiceFault(作为JSON)返回给客户端。
>调用错误回调,所以我可以处理项目4。
可能与:
> WCF IErrorHandler Extension not returning specified Fault
更新1
我检查了跟踪System.ServiceModel活动的输出,在调用UpdateCountry方法后的一个时刻,抛出异常,消息是
Server returned an invalid SOAP Fault.
就是这样一个内部的异常引发了串行器期望一个不同的根元素,但是我不能破译出很多其他的东西。
更新2
所以有了更多的烦恼,我有一些工作,虽然不是我认为理想的方式。这是我做的:
>删除< enableWebScript />选项从web.config的端点行为部分。
>从服务方法中删除FaultContract属性。
>实现了WebHttpBehavior的一个子类(称为ShipmentServiceWebHttpBehavior),并覆盖了AddServerErrorHandlers函数来添加ShipmentServiceErrorHandler。
>更改了ShipmentServiceErrorHandlerElement以返回一个ShipmentServiceWebHttpBehavior类型的实例,而不是错误处理程序本身。
>移动< errorHandler />从web.config的服务行为部分到端点行为部分。
这不太理想,因为现在WCF忽略了我想要的服务方法的BodyStyle = WebMessageBodyStyle.WrappedRequest(尽管我现在可以完全省略它)。我也必须更改JS服务代理中的一些代码,因为它正在寻找一个包装({d:…})对象的响应。
以下是所有相关代码(ShipmentServiceFault对象非常自我说明)。
服务
我的服务很简单(截断版本):
[ServiceContract(Namespace = "http://removed")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ShipmentService
{
[OperationContract]
[WebInvoke(Method = "POST",ResponseFormat = Webmessageformat.Json,BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[FaultContract(typeof(ShipmentServiceFault))]
public string UpdateCountry(Country country)
{
var checkName = (country.Name ?? string.Empty).Trim();
if (string.IsNullOrEmpty(checkName))
throw new ShipmentServiceException("Country name cannot be empty.");
// Removed: try updating country in repository (works fine)
return someHtml; // new country information HTML (works fine)
}
}
错误处理
IErrorHandler,IServiceBehavior实现如下:
public class ShipmentServiceErrorHandlerElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ShipmentServiceErrorHandler();
}
public override Type BehaviorType
{
get
{
return typeof(ShipmentServiceErrorHandler);
}
}
}
public class ShipmentServiceErrorHandler : IErrorHandler,IServiceBehavior
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
// We'll handle the error,we don't need it to propagate.
return true;
}
public void ProvideFault(Exception error,System.ServiceModel.Channels.MessageVersion version,ref System.ServiceModel.Channels.Message fault)
{
if (!(error is FaultException))
{
ShipmentServiceFault faultDetail = new ShipmentServiceFault
{
Reason = error.Message,FaultType = error.GetType().Name
};
fault = Message.CreateMessage(version,"",faultDetail,new DataContractJsonSerializer(faultDetail.GetType()));
this.ApplyJsonSettings(ref fault);
this.ApplyHttpResponseSettings(ref fault,System.Net.HttpStatusCode.InternalServerError,faultDetail.Reason);
}
}
#endregion
#region JSON Exception Handling
protected virtual void ApplyJsonSettings(ref Message fault)
{
// Use JSON encoding
var jsonFormatting = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name,jsonFormatting);
}
protected virtual void ApplyHttpResponseSettings(ref Message fault,System.Net.HttpStatusCode statusCode,string statusDescription)
{
var httpResponse = new HttpResponseMessageproperty()
{
StatusCode = statusCode,StatusDescription = statusDescription
};
httpResponse.Headers[HttpResponseHeader.ContentType] = "application/json";
httpResponse.Headers["jsonerror"] = "true";
fault.Properties.Add(HttpResponseMessageProperty.Name,httpResponse);
}
#endregion
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription,System.ServiceModel.ServiceHostBase serviceHostBase,System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
// Do nothing
}
public void ApplydispatchBehavior(ServiceDescription serviceDescription,System.ServiceModel.ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler = new ShipmentServiceErrorHandler();
foreach (ChanneldispatcherBase channeldispatcherBase in serviceHostBase.Channeldispatchers)
{
Channeldispatcher channeldispatcher = channeldispatcherBase as Channeldispatcher;
if (channeldispatcher != null)
{
channeldispatcher.ErrorHandlers.Add(errorHandler);
}
}
}
public void Validate(ServiceDescription serviceDescription,System.ServiceModel.ServiceHostBase serviceHostBase)
{
// Do nothing
}
#endregion
}
JavaScript
调用WCF方法开始于:
function SaveCountry() {
var data = $('#uxCountryEdit :input').serializeBoundControls();
ShipmentServiceProxy.invoke('UpdateCountry',{ country: data },function(html) {
$('#uxCountryGridResponse').html(html);
},onPageError);
}
我之前提到的服务代理需要处理很多事情,但是核心是我们来到这里:
$.ajax({
url: url,data: json,type: "POST",processData: false,contentType: "application/json",timeout: 10000,dataType: "text",// not "json" we'll parse
success: function(response,textStatus,xhr) {
},error: function(xhr,status) {
}
});
组态
我觉得这里的问题可能在这里,但是我已经尝试了每个组合的设置,我可以在网上找到一个例子。
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<behaviors>
<endpointBehaviors>
<behavior name="Removed.ShipmentServiceAspNetAjaxBehavior">
<webHttp />
<enableWebScript />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="Removed.ShipmentServiceServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<errorHandler />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ShipmentService" behaviorConfiguration="Removed.ShipmentServiceServiceBehavior">
<endpoint address=""
behaviorConfiguration="Removed.ShipmentServiceAspNetAjaxBehavior"
binding="webHttpBinding"
contract="ShipmentService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<add name="errorHandler" type="Removed.Services.ShipmentServiceErrorHandlerElement,Removed,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
笔记
我注意到这个问题正在获得一些喜爱。我找到了解决这个问题的办法,希望在找到一段时间的时候提供一个答案。敬请关注!