You can use code-generated, offline ViewModels in an MVC controller to display and update a controlled part of your MDriven model without posting business objects directly to the browser.
Why use a ViewModel in MVC
A ViewModel is a model-defined perspective on business objects for one UI task. Instead of giving a Razor view an entire business object, you expose only the columns, references, and derived values that the task needs.
For example, a customer-edit ViewModel can expose Name and a formatted address while keeping internal customer attributes out of the view. The MVC view binds to the ViewModel; it does not need to know how the information is stored across model classes.
This separation gives you:
- Controlled updates. A posted ViewModel can update only values included in that ViewModel.
- View-specific presentation. You can combine or transform model values for the UI. For example, several address attributes can be presented as one display value.
- Clear ownership between roles. The model author defines the information available for a use case, while the view author works against the generated ViewModel type.
- Static typing and Razor support. Code generation supplies a typed ViewModel class for MVC data binding and editor/display helpers.
MVC is one UI approach in MDriven. For MDriven Turnkey, MVC is used for index and login pages by default; setting the MVC=true tagged value on a ViewModel enables MVC for other Turnkey pages. This page describes handling a code-generated ViewModel in a custom MVC controller.
Understand online and offline ViewModels
An online ViewModel is created with an EcoSpace and a root business object. It is connected to the model and is the ViewModel you use to read current values and apply changes.
An offline ViewModel is the instance MVC reconstructs from posted form values. It can exist without an EcoSpace, which makes it suitable as an MVC action parameter. Do not treat the offline instance as the business object or commit it directly. Instead, resolve the requested business object, create a new online ViewModel, and copy the allowed posted values to it.
| Stage | ViewModel instance | Purpose |
|---|---|---|
| GET request | Online | Create a ViewModel over a business object and send it to the Razor view. |
| POST request | Offline | Receive values that MVC bound from the submitted form. |
| Before commit | Online | Recreate the ViewModel over the resolved business object and apply offline values to it. |
Configure and generate the ViewModel
- Define a ViewModel in your MDriven model for the specific MVC use case. Include only the editable and display values the page needs.
- Enable code generation for the ViewModel and generate code.
- Use the generated ViewModel type in the controller and Razor view.
- If an MVC validation attribute is required on a generated ViewModel property, add a tagged value whose name starts with
*to the ViewModel column. Tagged values with this prefix are emitted into generated code.
For example, a ViewModel-column tagged value that emits [System.ComponentModel.DataAnnotations.Required()] makes MVC treat the generated property as required. MVC can then report a missing value through its normal validation UI.
Display model data in a GET action
Resolve the object identity through EcoSpace.ExternalIds, create the generated ViewModel over that object, and pass the ViewModel to the view.
public ActionResult Details(string Identity)
{
IObject io = EcoSpace.ExternalIds.ObjectForId(Identity);
var item = io.AsObject as ElaborateClass;
return View(ElaborateVM.Create(EcoSpace, item));
}
The Razor view is strongly typed to the generated class and can use standard MVC helpers:
@model EcoMVC.ViewModelCodeGen_ElaborateVM.ElaborateVM
<div class="display-label">
@Html.DisplayNameFor(model => model.Attribute1)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Attribute1)
</div>
For an index page, create one ViewModel for each object returned from the extent:
public ActionResult Index()
{
var all = EcoSpace.Extents.AllInstances<ElaborateClass>()
.Select(o => ElaborateVM.Create(EcoSpace, o));
return View(all);
}
Apply a posted ViewModel safely
Use this pattern for an edit or create POST action:
- Accept the generated ViewModel as the offline MVC-bound parameter.
- Resolve the target object, or create a new one for a create action.
- Create an online ViewModel over that object.
- Call
ViewModelHelper.ApplyValuesto copy the posted values to the online ViewModel. - Commit the EcoSpace.
- On failure, return the offline ViewModel so the submitted values remain available to the view.
The following create action creates a business object, applies the values that were posted to the offline ViewModel, and commits the result:
[HttpPost]
public ActionResult Create(ElaborateVM offlineElaborateVM)
{
try
{
var onlineElaborateVM = ElaborateVM.Create(
EcoSpace,
new ElaborateClass(EcoSpace));
ViewModelHelper.ApplyValues(
offlineElaborateVM,
onlineElaborateVM,
null);
if (Commit())
return RedirectToAction("Index");
return View(offlineElaborateVM);
}
catch
{
return View(offlineElaborateVM);
}
}
For an existing object, resolve the identity first and then apply values to an online ViewModel for that object:
[HttpPost]
public ActionResult Details(string Identity, ElaborateVM offlineElaborateVM)
{
var item = EcoSpace.ExternalIds.ObjectForId(Identity)
.AsObject as ElaborateClass;
try
{
var onlineElaborateVM = ElaborateVM.Create(EcoSpace, item);
ViewModelHelper.ApplyValues(
offlineElaborateVM,
onlineElaborateVM,
null);
Commit();
return View("Details", onlineElaborateVM);
}
catch
{
return View("Details", offlineElaborateVM);
}
}
The ViewModel boundary is important: values not exposed by the ViewModel are not part of this update path. Put business rules and validation in the model, then use the ViewModel to decide which model information and constraints are relevant to this UI. For create, update, delete, and constraint-handling patterns, see Documentation:MVC View Model constraints.
Handle references with a combobox
A single-valued reference can be posted through an external-ID property generated for the ViewModel. For example, a generated Example1_AsExternalId property holds the selected identity while Example1_PickList supplies the choices:
@Html.DropDownListFor(
x => x.Example1_AsExternalId,
new SelectList(
Model.Example1_PickList,
"Identity",
"Presentation",
Model.Example1_AsExternalId))
The pick-list presentation class needs an identity column so MVC can post the selected item identity. Configure comboboxes in MVC from a model-driven ViewModel for the complete setup and round-trip example.
Generated-code changes to account for
If you are updating code written against an earlier generated ViewModel API, make these changes:
| Earlier usage | Current usage |
|---|---|
vm.Root.Attribute
|
vm.Attribute
|
Use VMClass as a Dictionary<>
|
Use vm.AsDictionary when dictionary behavior is required
|
Choose the right MVC rendering approach
This page applies when you build conventional, strongly typed MVC views around generated ViewModel classes. If you want MDriven to render the ViewModel UI from its Razor files, use the generated MVC UI approach instead. That approach uses a Razor model of type VMClass and renders the generated partial through @Html.Partial(Html.RazorPartialFile()).
For a stateless generated-UI postback, the form must post both the ViewModel name and the root object's external ID so the VMClassBinder can recreate the ViewModel. The generic view template and binder setup are documented in Documentation:MVC Generated ViewModel UI in MDrivenFramework. To render a ViewModel in an MVC project without Turnkey, see Documentation:Render MVC ViewModel without turnkey.
