【问题标题】:IErrorHandler returning wrong message body when HTTP status code is 401 Unauthorized当 HTTP 状态码为 401 Unauthorized 时,IErrorHandler 返回错误的消息体
【发布时间】:2016-07-06 19:10:16
【问题描述】:

我已经实现了 IErrorHandler 来处理我的 restful WCF 服务的构造函数中抛出的授权异常。当捕获到一般异常时,我的自定义类型按预期返回,但 ContentType 标头不正确。

HTTP/1.1 500 Internal Server Error
Content-Type: application/xml;
...

{"ErrorMessage":"Error!"}

但是,当错误处理程序尝试返回 401 Unauthorized http 状态代码时,消息正文将被覆盖为默认类型,但 ContentType 标头应如此。

HTTP/1.1 401 Unauthorized
Content-Type: application/json; 
...

{"Message":"Authentication failed.","StackTrace":null,"ExceptionType":"System.InvalidOperationException"}

显然这里出了点问题,但我不确定是什么。

如何实现 IErrorHandler 以使其在 json 中返回具有正确标头的自定义类型?

BaseDataResponseContract 对象:

[Serializable]
[DataContract( Name = "BaseDataResponseContract" )]
public class BaseDataResponseContract
{
    [DataMember]
    public string ErrorMessage { get; set; }

} // end

这是我要返回的对象。我的应用程序中的所有其他对象都继承自该对象。当抛出异常时,我们真正关心的是 http 状态码和错误消息。

IErrorHandler 实现(为简洁起见未显示日志记录):

namespace WebServices.BehaviorsAndInspectors
{
    public class ErrorHandler : IErrorHandler
    {
        public bool HandleError(Exception error)
        {
            return true;

        } // end

        public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
        {
            // Create a new instance of the object I would like to return with a default message
            var baseDataResponseContract = new BaseDataResponseContract { ErrorMessage = "Error!" };

            // Get the outgoing response portion of the current context 
            var response = WebOperationContext.Current.OutgoingResponse;

            // Set the http status code 
            response.StatusCode = HttpStatusCode.InternalServerError;

            // If the exception is a specific type change the default settings
            if (ex.GetType() == typeof(UserNotFoundException))
            {
                 baseDataResponseContract.ErrorMessage = "Invalid Username!";
                 response.StatusCode = HttpStatusCode.Unauthorized;
            }    

            // Create the fault message that is returned (note the ref parameter)
            fault = Message.CreateMessage(version, "", baseDataResponseContract, new DataContractJsonSerializer(typeof(BaseDataResponseContract)));

            // Tell WCF to use JSON encoding rather than default XML
            var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
            fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);

            // Add ContentType header that specifies we are using json 
            var httpResponseMessageProperty = new HttpResponseMessageProperty();
            httpResponseMessageProperty.Headers[HttpResponseHeader.ContentType] = "application/json";
            fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponseMessageProperty);

        } // end

    } // end class

} // end namespace

IServiceBehavior 实现:

namespace WebServices.BehaviorsAndInspectors
{
    public class ErrorHandlerExtensionBehavior : BehaviorExtensionElement, IServiceBehavior
    {
        public override Type BehaviorType
        {
            get { return GetType(); }
        }

        protected override object CreateBehavior()
        {
            return this;
        }

        private IErrorHandler GetInstance()
        {
            return new ErrorHandler();
        }

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            var errorHandlerInstance = GetInstance();

            foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
            {
                dispatcher.ErrorHandlers.Add(errorHandlerInstance);
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end

    } // end class

} // end namespace

Web.Config:

<system.serviceModel>

    <services>      
      <service name="WebServices.MyService">
        <endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
      </service>
    </services>

    <extensions>      
      <behaviorExtensions>        
        <!-- This extension if for the WCF Error Handling-->
        <add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />      
      </behaviorExtensions>    
    </extensions>

    <behaviors>          
      <serviceBehaviors>        
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <ErrorHandlerBehavior />
        </behavior>     
      </serviceBehaviors>    
    </behaviors>

    ....
</system.serviceModel>

最后,我在使用 WebFaultException 时看到了类似的行为。我的想法是,这是一些深埋的 .Net 恶作剧的结果。我选择实现 IErrorHandler 以便我可以捕获任何其他可能无法处理的异常。

参考:

https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx

http://www.brainthud.com/cards/5218/25441/which-four-behavior-interfaces-exist-for-interacting-with-a-service-or-client-description-what-methods-do-they-implement-and

其他例子:

IErrorHandler doesn't seem to be handling my errors in WCF .. any ideas?

How to make custom WCF error handler return JSON response with non-OK http code?

How do you set the Content-Type header for an HttpClient request?

【问题讨论】:

    标签: c# json wcf ierrorhandler


    【解决方案1】:

    我不太确定您的应用程序是如何实现的。根据您的描述,我建议使用 Visual Studio 调试您的 ErrorHandler 以查看异常是否到达您的回调。

    如果是,请以您想要的方式手动构建您的肥皂故障或响应。

    如果不是,则表示异常在到达您的服务操作之前发生,它可能已经在 Channel 堆栈中失败,在这种情况下,一个简单的方法是添加额外的 HttpModule 到自定义或映射响应。或者您可以尝试在 Channel stack 中自定义编码器。

    【讨论】:

    • 是的...但是如何?我得到了与 WebOperationContext.current.OutgoingResponse 相同的程序,但我没有 Write 方法,那么如何进行自定义响应?
    【解决方案2】:

    根据您编写的内容,您在服务实现的构造函数中抛出异常。因为 WCF 使用反射来创建你的服务实现,除非你的服务是单例的,否则你会得到一个 TargetInvocationException。

    示例(使用 LINQPad):

    void Main()
    {
        try
        {
            Activator.CreateInstance(typeof(Foo));
        }
        catch(Exception e)
        {
            e.Message.Dump();
            e.GetType().Name.Dump();
        }
    }
    
    public class Foo
    {
        public Foo()
        {
            throw new AuthorizationFailedException();
        }
    }
    
    public class AuthorizationFailedException : Exception
    {
    
    }
    

    基本上,避免在构造函数中基于业务逻辑抛出异常。仅用于处理编程错误。

    【讨论】:

      【解决方案3】:

      在为此苦苦挣扎了将近一整天后,我发现这是由 IIS 设置引起的。

      在 IIS 中的 API 项目下,在身份验证菜单下,我将“表单身份验证”设置为“启用”。我关闭了这个“功能”,上面的代码开始按预期工作。我发现这是由于我团队中的另一位开发人员将代码放入了更改 IIS 设置的 web.config 文件中。具体来说:

      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
          ...
          <system.web>
              <authentication mode="Forms" />
          </system.web>
          ...
      </configuration>
      

      此外,通过使用 WebOperationContext OutgoingResponse 对象上的 ContentType 属性,我能够正确显示 Content-Type 标头。

      // Get the outgoing response portion of the current context
      var response = WebOperationContext.Current.OutgoingResponse;
      
      // Add ContentType header that specifies we are using JSON
      response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-06-05
        • 1970-01-01
        • 2020-05-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多