ASP.NET MVC 2 Optional URL Parameters

asp.net, asp.net mvc, code 0 comments suggest edit

If you have a model object with a property named Id, you may have run into an issue where your model state is invalid when binding to that model even though you don’t have an “Id” field in your form.

The following scenario should clear up what I mean. Suppose you have the following simple model with two properties.

public class Product {
    public int Id { get; set; }
    public string Name { get; set; }
}

And you are creating a view to create a new Product. In such a view, you obviously don’t want the user to specify the Id.

<% using (Html.BeginForm()) {%>

  <fieldset>
    <legend>Fields</legend>
        
        
    <div class="editor-label">
      <%= Html.LabelFor(model => model.Name) %>
    </div>
    <div class="editor-field">
      <%= Html.TextBoxFor(model => model.Name) %>
      <%= Html.ValidationMessageFor(model => model.Name) %>
    </div>
       
    <p>
      <input type="submit" value="Create" />
    </p>
  </fieldset>

<% } %>

However, when you post it to an action method like so:

[HttpPost]
public ActionResult Index(Product p)
{
    if (!ModelState.IsValid) {
        throw new InvalidOperationException("Modelstate not valid");
    }
    return View();
}

You’ll find that the model state is not valid. What gives!?

Well the issue here is that the Id property of Product is being set to an empty string. Why is that happening when there is no “Id” field in your form? The answer to that, my friend, is routing.

When you crack open a freshly created ASP.NET MVC 1.0 application, you’ll notice the following default route defined.

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = "" }
);

To refresh your memory, that’s a route with three URL parameters (controller, action, id), each with a default value (“home”, “index”, “”).

What this means is if you post a form to the URL /Home/Index, without specifying an “ID” in the URL, you’ll still have an empty string route value for the key “id”. And as it turns out, we use route values to bind to action method parameters.

In the scenario above, it just so happens that your model object happens to have a property with the same name, “Id”, as that route value, so the model binder attempts to set the value of the Id property to empty string, and since Id is a non-nullable int, we get a type conversion error.

This wouldn’t be so bad if “Id” wasn’t such a common name for properties. ;)

In ASP.NET MVC 2 RC 2, we added an MVC specific means to work around this issue via the new UrlParameter.Optional value. If you set the default value for a URL parameter to this special value, MVC makes sure to remove that key from the route value dictionary so that it doesn’t exist.

Thus the fix to the above scenario is to change the default route to:

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

With this in place, if there’s no ID in the URL, there won’t be a value for ID in the route values and thus we’ll never try to set a property named “Id” unless you have a form field named “Id”.

Note that this should be the default in the project templates for ASP.NET MVC 2 RTM.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

26 responses

  1. Avatar for Donn
    Donn February 11th, 2010

    Good tip. Thanks Phil

  2. Avatar for Felipe Lima
    Felipe Lima February 11th, 2010

    This information happens to be very handy to me right now. So far, I've been using [Bind(Exclude = "ID")] to workaround the problem.
    Now I know the reason for that annoying exception :)
    Thanks Phil

  3. Avatar for tomas
    tomas February 12th, 2010

    Nice! This does take care of a lot of ugly binding attributes like the ones Felipe uses (I do too...) in a much cleaner way.
    On the other hand, it could be even better - why not make the framework assume that value by convention, if no value at all is provided?
    Let me illustrate what I mean with an example:
    routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index" }
    );

    When the route is parsed, it produces the same results as using UrlParameter.Optional for id, since there is an {id} in the route that isn't referenced in the default values object.

  4. Avatar for ali62b
    ali62b February 12th, 2010

    "Note that this should be the default in the project templates for ASP.NET MVC 2 RTM."
    Is MVC 2 RC 2 the same as RTM in this case?

  5. Avatar for haacked
    haacked February 12th, 2010

    @tomas that would be bad because you didn't specify "Id" in the set of defaults, so how can we know that you didn't mean for it to be REQUIRED?
    If it's not in the Defaults dictionary, it means it is required.

  6. Avatar for haacked
    haacked February 12th, 2010

    @ali62b No, RC 2 is the second release candidate for MVC. I don't think the change to the default template was included in RC 2. But we will include it in the next release of MVC 2 which will be RTM.

  7. Avatar for Vijay Santhanam
    Vijay Santhanam February 12th, 2010

    I use the [Bind(Exclude="id")] attribute too. Will that still work and not show up as a model binding error?

  8. Avatar for Gleb Chermennov
    Gleb Chermennov February 12th, 2010

    Good convenience improvement, thanks for detailed explanation, Phil.

  9. Avatar for ChrisNTR
    ChrisNTR February 12th, 2010

    As Ryan pointed out on Twitter, the line
    "To refresh your memory, that’s a route with three URL parameters (home, action, id), each with a default value ("home", "index", "")."
    should be
    To refresh your memory, that’s a route with three URL parameters (url, action, id), each with a default value ("home", "index", "").
    Cheers,
    ChrisNTR

  10. Avatar for haacked
    haacked February 12th, 2010

    @ChrisNTR, thanks, I fixed it. But the parameters are actually "controller", "action", "id". :)

  11. Avatar for ChrisNTR
    ChrisNTR February 12th, 2010

    Doh! Oh course it is. I mean... I was testing you and you passed. Well done Phil!

  12. Avatar for Barbaros Alp
    Barbaros Alp February 13th, 2010

    Great feature, i couldnt get used to "exclude id" in every class.
    Thanks

  13. Avatar for seminyak villas
    seminyak villas February 14th, 2010

    thx...
    good article.

  14. Avatar for Koistya `Navin
    Koistya `Navin February 14th, 2010

    I would rather use a special char to mark optional parameters; like "?" in the following example:
    routes.MapRoute(
    "Default",
    "{controller}/{action}/{id?}",
    new { controller = "Site", action = "Home" }
    );
    BTW, I would also call default controller "Site" and default action "Home" - which means that it's a home page for a particular area of the website and not an index page.

  15. Avatar for ali62b
    ali62b February 16th, 2010

    Hi Phil just find that the change was made in MVC 2 RC 2 default project template.I found that when I was playing with item templates and project templates in VS 2008 and VS 2010 and wanted to copy the templates to the VS 2010 regarding folder to work with the updated version of MVC 2 RC 2 in VS 2010 RC.

  16. Avatar for Eduardo Mendes those
    Eduardo Mendes those February 16th, 2010

    very simply!! tnks

  17. Avatar for MikeC
    MikeC February 16th, 2010

    I just rebuilt my development box, but I appear to be missing something. I'm getting the following error on building my MVC2 project, which worked fine two days ago:


    The name 'UrlParameter' does not exist in the current context


    Did I forget to install something?
    Thanks!
    Mike

  18. Avatar for MikeC
    MikeC February 16th, 2010

    I had installed MVC2 RC, NOT MVC2 RC2. One little character made a lot of difference. :)

  19. Avatar for santhosh
    santhosh February 17th, 2010

    Hi,
    I have seen in RC 2 release notes says
    "The new UrlParameter type allows default values in routes to be removed after URL routing runs."
    But I still get the route values in the path.
    Here what i get:
    <div id="PartialView">
    <%Html.RenderAction("Partial"); %>
    </div>
    <% using (Ajax.BeginForm("Partial", new { name = "test" }, new AjaxOptions() { UpdateTargetId = "PartialView" }))
    { %>
    <button type="submit">
    Submit</button>
    <%} %>
    In Partial Page:
    <% using (Ajax.BeginForm("Partial", new AjaxOptions() { UpdateTargetId = "PartialView" }))
    { %>
    <%=Html.TextBox("test") %>
    <%} %>
    Output of the Partial View Ajax URL is:
    /Home/Partial?name=test
    Where the Route Values also included in the Ajax Path.
    Is this default behavior or defect...?
    Thanks, Santhosh

  20. Avatar for haacked
    haacked February 20th, 2010

    @Santosh That is by design. It doesn't remove the value from route data when generating URLs.
    UrlParameter.Optional is only used when matching incoming requests. It only removes the value if the url parameter is not included in the URL.

  21. Avatar for santhosh
    santhosh February 21st, 2010

    Hi,
    I have been using "UrlHelper.GenerateUrl()" to get the URL. But it gets only with RouteData as per my previous query.
    Is there any way to get the URL without RouteData value.
    Thanks,
    Santhosh.

  22. Avatar for James
    James February 25th, 2010

    Hmmm I think using the following url pattern is much simplier and solves a few problems.

    ~/pages/{id}/{slug}
    where
    ~/pages/{id}
    Redirects to the previous route.

  23. Avatar for DorianT
    DorianT March 11th, 2010

    @MikeC
    Make sure the assembly you're referencing is System.Web.Mvc v2.0.50727.
    I had that same error you were getting, but I noticed the mvc 2 project was referencing a v1.0 of that assembly.

  24. Avatar for iqworks
    iqworks March 13th, 2010

    Instead of "Id", I have a field in my View called email. If I leave it blank in the View, in MVC1 this comes into my controller as "", but with MVC2 it now comes in as NULL. I am wondering what the reason is for that ?

  25. Avatar for Raj
    Raj March 6th, 2013

    Hi sir ,How can get empty texbox value in MVC2 application,I explain my problem
    One registration form  some fields are non required so that fields are not validated.At the value taken time(for inserting  to db)If the non required fields are null when error occured,How can solve that problem ?

  26. Avatar for haacked
    haacked March 6th, 2013

    Hi Raj, I'm not sure I understand the question. Perhaps ask it on http://stackoverflow.com/ and post the link to the question here.