1

I have an Action method in an ASP.NET MVC Controller class that handles form posts from a fairly basic "create/edit a user" page. I'm new to MVC so I've been following code samples from various Microsoft tutorials, this is how the method looks currently:

[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Save([Bind(Prefix = "ServiceUser")]ServiceUser SUser)
{
        if (SUser.ServiceUserId == 0) //new service user
            ServiceUserHelper.AddServiceUser(SUser);
        else //update to existing service user
        {
            using (ProjectDataContext db = DatabaseHelper.CreateContext())
            {
                this.UpdateModel(db.ServiceUsers.Single(su => su.ServiceUserId == SUser.ServiceUserId), "ServiceUser");
                db.SubmitChanges();
            }
        }

        //return a confirmation view
}

This works fine; however my instincts tell me that the 'ProjectDataContext...' code doesn't belong in the controller. If I were to move the Update functionality to an other class (in the way I have done with the Insert method), I'd lose the convenience of the Controller's UpdateModel() method, and probably end up having to do something quite verbose to read the existing entity, update its properties, and submit the changes.

So my question is, what is the best way to achieve this? Is there a method similar to UpdateModel() somewhere in LINQ that can merge two entities of the same type together before submitting?

Thanks.

Rex M
  • 142,167
  • 33
  • 283
  • 313
Lee D
  • 12,551
  • 8
  • 32
  • 34

3 Answers3

5

Most people will suggest using the "Repository Pattern" to move that data access code out of the controller (and to enable unit testing with mock objects instead of the real database).

Here are some places to read more:

Edit:

I highly recommend reading the entire Scott Guthrie chapter linked above. It has a wealth of good advice in it. That said, here are some relevant examples (excepted from the chapter)...

First, I generally like to have different actions for "Update" vs. "Add". Even if they are the same View to render the form, it generally feels cleaner to have different URLs for POSTing an edit vs POSTing a new record. So, here is what the repository pattern in use looks like in a controller's update action:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
    //get the current object from the database using the repository class
    Dinner dinner = dinnerRepository.GetDinner(id);
    try
    {
        //update the object with the values submitted
        UpdateModel(dinner);
        //save the changes
        dinnerRepository.Save();
        //redirect the user back to the read-only action for what they just edited
        return RedirectToAction("Details", new { id = dinner.DinnerID });
    }
    catch
    {
        //exception occurred, probably from UpdateModel, so handle the validation errors
        // (read the full chapter to learn what this extention method is)
        ModelState.AddRuleViolations(dinner.GetRuleViolations());
        //render a view that re-shows the form with the validation rules shown
        return View(dinner);
    }
}

Here is the "Add" example:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create()
{
    //create a new empty object
    Dinner dinner = new Dinner();
    try
    {
        //populate it with the values submitted
        UpdateModel(dinner);
        //add it to the database
        dinnerRepository.Add(dinner);
        //save the changes
        dinnerRepository.Save();
        //redirect the user back to the read-only action for what they just added
        return RedirectToAction("Details", new { id = dinner.DinnerID });
    }
    catch
    {
        //exception occurred, probably from UpdateModel, so handle the validation errors
        // (read the full chapter to learn what this extention method is)
        ModelState.AddRuleViolations(dinner.GetRuleViolations());
        //render a view that re-shows the form with the validation rules shown
        return View(dinner);
    }
}

For both examples above, the DinnerRepository looks like this:

public class DinnerRepository
{
    private NerdDinnerDataContext db = new NerdDinnerDataContext();
    //
    // Query Methods
    public IQueryable<Dinner> FindAllDinners()
    {
        return db.Dinners;
    }
    public IQueryable<Dinner> FindUpcomingDinners()
    {
        return from dinner in db.Dinners
               where dinner.EventDate > DateTime.Now
               orderby dinner.EventDate
               select dinner;
    }
    public Dinner GetDinner(int id)
    {
        return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
    }
    //
    // Insert/Delete Methods
    public void Add(Dinner dinner)
    {
        db.Dinners.InsertOnSubmit(dinner);
    }
    public void Delete(Dinner dinner)
    {
        db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs);
        db.Dinners.DeleteOnSubmit(dinner);
    }
    //
    // Persistence
    public void Save()
    {
        db.SubmitChanges();
    }
}
Community
  • 1
  • 1
Erv Walter
  • 13,737
  • 8
  • 44
  • 57
  • I've taken a look at Rob Conery's storefront app but I can't find any examples of how to implement what I'm looking for specifically; there's alot of video there to wade through. I just need an example of how to implement the Update functionality I mentioned outside of the controller. – Lee D Mar 25 '09 at 09:17
  • 1
    do yourself a favour and "wade thru" RobCon's stuff. all of it. even if you dont choose to adopt his more 'higher level' archecture (vs the Nerdinner example) you will get a better complete feel for MVC. for the record, i used Robs approach - seperate Service layer, and OO domain model (rather than using the LINQ classes). – Matt Kocaj Apr 28 '09 at 16:54
  • By moving your GetDinner and SaveDinner to a service (another level of indirection) you can remove the need for data access in the controller. This is arguably more testable and removes the data access responsibility to the service, even though it may seem like only a thin facade when you are writing it. – Liam Jun 25 '09 at 12:20
0

I agree with Lee D I've been looking for something as well. I did a similar thing with reflections early in the MVC previews that was used in the model and not the controller. It wasn't the best code and thought that something would be added to MVC final. Not something stuck in the controller. It would be ideal to pass form, or model if using strongly typed views, through the controller into the model and let all the validation and data moving be done there. Now even a pooryly done controller can't hose the data.

0

You currently have what I would call a 2 layer architecture which includes your MVC application layer (ie Controllers) and your data access layer.

You probably want to move to a 3 or 4 layer architecture by inserting a service layer between your Controllers and DAL. So you would end up with:

Controllers -> Services -> DAL

A 4 layer architecture might include a repository layer

Controllers -> Services -> Repository -> DAL

Your controllers would only be responsible for 3 things

1) argument handling 2) calling your service layer to do work 2) application flow

Your above example might look something like:

[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Save([Bind(Prefix = "ServiceUser")]ServiceUser SUser)
{
        // validate arguments
        if (SUser == null)
        {
              throw new ArgumentException("SUser can not be null");
        }

        // process form fields / query params / etc.
        this.TryUpdateModel(SUser, "ServiceUser");

        // update your model 
        // (toss in a try/catch to deal with validation errors etc)
        _userService.Save(SUser);

        //return a confirmation view
}

Then your service layer would be responsible for doing the actual work:

  public class UserService : IUserService
   {
       public void Save(ServiceUser SUser)
       {
            // insert or update user info

            if (SUser.ServiceUserId == 0) //new service user
                ServiceUserHelper.AddServiceUser(SUser);
            else //update to existing service user
            {
                using (ProjectDataContext db = DatabaseHelper.CreateContext())
                {
                    db.ServiceUsers.Single(su => su.ServiceUserId == 
                                           SUser.ServiceUserId);
                    db.SubmitChanges();
                }
            }
       }
   }
Todd Smith
  • 17,084
  • 11
  • 59
  • 78