The Big Picture:
I have found what seems like a limitation of Razor and I am having trouble coming up with a good way around it.
The Players:
Let's say I have a model like this:
public abstract class BaseFooModel<T>
where T : BaseBarType
{
public abstract string Title { get; } // ACCESSED BY VIEW
public abstract Table<T> BuildTable();
protected Table<T> _Table;
public Table<T> Table // ACCESSED BY VIEW
{
get
{
if (_Table == null)
{
_Table = BuildTable();
}
return _Table;
}
}
}
And a subclass like this:
public class MyFooModel : BaseFooModel<MyBarType>
{
// ...
}
public class MyBarType : BaseBarType
{
// ...
}
I want to be able to pass MyFooModel
into a razor view that is defined like this:
// FooView.cshtml
@model BaseFooModel<BaseBarType>
But, that doesn't work. I get a run-time error saying that FooView
expects BaseFooModel<BaseBarType>
but gets MyFooModel
. Recall that MyFooModel
in herits from BaseFooModel<MyBarType>
and MyBarType
inherits from BaseBarType
.
What I have tried:
I tried this out in non-razor land to see if the same is true, which it is. I had to use a template param in the View to get it to work. Here is that non-razor view:
public class FooView<T>
where T : BaseBarType
{
BaseFooModel<T> Model;
public FooView(BaseFooModel<T> model)
{
Model = model;
}
}
With that structure, the following does work:
new FooView<MyBarType>(new MyFooModel());
My Question:
How can I do that with Razor? How can I pass in a type like I am doing with FooView
?
I can't, but is there any way around this? Can I achieve the same architecture somehow?
Let me know if I can provide more info. I'm using .NET 4 and MVC 3.
EDIT:
For now, I am just adding a razor view for each subclass of BaseFooModel<BaseBarType>
. I'm not psyched about that because I don't want to have to create a new view every time I add a new model.
The other option is to just take advantage of the fact that I am able to get this working in regular c# classes without razor. I could just have my razor view @inherits
the c# view and then call some render method. I dislike that option because I don't like having two ways of rendering html.
Any other ideas? I know its hard to understand the context of the problem when I'm giving class names with Foo
and Bar
, but I can't provide too much info since it is a bit sensitive. My apologies about that.
What I have so far, using Benjamin's answer:
public interface IFooModel<out T>
where T : BaseBarModel
{
string Title { get; }
Table<T> Table { get; } // this causes an error:
// Invalid variance: The type parameter 'T' must be
// invariantly valid on IFooModel<T>.Table.
// 'T' is covariant.
}
public abstract class BaseFooModel<T> : IFooModel<T>
where T : BaseBarModel
{
// ...
}
What ended up working:
public interface IFooModel<out T>
where T : BaseBarModel
{
string Title { get; }
BaseModule Table { get; } // Table<T> inherits from BaseModule
// And I only need methods from BaseModule
// in my view.
}
public abstract class BaseFooModel<T> : IFooModel<T>
where T : BaseBarModel
{
// ...
}