共用方式為


WCF Extensibility – WebHttpBehavior

This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page .

In the last post about QueryStringConverter I mentioned that the way to replace the converter used by some operations in WCF WebHttp endpoints (they’re more widely known as WCF REST endpoints, although the purists may disagree with this name) is to create a class derived from WebHttpBehavior and override a virtual method from that base class. In this post I’ll go over the other virtual methods in that class, which makes extending WCF WebHttp endpoints (a little) easier than the general WCF endpoints.

Public implementations in WCF

The WebHttpBehavior class is concrete, so it can be used directly. There is one public subclass in WCF, WebScriptEnablingBehavior, which is the behavior used in endpoints compatible with the ASP.NET AJAX library--LINK. That behavior modifies the base with the following changes:

So that class changed a lot of the behavior. And the good thing is that all those changes were made via public virtual method, so it’s possible to recreate something similar with “normal” user code as well.

Class definition

  1. public class WebHttpBehavior : IEndpointBehavior
  2. {
  3.     // Properties
  4.     public virtual bool AutomaticFormatSelectionEnabled { get; set; }
  5.     public virtual WebMessageBodyStyle DefaultBodyStyle { get; set; }
  6.     public virtual WebMessageFormat DefaultOutgoingRequestFormat { get; set; }
  7.     public virtual WebMessageFormat DefaultOutgoingResponseFormat { get; set; }
  8.     public virtual bool FaultExceptionEnabled { get; set; }
  9.     public virtual bool HelpEnabled { get; set; }
  10.     protected internal string JavascriptCallbackParameterName { get; set; }
  11.  
  12.     // Methods
  13.     public virtual void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters);
  14.     protected virtual void AddClientErrorInspector(ServiceEndpoint endpoint, ClientRuntime clientRuntime);
  15.     protected virtual void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher);
  16.     public virtual void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime);
  17.     public virtual void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher);
  18.     protected virtual WebHttpDispatchOperationSelector GetOperationSelector(ServiceEndpoint endpoint);
  19.     protected virtual QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription);
  20.     protected virtual IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint);
  21.     protected virtual IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint);
  22.     protected virtual IClientMessageFormatter GetRequestClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint);
  23.     protected virtual IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint);
  24.     public virtual void Validate(ServiceEndpoint endpoint);
  25.     protected virtual void ValidateBinding(ServiceEndpoint endpoint);
  26. }

Those are the virtual members which can be overridden to change the behavior of the base type:

IEndpointBehavior methods: the class implements this interface, so those methods can also be overridden.

  • AddBindingParameters: as usual, it’s unused in the base class, but can be overridden by derived types
  • ApplyClientBehavior: Sets up the formatters for all operations and a message inspectors, by calling all the virtual methods which are client-specific listed below
  • ApplyDispatchBehavior: Sets up formatters for all operations, defines an inspector used for JSONP requests (if enabled), sets up the operation selector which is UriTemplate-aware, and essentially calls all the virtual methods which are server-specific and listed below
  • Validate: Applies some basic validation for the HTTP-ness of the endpoints, such as if it’s actually on an HTTP transport, among other things

Specific WebHttpBehavior properties: all the great virtual properties which are tailored to specific extensions and can be overridden by derived classes.

  • AutomaticFormatSelectionEnabled (new in 4.0): defines whether automatic format selection for responses is enabled. Derived classes can force / disable it by overriding this property (default = false)
  • DefaultBodyStyle: defines the body style for operations where they are not explicitly set by the BodyStyle property of [WebGet] or [WebInvoke] (default = Bare)
  • DefaultOutgoingRequestFormat: used for client-side only, defines the format for outgoing requests (incoming requests can be of any supported format) if not explicitly set by the OutgoingRequestFormat property of [WebGet] or [WebInvoke] (default = Xml)
  • DefaultOutgoingResponseFormat: used for server-side only, defines the format for outgoing responses (incoming responses can be of any supported format) if not explicitly set by the OutgoingResponseFormat property of [WebGet] or [WebInvoke] (default = Xml)
  • FaultExceptionEnabled (new in 4.0): defines whether a SOAP-like fault is to be returned (with a 500 status code) if an exception (even WebFaultException which specifies a status code) is thrown by the service (default = false)
  • HelpEnabled (new in 4.0): defines whether a help page is enabled for the endpoints using this behavior (default = false)
  • JavascriptCallbackParameterName (new in 4.0, protected): defines the default name for the query string parameter for a GET request to indicate that it’s a JSONP request (opposed to a “normal” GET request). If the request contains a parameter with that name (and the WebHttpBinding has the property CrossDomainScriptAccessEnabled set to true) then the response will be wrapped in a function call (default = “callback”)

Specific WebHttpBehavior methods: same as the previous list, this time with methods instead of properties. All of those methods are protected (i.e., can be overridden, but not called directly unless calling the base implementation by the derived type)

  • AddClientErrorInspector: the base class adds a client message inspector which throws exceptions for 500 HTTP responses
  • AddServerErrorHandlers: the base class adds an error handler which know how to deal with WebFaultException. Also, if FaultExceptionEnabled is enabled, the handler will create the appropriate “web fault” for errors
  • GetOperationSelector: the base class adds an instance of the WebHttpDispatchOperationSelector class, which knows how to select an operation based on UriTemplate matching
  • GetQueryStringConverter: as shown in the post about QueryStringConverter, the base class returns an instance of the QueryStringConverter class, but derived types can override it to return a subclass which can deal with more types.
  • GetReplyClientFormatter: allows derived classes to change the formatter used to convert the incoming responses into the operation outputs (return value and any out/ref parameters) in a per-operation basis
  • GetReplyDispatchFormatter: allows derived classes to change the formatter used to package the operation outputs for outgoing responses into a Message object in a per-operation basis
  • GetRequestClientFormatter: allows derived classes to change the formatter used to package the operation inputs for outgoing requests into a Message object in a per-operation basis
  • GetRequestDispatchFormatter: allows derived classes to change the formatter used to convert the incoming requests into the operation input parameters in a per-operation basis
  • ValidateBinding: allows derived classes to validate the binding only for compatibility. IMO this is another one of the extensibility points which weren’t absolutely necessary (derived classes could simply override Validate, but I’m sure there is some scenario where having this will save some lines of code)

Now, this class is fairly extensible, but it has a lot of room for improvement (which is coming in the new WCF Web APIs). The biggest problem is that some of the features, such as URI templates and the help page aren’t aware of the changes done in derived classes. And once you start overriding some of the methods (such as the request formatters, you actually have to implement the URI template yourself – we had to do that in the jQuery Support in the WCF Codeplex page, and it was a lot of work). Some derived classes (such as WebScriptEnablingBehavior simply disallow UriTemplates and the help pages to avoid dealing with this problem.

How to use a class derived from WebHttpBehavior

This is fairly trivial – you just derive from that class, and instead of adding a WebHttpBehavior to your endpoint, you add your own class.

Real world scenario: a JsonValue-aware behavior which supports both server and client side

The JsonValue type (and its derived classes) was originally introduced in Silverlight 2 and was a great way to deal with untyped JSON data – but it was limited to Silverlight. When we started the WCF Codeplex project, the first component we released (called “jQuery Support”, since it also added support for forms/url-encoded data, which is the default format for jQuery ajax calls) was a beefed up version of those classes, along with the integration with the WCF service model, by means of a new behavior (we called it WebHttpBehavior3, since WebHttpBehavior2 had been used in the REST Starter Kit – which by the way is deprecated, all of its features either made it to the framework or are being maintained at the WCF Codeplex site). The WebHttpBehavior3 class used many of the extensibility points of WebHttpBehavior to provide a server-side support for untyped JSON data.

But I’ve seen some people trying to do the same in the client side, so I decided to use this scenario as the sample for this post. To avoid problems with UriTemplates, I’ll limit the code to deal with either responses or simple GET requests without templates. Some of the code is “borrowed” from the Codeplex site, some of it is new (I simplified some of it a little to fit in this series format).

And before starting with the code, the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios (please let me know if you find a bug or something missing). A more complete implementation would also include UriTemplate support (or even better, it would be built on top of the new WCF Web APIs), it would have more error handling, and it may also include client-side support for GET operations with a single JsonValue parameter. It could also support complex objects in the query string (right now it only supports record-like data).

Let’s start with the services which I’ll use to test this. The first service implements two contracts, the first with one operation which returns a JsonValue (and takes an optional boolean parameter indicating whether the operation should throw), and the second takes a JsonValue argument which will be bound to the query string, and returns the JSON (as a Stream) back to the client. The second service doesn’t use JsonValue at all, but it will be used to validate that we can use a contract with JsonValue on the client side.

  1.     [ServiceContract]
  2.     public interface IJsonValueInResponse
  3.     {
  4.         [WebGet]
  5.         JsonValue DownloadData(bool shouldThrow);
  6.     }
  7.  
  8.     [ServiceContract]
  9.     public interface IJsonValueInRequest
  10.     {
  11.         [WebGet]
  12.         Stream GetJsonString(JsonValue arg);
  13.     }
  14.  
  15.     public class Service : IJsonValueInResponse, IJsonValueInRequest
  16.     {
  17.         public JsonValue DownloadData(bool shouldThrow)
  18.         {
  19.             if (shouldThrow)
  20.             {
  21.                 throw new WebFaultException<JsonValue>(new JsonArray(1, 2, 3), HttpStatusCode.Conflict);
  22.             }
  23.  
  24.             return new JsonObject
  25.             {
  26.                 { "name", "Scooby Doo" },
  27.                 { "kind", "Dog" },
  28.                 { "age", 10 },
  29.                 { "friends", new JsonArray
  30.                     {
  31.                         new JsonObject
  32.                         {
  33.                             { "name", "Shaggy" },
  34.                             { "kind", "Person" },
  35.                             { "age", 23 }
  36.                         },
  37.                         new JsonObject
  38.                         {
  39.                             { "name", "Fred" },
  40.                             { "kind", "Person" },
  41.                             { "age", 21 }
  42.                         },
  43.                         new JsonObject
  44.                         {
  45.                             { "name", "Daphne" },
  46.                             { "kind", "Person" },
  47.                             { "age", 20 }
  48.                         },
  49.                         new JsonObject
  50.                         {
  51.                             { "name", "Velma" },
  52.                             { "kind", "Person" },
  53.                             { "age", 25 }
  54.                         }
  55.                     }
  56.                 }
  57.             };
  58.         }
  59.  
  60.  
  61.         public Stream GetJsonString(JsonValue arg)
  62.         {
  63.             WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
  64.             MemoryStream ms = new MemoryStream();
  65.             arg.Save(ms);
  66.             ms.Position = 0;
  67.             return ms;
  68.         }
  69.     }
  70.  
  71.     [ServiceContract]
  72.     public class ServiceWithoutJsonValue
  73.     {
  74.         [WebGet]
  75.         public Stream DownloadData()
  76.         {
  77.             WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
  78.             string response = @"{
  79.     ""name"":""Scooby Doo"",
  80.     ""kind"":""Dog"",
  81.     ""age"":10,
  82.     ""friends"":[
  83.         {
  84.             ""name"":""Shaggy"",
  85.             ""kind"":""Person"",
  86.             ""age"":23
  87.         },
  88.         {
  89.             ""name"":""Fred"",
  90.             ""kind"":""Person"",
  91.             ""age"":21
  92.         },
  93.         {
  94.             ""name"":""Daphne"",
  95.             ""kind"":""Person"",
  96.             ""age"":20
  97.         },
  98.         {
  99.             ""name"":""Velma"",
  100.             ""kind"":""Person"",
  101.             ""age"":25
  102.         }
  103.     ]
  104. }";
  105.             return new MemoryStream(Encoding.UTF8.GetBytes(response));
  106.         }
  107.     }

Now we can go to the behavior. First we’ll override the default outgoing formats from Xml to Json. Notice that we can’t simply set those values in the base class from the constructor, since those are virtual properties which shouldn’t be set while the object is being created.

  1. public class JsonValueAwareBehavior : WebHttpBehavior
  2. {
  3.     WebMessageFormat defaultOutgoingResponseFormat;
  4.     WebMessageFormat defaultOutgoingRequestFormat;
  5.  
  6.     public JsonValueAwareBehavior()
  7.     {
  8.         this.defaultOutgoingRequestFormat = WebMessageFormat.Json;
  9.         this.defaultOutgoingResponseFormat = WebMessageFormat.Json;
  10.     }
  11.  
  12.     public override WebMessageFormat DefaultOutgoingRequestFormat
  13.     {
  14.         get
  15.         {
  16.             return this.defaultOutgoingRequestFormat;
  17.         }
  18.         set
  19.         {
  20.             this.defaultOutgoingRequestFormat = value;
  21.         }
  22.     }
  23.  
  24.     public override WebMessageFormat DefaultOutgoingResponseFormat
  25.     {
  26.         get
  27.         {
  28.             return this.defaultOutgoingResponseFormat;
  29.         }
  30.         set
  31.         {
  32.             this.defaultOutgoingResponseFormat = value;
  33.         }
  34.     }
  35. }

Next we’ll just add a little more validation to the base type. Since our reply formatters can’t handle out / ref parameters, we want to have an error when the service / client is being opened (and not when a request is being made).

  1. public override void Validate(ServiceEndpoint endpoint)
  2. {
  3.     base.Validate(endpoint);
  4.  
  5.     foreach (var operation in endpoint.Contract.Operations)
  6.     {
  7.         if (!operation.IsOneWay)
  8.         {
  9.             var replyMessage = operation.Messages[1];
  10.             if (IsJsonValueType(replyMessage.Body.ReturnValue.Type) && replyMessage.Body.Parts.Count > 0)
  11.             {
  12.                 throw new InvalidOperationException("Operations returing JsonValue types cannot have out/ref parameters.");
  13.             }
  14.         }
  15.     }
  16. }

Now for the reply formatters. If the operation has a single parameter of type JsonValue (or its subclasses), and the request is not wrapped, we’ll use our own formatter (coming up later). Otherwise, we’ll just return the formatter from the base class.

  1. protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
  2. {
  3.     if (this.IsJsonValueReply(operationDescription))
  4.     {
  5.         return new JsonValueAwareReplyDispatchFormatter(operationDescription);
  6.     }
  7.     else
  8.     {
  9.         return base.GetReplyDispatchFormatter(operationDescription, endpoint);
  10.     }
  11. }
  12.  
  13. protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
  14. {
  15.     if (this.IsJsonValueReply(operationDescription))
  16.     {
  17.         return new JsonValueAwareReplyClientFormatter(operationDescription);
  18.     }
  19.     else
  20.     {
  21.         return base.GetReplyClientFormatter(operationDescription, endpoint);
  22.     }
  23. }
  24.  
  25. private bool IsJsonValueReply(OperationDescription operationDescription)
  26. {
  27.     bool result = operationDescription.Messages.Count > 1 && IsJsonValueType(operationDescription.Messages[1].Body.ReturnValue.Type);
  28.     if (result)
  29.     {
  30.         WebMessageBodyStyle bodyStyle = GetReplyBodyStyle(operationDescription);
  31.         result = bodyStyle == WebMessageBodyStyle.Bare || bodyStyle == WebMessageBodyStyle.WrappedRequest;
  32.     }
  33.  
  34.     return result;
  35. }

This is a new “feature” on this sample (which doesn’t exist on Codeplex) which lets a [WebGet] operation have a single JsonValue (or JsonObject, since the query string is a key/value pair collection which maps well to a JsonObejct) parameter – and all of the query string will be bound to it.

  1. protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
  2. {
  3.     var requestBodyParts = operationDescription.Messages[0].Body.Parts;
  4.     if (requestBodyParts.Count == 1)
  5.     {
  6.         WebGetAttribute wga = operationDescription.Behaviors.Find<WebGetAttribute>();
  7.         if (wga != null)
  8.         {
  9.             if (wga.UriTemplate == null)
  10.             {
  11.                 if (requestBodyParts[0].Type == typeof(JsonValue) || requestBodyParts[0].Type == typeof(JsonObject))
  12.                 {
  13.                     return new JsonValueAwareGetOnlyRequestDispatchFormatter(operationDescription);
  14.                 }
  15.             }
  16.         }
  17.     }
  18.  
  19.     return base.GetRequestDispatchFormatter(operationDescription, endpoint);
  20. }

Finally, we’ll add an error handler which knows how to deal with WebFaultException<T> exceptions where the detail type is a JsonValue type. This time we’ll use the same trick as we did on the Codeplex project (since the base type doesn’t return the error handlers it added), which is to let the base add first, then find which ones it did, then remove them and pass to our error handler implementation to delegate as necessary.

  1. protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  2. {
  3.     int originalErrorHandlersCount = endpointDispatcher.ChannelDispatcher.ErrorHandlers.Count;
  4.     base.AddServerErrorHandlers(endpoint, endpointDispatcher);
  5.     int newErrorHandlersCount = endpointDispatcher.ChannelDispatcher.ErrorHandlers.Count;
  6.     List<IErrorHandler> newHandlers = endpointDispatcher.ChannelDispatcher.ErrorHandlers
  7.         .Skip(originalErrorHandlersCount)
  8.         .Take(newErrorHandlersCount - originalErrorHandlersCount)
  9.         .ToList();
  10.     while (endpointDispatcher.ChannelDispatcher.ErrorHandlers.Count > originalErrorHandlersCount)
  11.     {
  12.         endpointDispatcher.ChannelDispatcher.ErrorHandlers.RemoveAt(endpointDispatcher.ChannelDispatcher.ErrorHandlers.Count - 1);
  13.     }
  14.  
  15.     endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonValueAwareErrorHandler(newHandlers));
  16. }

And the behavior class is done. Now let’s go to the classes which implement the extensions which we overrode. First is the request formatter for GET requests. It uses the HttpUtility class to parse the query string, and creates a simple JsonObject based on the query string values.

  1. class JsonValueAwareGetOnlyRequestDispatchFormatter : IDispatchMessageFormatter
  2. {
  3.     private OperationDescription operationDescription;
  4.  
  5.     public JsonValueAwareGetOnlyRequestDispatchFormatter(OperationDescription operationDescription)
  6.     {
  7.         this.operationDescription = operationDescription;
  8.     }
  9.  
  10.     public void DeserializeRequest(Message message, object[] parameters)
  11.     {
  12.         var prop = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
  13.         NameValueCollection nvc = HttpUtility.ParseQueryString(prop.QueryString);
  14.         JsonObject result = new JsonObject();
  15.         foreach (string key in nvc.Keys)
  16.         {
  17.             result[key] = nvc[key];
  18.         }
  19.  
  20.         parameters[0] = result;
  21.     }
  22.  
  23.     public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
  24.     {
  25.         throw new NotSupportedException("This formatter should be used for request only.");
  26.     }
  27. }

Now the reply formatter for the server side. The implementation is fairly simple, as it delegates creating the message to a body writer which is JsonValue-aware, and setting the appropriate message properties to have the response serialized as JSON.

  1. class JsonValueAwareReplyDispatchFormatter : IDispatchMessageFormatter
  2. {
  3.     OperationDescription operation;
  4.     public JsonValueAwareReplyDispatchFormatter(OperationDescription operation)
  5.     {
  6.         this.operation = operation;
  7.     }
  8.  
  9.     public void DeserializeRequest(Message message, object[] parameters)
  10.     {
  11.         throw new NotSupportedException("This formatter should be used for reply only.");
  12.     }
  13.  
  14.     public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
  15.     {
  16.         Message reply = Message.CreateMessage(messageVersion, operation.Messages[1].Action, new JsonValueBodyWriter(result as JsonValue));
  17.         reply.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
  18.         HttpResponseMessageProperty httpResp = new HttpResponseMessageProperty();
  19.         httpResp.Headers[HttpResponseHeader.ContentType] = "application/json";
  20.         reply.Properties.Add(HttpResponseMessageProperty.Name, httpResp);
  21.         return reply;
  22.     }
  23. }

The body writer is also simple, since it uses one of the methods defined in the JsonValueExtension class (from the Codeplex project) which knows how to write the JsonValue in a XmlWriter using the JSON to XML mapping rules.

  1. class JsonValueBodyWriter : BodyWriter
  2. {
  3.     private JsonValue value;
  4.  
  5.     public JsonValueBodyWriter(JsonValue jsonValue)
  6.         : base(true)
  7.     {
  8.         this.value = jsonValue;
  9.     }
  10.  
  11.     protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
  12.     {
  13.         value.Save(writer);
  14.     }
  15. }

Next is the client reply dispatcher. Again, using a function from the JsonValueExtensions class to handle the JSON to XML mapping, its implementation is quite trivial.

  1. class JsonValueAwareReplyClientFormatter : IClientMessageFormatter
  2. {
  3.     OperationDescription operation;
  4.     public JsonValueAwareReplyClientFormatter(OperationDescription operation)
  5.     {
  6.         this.operation = operation;
  7.     }
  8.  
  9.     public object DeserializeReply(Message message, object[] parameters)
  10.     {
  11.         return JsonValueExtensions.Load(message.GetReaderAtBodyContents());
  12.     }
  13.  
  14.     public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
  15.     {
  16.         throw new NotSupportedException("This formatter should be used for reply only.");
  17.     }
  18. }

Finally the error handler, which is the biggest of the classes, although it’s also not that complex. On ProvideFault, if it turns out that the exception which caused the handler to be invoked is a WebFaultException<T>, where the T is a JsonValue type, then we create a JSON message with the detail as given in the exception, and the status code from there as well.

  1. class JsonValueAwareErrorHandler : IErrorHandler
  2. {
  3.     private List<IErrorHandler> baseHandlers;
  4.  
  5.     public JsonValueAwareErrorHandler(List<IErrorHandler> baseHandlers)
  6.     {
  7.         this.baseHandlers = baseHandlers;
  8.     }
  9.  
  10.     public bool HandleError(Exception error)
  11.     {
  12.         foreach (var baseHandler in this.baseHandlers)
  13.         {
  14.             if (baseHandler.HandleError(error))
  15.             {
  16.                 return true;
  17.             }
  18.         }
  19.  
  20.         return false;
  21.     }
  22.  
  23.     public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
  24.     {
  25.         bool handled = false;
  26.         if (error != null)
  27.         {
  28.             Type errorType = error.GetType();
  29.             if (errorType.IsGenericType && errorType.GetGenericTypeDefinition() == typeof(WebFaultException<>))
  30.             {
  31.                 Type detailType = errorType.GetGenericArguments()[0];
  32.                 if (IsJsonValueType(detailType))
  33.                 {
  34.                     handled = true;
  35.                     HttpStatusCode statusCode = (HttpStatusCode)errorType.GetProperty("StatusCode").GetValue(error, null);
  36.                     JsonValue detail = (JsonValue)errorType.GetProperty("Detail").GetValue(error, null);
  37.                     if (detail == null)
  38.                     {
  39.                         WebOperationContext.Current.OutgoingResponse.StatusCode = statusCode;
  40.                         fault = WebOperationContext.Current.CreateStreamResponse(Stream.Null, "");
  41.                     }
  42.                     else
  43.                     {
  44.                         fault = Message.CreateMessage(version, "", new JsonValueBodyWriter(detail));
  45.                         HttpResponseMessageProperty httpResp = new HttpResponseMessageProperty();
  46.                         httpResp.StatusCode = statusCode;
  47.                         httpResp.Headers[HttpResponseHeader.ContentType] = "application/json";
  48.                         fault.Properties.Add(HttpResponseMessageProperty.Name, httpResp);
  49.                         WebBodyFormatMessageProperty bodyFormat = new WebBodyFormatMessageProperty(WebContentFormat.Json);
  50.                         fault.Properties.Add(WebBodyFormatMessageProperty.Name, bodyFormat);
  51.                     }
  52.                 }
  53.             }
  54.         }
  55.  
  56.         if (!handled)
  57.         {
  58.             foreach (var baseHandler in this.baseHandlers)
  59.             {
  60.                 baseHandler.ProvideFault(error, version, ref fault);
  61.             }
  62.         }
  63.     }
  64. }

Now that everything is set up we can start testing the program. To test the server part I’ll use a HttpWebRequest-based client, and to test the client part we’ll talk to the service which doesn’t use JsonValue (the one with a single operation with no JsonValue in its signature).

  1. static void Main(string[] args)
  2. {
  3.     string baseAddress1 = "https://" + Environment.MachineName + ":8000/Service";
  4.     string baseAddress2 = "https://" + Environment.MachineName + ":8008/Service";
  5.     ServiceHost host1 = new ServiceHost(typeof(Service), new Uri(baseAddress1));
  6.     ServiceHost host2 = new ServiceHost(typeof(ServiceWithoutJsonValue), new Uri(baseAddress2));
  7.     host1.AddServiceEndpoint(typeof(IJsonValueInResponse), new WebHttpBinding(), "resp").Behaviors.Add(new JsonValueAwareBehavior());
  8.     host1.AddServiceEndpoint(typeof(IJsonValueInRequest), new WebHttpBinding(), "req").Behaviors.Add(new JsonValueAwareBehavior());
  9.     host2.AddServiceEndpoint(typeof(ServiceWithoutJsonValue), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
  10.     host1.Open();
  11.     host2.Open();
  12.     Console.WriteLine("Hosts opened");
  13.  
  14.     SendGetRequest(baseAddress1 + "/resp/DownloadData");
  15.     SendGetRequest(baseAddress1 + "/resp/DownloadData?shouldThrow=true");
  16.     SendGetRequest(baseAddress1 + "/req/GetJsonString?a=1&b=hello&c=&d=hello%20world");
  17.  
  18.     ChannelFactory<IJsonValueInResponse> factory = new ChannelFactory<IJsonValueInResponse>(new WebHttpBinding(), new EndpointAddress(baseAddress2));
  19.     factory.Endpoint.Behaviors.Add(new JsonValueAwareBehavior());
  20.     IJsonValueInResponse proxy = factory.CreateChannel();
  21.     Console.WriteLine(proxy.DownloadData(false));
  22.  
  23.     Console.WriteLine("Press ENTER to close");
  24.     Console.ReadLine();
  25.     host1.Close();
  26.     host2.Close();
  27. }

And that’s it. Extending WebHttpBehavior is a little easier than the normal extensibility in WCF, but it still has some ways to go to become easier. Hopefully the WCF Web APIs will solve them to consolidate WCF as a top player in the services world for REST.

[Code in this post]

[Back to the index]

Comments

  • Anonymous
    April 29, 2012
    Hi Carlos,Very nice post, can you help me how to configure this in web.config file.. i am getting some error when i configured it...Anil
  • Anonymous
    April 30, 2012
    Hi Anil, if you want to configure the new behavior in web.config, you'll need to write a behavior configuration extension for it. You can find more information about that in blogs.msdn.com/.../wcf-extensibility-behavior-configuration-extensions.aspx.