12

We have an HTTP Module that decodes all encoded requests. It works great with all WCF requests, but NOT in Web Api requests- in Web Api the request (both POST and GET) gets to the service still encoded

I see that it Hits the HTTP Module but,again,still gets to the service encoded. How can i fix it? or what am i doing wrong? i know that its better to work with Message Handlers in Web Api, but HTTP Modules suppose to work too- no?

HTTP Module:

public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.EndRequest += context_PreSendRequestContent;
    }

    void context_PreSendRequestContent(object sender, EventArgs e)
    {
        string encodedQuerystring = HttpContext.Current.Request.QueryString.ToString();
        if (!string.IsNullOrEmpty(encodedQuerystring))
        {
            System.Collections.Specialized.NameValueCollection col = new System.Collections.Specialized.NameValueCollection();
            col.Add("q", encodedQuerystring);
            WebFunction.CreateQuerystring(HttpContext.Current, col);
        }


    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        string encodedQueryString = String.Empty;
        if (HttpContext.Current.Request.QueryString.Count > 0 && HttpContext.Current.Request.QueryString["q"] != null)
        {

            object _p = HttpContext.Current.Request.QueryString;
            encodedQueryString = HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.QueryString["q"].ToString());

            string originalQueryString = HttpContext.Current.Server.UrlDecode(WebFunction.Base64Decode(encodedQueryString));

            if (!string.IsNullOrEmpty(originalQueryString))
            {
                WebFunction.CreateQuerystring(HttpContext.Current, WebFunction.ConvertQueryToCollection(originalQueryString));

            }
        }
    }

WebFunction:

 public static void CreateQuerystring(HttpContext context, System.Collections.Specialized.NameValueCollection nameValueCollection)
    {
        // reflect to readonly property
            PropertyInfo isreadonly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
            // make collection editable
            isreadonly.SetValue(context.Request.QueryString, false, null);
            context.Request.QueryString.Clear();
            context.Request.QueryString.Add(nameValueCollection);         
            // make collection readonly again
            isreadonly.SetValue(context.Request.QueryString, true, null);            
    }

Web Api:

  public class NamesController : ApiController
{
    [HttpGet]
    [ActionName("GET_NAMES")]
    public Drugs_ResponseData Get(string q)
    {
//need to add the decode function to get it to work
        string[] arrAmpersant = Commonnn.DecodeFrom64(q).Split('&');

        Names_obj = new Names();
        return _obj.GetResult(Convert.ToInt32(Commonnn.GetValFromEqual(arrAmpersant[0])));
    }
}
DasDas
  • 571
  • 3
  • 9
  • 32
  • where did you register your http module? If you registered inside ``, your application pool must run in integrated mode. – Khanh TO Feb 28 '16 at 02:04
  • @KhanhTO it is in , and it runs in integrated mode. it does get to the HTTP Module but it still gets encoded to the request. – DasDas Feb 28 '16 at 08:28
  • May I ask in how many places the encoded querystring is being used per request and how many visitors hit the page per hour? – Asons Mar 03 '16 at 08:39

5 Answers5

2

It seems that the Web API doesn't use the QueryString collection in the request, but it parses the URL itself.

See the GetQueryNameValuePairs method in this file - they take the Uri and parse its Query property.

So you have two options to do that:

  1. The dirty one is to change the Uri of the request in your the HTTP module. I don't know whether it's possible, but some reflection could do the trick.
  2. The nicer way would be to use the Web API message handler.
Emzor
  • 1,380
  • 17
  • 28
Tomáš Herceg
  • 1,595
  • 1
  • 13
  • 18
  • Do you know if there is anything structural to IIS' default request pipeline that would prevent a POST with an Encoded body from being delivered through my application's DelegatingHandlers? – K. Alan Bates Jun 13 '17 at 16:47
2

May I suggest you use Context.Items and let the QueryString have the encoded version.

It's a not very well known built in key/value dictionary which last throughout a request where you easily store any object and then share it between module, handlers, etc.

Using this would very like give you a better performance than unlocking the QueryString object, but more importantly, you process the value in one place and reuse it in many, and when needed, you just add a second value, the complete QueryString collection or any other value you want to share across a request.

void context_BeginRequest(object sender, EventArgs e)
{
    string encodedQueryString = String.Empty;
    if (HttpContext.Current.Request.QueryString.Count > 0 && HttpContext.Current.Request.QueryString["q"] != null)
    {

        string encodedQueryString = HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.QueryString["q"].ToString());

        HttpContext.Current.Items("qs_d") = HttpContext.Current.Server.UrlDecode(WebFunction.Base64Decode(encodedQueryString));

    }
}

Web Api:

public class NamesController : ApiController
{
  [HttpGet]
  [ActionName("GET_NAMES")]
  public Drugs_ResponseData Get(string q)
  {
    string[] arrAmpersant = Commonnn.DecodeFrom64(HttpContext.Current.Items("qs_d").ToString()).Split('&');

    Names_obj = new Names();
    return _obj.GetResult(Convert.ToInt32(Commonnn.GetValFromEqual(arrAmpersant[0])));
  }
}

Side note: I see you call HttpContext.Current.Server.UrlDecode twice. I don't think you need that unless your Base64Decode method encode the value again.

Asons
  • 84,923
  • 12
  • 110
  • 165
1

You can handle like this

protected void Application_BeginRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            string path = app.Context.Request.Url.PathAndQuery;
            int pos = path.IndexOf("?");
            if (pos > -1)
            {
                string[] array = path.Split('?');
                app.Context.RewritePath(array[0]+"?"+ HttpContext.Current.Server.UrlDecode(array[1]));
            }
        }
Sercan Timoçin
  • 658
  • 5
  • 11
1

Adding on to @Tomáš Herceg 's answer, I would implement a Web Api message handler rather than modifying your HttpModule to accommodate Web Api.

public class DecodeQueryStringMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            if (request.Method == HttpMethod.Get)
            {
                var originalQueryString = request.RequestUri.Query;
                if (!string.IsNullOrEmpty(originalQueryString))
                {
                    var ub = new UriBuilder(request.RequestUri) { Query = HttpUtility.UrlDecode(originalQueryString) };
                    request.RequestUri = ub.Uri;
                }
            }

            return base.SendAsync(request, cancellationToken);
        }
    }
Nick
  • 324
  • 1
  • 2
  • 4
0

It is possible, but you will need reflection, what means that exist a risk here. Please, let me suggest you what I consider to be a more clean solution after the solution.

Solution

if (!string.IsNullOrEmpty(originalQueryString))
{
   var request = HttpContext.Current.Request;
   request.GetType().InvokeMember("QueryStringText", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, request, new[] { "q=" + originalQueryString });
   //WebFunction.CreateQuerystring(HttpContext.Current, WebFunction.ConvertQueryToCollection(originalQueryString));
}

This will update the following properties of the Request:

Request.Param  
Request.QueryString  
Request.ServerVariables  
Request.Url

but will not update the:

Request.RawUrl

Cleaner Solution

IIS URL Rewrite Module
http://www.iis.net/learn/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module

  • This looked like an interesting approach, though when I tried, the new query string is still encoded? ... Is it suppose to or did I miss something here, because the OP is looking for a decoded query string. – Asons Mar 05 '16 at 22:55
  • My steps were: 1 - Created a WebAPI solution; 2 - Created a module that gets que queryString "q", decoded it and ran the code I posted here. 3 - Modified the ValuesController created to receive a string parameter q and return it. Before registering the module the api return the string encoded, after, the string returned is decoded. – Daniel Frederico Lins Leite Mar 06 '16 at 12:04