I am trying to work out the best way of using a viewmodel in the case of creating a new object.
I have a very simple view model that contains a contact object and a select list of companies.
private ICompanyService _Service;
public SelectList ContactCompanyList { get; private set; }
public Contact contact { get; private set; }
public ContactCompanyViewModel(Contact _Contact)
{
_Service = new CompanyService();
contact = _Contact;
ContactCompanyList = GetCompanyList();
}
private SelectList GetCompanyList()
{
IEnumerable<Company> _CompanyList = _Service.GetAll();
return new SelectList(_CompanyList, "id", "name");
}
I then have contact controller that uses this viewmodel and enable me to select a related company for my contact.
[Authorize]
public ActionResult Create()
{
return View(new ContactCompanyViewModel(new Contact()));
}
My issue is with the create method on the controller.
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Contact _Contact)
{
try
{
_Service.Save(_Contact);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is that the view returns an empty contact object, but! the company id is populated, this is because the dropdown list explicitly declares its field name.
@Html.DropDownList("parent_company_id",Model.ContactCompanyList)
The standard html form fields pass the objects values back in the format of contact.forename
when using the HTML.EditorFor
helper...
@Html.EditorFor(model => model.contact.forename)
I can access them if I use a FormCollection
as my create action method paremeter and then explicitly search for contact.value
but I cannot use a Contact object as a parameter to keep my code nice and clean and not have to build a new contact object each time.
I tried passing the actual view model object back as a parameter but that simply blows up with a constructor error (Which is confusing seeing as the view is bound to the view model not the contact object).
Is there a way that I can define the name of the Html.EditFor
field so that the value maps correctly back to the contact object when passed back to the create action method on my controller? Or Have I made some FUBAR mistake somewhere (that is the most likely explanation seeing as this is a learning exercise!).
The view model pattern is used extensively in MVC application development, where it mainly represents data, but typically little behaviour. In Razor Pages, the PageModel is also the view model. Razor Pages is sometimes described as implementing the MVVM (Model, View ViewModel) pattern.
The concept of ViewModels is not only for ASP.NET MVC. It is also used in MVC, MVP, and MVVM design patterns. In this article, you'll learn what are ViewModels and how can we manage and clean the code with ViewModels.
They're all MVC and you pretty much just incorporate a view model if you want one. With ASP.NET MVC, in particular, you just create a class, generally with a name in the form of [Model Name]ViewModel or [Model Name]VM .
ViewModel is nothing but a single class that may have multiple models. It contains multiple models as a property. It should not contain any method. In the above example, we have the required View model with two properties.
Your view model seems wrong. View models should not reference any services. View models should not reference any domain models. View models should have parameterless constructors so that they could be used as POST action parameters.
So here's a more realistic view model for your scenario:
public class ContactCompanyViewModel
{
public string SelectedCompanyId { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; }
... other properties that the view requires
}
and then you could have a GET action that will prepare and populate this view model:
public ActionResult Create()
{
var model = new ContactCompanyViewModel();
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
and a POST action:
[HttpPost]
public ActionResult Create(ContactCompanyViewModel model)
{
try
{
// TODO: to avoid this manual mapping you could use a mapper tool
// such as AutoMapper
var contact = new Contact
{
... map the contact domain model properties from the view model
};
_Service.Save(contact);
return RedirectToAction("Index");
}
catch
{
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
}
and now in your view you work with your view model:
@model ContactCompanyViewModel
@using (Html.BeginForm())
{
@Html.DropDownListFor(x => x.SelectedCompanyId, Model.CompanyList)
... other input fields for other properties
<button type="submit">Create</button>
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With