John Meyer John Meyer - 4 months ago 69
JSON Question

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?

Answer

After struggling with this for almost a full day I discovered that this was caused by an IIS setting.

Under my API project in IIS, under the Authentication menu I had 'Forms Authentication' set to 'Enabled'. I turned off this 'feature' and the code above started working as expected.

Further, I was able to get the Content-Type header to appear correctly by using the ContentType Property on the WebOperationContext OutgoingResponse object.

// 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();