简体   繁体   中英

WCF UriTemplate UrlEncode

Imagine I want to call a external (meaning I have no control over the contract) REST service using WCF. I have the following contract

[ServiceContract]
public interface ISomeRestApi
{
    [OperationContract]
    [WebInvoke(Method = "PUT", UriTemplate = "blablabla/{parameter1}/{parameter2}")]
    void PutSomething(string parameter1, string parameter2);
}

Say that one of my parameters is a forward slash (/)

public class Test{

    [Fact]
    public void TestPutSomething()
    {
        ISomeRestApi api = CreateApi();

        //this results in the url: http://server/blablabla///someotherparam
        api.PutSomething("/", "someotherparam");

        //this also results in the url: http://server/blablabla///someotherparam
        api.PutSomething(HttpUtility.UrlEncode("/"), "someotherparam");

        //but i want: http://server/blablabla/%2F/someotherparam
    }
}

How do I force WCF to UrlEncode my UriTemplate path parameter?

With lots of trial and error I found a very ugly and totally illogic solution to my problem. But still... Maybe this post can help someone in the future. Note that this "solution" works for me in .NET 4.5. I do not guarantee it'll work for you.

The problem comes down to this:

  • it's impossible (AFAIK) to put a escaped forward slash in a Uri in .NET
  • for communicating with an external service (RabbitMQ) I really need to be able put %2f (ie forward slash) in my request Url

The following post put me in the "right" direction: How to stop System.Uri un-escaping forward slash characters

I tried the solution proposed in the post, but... to no avail

Then after lots of cursing, googling, reverse engineering and so forth i came up with the following piece of code:

/// <summary>
/// Client enpoint behavior that enables the use of a escaped forward slash between 2 forward slashes in a url
/// </summary>
public class EncodeForwardSlashBehavior:IEndpointBehavior
{
    public void Validate(ServiceEndpoint endpoint)
    {

    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {

    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {

    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(new ForwardSlashUrlInspector());
    }
}

/// <summary>
/// Inspector that modifies a an Url replacing /// with /%2f/
/// </summary>
public class ForwardSlashUrlInspector:IClientMessageInspector
{
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        string uriString = request.Headers.To.ToString().Replace("///", "/%2f/");
        request.Headers.To = new Uri(uriString);
        AddAllowAnyOtherHostFlagToHttpUriParser();

        return null;
    }

    /// <summary>
    /// This is one of the weirdest hacks I ever had to do, so no guarantees can be given to this working all possible scenarios
    /// What this does is, it adds the AllowAnyOtherHost flag to the private field m_Flag on the UriParser for the http scheme.
    /// Replacing /// with /%2f/ in the request.Headers.To uri BEFORE calling this method will make sure %2f remains unescaped in your Uri
    /// Why does this work, I don't know!
    /// </summary>
    private void AddAllowAnyOtherHostFlagToHttpUriParser()
    {
        var getSyntaxMethod =
            typeof(UriParser).GetMethod("GetSyntax", BindingFlags.Static | BindingFlags.NonPublic);
        if (getSyntaxMethod == null)
        {
            throw new MissingMethodException("UriParser", "GetSyntax");
        }
        var uriParser = getSyntaxMethod.Invoke(null, new object[] { "http" });

        var flagsField =
            uriParser.GetType().BaseType.GetField("m_Flags", BindingFlags.Instance|BindingFlags.NonPublic);
        if (flagsField == null)
        {
            throw new MissingFieldException("UriParser", "m_Flags");
        }
        int oldValue = (int)flagsField.GetValue(uriParser);
        oldValue += 4096;
        flagsField.SetValue(uriParser, oldValue);
    }


    public void AfterReceiveReply(ref Message reply, object correlationState)
    {

    }
}

So basically I'm creating a custom EndpointBehavior that uses reflection to add an enum flag to a private variable inside the UriParser. This apparently prevents the escaped forward slash in my request.Headers.To uri from being unescaped.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM