You can build MVC create, edit, and delete actions around model-defined ViewModels so that users edit only the data intended for a screen while model constraints continue to protect the domain model.
An MVC ViewModel is a screen-specific projection of your model. It can expose selected attributes, relations, and derived values without exposing the complete business object to a Razor view. This is useful when a page must edit an `Example2` object but should not be able to change every attribute and association on that object.
Why use ViewModels with MVC constraints
MVC can bind a posted form directly to business objects, but a model-defined ViewModel gives you a controlled boundary between the view and the model.
| Concern | ViewModel-based approach | Example |
|---|---|---|
| Data exposure | Include only the columns and relations needed by the screen. | An `Example2` details page exposes `Attribute1` but does not expose unrelated administrative attributes. |
| Update scope | Apply posted values to an online ViewModel rather than directly to the business object. | A user can submit values for the properties in `VMExample2`; properties outside that ViewModel are not part of the update. |
| Presentation values | Add view-specific derivations and compositions in the ViewModel. | A ViewModel can present several model attributes as one display value, such as an address string. |
| Business rules | Keep rules in model constraints and evaluate them when the model changes. | A required relation or a custom class constraint can prevent an invalid state after an edit or delete. |
| Per-screen feedback | Control whether automatically included constraints are relevant to a particular ViewModel. | A constraint that is meaningful in one workflow can be opted out of another ViewModel when it does not make sense for that screen. |
Constraints are model rules that evaluate to true or false. Association cardinalities provide implicit constraints; you can also define explicit constraints. A broken constraint can be presented as information, a warning, or an error. For constraint design, delete constraints, and association-end business delete rules, see Training:Constraints.
Use online and offline ViewModels
A generated ViewModel can be used in two modes:
- An online ViewModel is created with an `EcoSpace` and a model object. It acts as the façade over the live object graph.
- An offline ViewModel is the ViewModel MVC receives from a posted form. It carries the submitted values but is not the live model object.
This separation is the core MVC update pattern:
- Resolve the posted external identity to the live business object.
- Create an online ViewModel for that object.
- Copy values from the offline ViewModel to the online ViewModel with `ViewModelHelper.ApplyValues`.
- Commit the changes so model validation and constraint handling can take place.
The generated ViewModel provides static typing and data-binding support. In generated code, ViewModel properties are accessed directly; earlier code using `vm.Root.Attribute` must use `vm.Attribute`. `VMClass` no longer inherits `Dictionary<>`; use `vm.AsDictionary` when dictionary behavior is required. For the broader MVC binding setup and generated ViewModel usage, see Documentation:MVC View Model handling.
Build the ViewModels around the task
A small sample can use three ViewModels:
- `VMExample1Index` for an index page.
- `VMExample1` for an `Example1` object and its related `Example2` objects.
- `VMExample2` for one `Example2` object.
Keep each ViewModel focused on what the corresponding page needs. For example, an index ViewModel can provide navigation data, while an edit ViewModel provides only the editable values and the constraint feedback relevant to that edit task.
Model constraints automatically appear in ViewModels. You can opt out of a constraint per ViewModel when that constraint is not meaningful in that view. Do not use this to weaken a domain rule: the model rule still defines whether the domain state is valid. Use ViewModel-level control to avoid presenting irrelevant feedback in a particular UI.
For a pattern that explicitly links a ViewModel validation to a named class constraint, see Documentation:ViewModel validations.
Controller patterns
The following examples use an `EcoController`, an `EcoSpace`, generated ViewModels, and external identities. Replace `Example1`, `Example2`, and the ViewModel names with the types in your model.
Display one object
Resolve the identity, create an online ViewModel, and pass the ViewModel to the view. The Razor view receives the screen-specific façade rather than the business object.
public ActionResult Details(string Identity)
{
Example2 e2 = this.EcoSpace.ExternalIds.ObjectForId(Identity).AsObject as Example2;
return View("Details", VMExample2.Create(this.EcoSpace, e2));
}
For example, `VMExample2` can display `Attribute1` and a derived display value without making every `Example2` member available to the view.
Update one object
On a POST, MVC binds the form values into an offline ViewModel. Create a new online ViewModel over the resolved object, then apply the posted values to it.
[HttpPost]
public ActionResult Details(string Identity, VMExample2 offlinevm)
{
Example2 e2 = EcoSpace.ExternalIds.ObjectForId(Identity).AsObject as Example2;
try
{
VMExample2 onlinevm = VMExample2.Create(EcoSpace, e2);
ViewModelHelper.ApplyValues(offlinevm, onlinevm, null);
Commit();
return View("Details", onlinevm);
}
catch
{
return View("Details", offlinevm);
}
}
`ViewModelHelper.ApplyValues` limits the update to data represented by the ViewModel. The view does not need to know where a value belongs in the business-object graph; the ViewModel can combine data from several parts of the model.
Call `Commit()` after applying values. This is the point at which the changed model must be validated according to its rules. If the operation fails, return a model that allows the user to correct the submitted values and review validation feedback.
Delete through business deletion
Delete operations can affect several objects and associations. Use `BusinessDelete` so that delete constraints and business delete rules are evaluated before the commit.
public ActionResult Delete(string Identity)
{
Example1 e1 = this.EcoSpace.ExternalIds.ObjectForId(Identity).AsObject as Example1;
BusinessDelete(e1);
Commit();
return View("Index", VMExample1Index.Create(this.EcoSpace, null));
}
For example, a delete constraint can state that a `Car` cannot be deleted while a deposit exists unless the car is in the `Scrapped` state. Association ends can also define business delete rules, such as `MustBeEmpty`. These rules belong in the model and are checked when MDriven performs deletion. See delete constraints.
Create an object
Create the business object in the `EcoSpace`, then populate it through the ViewModel update pattern when the create page accepts user input. The following sample creates an object with initial values:
public ActionResult Create()
{
Example1 x1 = new Example1(this.EcoSpace)
{
Attribute1 = "HoHoHo",
Attribute2 = "HeHeHe"
};
CommitSkipValidate();
return View("Index", VMExample1Index.Create(this.EcoSpace, null));
}
`CommitSkipValidate()` skips validation. The sample uses it because it creates an object that may break constraints at creation time. Treat this as a deliberate exception: if an object is persisted before it satisfies the model rules, your workflow must have a defined way to complete it. When the create operation is based on posted ViewModel values, use the same offline-to-online pattern as an update and call `Commit()`:
[HttpPost]
public ActionResult Create(VMExample2 offlinevm)
{
try
{
var onlinevm = VMExample2.Create(
EcoSpace,
new Example2(EcoSpace));
ViewModelHelper.ApplyValues(offlinevm, onlinevm, null);
if (Commit())
return RedirectToAction("Index");
return View(offlinevm);
}
catch
{
return View(offlinevm);
}
}
Bind generated ViewModels in Razor views
Generated ViewModels provide typed properties, so Razor helpers can provide code completion. An index view can iterate a ViewModel relation and use each row's external identity for controller actions.
<table>
@foreach (var item in Model.Example2) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Attribute1)
</td>
<td>
@Html.ActionLink("Details & Edit", "DetailsForExample2",
new { Identity = item.Identity }) |
@Html.ActionLink("Delete", "DeleteExample2",
new { Identity = item.Identity })
</td>
</tr>
}
</table>
Decorate generated ViewModel properties for MVC when needed. Tagged values on a ViewModel column whose names start with `*` are emitted into generated code. For example, a tagged value that generates `[System.ComponentModel.DataAnnotations.Required()]` lets MVC treat the generated property as required.
For MVC-specific controls and generated UI rendering, see Documentation:MVC Generated ViewModel UI in MDrivenFramework, Documentation:Render MVC ViewModel without turnkey, and Documentation:Comboboxes in MVC from model driven ViewModel.
Maintain constraints and ViewModels
Use the model maintenance tools before relying on a ViewModel in a controller:
- Run CheckModel to check expressions in ViewModel columns, constraints, derived attributes, and derived associations.
- Use the CrossReference menu on a class, attribute, or association to find where it is used. For example, use it to determine whether an attribute appears in a ViewModel or is used by an association.
- Review each ViewModel after changing a constraint or an attribute. Confirm that the screen exposes the intended values and that its constraint feedback remains relevant.
This keeps business logic in the model, makes the MVC controller responsible for orchestration, and keeps Razor views focused on presentation.
