54

Is it possible to use a generic model in ASP.NET MVC 3 (w/ Razor)? The following fails with a syntax error:

@model DtoViewModel<T> where T : IDto

6 Answers 6

33

given that @model is expecting a type - not a type declaration you could use:

@model DtoViewModel<IDto>

and take advantage of generic covariance

Sign up to request clarification or add additional context in comments.

Comments

26

Such syntax is not supported by Razor, sorry.

2 Comments

Bummer. I'd love to see support for it in the future, but I haven't seen anyone else griping about it so I doubt that it'll gain any kind of traction. Thanks for your answer.
I want this! So that's gripe or at least a request from one other person.
7

Assuming, you want to use a generic type in order to avoid code duplications in each view of ViewModel<T> you can do it this way:

1. create a view for the parts of ViewModel<T> that are unique to the view

ModelView.cshtml:

@model ViewModel<specificType>

@{Layout = "~/Views/Shared/Layout.cshtml";}
<h2 class="sub-header">Specific type view</h2>

2. create a view for the common parts, that should be rendered in each view of <T>

Grid.cshtml:

@{ var webGrid = new WebGrid(Model.PageItems); }

<div class="row" style="overflow: auto">
    @webGrid.GetHtml("table-striped", mode: WebGridPagerModes.All, firstText: "First", lastText: "Last")
</div>

Since it's a partial view, you don't need to declare the type of Model again. It will simply use the model you defined in the parent view, that renders it. The property IList<T> PageItems of your model, will remain strongly typed with <specificType>.

3. Don't forget to actually render the partial view of your common parts

ModelView.cshtml:

@RenderPage("~/Views/Shared/Grid.cshtml")

Comments

1

This is not ideal, but it works and could get pretty creative with this pattern.

@model YourNameSpace.MyModel


public MyModel
{
    public MyGenericType<string> ModelAStuff {get;set;}
    public MyGenericType<int> ModelBStuff {get;set;}
    public MyGenericType<DateTime> ModelCStuff {get;set;}
}

public class MyGenericType<T>
{
  //use T how ever you like
  public T Color {get;set;}
  public T Year  {get;set;}
}

1 Comment

Since long, I have been working this way. It is not extremely nice, but seems to be the best solution at the moment.
1

I found this thread and did some further digging. Looks like the feature is on the 2.2.0 backlog. If anyone wants to get involved you can check out the issue on Github. https://github.com/aspnet/Mvc/issues/7152

1 Comment

Too bad. The issue is closed as not planned.
0

Microsoft uses interfaces in many of their libraries to overcome this type of restriction. Here is my implementation that allows me to send in any generic model. My view is a partial.

Note: because I want the caller to define where the values come from, the model mostly takes lambda accessors.

Partial reference from parent view:

<partial name="EntitySelector.partial" model="
    new EntitySelectionModel<Image>
    { 
        DbSet = (new EcoContext()).Images,
        GetDbEntityDisplayName = e => e.DisplayName,
        GetDbEntityId = e => e.Id,
        GetDbEntityImageValue = e => e.Value,
        GetDbEntityName = e => e.Name,
        Multiselect = false     
    }" />

Model with interface:

using Microsoft.EntityFrameworkCore;

namespace MyNamespace
{
    public interface IEntitySelectionModel
    {
        IQueryable DbSet { get; }
        Func<object, string> GetDbEntityDisplayName { get; }
        Func<object, int> GetDbEntityId { get; }
        Func<object, byte[]> GetDbEntityImageValue { get; }
        Func<object, string> GetDbEntityName { get; }
        bool Multiselect { get; }
    }

    public class EntitySelectionModel<TEntity> : IEntitySelectionModel
            where TEntity : class
    {
        public DbSet<TEntity> DbSet { get; set; }
        IQueryable IEntitySelectionModel.DbSet => DbSet;
        public Func<TEntity, string> GetDbEntityDisplayName { get; set; }
        Func<object, string> IEntitySelectionModel.GetDbEntityDisplayName => e => GetDbEntityDisplayName(e as TEntity);
        public Func<TEntity, int> GetDbEntityId { get; set; }
        Func<object, int> IEntitySelectionModel.GetDbEntityId => e => GetDbEntityId(e as TEntity);
        public Func<TEntity, byte[]> GetDbEntityImageValue { get; set; }
        Func<object, byte[]> IEntitySelectionModel.GetDbEntityImageValue => e => GetDbEntityImageValue(e as TEntity);
        public Func<TEntity, string> GetDbEntityName { get; set; }
        Func<object, string> IEntitySelectionModel.GetDbEntityName => e => GetDbEntityName(e as TEntity);
        public bool Multiselect { get; set; }
    }
}

Partial view implementation:

@model IEntitySelectionModel

<dialog @(Model.Multiselect ? "multiple" : "") >
    @foreach(var entity in Model.DbSet)
    {
        var name = Model.GetDbEntityName(entity);
        <figure id="@Model.GetDbEntityId(entity)" name="@name">
            <img src="@(await Model.GetDbEntityImageValue(entity).Resize(name, 16, 16))" alt="The Pulpit Rock" />
            <figcaption>@Model.GetDbEntityDisplayName(entity)</figcaption>
        </figure>
    }
</dialog>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.