views:

557

answers:

8

We are having this problem with a controller right now; the controller looks like this:

public class AccountsController:Controller {
    public ActionResult List(int? page, int? pageSize, string keywords) {...}
}

We are posting to this page via jquery:

$.post("/myapp/Accounts/List",
       {"page":0,"pageSize":10,"keywords":"asdf"}, 
       updategrid,
       "json");
...
function updategrid(result) {...}

Inside the action: Request.Form["keywords"] == "asdf", but keywords=="" and here I am at a loss. Why doesn't keywords have the value we want?

A: 

Dont you want to pass the values on the URL and let the routing of MVC take care of passing the values? Not pass as actual variables?

CSharpAtl
Perhaps I should have been more clear:pageSize is definitely being passed in (inside the action it has the expected value of 10)I am sending the arguments in as part of a form post. MVC does take care of moving all my values from the form into the method arguments, except for this string value.
Bill Barry
Might want to check the routing for that particular action
CSharpAtl
can you show your registered routes?
CSharpAtl
the actual url rendered is ResolveUrl("~/API/Accounts/List") and the only registered route is: routes.MapRoute("Default", "API/{Controller}/{Action}");
Bill Barry
could try to register a route specifically for that one to see if it comes through properly.
CSharpAtl
A: 

If you are posting the values, you could just use a FormCollection..

public class AccountsController:Controller {
public ActionResult List(int? page, FormCollection collection) {

  string keywords=collection["keywords"];
  ....
}

}

Also, for the post, you could append the id to the url and put the other params in json. I also do not think it is necessary to put your property names in quotes in json..

$.post("/myapp/Accounts/List/"+page,
   {pageSize:10,keywords:"asdf"}, 
   updategrid,
   "json");

... function updategrid(result) {...}

... or as others suggested, you could create a route that has all the possible parameters ..

markt
I could, but I would like to move away from magic strings if I can. The jquery post will be generated from an expression that pretends to actually call the controller.
Bill Barry
Not sure what you mean by 'magic strings' and pretending to call the controller.. could you explain?
markt
Magic strings are strings that actually represent code, but do not get compiled (instead they are evaluated at runtime). Instead, I can "pretend" to call the action in my views in order to generate links with a command like <%=LinkToActions.RenderJQueryPostJson<AccountsController>(c=>c.List(0,10,"asdf"), "updategrid")%>The method takes an expression tree and generates the necessary javascript into my view. Doing this, I avoid runtime errors caused by doing things like renaming parameters in the action (or for that matter adding new ones because my views will not compile).
Bill Barry
note: I use spark for my views so that I can compile them as part of my build and it finds errors like these.
Bill Barry
Interesting.. I hadn't seen spark before. I will check it out. Thanks.
markt
A: 

Check you have a route that match this action:

routes.MapRoute(
                "Default",                                              // Route name
                "myApp/Accounts/List/{page}/{pageSize}/{keywords}",                           // URL with parameters
                new { controller = "Accounts", action = "List", page = 1,pageSize=10,keywords=""}  // Parameter defaults
            );
Marwan Aouida
Even if he did have such a route defined, Form post values should take precedence. The priority is: 1) Form post values, 2) Route variables, 3) query string variables. Something else must be amiss...
DSO
P.S. I did not downvote this, I think its a valid thing to ask.
DSO
A: 

Using this attribute:

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class StringParamFilterAttribute:ActionFilterAttribute {
    public string Param { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        filterContext.ActionParameters[Param] = filterContext.HttpContext.Request[Param];
    }
}

I was able to put an attribute on the action in order to give this parameter to the method:

[StringParamFilter(Param="keywords")]
public ActionResult List(int? page, int? pageSize, string keywords) {...}

and now keywords has the desired value

Bill Barry
Now that I have it working, the only question I have left is why is it this way (it seems that everything other than strings will be retrieved)?
Bill Barry
I understand this works for you, but it just doesn't seem right...
Gidon
I am with Gidon....something seems strange about this "solution"
CSharpAtl
I agree (I wouldn't have asked the question if it felt right).
Bill Barry
Is it possible for you to publish your registered routes?
CSharpAtl
The actual url rendered is ResolveUrl("~/API/Accounts/List") and the only registered route is: routes.MapRoute("Default", "API/{Controller}/{Action}");
Bill Barry
A: 

Since the attribute specified above works, have you checked to compare the contents of the HttpContext.Request.Form collection against the MVC-Binder-Bound FormCollection object.? Seems like there's a difference between the two sets. Also, try other ways of accessing the FormCollection - Get(), GetValue() ... check AllKeys for the key you're interested in???

Also, with regards to the route-registering idea ...

If you don't have the route myApp/Accounts/List/{page}/{pageSize} registered and the pagesize is still coming through, it stands to reason that there isn't a need to register a myApp/Accounts/List/{page}/{pageSize}/{keywords} route either.

feemurk
A: 

That is very odd, because the code on my machine works. What happens when you request the URL directly from a browser (without the jQuery .post() call) like so:

/myapp/Accounts/List?page=0&pageSize=10&keywords=asdf

That generates a GET request (instead of the POST request generated by the jQuery .post() method -- but the action method parameters should be populated nonetheless).

For the purposes of debugging, you might want to change your action method to something like:

public ActionResult List(int? page, int? pageSize, string keywords)
{
     return Content(String.Format("page = {0}, pageSize = {1}, keywords = {2}",
                    page, pageSize, keywords));
}

If that works, the next step would be to test your jQuery call with something like (changing the returned data format from JSON to text):

 $.post('/myapp/Accounts/List',
        { 'page' : 0, 'pageSize' : 10, 'keywords' : 'asdf' },
        function(result) {
           alert(result);
        },
        'text');

Everything above worked correctly for me, so unless I'm missing something... I'm puzzled why it didn't work for you? Where did I go wrong?

GuyIncognito
A: 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class JsonParametersFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string contentType = filterContext.HttpContext.Request.ContentType;

            if (string.IsNullOrEmpty(contentType))
                return;

            if (!contentType.Contains("application/json"))
                return;

            string paramValue;

            using (var reader = new StreamReader(filterContext.HttpContext.Request.InputStream))
                paramValue = reader.ReadToEnd();

            var serializer = new JavaScriptSerializer();
            var rawResult = (IDictionary<string, object>)serializer.DeserializeObject(paramValue);

            var keys = new List<string>(filterContext.ActionParameters.Keys);

            foreach (var key in keys)
            {
                object value = null;

                if (rawResult.TryGetValue(key, out value))
                {
                    filterContext.ActionParameters[key] = value;
                }
            }
        }
    }

This will attempt to populate all parameters values from inputstream.

Taliesin
A: 

quoting answer provided as comment from Mark Worth...

How is your controller being instantiated? I had this problem and I found that it was my SpringControllerFactory creating my controllers as singletons (and hence was always using the values from the first request). – Mark Worth May 19 at 8:19

That's it! My controller was registered as a singleton into my Windsor container so my WindsorControllerFactory returned a singleton. – Bill Barry May 19 at 18:14

Bill Barry