You can use MVC to render selected MDriven ViewModels in an ASP.NET MVC application or in Turnkey, when you need server-rendered Razor pages rather than the default AngularJS pages.
MVC in MDriven
MVC (Model-View-Controller) separates application data and behavior from the way a web page is rendered and handled. In MDriven, a ViewModel defines the information and actions for a use case, while MVC can render that ViewModel through Razor views and process form posts through a controller.
Turnkey uses MVC for its index and login pages. Other Turnkey pages use AngularJS by default. To render a particular Turnkey ViewModel with MVC, set the ViewModel tagged value MVC=true.
For example, if a ViewModel named SampleViewModel is intended to be a server-rendered page, add the MVC=true tagged value to that ViewModel. Keep the ViewModel focused on the data and actions needed by that page.
Choose an MVC approach
| Goal | Recommended starting point | What you build |
|---|---|---|
| Render a ViewModel as part of Turnkey | Documentation:Turnkey MVC Controllers | A Turnkey MVC controller and MVC-enabled ViewModel. |
| Render MDriven-generated ViewModel UI in a standard MVC project | Documentation:MVC Generated ViewModel UI in MDrivenFramework | A controller, a GenericView.cshtml host view, and generated Razor partial content.
|
| Render one ViewModel without adopting the full Turnkey UI | Documentation:Render MVC ViewModel without turnkey | A Razor view whose model is VMClass and which renders the generated partial.
|
| Create typed, offline ViewModels and use standard MVC scaffolding | Documentation:MVC View Model handling | Generated ViewModel classes, MVC views, and an EcoController-based controller.
|
Do not expose business objects directly to an MVC view when a ViewModel can define the required projection instead. A ViewModel provides a specific, controlled set of values for a task. For example, a customer edit page can expose a display-ready address and the editable fields required for that page, rather than the complete customer object graph.
Differences from AngularJS pages
MVC rendering does not provide all interaction behavior that an AngularJS Turnkey page provides. Plan the ViewModel actions and navigation with these differences in mind.
| Interaction | MVC behavior | AngularJS behavior |
|---|---|---|
| Click a grid row when the row has no actions or multiple actions | Selects the row. | Can show a popup menu for multiple available actions. |
| Click a grid row when exactly one ViewModel action is defined | Executes that action. | Uses its AngularJS interaction handling. |
| Multiple grid-row actions | Does not show a popup menu of action choices. | Can show a popup menu of multiple actions. |
For example, a grid with one Open action can open the selected item when the user clicks its row in MVC. If the same grid has both Open and Delete, MVC selects the row rather than presenting an action-choice popup. Provide explicit buttons or implement the required interaction in your MVC application when users must choose between actions.
Render generated ViewModel UI in an MVC project
Use the generated MVC UI when you want MDriven to render the controls defined by a ViewModel inside a standard MVC project.
- Create a standard MVC project.
- Create a host Razor view named
GenericView.cshtmlunder the view folder for your controller, for exampleViews/My/GenericView.cshtml. - Set the Razor model type to
VMClass. - Render the MDriven-generated Razor partial with
@Html.Partial(Html.RazorPartialFile()). - Create a controller derived from
ModelDrivenControllerBase<YourEcoSpace>. - Create the required ViewModel through
Eco.ViewModel.Runtime.ViewModelHelperand returnGenericView.
A minimal controller action follows this pattern. Replace EcoProject1EcoSpace and SampleViewModel with your generated EcoSpace type and ViewModel name.
public class MyController : ModelDrivenControllerBase<EcoProject1EcoSpace>
{
public MyController() : base()
{
RenderSettings.UseCSSGridByDefault = true;
}
public ActionResult TestStart2()
{
var vmc = Eco.ViewModel.Runtime.ViewModelHelper.CreateFromViewModel(
"SampleViewModel", this.EcoSpace, null, false);
return View("GenericView", vmc);
}
}
The central part of the host view is the generated partial:
@model VMClass
@using (Html.BeginForm("Submit", Html.GetControllerName(), FormMethod.Post))
{
<div id="viewmodelWrapper">
@Html.Partial(Html.RazorPartialFile())
</div>
}
The full host-view template, including submission handling, validation output, action handling, and layout details, is maintained in Documentation:MVC Generated ViewModel UI in MDrivenFramework. For a form that uploads files, the form must use enctype="multipart/form-data"; see that page for the required Html.BeginForm form definition.
Support stateless form posts
A generated ViewModel must be recreated when MVC receives a form post. For a truly stateless round trip, post both the root object's external ID and the ViewModel name. The MVC binder uses these values to recreate the ViewModel before it applies the posted values.
Include hidden fields equivalent to the following in the form:
<input asp-for="ThisAsExternalId" hidden />
<input asp-for="ViewModelClass.ViewModel.Name" hidden />
In the generated host-view template, the same purpose is served by VMClass.ThisAsExternalId_nameOf and VMClassBinder.ViewModelNameFormAttribute. Use the established template rather than mixing the two patterns without checking the generated model and binder used by your project.
ASP.NET Core and generated ViewModels
The following points apply to the .NET 5/.NET Standard MVC setup described in the MDriven MVC notes.
Add model packages to an EcoSpace
MDriven packages are published with names beginning MDriven.*. Add a model package reference to an EcoSpace in code with EcoSpacePackage.
[EcoSpace]
[UmlTaggedValue("Eco.InitializeNullableStringsToNull", "true")]
[UmlTaggedValue("Eco.GenerateMultiplicityConstraints", "true")]
[EcoSpacePackage(typeof(Package1Package))]
public partial class EcoSpace1 : Eco.Handles.DefaultEcoSpace
{
}
Bind a generated ViewModel
Use VMClassBinder on an action parameter that receives a generated ViewModel.
public IActionResult View(
[ModelBinder(typeof(VMClassBinder))] SampleViewModel value)
{
return View("View");
}
Prevent recursive child validation
A VMClass can reference a larger object graph. Configure MVC to suppress child validation for VMClass, so MVC does not attempt to validate that complete graph.
var mvc = services.AddMvc(options =>
{
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(VMClass)));
});
Enable runtime Razor generation
When you use Razor files generated at runtime from a ViewModel, configure Razor runtime compilation and the MDriven MVC file provider. The configuration shown below also adds memory cache and session state used to retain selection and other ViewModel state during an MVC round trip.
services.AddControllersWithViews();
services.AddMemoryCache();
var mvc = services.AddMvc(options =>
{
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(VMClass)));
});
mvc.AddSessionStateTempDataProvider();
services.AddSession();
services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Clear();
options.FileProviders.Add(
new MDrivenMVCCoreFileProvider(
_HostingEnvironment.ContentRootFileProvider,
Directory.GetCurrentDirectory()));
});
services.AddRazorPages().AddRazorRuntimeCompilation();
Enable session middleware before endpoint mapping:
app.UseSession();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
The MVC project also needs an IWebHostEnvironment instance available to construct MDrivenMVCCoreFileProvider. The source configuration injects it through the Startup constructor and stores it in _HostingEnvironment.
Navigation from an MVC page to an AngularJS page requires two EcoSpaces. The MVC EcoSpace is handed to the streaming API so that action continuity is retained: work performed in the MVC action remains available in the AngularJS EcoSpace.
The MVC controller first handles the request for the current page, returns a redirect to the client, and the new MVC page redirects to AngularJS. Follow Documentation:Serverside Turnkey and MVC functioning for the controller sequence and prerequisites.
Related ViewModel topics
- Use Documentation:MVC View Model constraints to handle ViewModel constraints in MVC.
- Use Documentation:Comboboxes in MVC from model driven ViewModel for model-driven combo boxes.
- Find suggested ViewModel bindings for both MVC and AngularJS in Documentation:Bindings for angular.
