Custom processor changing parameters on URI problems

Topics: Web Api
Dec 20, 2010 at 3:26 PM
Edited Dec 20, 2010 at 5:09 PM

Hello !

Here is my problem :

I have a custom processor. It take the HttpRequestMessage as InputType. On this processor I want to intercept the request to change some parts of the URI  based on some personnal logical. To simplify, here is my Processor class :

public class MyProcessor2 : Processor
{
public override ProcessorResult<object> OnExecute(HttpRequestMessage httpRequestMessage)
{
//access httpRequestMessage.Content here to decrypt
httpRequestMessage.Uri = new Uri("http://localhost:4096/contact/contact/fr-fr/1/2");

return new ProcessorResult<object>(); //necessary
}
}</object></object>

 

 

 

When my processor is execute, using this URL for example : http://localhost:4096/contact/contact/fr-fr/1/1 (that should be replaced by http://localhost:4096/contact/contact/fr-fr/1/2 by my processor) everything seems to be fine...

But when I'am entering in my service methode, the last parameter ({id}) string get value "1" and not "2" why that ??? .Here is the service :

 

 

[WebGet(UriTemplate = "/contact/{culture}/{IdSite}/{id}")]       
public Contact Get(CultureInfo culture, string IdSite, string id,HttpRequestMessage mess, HttpResponseMessage response)
{
var contact = this.repository.Get(int.Parse(id, CultureInfo.InvariantCulture));
if (contact == null)
{
response.StatusCode = HttpStatusCode.NotFound;
response.Content = HttpContent.Create("Contact not found");
}

return contact;
}

 

Maybe I miss something...

Thanks for any help.

 

 

HttpRequestMessage
Coordinator
Dec 20, 2010 at 9:55 PM

That won't work. Your custom processor is executing after values were already extracted from the uri based on the template. If you want to actually rewrite the uri / change it, the recommended way would be to use our new HTTP channels which do not yet exist.

What you can do with a processor is update the id parameter from 1 to 2 directly. To do that you would have a processor that takes a string input of ID and then outputs a string of ID. This would then change the ID that was extracted from the uri before it hits the method.

Thanks

Glenn

Dec 21, 2010 at 7:16 AM

Hello, first, thank you for your response !

I understand better now. In fact I can't use your solution and write a processor taking the Id parameter because it's for a more complex scenario that the one I explain before.

Imagine that I have many parts of my Url that are encrypted for some security reasons. I need to decrypt this URL part before the operation is selected… (It's only  decrypted that we can known which operation is called).  So processors doesn't seems to be the good way to do that.

Maybe the future Http channels could be an option later. But for now, for you, what is the best option for me ?
Is there any way to intercept request earlier in the pipeline the request ?
Re-using a kind of RequestInterceptor (but will it be compatible with your "processors")?
Create my own DispatchOperationSelector ?

Other ideas ?...


Thanks a lot.

Coordinator
Dec 21, 2010 at 7:37 AM
MessageInspector runs before processors. You could use that. In the inspector grab the Message using OperationContext.Current. We have extension methods for grabbing the HttpRequestMessage, ToHttpRequestMessage. Then you can change the uri before the processor has ran.

Glenn
Dec 21, 2010 at 9:09 AM

Using MessageInspectors, iIt's works !

Thanks very much for your help and for WCF's Web API.

Ben

Coordinator
Dec 21, 2010 at 9:11 AM

Fantastic, glad that worked and glad you like our API. MessageInspector exists in 4.0 btw.

Glenn

Dec 21, 2010 at 5:17 PM

I have one question more.

When I'am processing on my "processor", how I have to raise Errors or Exceptions ? Can I customize the http code returned to client or eventually add a custom message (that will be serialized based on "Accept" header or "Content-Type") ?

On my "OnExecute" method I try,

return new ProcessorResult
                {
                    Status = ProcessorStatus.Error,                    
                    Error = new Exception("Blabla")
                };

And :

HttpResponseMessage reponse = new HttpResponseMessage();
                reponse.Content = HttpContent.Create("You can't access to that !!!");
                throw new WebFaultException(reponse, System.Net.HttpStatusCode.Unauthorized);

 

And, last I try throwing classics exception with a custom "ErrorHandler" implementing "HttpMessageErrorHandler" but on my overrided method "protected override void ProvideResponse(Exception error, Microsoft.Http.HttpResponseMessage response)", the exception object "error" is always a "Null Exception"...

 

Any suggestions ? Thanks for help.

 

Ben

 

Dec 22, 2010 at 7:53 PM

No suggestion ?...

:(

Coordinator
Dec 22, 2010 at 7:58 PM

Wait, there's no 2 hour SLA here :-)

With the current bits it depends.

If you want to modify the reponse to return a custom status code etc, then take the HttpResponseMessage as an input parameter. Make sure the name is "HttpResponseMessage" or I don't think it will match. Then you can modify the response. You don't have to return it as it is a reference.

Then set the status to Error, but don't set the exception. I "believe" that will work in the current bits.

In the near future you will be able to use WebFaultException<HttpResponseMessage> to achieve the same, but it doesn't work with the bits that are there now.

Thanks

Glenn

Dec 23, 2010 at 7:40 AM

How did you add the MessageInspector?

I don't find where to add it.

I am adding a route to my service on the Global.asax, with host configuration, but I don't see where to add my MessageInspector class.

 

Thanks,

Alon

Dec 23, 2010 at 8:08 AM
Edited Dec 23, 2010 at 8:32 AM

Hi Alon !

I create a custom WebHttpServiceHost and a custom factory for MyWebHttpServiceHost.

Finally, I got something like that :

 

public class MyWebHttpServiceHost : WebHttpServiceHost
    {
        Collection<MessageInterceptor> _msgReqInterceptors = new Collection<MessageInterceptor>();

        Collection<MessageInterceptor> _msgResInterceptors = new Collection<MessageInterceptor>();

 
        
        public MyWebHttpServiceHost(Type serviceType, HostConfiguration configuration, params Uri[] baseAddresses) : base(serviceType, configuration, baseAddresses)
        {

        }



        protected override void OnOpening()
        {
            base.OnOpening();
            this.Description.Behaviors.Insert(0, new MessageInspector(_msgReqInterceptors, _msgResInterceptors));           
        }

        public Collection<MessageInterceptor> RequestInterceptors { get { return this._msgReqInterceptors; } }

        public Collection<MessageInterceptor> ResponseInterceptors { get { return this._msgResInterceptors; } }
    }


public class MyWebHttpServiceHostFactory : WebHttpServiceHostFactory
    {

         HostConfiguration conf;
         public MyWebHttpServiceHostFactory(HostConfiguration processorFactory = null) : base(processorFactory)
         {
             this.conf = processorFactory;
         }

         protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
         {
             MyWebHttpServiceHost host = new MyWebHttpServiceHost(serviceType, this.conf, baseAddresses);

             // Request Interceptor
             host.RequestInterceptors.Add(new MyInterceptor());

             // Response Interceptor
             host.ResponseInterceptors.Add(new MyInterceptor2());
             
             return host;
         }
           
    }

 

"MyInterceptor" inherits of "MessageInterceptor". And On my global.asax I use my custom factory :

 

 

// Solution with Interceptors
            var route = new ServiceRoute("contact/", new MyWebHttpServiceHostFactory(configuration), typeof(ContactService));
            RouteTable.Routes.Add(route);

I hope it will help you. Good luck !


Ben

 

Edit : I forgot the "MessageInterceptor" class. I get it on the web (I don't remember where exactly now), but you can easily find it I think...

 

internal delegate void Process(ref System.ServiceModel.Channels.Message request, ref System.ServiceModel.Channels.Message message);
    public abstract class MessageInterceptor
    {
        Process process;
        public MessageInterceptor()
        {
            process = this.ProcessMessage;
        }

        public abstract void ProcessMessage(ref System.ServiceModel.Channels.Message request, ref System.ServiceModel.Channels.Message message);
    }


    public class MessageInspector : IDispatchMessageInspector, IServiceBehavior
    {

        Collection _msgReqInterceptors = new Collection();

        Collection _msgResInterceptors = new Collection();

        System.ServiceModel.Channels.Message _request;

        public MessageInspector(Collection reqestMessageInterceptors, Collection responseMessageInspector)
        {
            _msgReqInterceptors = reqestMessageInterceptors;
            _msgResInterceptors = responseMessageInspector;
        }

        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {

            _request = request;

            if (_msgReqInterceptors != null)
            {

                foreach (var msgInterceptor in _msgReqInterceptors)
                {
                    msgInterceptor.ProcessMessage(ref request, ref request);

                }
            }
            return null;

        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            if (_msgReqInterceptors != null)
            {
                foreach (var msgInterceptor in _msgResInterceptors)
                {
                    msgInterceptor.ProcessMessage(ref this._request, ref reply);
                }
            }
        }

        #endregion

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher endPointDispatcher in channelDispatcher.Endpoints)
                {
                    endPointDispatcher.DispatchRuntime.MessageInspectors.Add(
                    this);
                }

            }

        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }

        #endregion

    }

Dec 23, 2010 at 8:45 AM
Edited Dec 23, 2010 at 8:50 AM

Hi glenn,

Thanks for your help !

I try to do what you suggest :" taking the HttpResponseMessage as an input parameter". But it doesn't seems to work. Here Is my processor class :

public class CustomProcessor2 : Processor
    {

        private OperationType operationType;


        public CustomProcessor2(OperationType opeType, string idApp)
            : base()
        {
            this.operationType = opeType;
            this.InArguments[0].Name = idApp;
            this.InArguments[1].Name = "HttpResponseMessage";
        }

        public override ProcessorResult OnExecute(string input, HttpResponseMessage responseMessage)
        {
            //Make verifications
            
                // Error test
                responseMessage.Content = HttpContent.Create("One problem");
                responseMessage.StatusCode = System.Net.HttpStatusCode.Forbidden;

                return new ProcessorResult
                {
                    Status = ProcessorStatus.Error,
                };

            }
        }
        
    }

 

A null exception occurs on your channel. It 'seems that is on the "HttpPipelineFormatter" on this function (on the "for" loop).

 

 

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Response message cannot be disposed")]
        protected override void DeserializeRequest(HttpRequestMessage message, object[] parameters)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }

            var httpMessageProperty = new HttpMessageProperty() { Request = message, Response = new HttpResponseMessage() };
            MessageProperties.Add(HttpMessageProperty.Name, httpMessageProperty);

            var result = this.requestPipeline.Execute(GetRequestInArgumentValues(message, httpMessageProperty.Response).ToArray());
            for (int i = 0; i < parameters.Length; i++)
            {
                parameters[i] = result.Output[i];
            }
        }

 

Thanks.

Ben

 

PS : when is the "the near future" when we "will be able to use WebFaultException<HttpResponseMessage>" ?... :)

Dec 23, 2010 at 5:39 PM

Thank you TraPpeur it worked!!!!

:)

Coordinator
Dec 23, 2010 at 7:04 PM
Alternatively create either a service behavior or an operation behavior. Then you can simply annotate your service with the behavior (attribute)

On Dec 23, 2010, at 9:40 AM, "shmely" <notifications@codeplex.com> wrote:

From: shmely

Thank you TraPpeur it worked!!!!

:)