Implementing ASP.NET MVC 3 Interface Model Binding

2020-07-29 07:33发布

问题:

I just learning ASP.NET MVC 3 and I'm a bit confused on how I can implement something like what is shown in this link:

http://www.codethinked.com/easy-and-safe-model-binding-in-aspnet-mvc

Why I'm confused is most likely based on my ignorance in the subject. In the example on the page linked above, I see Justin is using a ViewModel that directly contains properties of his "Person" object.

What I want to do is similar, but I want to have a ViewModel actually contain the domain class, which is in my case Company, rather than Person. I'm having the Company class itself implement the ViewModel subset views (ex. IEditCompanyAsUser).

What I've tried so far looks something like so:

public class Company : IValidatableObject, ICompanyUpdateAsUser
{
    [Key]
    [ScaffoldColumn(false)]
    public virtual int CompanyID { get; set; }

    [Required]
    public virtual Boolean Active { get; set; }

    [Required]
    public virtual string Name { get; set; }

    [Required]
    public virtual string Phone { get; set; }

    // Containing some more members below
 }

With ICompanyUpdateAsUser containing the properties I would like my controller's Edit method to update:

public interface ICompanyUpdateAsUser
{
    bool Active { get; set; }
    string Name { get; set; }
    string Phone { get; set; }  
}

And a ViewModel class that looks like so:

public class CompanyViewModel
{
    public Company Company;
    // Will be adding more members in the future
}

What I can't figure out is how to properly code the controller. My controller currently looks like so:

    /// <summary>
    /// POST: /Company/Edit/5
    /// </summary>
    [HttpPost]
    public ActionResult Edit([Bind(Prefix="Company")]Company model)
    {
        var company = _companyService.GetCompany(model.CompanyID);

        if (company == null)
            return RedirectToAction("Index");

        if (ModelState.IsValid)
        {
            if (TryUpdateModel<ICompanyUpdateAsUser>(company))
            {
                _companyService.SaveCompany();
                return RedirectToAction("Details", new { companyId = model.CompanyID });
            }
        }

        return View(model);
    }

I know something is absolutely wrong with my controller code but I can't figure out what components I need to use to implement such a pattern? Do I need to use a custom model binder? Do I need to use some other feature of ASP.NET MVC3 to implement the pattern?

Any advice would be greatly appreciated.

Thanks!

*EDIT*

What I need to do is have the Edit view post back a "Company" object, then update the object from the database with the values on the post'ed Company (according to the properties on the interface passed to TryUpdateModel). Everything appears to be working (validation, etc.), but the TryUpdateModel(company) does not seem to be working, though evaluating to true. When I step through the code with a debugger, the Company object pulled from the DB is simply never updated.

After a bit more reading, I've determined the TryUpdateModel method updates the passed item from the method's IValueProvider. How can I associated the passed in "Company model" with the IValueProvider for the method?

回答1:

I ended up resolving the issue by first making a couple of modifications..in my question, my controller failed to use the ViewModel class I had created. Once I modified the controller to look like so:

 /// <summary>
    /// POST: /Company/Edit/5
    /// </summary>
    [HttpPost]
    public ActionResult Edit(CompanyViewModel viewModel)
    {
        var company = _companyService.GetCompany(viewModel.Company.CompanyID);

        if (company == null)
            return RedirectToAction("Index");

        if (ModelState.IsValid)
        {
            if (TryUpdateModel<ICompanyUpdateAsUser>(company, "Company"))
            {
                _companyService.SaveCompany();
                return RedirectToAction("Details", new { companyId = viewModel.Company.CompanyID });
            }
        }

        return View(viewModel);
    }

I found that everything appeared to be working properly, but as stated in the comments above TryUpdateModel simply DID NOT work, though returning true!

After sifting through tons of Stack Overflow & other forum posts, I eventually found that TryUpdateModel does not work on fields, only properties. To get it working properly I had to modify my ViewModel to look like so:

public class CompanyViewModel
{
    public Company Company { get; set; }

    public CompanyViewModel(Company company)
    {
        Company = company;
    }

    public CompanyViewModel()
    {
        Company = new Company();
    }
 }

Such a silly mistake, though I feel this point is far overlooked by many of the model binding tutorials I have read.

Hopefully this answer will save someone some time.



回答2:

There should be an overload of TryUpdateModel that takes in a prefix. Since it sounds like your original view model contained your Company object, then the name of the form fields that were generated for the Company object (assuming you're using standard form input names), contain a prefix of "Company", it looks like you've recognized this with the model that the method is taking in, try using the same prefix when updating your domain object.

FYI, it's generally not best practice to use your domain objects in your view model, the main reason for this is that there could be fields you don't want to allow a user to update. If a user has enough knowledge about your domain, they can add additional form fields into the post data, and MVC would blindly update those property/fields and that could cause a problem. There are ways of telling MVC to exclude certain properties/fields when binding, but it becomes a maintenance nightmare and is usually easier to use separate objects for your views.