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
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