views:

472

answers:

2

First, a little bit of context (cause I know you all love it!):

I have a model called Note. It has a field called Type which, in the database, is an integer value that designates which type of object this note belongs to (1 = Customer, 2 = Sales Lead, etc). A second field called Source designates the ID of the specific object that this note belongs to.

For example, if I wanted to add a note to Customer #34, I would set Type to 1 and Source to 34.

To help make things a little more readable, I have an Enum called DataObjectType that contains a mapping for each of the possible Type integer values to a friendly name. So, for example, DataObjectType.Customer is the same as a value of 1 in the Type field.

Next, I have a custom route:

/Notes/New/{type}/{id}

and two controller actions to handle the creation of a new note:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult New(DataObjectType type, int id) {
    Note note = new Note { Type = type, Source = id };
    return View(note);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Note note) {
    // This is just an example
    note.Persist();
    return View("New", note);
}

First of all, I need to make sure that the value supplied to the {type} route value is valid for the DataObjectType enum. Can this be done with a route constraint? At the moment, the type argument of New() will accept any integer, whether it exists in DataObjectType or not.

Second, I need to make sure that the object the user is trying to add the note to actually exists in the database before showing the New Note form at all. I could do this in the New() method easily, but there may come a time when a user posts the data directly to Create() without ever visiting New(). In such a case, I also need to verify that the object exists in the Create() method. What's the best way to do this in both methods without duplicating code?

A: 

When setting up your routes, there's a parameter you can use to specify constraints (done via Regex). You could use this to restrict the id there, but you would have to create a separate route for this scenario because you wouldn't want all "id"s to be filtered on that rule.

Alternatively, you could just stick this check at the beginning of your controller action.

As for ensuring that the note already exists in the db, I can't see any way around sticking a check at the beginning of the Create action.

Kevin Pang
A: 

You are correct that both the New and the Create actions need to ensure that they have a valid model passed to them. The correct place to do this is in a model binder. The model binder should add a model state error if an invalid object (or Id) is passed. New and Create can likely use the same model binder(s).

Don't confuse route constraints with user input validation. Route constraints simply help the routing system find the appropriate route when you don't specify the route yourself (i.e., when you use Html.ActionLink instead of RouteLink). If you use named routes, and you should, then the constraint is not needed to find the appropriate route, because you have specified the route directly. Route constraints are intended to prevent a certain route from being selected for actions it is not designed to handle, and are not the place to validate user input.

The feature you are asking for is a kind of user input validation, even though user input in question happens to be in the URL. If you are going to go beyond this simple, single case, you might want to look into using a validation framework, as this will make other types of validation much simpler. Here is one which looks interesting to me.

Craig Stuntz