A ViewModel defines the data, structure, and behavior that a user interface, API-style output, export, or report uses, and is for MDriven developers who need to present a focused part of their UML model.
Your UML model defines the application's general-purpose business objects and their associations. A ViewModel puts a specific perspective on those objects. It unfolds the required part of the model, follows the relevant associations, and exposes the attributes and collections needed by one use case.
For example, a model may contain `Customer`, `Order`, and `OrderLine` classes. A customer-detail ViewModel can show one customer's name and a collection of that customer's orders. The UI or output consumes the ViewModel structure rather than needing to know the complete business model.
What you can use a ViewModel for
Use a ViewModel whenever you need a defined shape of model data. Common uses include:
- A read/write user interface.
- A search or selection list.
- Read-only or read/write API-style data exposure.
- Data extraction as JSON or XML.
- XML exports and reports, including reports that merge ViewModel data into ODS or ODT (OpenDocument Format) documents.
- A generated C# representation of the ViewModel when you select the Codegen checkbox.
When a report uses ViewModel data, tags in the document are replaced by information from the ViewModel structure. Design the ViewModel around the information that the report needs; do not expose unrelated parts of the model.
Choose rooted or unrooted
A ViewModel is either rooted or unrooted. The choice determines whether the root ViewModel class starts with one specific model object.
| Type | Starting context | Use it when | Example |
|---|---|---|---|
| Rooted | A specific model object is the context of the root ViewModel class. That object is available as `self`. | You are displaying or editing one object and information related to it. | Start at one `Customer`, show its attributes, then show the customer's orders. |
| Unrooted | The root ViewModel class has no specific model object as its context, so there is no root `self`. | You need to find, list, or select objects without starting from a known object. | Find `ValueStore` objects and display the matching objects in a list. |
Rooted ViewModels
Use a rooted ViewModel for an object-focused page or output. The root class has a model-object context, so expressions on that class can use `self` to read or write the root object's attributes and navigate its associations.
For a model class called `Thing` with an association to `Detail`, a rooted ViewModel can:
- Start with one `Thing` object as `self`.
- Expose attributes of that `Thing` in the root ViewModel class.
- Add a collection ViewModel class for the associated details.
- Use an expression equivalent to navigating from `self` to its details collection.
- Expose attributes of each `Detail` object in that collection.
The collection class has a `Detail` object as its context. Expressions in that class therefore use `self` as the current `Detail`.
A rooted ViewModel can technically reach other objects in the database, but it is clearest and most maintainable when the displayed information is related to the root object.
Unrooted ViewModels
Use an unrooted ViewModel for a search, a complete list, or a partial list. Because the root has no model-object context, it has no root `self`. Instead, a collection within the ViewModel finds the objects to display.
A typical pattern is a Seeker: the user enters search input, and the ViewModel retrieves matching objects for display or editing. For example, a collection can use:
ValueStore.allinstancesThis obtains the collection of `ValueStore` instances from the model. The collection ViewModel class for those objects has a `ValueStore` context; within that collection class, `self` refers to the current `ValueStore` object.
The model class selected for an unrooted root class does not usually determine the data retrieval. Use a name and class choice that make the ViewModel's purpose clear to other developers.
Understand ViewModel classes
A ViewModel consists of ViewModel classes. They are called classes because they define a new, view-specific kind of object assembled from pieces of the main model.
A ViewModel class can expose:
- Attributes backed by attributes or expressions in the main model.
- A single object.
- A collection of objects, represented by another ViewModel class.
In the MDriven Designer, the root ViewModel class is shown with a green background. Nested ViewModel classes are shown in blue beneath it. The blue classes commonly define collections of data.
For the customer-detail example, the ViewModel can be understood as two view-specific classes:
| ViewModel class | Context | Contents |
|---|---|---|
| `CustomerDetail` (root) | One `Customer` object | Customer attributes and an orders collection |
| `Order` | Each `Order` object returned by the customer-to-orders association | Order attributes |
This structure lets the view display customer information once and repeat the order rows for the customer's collection.
Build the data shape step by step
Use this approach when defining a ViewModel:
- Identify the consumer: a detail UI, a Seeker, an export, a report, or another output.
- Decide whether the consumer starts with one known model object. If it does, use a rooted ViewModel; otherwise, use an unrooted ViewModel.
- Define the root ViewModel class and expose only the root attributes the consumer needs.
- Add nested ViewModel classes for related single objects or collections.
- For each nested class, define the expression that obtains its object or collection from the parent context.
- Add the attributes required for display, editing, or output on each ViewModel class.
- Review the result from the consumer's perspective: a customer detail should not need unrelated customer data, and a search list should return the objects the user needs to select or edit.
Keep context in mind at every level. In a rooted root class, `self` is the root model object. In a nested class, `self` is the object represented by that nested class. In an unrooted root class, there is no root `self`; use a collection expression to locate the objects instead.
ViewModels and C#
When you select the Codegen checkbox, ViewModel classes can be generated as C# classes. From C#, treat each generated ViewModel class as an object with properties.
A property read obtains its value from the backing object in the main model. A property write executes its setter and updates the backing main-model object. If an expression produces a list, the property is a .NET runtime list of ViewModel objects for the corresponding nested ViewModel class. Each of those objects is, in turn, backed by its own main-model object.
This gives C# code a view-specific facade rather than requiring the view to work directly with the entire business object. For MVC-specific examples of creating a ViewModel, applying posted values, and committing changes, see Documentation:MVC View Model constraints. To render a ViewModel from an MDriven Framework MVC project without Turnkey, see Documentation:Render MVC ViewModel without turnkey.
Related ViewModel features
- Configure layout-related column, row, width, height, and nesting settings in Documentation:ViewModel settings.
- Add a ViewModel inside another ViewModel with Documentation:Mounted ViewModel. A mounted ViewModel cannot use `vCurrent_Root`, because that value belongs to the outer ViewModel.
- Use Documentation:ViewInView when you want to place a mounted ViewModel in a view without using the table grid.
- Define actions that operate on a view in Documentation:ViewModel actions. ViewModel actions do not have `self`; use the applicable `vCurrent_` variables for the current focus and `vSelected_` collections for multiple grid selections.
- When an OCL expression needs a ViewModel name, use the safe ViewModels operator described in Documentation:OCLOperators ViewModels rather than a string. For example:
self.opendocumentreportshow(<classname>.ViewModels.<myviewmodelname>). Renaming the ViewModel then causes model validation to identify the expression that needs updating.
