views:

110

answers:

1

I'm working on my first ASP.NET MVC (beta for version 3) application (using EF4) and I'm struggling a bit with some of the conventions around saving a new record and updating an existing one. I am using the standard route mapping.

When the user goes to the page /session/Evaluate they can enter a new record and save it. I have an action defined like this:

[ActionName("Evaluate")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EvaluateSave(EvaluteSessionViewModel evaluatedSession)
{
}

When they save I grab an entity off the view model and attach it to my context and save. So far, so good. Now I want the user to be able to edit this record via the url /session/Evaluate/1 where '1' is the record ID.

Edit: I have my EF entity attached as a property to the View Model.

If I add an overloaded method, like this (so I can retrieve the '1' portion automatically).

[ActionName("Evaluate")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EvaluateSave(ID, EvaluteSessionViewModel evaluatedSession)
{
}

I get an "The current request for action 'Evaluate' on controller type 'SessionsController' is ambiguous between the following action" error. I'm not sure why they're ambiguous since they look unique to me.

I decided that I was just going to skip over this issue for now and see if I could get it to update an existing record, so I commented out the EvaluateSave that didn't have the ID parameter.

What I'd like to do is this:

// Load the original entity from EF
// Rebind the postback so that the values posted update the entity
// Save the result

Since the entity is populated as the parameter (evaluatedSession) the rebinding is happening too soon. But as I look at the approach I'd like to take I realized that it opens my code up to hacking (since a user could add in fields into the posted back page and these could override the values I set in the entity).

So it seems I'm left with having to manually check each field to see if it has changed and if it has, update it. Something like this:

if (evaluatedSession.MyEntity.myField <> savedSession.myField)
   savedSession.myField = evaluatedSession.MyEntity.myField;

Or, save a copy of the entity and make sure none of the non-user editable ones have changed. Yuck.

So two questions:

First: how do I disambiguate the overloaded methods?

Second: is there a better way of handling updating a previously saved record?

Edit: I guess I could use something like Automapper...

Edit 9/22/2010 - OK, it looks like this is supposed to work with a combination of two items: you can control what fields bind (and specifically exclude some of them) via the [Bind(Exclude="field1,field2")] attribute either on the class level or as part of the method doing the saving, ex.

public ActionResult EvaluateSave([Bind(Exclude="field1")] EvaluateSessionViewModel evaluatedSession)

From the EF side of things you are supposed to be able to use the ApplyCurrentValues() method from the context, ex.

context.ApplyCurrentValues(savedEval.EntityKey.EntitySetName, evaluatedSession);

Of course, that doesn't appear to work for me. I keep getting "An object with a key that matches the key of the supplied object could not be found in the ObjectStateManager. Verify that the key values of the supplied object match the key values of the object to which changes must be applied.".

I tried attaching the original entity that I had just loaded, just in case it wasn't attached to the context for some reason (before ApplyCurrentValues):

context.AttachTo(savedEval.EntityKey.EntitySetName, savedEval);

It still fails. I'm guessing it has something to do with the type of EF entity object MVC creates (perhaps it's not filled in enough for EF4 to do anything with it?). I had hoped to enable .NET framework stepping to walk through it to see what it was attempting to do, but it appears EF4 isn't part of the deal. I looked at it with Reflector but it's a little hard for me to visualize what is happening.

+1  A: 

Well, the way it works is you can only have one method name per httpverb. So the easiest way is to create a new action name. Something like "Create" for new records and "Edit" for existing records.

You can use the AntiForgeryToken ( http://msdn.microsoft.com/en-us/library/dd492767.aspx ) to validate the data. It doesn't stop all attempts at hacking but it's an added benefit.

Additional

The reason you can only have one action name per httpverb is because the model binders only attempt to model bind and really aren't type specific. If you had two methods with the same action name and two different types of parameters it can't just try and find the best match because your intent might be clearly one thing while the program only sees some sort of best match. For instance, your might have a parameter Id and a model that contains a property Id and it might not know which one you intend to use.

BuildStarted