【问题标题】:Returning Error Details from AJAX-Enabled WCF Service从启用 AJAX 的 WCF 服务返回错误详细信息
【发布时间】:2010-11-19 08:41:21
【问题描述】:

短版:当在启用 AJAX 的 WCF 服务中引发异常时,是否有建议的方式向客户端返回错误详细信息(除了打开大门和发回所有异常详细信息)?

加长版:

我有一个相对简单的启用 AJAX 的 WCF 服务,我使用默认服务代理从客户端调用该服务。我在下面提供了代码 sn-ps,但我不认为代码本身有任何问题。

我的问题是,如果我在服务中抛出异常,返回给客户端的错误对象总是泛型的:

{
    "ExceptionDetail":null,
    "ExceptionType":null,
    "Message":"The server was unable to process the request..."
    "StackTrace":null
}

理想情况下,我希望根据问题在客户端上显示不同的错误消息。

一种选择是允许 WCF 错误中的异常,这将为我提供完整的堆栈跟踪和所有内容,但我很欣赏由此带来的安全问题,这实际上比我需要的信息多得多。我可以只发回一个描述问题或其他内容的字符串,但我看不到这样做的方法。

我的服务代码:

[ServiceContract(Namespace = "MyNamespace")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService
{
    [OperationContract]
    public void DoStuff(string param1, string etc)
    {
        //Do some stuff that maybe causes an exception
    }
}

在客户端:

MyNamespace.MyService.DoStuff(
    param1,
    etc,
    function() { alert("success"); },
    HandleError);

其中“HandleError”只是一个通用的错误处理方法,它会显示有关错误的详细信息。

【问题讨论】:

    标签: asp.net ajax wcf json


    【解决方案1】:

    编辑:使用适当的自定义 json 错误处理程序更新帖子

    快速但非首选的方式。

    <serviceDebug includeExceptionDetailInFaults="true"/>
    

    在你的服务行为中会给你所有你需要的细节。

    好方法

    应用程序的所有异常都转换为JsonError 并使用DataContractJsonSerializer 进行序列化。直接使用Exception.Message。 FaultExceptions 提供 FaultCode 和其他异常被威胁为未知错误代码 -1。

    FaultException 与 HTTP 状态代码 400 一起发送,其他异常是 HTTP 代码 500 - 内部服务器错误。这不是必需的,因为故障代码可用于确定它是否是未知错误。然而,它在我的应用程序中很方便。

    错误处理程序

    internal class CustomErrorHandler : IErrorHandler
    {
        public bool HandleError(Exception error)
        {
            //Tell the system that we handle all errors here.
            return true;
        }
    
        public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
        {
            if (error is FaultException<int>)
            {
                FaultException<int> fe = (FaultException<int>)error;
    
                //Detail for the returned value
                int faultCode = fe.Detail;
                string cause = fe.Message;
    
                //The json serializable object
                JsonError msErrObject = new JsonError { Message = cause, FaultCode = faultCode };
    
                //The fault to be returned
                fault = Message.CreateMessage(version, "", msErrObject, new DataContractJsonSerializer(msErrObject.GetType()));
    
                // tell WCF to use JSON encoding rather than default XML
                WebBodyFormatMessageProperty wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
    
                // Add the formatter to the fault
                fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
    
                //Modify response
                HttpResponseMessageProperty rmp = new HttpResponseMessageProperty();
    
                // return custom error code, 400.
                rmp.StatusCode = System.Net.HttpStatusCode.BadRequest;
                rmp.StatusDescription = "Bad request";
    
                //Mark the jsonerror and json content
                rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
                rmp.Headers["jsonerror"] = "true";
    
                //Add to fault
                fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
            }
            else
            {
                //Arbitraty error
                JsonError msErrObject = new JsonError { Message = error.Message, FaultCode = -1};
    
                // create a fault message containing our FaultContract object
                fault = Message.CreateMessage(version, "", msErrObject, new DataContractJsonSerializer(msErrObject.GetType()));
    
                // tell WCF to use JSON encoding rather than default XML
                var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
                fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
    
                //Modify response
                var rmp = new HttpResponseMessageProperty();
    
                rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
                rmp.Headers["jsonerror"] = "true";
    
                //Internal server error with exception mesasage as status.
                rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
                rmp.StatusDescription = error.Message;
    
                fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
            }
        }
    
        #endregion
    }
    

    用于安装上述错误处理程序的网络行为

    internal class AddErrorHandlerBehavior : WebHttpBehavior
    {
        protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            base.AddServerErrorHandlers(endpoint, endpointDispatcher);
    
            //Remove all other error handlers
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
            //Add our own
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new CustomErrorHandler());
        }
    }
    

    json 错误数据合约

    指定 json 错误格式。 在此处添加属性以更改错误格式。

    [DataContractFormat]
    public class JsonError
    {
        [DataMember]
        public string Message { get; set; }
    
        [DataMember]
        public int FaultCode { get; set; }
    }
    

    使用错误处理程序

    自托管

    ServiceHost wsHost = new ServiceHost(new Webservice1(), new Uri("http://localhost/json")); 
    
    ServiceEndpoint wsEndpoint = wsHost.AddServiceEndpoint(typeof(IWebservice1), new WebHttpBinding(), string.Empty);
    
    wsEndpoint.Behaviors.Add(new AddErrorHandlerBehavior());
    

    App.config

    <extensions>  
      <behaviorExtensions>  
        <add name="errorHandler"  
            type="WcfServiceLibrary1.ErrorHandlerElement, WcfServiceLibrary1" />  
      </behaviorExtensions>  
    </extensions> 
    

    【讨论】:

    • 我读过的大多数文章似乎都不赞成在生产设置中包含异常详细信息,因为它会公开有关您的服务实施的信息。也就是说,这些文章似乎都没有提供“更好”的返回错误信息的解决方案。
    • 需要注意的是WCF有一个bug,就是在behaviorExtension标签中添加程序集信息时,需要使用全限定名(如果代码在, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null与您的服务相同的程序集)。
    • 我不知道为什么,但是我的 Web.config 中服务行为中的简单 &lt;serviceDebug includeExceptionDetailInFaults="true" /&gt; 对我不起作用。但是当我将相同的指令添加到我的代码文件中时(作为类声明上方的[ServiceBehavior(IncludeExceptionDetailInFaults = true)],来自this question)它起作用了。
    【解决方案2】:

    我能够获取异常详细信息的唯一方法是使用

    <serviceDebug includeExceptionDetailInFaults="true"/>
    

    我尝试了扩展 HttpWebBehavior 的建议方法,但是当它与 enableWebScript 一起使用时,如下所示:

    <behavior name="JsonBehavior">
        <myWebHttp/>
        <enableWebScript/>
    </behavior>
    

    WCF 调用将返回状态代码 202,没有任何信息。 如果配置变成了

    <behavior name="JsonBehavior">
            <enableWebScript/>
            <myWebHttp/>
        </behavior>
    

    你会得到格式正确的消息,但是你会失去所有的 json 格式功能以及 enableWebScript 的请求参数解析,这完全违背了使用 enableWebScript 的目的

    我也尝试过 FaultContract,但它似乎只适用于服务引用,而不适用于来自 JQuery 的 AJAX 调用。

    如果能够覆盖 WebScriptEnablingBehavior 或服务本身以提供自定义错误处理,那就太好了。

    【讨论】:

      【解决方案3】:

      我遇到了与 Bernd Bumüller 和 user446861 相同的问题,最后,我只是恢复为使用 WCF 服务的行为。你瞧,你不再需要任何 IErrorHandler 东西了。您可以只抛出 WebFaultException/WebFaultException 类型,客户端的一切都是黄金。

      webHttp 基本上与 enableWebScript 相同(webscript 从我理解的 webHttp 派生)减去 ASP.NET Ajax 的东西(ScriptManager ServiceReference 等等)。因为我使用的是 jQuery,所以我不需要自动生成的 JS 服务代理和其他 Ajax.net 包。这对我的需求非常有效,所以我想我会发表评论,以防其他人仍在寻找一些信息。

      【讨论】:

        【解决方案4】:

        看起来区分错误状态的首选方法是通过响应上的 http 状态代码。这可以在服务方法中手动设置,但我不确定这是否是解决此问题的最佳方法:

        [ServiceContract(Namespace = "MyNamespace")]
        AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
        public class MyService
        {
            [OperationContract]
            public void DoStuff(string param1, string etc)
            {
                try
                {
                //Do some stuff that maybe causes an exception
                }
                catch(ExceptionType1 ex)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest;
                }
                catch(ExceptionType2 ex)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Conflict;
                }
                ... etc.
            }
        }
        

        【讨论】:

          【解决方案5】:

          我建议包装异常并让所有异常通过包装的服务。您希望您将过滤掉的那些(如上面的示例中)有意义的消息。一般情况只会说:

          throw new ApplicationException("Unknown Error");

          这样您就不会向客户端提供有关服务内部运作的信息,但可以在需要将错误信息(如安全异常等)传递回客户端的情况下显示有意义的消息。

          【讨论】:

            猜你喜欢
            • 2016-04-28
            • 1970-01-01
            • 1970-01-01
            • 2011-01-31
            • 1970-01-01
            • 1970-01-01
            • 2011-10-12
            • 2019-10-12
            • 1970-01-01
            相关资源
            最近更新 更多