Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
335 views
in Technique[技术] by (71.8m points)

c# - IErrorHandler returning wrong message body when HTTP status code is 401 Unauthorized

I have implemented IErrorHandler to handle authorization exceptions thrown within the constructor of my restful WCF service. When a general exception is caught my custom type is returned as expected, but the ContentType header is incorrect.

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

{"ErrorMessage":"Error!"}

However when the error handler tries to return a 401 Unauthorized http status code the message body is overridden to the default type but the ContentType header is as it should be.

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

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

Obviously something is wrong here, but I'm not sure what.

How do I implement IErrorHandler such that it returns my custom type in json with the correct headers?

BaseDataResponseContract Object:

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

} // end

This is the object I want to return. All of the other objects in my application inherit from this object. When an exception is thrown all we really care about is the http status code and the error message.

IErrorHandler Implementation (logging is not shown for brevity):

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 Implementation:

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>

Finally, I am seeing similar behavior when using WebFaultException. My thought is that this is the result of some deeply buried .Net shenanigans. I am choosing to implement IErrorHandler so that I can catch any other exceptions that may not be handled.

Reference:

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

Other Examples:

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I am not quite sure how your application is implemented. Based on your description, I suggest using visual studio to debug your ErrorHandler to see whether the exception arrive your callback.

If yes, manually construct your soap fault or response in the way you want.

If not, it means the exception happens before arriving your service operation, it may fail already in Channel stack, in this case, an easy approach is add extra HttpModule to custom or map the response. Or you can try custom the encoder in Channel stack.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...