none
How do I use x-http-method-override for a REST service in WCF 4.0

    Question

  • Back in WCF 3.5 using the WCF REST Starter Kit, I was able to create a request interceptor to handle x-http-method-override headers for clients that cannot send PUT or DELETE commands.  How do I do the equivalent in WCF 4.0?

    Wednesday, October 13, 2010 3:15 PM

Answers

  • Hi,

    If you need a simple way just try this in Global.asax:

      protected void Application_BeginRequest(object sender, EventArgs e)
            {

    // Add some conditions here. Here I just change the Method to POST
               var httpMethod= Request.GetType().GetField("_httpMethod", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
               httpMethod.SetValue(Request, "POST");
            }


    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    • Marked as answer by mlrenze Friday, October 22, 2010 1:33 PM
    Thursday, October 21, 2010 7:56 AM

All replies

  • Hi,

    I'm not sure why it's different in WCF 4.0. Could you elaborate why the original method cannot work in WCF 4.0? To add HTTP header for WCF client:

    http://caught-in-a-web.blogspot.com/2008/04/how-to-add-custom-http-header-in-wcf.html

     


    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    Friday, October 15, 2010 6:54 AM
  • Allen,

    Thank you for your reply.  I apologize for taking so long to respond. 

    The situation I’m specifically referring to is HTTP verb override in WCF 4.0 on the server.  It involves having a WCF REST web service (which is receiving a request from a client) intercept the X-Http-Method-Override header in the client’s request and route the request to the appropriate WCF endpoint method using the overriding HTTP verb.  This workaround is typically used in scenarios where the client application cannot send PUT or DELETE verbs (e.g. if it’s an HTTP 1.0 client) or because the client cannot add custom headers to a GET request (e.g. Flash, ActionScript, etc.) and must submit all requests as a POST and override the POST using the X-Http-Method-Override.

    In WCF 3.5 using the WCF REST Starter kit, I could create a CustomServiceHostFactory and an XHttpMethodOverrideInterceptor class.  The CustomServiceHostFactory would add the XHttpMethodOverrideIntercepter to the WebServiceHost2’s Interceptors collection.   I would then use the CustomServiceHostFactory to create the service hosts.  The code for these two classes is below:

     public class CustomServiceHostFactory : ServiceHostFactory
     {
      protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
      {
        //Create host
        WebServiceHost2 host = new WebServiceHost2(serviceType, true, baseAddresses);
    
        //Add interceptors
        host.Interceptors.Add(new XHttpMethodOverrideInterceptor());
    
        //Return host
        return host;
      }
    }
    
    public class XHttpMethodOverrideInterceptor : RequestInterceptor
    {
      public XHttpMethodOverrideInterceptor() : base(true) {}
    
      public override void ProcessRequest(ref RequestContext requestContext)
      {
        // If request context is null or request message is null then return
        if (requestContext == null || requestContext.RequestMessage == null)
        {
          return;
        }
    
        // Get the request message
        Message message = requestContext.RequestMessage;
        HttpRequestMessageProperty reqProp =
          (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
    
        // If the message contains an "X-Http-Method-Override" header 
        // then set the method to the overriding method
        string overrideVal = reqProp.Headers["X-HTTP-Method-Override"];
        if (!string.IsNullOrEmpty(overrideVal))
        {
          reqProp.Method = overrideVal;
        }
      }
    }
    

    When using these classes in WCF 3.5 if an incoming request contained the X-Http-Method-Override header then the Interceptor class would see this header and switch the request method to the overriding method specified in the X-Http-Method-Override header. 

    In WCF 4.0 if I reference the WCF REST Starter Kit 3.5 assembly and use the same code and wire up the CustomServiceHostFactory while setting up my ServiceRoutes in the Global.asax file, I can get the X-Http-Method-Override to work.  However, this also changes other behaviors of my WCF 4.0 service endpoints unintentionally.  For example, if I throw a WebFaultException and set the return status code to 401 (Unauthorized), rather than WCF 4.0 returning a 401 (Unauthorized) status code to the client (like it normally would) WCF 4.0 will return a 200 (Success) with a message indicating the user is unauthorized in the content body.

    What I would like to do is recreate the same X-Http-Method-Override feature in WCF 4.0 without having to use the WCF REST Starter Kit 3.5 and thus avoiding the unintended changes to the behavior my WCF service endpoints.

    It is to my understanding that WCF 4.0 is supposed to include the features that were in the WCF REST Starter Kit 3.5.  If this is the case, I am unable to find a RequestInterceptor collection on the WebServiceHost class like the WCF REST Starter Kit had in the WebServiceHost2 class.

    If you are aware of a way to implement an X-Http-Method-Override feature in WCF 4.0 without using the WCF REST Starter Kit 3.5 I would love to hear how it is done.  In addition, I would also be interested in knowing how to accomplish this in a more general way; that is, not specifically solving the problem within the WCF 4.0 architecture but somewhere outside of WCF (e.g. in ASP.NET or in IIS 7.0) in a way that creates the same outcome.

    Thank you in advance for your time.


    Matthew
    Wednesday, October 20, 2010 3:24 PM
  • Hi,

    If you need a simple way just try this in Global.asax:

      protected void Application_BeginRequest(object sender, EventArgs e)
            {

    // Add some conditions here. Here I just change the Method to POST
               var httpMethod= Request.GetType().GetField("_httpMethod", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
               httpMethod.SetValue(Request, "POST");
            }


    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    • Marked as answer by mlrenze Friday, October 22, 2010 1:33 PM
    Thursday, October 21, 2010 7:56 AM
  • Hi Matthew,

    You can use a custom operation selector which will replace the HTTP method on the operation context prior to passing the selection to the default selector. If I understand your problem correctly, this should accomplish what you need.

      public class Post_1dc19d08_71b0_4f4d_9d8a_698d7af877d8
      {
        const string HttpMethodOverrideHeaderName = "X-HTTP-Method-Override";
        const string OriginalHttpMethodPropertyName = "OriginalHttpMethod";
    
        [ServiceContract]
        public interface ITest
        {
          [WebInvoke(Method = "PUT", UriTemplate = "/Operation", ResponseFormat = WebMessageFormat.Json)]
          string Put(string text);
          [WebInvoke(Method = "POST", UriTemplate = "/Operation", ResponseFormat = WebMessageFormat.Json)]
          string Post(string text);
          [WebInvoke(Method = "DELETE", UriTemplate = "/Operation", ResponseFormat = WebMessageFormat.Json)]
          string Delete(string text);
        }
        public class Service : ITest
        {
          public string Put(string text)
          {
            return Operation("PUT", text);
          }
          public string Post(string text)
          {
            return Operation("POST", text);
          }
          public string Delete(string text)
          {
            return Operation("DELETE", text);
          }
          private string Operation(string method, string text)
          {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Method: " + method);
            sb.AppendLine("Method (from WOC): " + WebOperationContext.Current.IncomingRequest.Method);
    
            if (OperationContext.Current.IncomingMessageProperties.ContainsKey(OriginalHttpMethodPropertyName))
            {
              sb.AppendLine("Original HTTP request method: " + (string)OperationContext.Current.IncomingMessageProperties[OriginalHttpMethodPropertyName]);
            }
    
            sb.AppendLine("Headers:");
            foreach (string headerName in WebOperationContext.Current.IncomingRequest.Headers.AllKeys)
            {
              string headerValue = WebOperationContext.Current.IncomingRequest.Headers[headerName];
              sb.AppendLine(string.Format(" {0}: {1}", headerName, headerValue));
            }
            sb.AppendLine("Text: " + text);
            return sb.ToString();
          }
        }
        static void SendRequest(string uri, string method, string body, string httpOverrideHeader)
        {
          string contentType = "application/json";
          HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
          req.Method = method;
          req.ContentType = contentType;
          if (httpOverrideHeader != null)
          {
            req.Headers[HttpMethodOverrideHeaderName] = httpOverrideHeader;
          }
    
          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;
          }
    
          if (resp == null)
          {
            Console.WriteLine("Response is null");
          }
          else
          {
            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)
            {
              string responseBody = new StreamReader(respStream).ReadToEnd();
              responseBody = responseBody.Replace("\\u000d", "\r").Replace("\\u000a", "\n").Replace("\\/", "/");
              Console.WriteLine(responseBody);
            }
            else
            {
              Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
            }
          }
    
          Console.WriteLine();
          Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
          Console.WriteLine();
        }
        public class HttpOverrideInspector : IEndpointBehavior
        {
          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
          {
          }
    
          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
          }
    
          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
            endpointDispatcher.DispatchRuntime.OperationSelector = new HttpOverrideOperationSelector(endpointDispatcher.DispatchRuntime.OperationSelector);
          }
    
          public void Validate(ServiceEndpoint endpoint)
          {
          }
        }
        public class HttpOverrideOperationSelector : IDispatchOperationSelector
        {
          IDispatchOperationSelector originalSelector;
          public HttpOverrideOperationSelector(IDispatchOperationSelector originalSelector)
          {
            this.originalSelector = originalSelector;
          }
    
          public string SelectOperation(ref Message message)
          {
            if (message.Properties.ContainsKey(HttpRequestMessageProperty.Name))
            {
              HttpRequestMessageProperty reqProp = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
              string httpMethodOverride = reqProp.Headers["X-HTTP-Method-Override"];
              if (!string.IsNullOrEmpty(httpMethodOverride))
              {
                message.Properties[OriginalHttpMethodPropertyName] = reqProp.Method;
                reqProp.Method = httpMethodOverride;
              }
            }
    
            return this.originalSelector.SelectOperation(ref message);
          }
        }
        public static void Test()
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
          ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
          endpoint.Behaviors.Add(new WebHttpBehavior());
          endpoint.Behaviors.Add(new HttpOverrideInspector());
          host.Open();
          Console.WriteLine("Host opened");
    
          SendRequest(baseAddress + "/Operation", "POST", "\"Method POST, override PUT\"", "PUT");
          SendRequest(baseAddress + "/Operation", "POST", "\"Method POST, no override\"", null);
          SendRequest(baseAddress + "/Operation", "POST", "\"Method POST, override DELETE\"", "DELETE");
    
          Console.Write("Press ENTER to close the host");
          Console.ReadLine();
          host.Close();
        }
      }
    
    
    Thursday, October 21, 2010 4:16 PM
  • Allen,

    Thank you!  Your solution worked beautifully.  

    I appreciate all your help!

     

    Matthew

    P.S.  For anyone future readers of this thread who are looking for a solution to this same problem, here's a simplified version of the solution I implemented based on Allen's advice:

     if (Request.Headers["X-Http-Method-Override"] != null)
          {
            string xHttpMethodOverride = Request.Headers["X-Http-Method-Override"];
            var httpMethod = Request.GetType().GetField("_httpMethod", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            httpMethod.SetValue(Request, xHttpMethodOverride);
          }


    Matthew
    • Proposed as answer by HedgehogJim Tuesday, August 02, 2011 9:01 PM
    Friday, October 22, 2010 1:38 PM
  • To catch all types of HttpMethodOverride: header, body, and query.

    This is really a bug.

    Why provide HttpMethodOverride if it does not affect HttpMethodConstraint?

    //Global.asax.cs
    //...
    namespace Application {
        public class MvcApplication : System.Web.HttpApplication {
            //...
            protected void Application_BeginRequest(object sender, EventArgs e) {
                //patch for HttpMethodOverride and HttpMethodConstraint
                var override_http_method = Request.Headers["X-Http-Method-Override"] ?? Request.Form["X-Http-Method-Override"] ?? Request.QueryString["X-Http-Method-Override"];
                if (override_http_method != null) {
                    var http_method = Request.GetType().GetField("_httpMethod", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                    http_method.SetValue(Request, override_http_method);
                }
            }
            //...
        }
    }
    
    
    • Proposed as answer by hawleyal1 Tuesday, September 13, 2011 12:42 PM
    Tuesday, September 13, 2011 12:38 PM
  • Allen and mlrenze: I don't know how significant this is for performance, but I'd cache the field info to reduce the overhead from reflection.

        private static readonly System.Reflection.FieldInfo HttpMethodField;
    
    
        static Global()
        {
            HttpMethodField = typeof(System.Web.HttpRequest).GetField("_httpMethod", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        }
    • Edited by kpozin Thursday, October 13, 2011 7:52 PM
    Thursday, October 13, 2011 7:51 PM