none
JSON endpoint with custom errorhandler

    Question

  • I'm new to ASP.NET and WCF... not a good combo.

    What I would like to do is use WCF endpoint with Microsoft AJAX extensions and have the ability to have nice error messages returned instead of 

    {"ExceptionDetail":null,"ExceptionType":null,"Message":"The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.","StackTrace":null}
    
    Reasonable enough... Below is my example.

     

    Anyways, I'm trying to create an endpoint that simply returns an object with two strings:

    [Serializable]
    [DataContract]
    public class StringExample
    {
    	internal StringExample(string str1, string str2)
    	{
    		String1= str1;
    		String2= str2;
    	}
    	[DataMember]
    	public string String1{ get; private set; }
    	[DataMember]
    	public string String2{ get; private set; }
    }
    

    Simple enough.

    Here's my service interface:

     

    [ServiceContract]
    public interface IStringExampleService
    {
    	[OperationContract]
    	[WebGet(ResponseFormat = WebMessageFormat.Json)]
    	StringExample GetStringExample();
    }

     

    And my actual service:

     

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class StringExampleService: IStringExampleService
    {
    	public StringExample GetStringExample()
    	{
    		if(CanHazString()) //The contents of this method are unimportant...
    		{
    			return new StringExample("Yes", "(s)he can");
    		}
    		else
    			throw new Exception("NO STRING FOR YOU");
    	}
    }

     

    And here's the relevant parts of my web.config:

     

    <system.serviceModel>
    	<extensions>
    		<behaviorExtensions>
    			<add name="ErrorLogging" type="ErrorHandlerBehavior, StringExample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9d04a1dca2c654ed" />
    		</behaviorExtensions>
    	</extensions>
    	<behaviors>
    		<endpointBehaviors>
    			<behavior name="AspWebHandlingBehavior">
    				<enableWebScript />
    				<ErrorLogging /> <!-- ####### I WANT TO USE THE HANDLER -->					
    			</behavior>
    		</endpointBehaviors>
    		<serviceBehaviors>
    			<behavior name="StringExampleBehavior">
    				<serviceMetadata httpGetEnabled="true" />
    				<serviceDebug includeExceptionDetailInFaults="false" />
    			</behavior>
    		</serviceBehaviors>
    	</behaviors>
    	<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    	<services>
    		<service behaviorConfiguration="StringExampleBehavior" name="StringExampleService"><br/>			<endpoint address="" behaviorConfiguration="AspWebHandlingBehavior" binding="webHttpBinding" contract="IStringExampleService">
    			</endpoint>
    			<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    		</service>
    	</services>
    </system.serviceModel>
    

     

    And finally the error handler code:

     

    public class ErrorHandler : IErrorHandler, IServiceBehavior
    {
    
    	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);
    		}
    	}
    
    	public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    	{
    		IErrorHandler errorHandler = new ErrorHandler();
    
    		foreach(ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
    		{
    			ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
    			channelDispatcher.ErrorHandlers.Clear();
    			channelDispatcher.ErrorHandlers.Add(errorHandler);
    		}
    	}
    
    	public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    	{
    	}
    
    	public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    	{
    
    	}
    }
    
    class ErrorHandlerBehavior : BehaviorExtensionElement
    {
    	protected override object CreateBehavior()
    	{
    		return new ErrorBehavior();
    	}
    
    	public override Type BehaviorType
    	{
    		get { return typeof(ErrorBehavior); }
    	}
    }
    internal class ErrorBehavior : WebHttpBehavior
    {
    	protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    	{
    		// clear default error handlers. 
    		endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
    
    		// add the Json error handler. 
    		endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new ErrorHandler());
    	}
    }
    
    [DataContractFormat]
    public class JsonError
    {
    	[DataMember]
    	public string Message { get; set; }
    	[DataMember]
    	public int FaultCode { get; set; }
    }
    

     

    If I have my web.config as above I get both the return function return and the fault return in pure JSON:

    {"String1":"Yes","String2":"(s)he can"}
    

    and for the error:

    {"FaultCode":-1,"Message":"NO STRING FOR YOU"}
    

    However, it appears that the microsoft ajax extensions can't handle these properly, and needs the responses formatted like so:

    {"d":{"__type":"StringExample","String1":"Yes","String2":"(s)he can"}}
    

    Which is the format i get if I remove the line in my webconfig with the ######, but if I remove that line I get the message at the top of this post instead of my preferred fault object... 

     

    Please help!


    Thank you in advance for your help. If you think you may be able to help with any of my unanswered threads please look at them here
    Thursday, January 27, 2011 10:38 PM

Answers

  • Your example is in the right track. You don't need to use another behavior which is also a WebHttpBehavior (your ErrorBehavior) - you can use the WebScriptEnablingBehavior, and add the behavior after it to simply replace the error handler with the one you have (I rewrote it a little to reduce the size).

      public class Post_fb906fa1_8ce9_412e_a16a_5d4a2a0c2ac5
      {
        [DataContract(Name = "StringExample")]
        public class StringExample
        {
          public StringExample(string str1, string str2)
          {
            String1 = str1;
            String2 = str2;
          }
          [DataMember]
          public string String1 { get; private set; }
          [DataMember]
          public string String2 { get; private set; }
        }
    
        [ServiceContract]
        public interface IStringExampleService
        {
          [WebGet(ResponseFormat = WebMessageFormat.Json)]
          StringExample GetStringExample(int i);
        }
    
        public class StringExampleService : IStringExampleService
        {
          public StringExample GetStringExample(int i)
          {
            if (i == 0) //The contents of this method are unimportant...
            {
              return new StringExample("Yes", "(s)he can");
            }
            else if (i == 1)
            {
              throw new Exception("NO STRING FOR YOU");
            }
            else
            {
              throw new FaultException<int>(234, "No String For You Either");
            }
          }
        }
    
        [DataContract(Name = "JsonError")]
        public class JsonError
        {
          [DataMember]
          public string Message { get; set; }
          [DataMember]
          public int FaultCode { get; set; }
        }
    
        public class MyErrorHandler : IErrorHandler
        {
          public bool HandleError(Exception error)
          {
            //Tell the system that we handle all errors here.
            return true;
          }
    
          public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
          {
            FaultException<int> fe = error as FaultException<int>;
            JsonError msErrObject = new JsonError { FaultCode = fe == null ? -1 : fe.Detail, Message = error.Message };
    
            //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);
          }
        }
    
        public class MyEndpointBehavior : IEndpointBehavior
        {
          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
          {
          }
    
          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
          }
    
          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new MyErrorHandler());
          }
    
          public void Validate(ServiceEndpoint endpoint)
          {
          }
        }
    
        public static void Test()
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(StringExampleService), new Uri(baseAddress));
          ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IStringExampleService), new WebHttpBinding(), "");
          endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
          endpoint.Behaviors.Add(new MyEndpointBehavior());
          host.Open();
          Console.WriteLine("Host opened");
    
          Util.SendRequest(baseAddress + "/GetStringExample?i=0", "GET", null, null);
          Util.SendRequest(baseAddress + "/GetStringExample?i=1", "GET", null, null);
          Util.SendRequest(baseAddress + "/GetStringExample?i=2", "GET", null, null);
    
          Console.Write("Press ENTER to close the host");
          Console.ReadLine();
          host.Close();
        }
      }
      public static class Util
      {
        public static string SendRequest(string uri, string method, string contentType, string body)
        {
          string responseBody = null;
    
          HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
          req.Method = method;
          if (!String.IsNullOrEmpty(contentType))
          {
            req.ContentType = contentType;
          }
          if (body != null)
          {
            byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
            req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
            req.GetRequestStream().Close();
          }
    
          HttpWebResponse resp;
          try
          {
            resp = (HttpWebResponse)req.GetResponse();
          }
          catch (WebException e)
          {
            resp = (HttpWebResponse)e.Response;
          }
          Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
          foreach (string headerName in resp.Headers.AllKeys)
          {
            Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
          }
          Console.WriteLine();
          Stream respStream = resp.GetResponseStream();
          if (respStream != null)
          {
            responseBody = new StreamReader(respStream).ReadToEnd();
            Console.WriteLine(responseBody);
          }
          else
          {
            Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
          }
          Console.WriteLine();
          Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
          Console.WriteLine();
    
          return responseBody;
        }
      }
    
    
    Friday, January 28, 2011 5:08 AM

All replies

  • Your example is in the right track. You don't need to use another behavior which is also a WebHttpBehavior (your ErrorBehavior) - you can use the WebScriptEnablingBehavior, and add the behavior after it to simply replace the error handler with the one you have (I rewrote it a little to reduce the size).

      public class Post_fb906fa1_8ce9_412e_a16a_5d4a2a0c2ac5
      {
        [DataContract(Name = "StringExample")]
        public class StringExample
        {
          public StringExample(string str1, string str2)
          {
            String1 = str1;
            String2 = str2;
          }
          [DataMember]
          public string String1 { get; private set; }
          [DataMember]
          public string String2 { get; private set; }
        }
    
        [ServiceContract]
        public interface IStringExampleService
        {
          [WebGet(ResponseFormat = WebMessageFormat.Json)]
          StringExample GetStringExample(int i);
        }
    
        public class StringExampleService : IStringExampleService
        {
          public StringExample GetStringExample(int i)
          {
            if (i == 0) //The contents of this method are unimportant...
            {
              return new StringExample("Yes", "(s)he can");
            }
            else if (i == 1)
            {
              throw new Exception("NO STRING FOR YOU");
            }
            else
            {
              throw new FaultException<int>(234, "No String For You Either");
            }
          }
        }
    
        [DataContract(Name = "JsonError")]
        public class JsonError
        {
          [DataMember]
          public string Message { get; set; }
          [DataMember]
          public int FaultCode { get; set; }
        }
    
        public class MyErrorHandler : IErrorHandler
        {
          public bool HandleError(Exception error)
          {
            //Tell the system that we handle all errors here.
            return true;
          }
    
          public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
          {
            FaultException<int> fe = error as FaultException<int>;
            JsonError msErrObject = new JsonError { FaultCode = fe == null ? -1 : fe.Detail, Message = error.Message };
    
            //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);
          }
        }
    
        public class MyEndpointBehavior : IEndpointBehavior
        {
          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
          {
          }
    
          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
          }
    
          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new MyErrorHandler());
          }
    
          public void Validate(ServiceEndpoint endpoint)
          {
          }
        }
    
        public static void Test()
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(StringExampleService), new Uri(baseAddress));
          ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IStringExampleService), new WebHttpBinding(), "");
          endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
          endpoint.Behaviors.Add(new MyEndpointBehavior());
          host.Open();
          Console.WriteLine("Host opened");
    
          Util.SendRequest(baseAddress + "/GetStringExample?i=0", "GET", null, null);
          Util.SendRequest(baseAddress + "/GetStringExample?i=1", "GET", null, null);
          Util.SendRequest(baseAddress + "/GetStringExample?i=2", "GET", null, null);
    
          Console.Write("Press ENTER to close the host");
          Console.ReadLine();
          host.Close();
        }
      }
      public static class Util
      {
        public static string SendRequest(string uri, string method, string contentType, string body)
        {
          string responseBody = null;
    
          HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
          req.Method = method;
          if (!String.IsNullOrEmpty(contentType))
          {
            req.ContentType = contentType;
          }
          if (body != null)
          {
            byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
            req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
            req.GetRequestStream().Close();
          }
    
          HttpWebResponse resp;
          try
          {
            resp = (HttpWebResponse)req.GetResponse();
          }
          catch (WebException e)
          {
            resp = (HttpWebResponse)e.Response;
          }
          Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
          foreach (string headerName in resp.Headers.AllKeys)
          {
            Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
          }
          Console.WriteLine();
          Stream respStream = resp.GetResponseStream();
          if (respStream != null)
          {
            responseBody = new StreamReader(respStream).ReadToEnd();
            Console.WriteLine(responseBody);
          }
          else
          {
            Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
          }
          Console.WriteLine();
          Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
          Console.WriteLine();
    
          return responseBody;
        }
      }
    
    
    Friday, January 28, 2011 5:08 AM
  • Perfect! Thank you very much Carlos
    Thank you in advance for your help. If you think you may be able to help with any of my unanswered threads please look at them here
    Friday, January 28, 2011 4:56 PM