Search code examples
asp.net-mvct4scaffoldingasp.net-mvc-scaffolding

Using view models in MvcView scaffolding templates


I have viewmodel

public class ClientIndexViewModel
{
    [Key]
    public int SurrogateId { get; set; } // not primary key just used in templating
    public IEnumerable<Client> Clients { get; set; }
}

And I have created a new MvcView t4 template called Index.cs.t4. When I create a new view from the controller and add my model class as the ClientIndexViewModel, the view template ( index.cshtml ) is generated, but without the properties from the IEnumerable<Client>

Templated Index.cshtml

<table class="table">
<!-- Client properties should be here .. i think -->
<tr>
    <th></th> <!-- This is all empty because i dont really know what i am doing -->
</tr>

@foreach (var item in Model) {
<tr>
    <td>
        @Html.ActionLink("Edit", "Edit", new { id=item.SurrogateId }) |
        @Html.ActionLink("Details", "Details", new { id=item.SurrogateId }) |
        @Html.ActionLink("Delete", "Delete", new { id=item.SurrogateId })
    </td>
</tr>
}

Which makes sense when i look at the template file.

Index.cs.t4

<#
IEnumerable<PropertyMetadata> properties = ModelMetadata.Properties;
foreach (PropertyMetadata property in properties) {
if (property.Scaffold && !property.IsPrimaryKey && !property.IsAssociation)     {
#>
<#
    if (property.IsAssociation && GetRelatedModelMetadata(property) == null)     {
        continue;
    }
#>
    <th>
        @Html.DisplayNameFor(model => model.<#= GetValueExpression(property)     #>)
    </th>
<#
    }
}
#>

I would like to be able to specifically target the IEnumerable<Client> property on the viewmodel for the properties variable, to be the properties for the enumeration on PropertyMetaData

I figured I might be able to create a method in ModelMEtadataFunctions.include.t4 file. Just not too sure how , or if i even should be doing that


Solution

  • I found a hack that makes this work. This is by no means any sort of ideal to use, but considering it is just scaffolding code for visual studio and not project code it doesn't seem too bad

    Firstly . The Model Class, for the view, is the entity type that i want to enumerate through. So i get all the properties for the Client from the client entity and not my viewmodel. However the model is wrong ( of course ) . So in the template file, Index.cs.t4 I change the control blocks based on the ViewDataTypeShortName parameter ( found in the Imports.include.t4 ) . This gives me Client , because i am selecting the client entity.

    So for instance in a foreach it would typically display

     @foreach (var item in Model){
     <tr>
        <td>
            @Html.DisplayFor(modelItem => <#= "item." + GetValueExpression(property) #>) 
        </td>
     </tr>
     }
    

    Now i control that by adding

    @foreach (var item in Model.<#= NewViewDataTypeShortName #>)
    // NewViewDataTypeShortName is created with 
    // <# string NewViewDataTypeShortName = ViewDataTypeShortName + 's'; #> 
    // Based on my naming convention.
    

    Which produces this in the Index.cshtml

    @foreach (var item in Model.Clients) {
    

    Then i add my @model by creating a method in the ModelMetaDataFunctions.cs.include.t4

    string GetViewModelName(string ViewDataTypeName,string   
                           ViewDataTypeShortName,string ViewName){
    
           string[] names = ViewDataTypeName.Split(new string[]{"."},StringSplitOptions.None);
           return names[0] + ".Domain.ViewModels." + 
           ViewDataTypeShortName + ViewName +"ViewModel";
    }
    

    Which is called in the Index.cs.t4 like so

    @model <#= GetViewModelName(ViewDataTypeName,ViewDataTypeShortName,ViewName) #>
    

    And then display in the Index.cshtml as

    @model TaskR.Domain.ViewModels.ClientIndexViewModel
    

    This gives me basically everything i need. Its not ideal and relies way to heavily on naming conventions that i am not sure i would like to hand over to another developer. For instance this will not scaffold correctly if the viewmodel is created in the main project, because it is based on a separate project for the data class's. Also the entity type names and view model names have a very strict convention for this to work ..

    Please post if you have a better idea, i dont think this is a best practice