views:

247

answers:

3

I am working on an ASP.Net MVC website and I am stuck on a getting my C# code using generics to play nice with my views.

I have a Controller that passes a Model to a View (so far so good). The Model is of type IDescribedEnumerable<T> with some constraints on T, among those is the constraint that T inherits from an interface (IDescribedModel).

I can easily write a View that accepts an IDescribedEnumerable<Country> and that will work as long as T is in fact the type Country.

However, I'd also like to write a default view that accepts an IDescribedEnumerable of <<whatever>> and that will render it. This should be entirely possible. I don't always need to know the specific type of the Model. Often just knowing that it's an IDescribedModel is enough.

As long as I stay in C# there is no problem. When I don't care about the specific type I just declare methods and objects as accepting a <T>. When I do care I declare them as accepting Country.

But:
(1) If I want to render a View I have to pick an type. I can't just say Inherits="System.Web.Mvc.ViewUserControl<IDescribedEnumerable<T>>" I have to specify an existing type between the <>. (even if I were to inherit from ViewUserControl I'd have to cast it to an IDescribedEnumerable<<something>>. (2) Ideally I'd say that Model is IDescribedEnumerable<IDescribedModel> in the default View and IDescribedEnumerable<Country> in the specific implementation. However, then my Controller needs to know whether he's going to render to the default View or the specific view. It is not possible to simply cast an object that is IDescribedEnumerable<Country> to IDescribedEnumerable<IDescribedModel>. (IIRC it is possible in C# 4, but I'm using 3.5)

So what should I do? All options I can think of are very sub-optimal (I'm not looking forward to removing the generics and just casting objects around, nor to copy pasting the default view 65 times and keeping the copies synchoronized, nor going reflection gallore and creating an object based on a known Type object)

A: 

while awaiting some C# genius to come along with the answer to all my problems I have implemented the trick that IEnumerable also uses:

I added a method public IDescribedEnumerable<IDescribedModel> AsIDescribedModels() to the IDescribedEnumerable interface and created a new class GenericDescribedEnumerable<T> : IDescribedEnumerable<IDescribedModel>. In my DescribedEnumerable<T> class I create a GenericDescribedEnumerable and return that. In the GenericDescribedEnumerable I return this

in full code:

public interface IDescribedModel<T> : IDescribedModel{
 T original {
  get;
 }
}

public interface IDescribedEnumerable {
 IDescribedEnumerable<IViewModel> AsIViewModels();
}

public interface IDescribedEnumerable<T> : IDescribedEnumerable
 where T : IDescribedModel{
 IEnumerable<T> GetViewModels();
}

public class DescribedEnumerable<T> : IDescribedEnumerable<IDescribedModel<T>>{

 public DescribedEnumerable(IEnumerable<T> enumerable) {}

 public IDescribedEnumerable<IViewModel> AsIViewModels() {
  return new GenericDescribedEnumerable<T>(/*allProperties*/);
 }

 public IEnumerable<T> GetViewModels() {
  foreach ( T obj in _enumerable ) {
   var vm = new DescribedModel<T>( obj);
   yield return vm;
  }
 }
}

public class GenericDescribedEnumerable<T> : IDescribedEnumerable<IViewModel>{
 //pass in the constructor everything you need, or create in the 
            //constructor of DescribedEnumerable<T>
 public GenericDescribedEnumerable(/*allProperties*/) {
 }

 public IEnumerable<IViewModel> GetViewModels() {
  foreach ( T obj in _enumerable ) {
   var vm = new PlatoViewModel<T>( obj );
   yield return vm;
  }
 }

 public IDescribedEnumerable<IViewModel> AsIViewModels() {
  return this;
 }
}
Jauco
A: 

Yeah, that will work or use the model as object, and cast it appropriately. That's the approach the ViewPage class uses, with ViewPage and ViewPage : ViewPage.

Brian
A: 
Inherits="System.Web.Mvc.ViewUserControl<IDescribedEnumerable<T>>"

...

Model.Cast<IModel>()
George Polevoy
In my version of ASP.Net MVC you can't do Inherits="System.Web.Mvc.ViewUserControl<IDescribedEnumerable<T>>" because it expects T to be an actual name of a type. Furthermore model.Cast() returns an IEnumerable, not my IDescribedEnumerable
Jauco