4
votes

I have a webforms project, and am attempting to run some code that allows me to make a call to an MVC route and then render the result within the body of the web forms page.

There are a couple of HttpResponse/Request/Context wrappers which I use to execute a call to an MVC route, e.g.:

private static string RenderInternal(string path)
{
  var responseWriter = new StringWriter();

  var mvcResponse = new MvcPlayerHttpResponseWrapper(responseWriter, PageRenderer.CurrentPageId);
  var mvcRequest = new MvcPlayerHttpRequestWrapper(Request, path);
  var mvcContext = new MvcPlayerHttpContextWrapper(Context, mvcResponse, mvcRequest);

  lock (HttpContext.Current)
  {
    new MvcHttpHandlerWrapper().PublicProcessRequest(mvcContext);
  }

  ...

The code works fine for executing simple MVC routes, for e.g. "/Home/Index". But I can't specify any query string parameters (e.g. "/Home/Index?foo=bar") as they simply get ignored. I have tried to set the QueryString directly within the RequestWrapper instance, like so:

public class MvcPlayerHttpRequestWrapper : HttpRequestWrapper
{
  private readonly string _path;
  private readonly NameValueCollection query = new NameValueCollection();

  public MvcPlayerHttpRequestWrapper(HttpRequest httpRequest, string path)
    : base(httpRequest)
  {
    var parts = path.Split('?');

    if (parts.Length > 1)
    {
      query = ExtractQueryString(parts[1]);
    }

    _path = parts[0];
  }

  public override string Path
  {
    get
    {
      return _path;
    }
  }

  public override NameValueCollection QueryString
  {
    get
    {
      return query;
    }
  }

  ...

When debugging I can see the correct values are in the "request.QueryString", but the values never get bound to the method parameter.

Does anyone know how QueryString values are used and bound from an http request to an MVC controller action?

It seems like the handling of the QueryString value is more complex than I anticipated. I have a limited knowledge of the internals of the MVC Request pipeline.

I have been trying to research the internals myself and will continue to do so. If I find anything I will update this post appropriately.

I have also created a very simple web forms project containing only the code needed to produce this problem and have shared it via dropbox: https://www.dropbox.com/s/vi6erzw24813zq1/StackMvcGetQuestion.zip The project simply contains one Default.aspx page, a Controller, and the MvcWrapper class used to render out the result of an MVC path. If you look at the Default.aspx.cs you will see a route path containing a querystring parameter is passed in, but it never binds against the parameter on the action.

As a quick reference, here are some extracts from that web project.

The controller:

public class HomeController : Controller
{
  public ActionResult Index(string foo)
  {
    return Content(string.Format("<p>foo = {0}</p>", foo));
  }
}

The Default.aspx page:

protected void Page_Load(object sender, EventArgs e)
{
  string path = "/Home/Index?foo=baz";

  divMvcOutput.InnerHtml = MvcWrapper.MvcPlayerFunctions.Render(path);
}

I have been struggling with this for quite a while now, so would appreciate any advice in any form. :)

2
I assume you have debug access to the MVC method? if so, check the URL of the request when it is received, as well as the ValueProvider of the controller, to see what data it has received.JTMon
Well the request is an instance that I created which wraps the incoming request for the webforms default.aspx page. I debugged and checked that the QueryString NameValueCollection did in fact contain the "foo" variable within it, which it does.ctrlplusb

2 Answers

0
votes

MVC framework will try to fill the values of the parameters of the action method from the query string (and other available data such as posted form fields, etc.), that part you got right. The part you missed is that it does so by matching the name of the parameter with the value names passed in. So if you have a method MyMethod in Controller MyController with the signature:

public ActionResult MyMethod(string Path)
{
    //Some code goes here
}

The query string (or one of the other sources of variables) must contain a variable named "Path" for the framework to be able to detect it. The query string should be /MyController/MyMethod?Path=Baz

0
votes

Ok. This was a long debugging session :) and this will be a long response, so bear with me :)

First how MVC works. When you call an action method with input parameters, the framework will call a class called "DefaultModelBinder" that will try and provide a value for each basic type (int, long, etc.) and instance of complex types (objects). This model binder will depend on something called the ValueProvider collection to look for variable names in query string, submitted forms, etc. One of the ValueProviders that interests us the most is the QueryStringValueProvider. As you can guess, it gets the variables defined in the query string. Deep inside the framework, this class calls HttpContext.Current to retrieve the values of the query string instead of relying on the ones being passed to it. In your setup this is causing it to see the original request with localhost:xxxx/Default.aspx as the underlying request causing it to see an empty query string. In fact inside the Action method (Bar in your case) you can get the value this.QueryString["variable"] and it will have the right value. I modified the Player.cs file to use a web client to make a call to an MVC application running in a separate copy of VS and it worked perfectly. So I suggest you run your mvc application separately and call into it and it should work fine.