MVC Generated ViewModel UI in MDrivenFramework

Note: Since this article was written, we have since added FileUpload control to MVC. To use it, you must have a form definition that sets the encoding type to multipart like this:

Html.BeginForm("Submit", Html.GetControllerName(), FormMethod.Post, new { enctype = "multipart/form-data" }))

This article describes how to use a standard MVC C# Visual Studio 2019 project with MDriven-generated ViewModel views.

You are free to use any name on the controller. In this sample, it is called "My".

The MDriven-generated MVC UI needs a view called GenericView.cshtml - a template is provided here:

@using Eco.ViewModel.Runtime
@using Eco.MVC
@model VMClass

@{
    ViewBag.Title = Model.ViewModelClass.Name;
}

<style>
    .tk-data-table {
        padding: 5px;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
        flex-grow: 1;
        width: 100%;
        min-height: 90px;
        height: 100%;
        overflow-y: auto;
    }

    .tk-data-table.ctGridMidAirY .tk-data-table__content {
        height: 100%;
    }

        .tk-data-table.ctGridYBottom .tk-data-table__content {
            height: 100%;
        }

    .tk-data-table__content {
        border-color: #dadce0;
    }

    .tk-data-table__content {
        margin-top: 10px;
        position: relative;
        width: 100%;
        overflow: auto;
        border-radius: 4px;
        border: 1px solid #dadce0;
    }

    .tk-text-field {
        padding: 5px;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
    }

    .tk-button.NoLabel {
         padding: calc(1rem * 1.5 + 5px) 5px 5px 5px; 
    }



</style>

@using (Html.BeginForm("Submit", Html.GetControllerName(), FormMethod.Post))
{
<fieldset>
    <div id="contentWrapper" class="@Model.ViewModelClass.Name mvc-rendered">
        <!-- Start of inserted MVC ViewModel body -->
        <div id="viewmodelWrapper">
            @Html.Partial(Html.RazorPartialFile());
            <!-- End of inserted MVC ViewModel body -->
        </div>
    </div>
    <div>
        <button id="SubmitButton" type="submit" value="Submit" class="tk-state-action ripple-effect update-action">Submit</button>
    </div>
    <div id="validationMessageWrapper">
        <div class="validationMessage">
            @Html.ValidationSummary(true)
        </div>
    </div>
    <div class="form-group" style="display:none">
        @* When form is posted, the VMClassBinder recreates the viewmodel with data from the form, starting with the viewmodels name and root id below *@
        @Html.Hidden(VMClass.ThisAsExternalId_nameOf) <!-- This creates a hidden form attribute with the root objects external ID -->
        @Html.Hidden(VMClassBinder.ViewModelNameFormAttribute, Model.ViewModelClass.ViewModel.RootViewModelClass.Name)
    </div>
</fieldset>
}


<script>
    var _this = this;


    function SubmitAction(event, target, theaction, areYouSureQuestion) {
        if (areYouSureQuestion === void 0) { areYouSureQuestion = ""; }
        if (event !== undefined)
            event.stopPropagation();
        if (areYouSureQuestion !== undefined && areYouSureQuestion != '') {
            var answer = window.confirm(areYouSureQuestion);
            if (!answer)
                return;
        }
        var theform = $(target).closest('form');
        $(theform).attr('action', '/'+theaction); // a bit unclear why I need the '/' , but routing adds controller twice otherwise ; probably not needed in Turnkey because of use of Angular JQuery
        theform.submit();
    };

    function NavigateTo(event, target, url, areYouSureQuestion) {
        if (areYouSureQuestion === void 0) { areYouSureQuestion = ""; }
        if (event !== undefined)
            event.stopPropagation();
        if (areYouSureQuestion !== undefined && areYouSureQuestion != '') {
            var answer = window.confirm(areYouSureQuestion);
            if (!answer)
                return;
        }
        var theform = $(target).closest('form');
        window.location.href = url;
    };
    // Toggle row check-box
    function ToggleRow(event, target) {
        if (event !== undefined) {
            event.stopPropagation();
            if (event.target.type == 'checkbox')
                return; // if the automated click below bubbles up and calls again
        }
        var theform = $(".selector", target).click();
    };
    // Toggle row check-box
    function SetCurrentRow(event, target) {
        if (event !== undefined) {
            event.stopPropagation();
            if (event.target.type == 'checkbox')
                return; // if the automated click below bubbles up and calls again
        }
        $(".selector", target.parentElement).removeAttr('checked'); // Unselect all checked in the list
        $(".vmTableRow", target.parentElement).removeClass("vmCurrentRow"); // Remove the current visual style 
        $(".selector", target).click(); // Click the hidden checkbox
        var theform = $(target).closest('form');
        theform.submit();
    };

    
</script>

Note: You may want to separate the CSS and script into your own files, but they are kept as one unit for a better overview here.

The scripts are mainly for allowing buttons to execute actions and grid rows to allow row select and possibly action execution.

The central part in which the generated UI is inserted by Eco.MVC is here:

 @Html.Partial(Html.RazorPartialFile());

Copy the HTML template above into a file you create in your MVC project here: Views/My/GenericView.cshtml.

Create a controller in Controllers/MyController.cs and make the controller a subclass of a ModelDrivenControllerBase like this:

  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 "SampleViewModel" should be an existing MDriven ViewModel. You can then add an MVC menu option in your _Layout.cshtml to show this view like this:

@Html.ActionLink("Test2", "TestStart2", "My")

Note: the content of the view can be made to shift (navigate to another view) by placing navigating buttons on your ViewModel in MDriven or having a single action on grid rows.

To do more advanced stuff, such as rendering the main menu from the model, bringing up modal windows, ajax async load data, keeping unsaved state, bringing up action choices, etc, you can either roll your own with standard C# MVC or refer to MDriven Turnkey that provides all this functionality for you.

This page was edited 5 days ago on 06/17/2024. What links here