Web API 2.2, Is there an easy way to update the HttpRequestMessage's QueryString in a DelegatingHandler?

1.6k views Asked by At

I've created a custom MessageHandler as such:

public class WebAPICustomMessageHandler : DelegatingHandler {

  protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
  //Parse QueryString
    NameValueCollection queryString = HttpUtility.ParseQueryString(request.RequestUri.Query);

    if (queryString != null) {
    //Find my token
      String token = queryString.Get("qsVariable");

      if (!String.IsNullOrWhiteSpace(token)) {
      //Remove token so it doesn't impact future handlers / controllers / etc.
        queryString.Remove("qsVariable");
        request.RequestUri.Query = queryString.ToString(); //How can we modify the querystring? apparently it's readonly?

      //Append token as custom header to the request
        request.Headers.Add("token", new String[] { token });
      }
    }

    HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

    return response;
  }

}

It appears that I can't directly change the QueryString, which is a bit odd as I thought that the entire point of custom message handlers in the Web API pipeline was to allow this exact sort of thing. The ability issue pre and post operations on request/response, including things like data scrubbing / injection etc.

I already know I can do what I want very easily utilizing OWIN (as I've already done it), but now I'm trying to do this without OWIN. Am I going to have to create an entirely new HttpRequestMessage in order to just change the QueryString? From the looks of it, I'll have to build a new Uri, then construct the HttpRequestMessage, then copy over each and every other piece from the original.

Is that the only way to do this? or is there a better way that I'm just not aware of?

  • Please note, that other routines later in the pipeline are setup to use a token found in the header of the request, but setting the header is not possible if the request came from a submission to an iframe, which is where the process above comes into place. The token is added to the querystring, then converted to a header to prevent changes to the rest of the pipeline.
1

There are 1 answers

1
Ben Ripley On

You're correct, the query string is read-only. Just use a standard redirect with the substituted query string values.

public class WebAPICustomMessageHandler : DelegatingHandler {

  protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
    var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
    var token = query["qsVariable"];

    // just 'short-circuit' if our token was not found...
    if (token == null) 
      return await base.SendAsync(request, cancellationToken);

    token = "newValue"; // modify your value here...
    query["token"] = token;

    // redirect with new query string...
    var response = request.CreateResponse(HttpStatusCode.Redirect);
    var uri = request.RequestUri;
    var ub = new UriBuilder(uri.Scheme,
      uri.Host,
      uri.Port,
      uri.AbsolutePath);
    ub.Query = query.ToString();
    response.Headers.Location = ub.Uri;
    return response;
  }
}

For interest sake, it is possible to modify the query string even though it is read-only but I would avoid this practice.