ASP.NET MVC 2 Optional URL Parameters

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.

What others have said

Requesting Gravatar... Donn Feb 12, 2010 10:17 AM
# re: ASP.NET MVC 2 Optional URL Parameters
Good tip. Thanks Phil
Requesting Gravatar... Felipe Lima Feb 12, 2010 10:41 AM
# re: ASP.NET MVC 2 Optional URL Parameters
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
Requesting Gravatar... tomas Feb 12, 2010 12:32 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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.
Requesting Gravatar... ali62b Feb 12, 2010 12:50 PM
# re: ASP.NET MVC 2 Optional URL Parameters
"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?
Requesting Gravatar... haacked Feb 12, 2010 12:52 PM
# re: ASP.NET MVC 2 Optional URL Parameters
@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.
Requesting Gravatar... haacked Feb 12, 2010 12:53 PM
# re: ASP.NET MVC 2 Optional URL Parameters
@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.
Requesting Gravatar... Vijay Santhanam Feb 12, 2010 1:40 PM
# re: ASP.NET MVC 2 Optional URL Parameters
I use the [Bind(Exclude="id")] attribute too. Will that still work and not show up as a model binding error?


Requesting Gravatar... Gleb Chermennov Feb 12, 2010 3:09 PM
# re: ASP.NET MVC 2 Optional URL Parameters
Good convenience improvement, thanks for detailed explanation, Phil.
Requesting Gravatar... ChrisNTR Feb 12, 2010 7:35 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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
Requesting Gravatar... haacked Feb 12, 2010 8:52 PM
# re: ASP.NET MVC 2 Optional URL Parameters
@ChrisNTR, thanks, I fixed it. But the parameters are actually "controller", "action", "id". :)
Requesting Gravatar... ChrisNTR Feb 13, 2010 3:58 AM
# re: ASP.NET MVC 2 Optional URL Parameters
Doh! Oh course it is. I mean... I was testing you and you passed. Well done Phil!
Requesting Gravatar... Barbaros Alp Feb 14, 2010 3:09 AM
# re: ASP.NET MVC 2 Optional URL Parameters
Great feature, i couldnt get used to "exclude id" in every class.

Thanks
Requesting Gravatar... seminyak villas Feb 14, 2010 9:59 PM
# re: ASP.NET MVC 2 Optional URL Parameters
thx...

good article.
Requesting Gravatar... Koistya `Navin Feb 15, 2010 10:59 AM
# ASP.NET MVC 2 Optional URL Parameters - Special Mark
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.
Requesting Gravatar... ali62b Feb 16, 2010 11:51 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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.
Requesting Gravatar... Eduardo Mendes those Feb 17, 2010 3:03 AM
# re: ASP.NET MVC 2 Optional URL Parameters
very simply!! tnks
Requesting Gravatar... MikeC Feb 17, 2010 6:38 AM
# re: ASP.NET MVC 2 Optional URL Parameters
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
Requesting Gravatar... MikeC Feb 17, 2010 6:57 AM
# Solved my own problem
I had installed MVC2 RC, NOT MVC2 RC2. One little character made a lot of difference. :)
Requesting Gravatar... santhosh Feb 17, 2010 7:50 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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
Requesting Gravatar... haacked Feb 20, 2010 10:50 PM
# re: ASP.NET MVC 2 Optional URL Parameters
@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.
Requesting Gravatar... santhosh Feb 21, 2010 11:07 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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.
Requesting Gravatar... James Feb 25, 2010 3:15 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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.
Requesting Gravatar... DorianT Mar 12, 2010 7:36 AM
# re: ASP.NET MVC 2 Optional URL Parameters
@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.
Requesting Gravatar... iqworks Mar 13, 2010 1:43 PM
# re: ASP.NET MVC 2 Optional URL Parameters
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 ?

What do you have to say?

(will show your gravatar)
Please add 3 and 8 and type the answer here: