tags:

views:

380

answers:

4

There's no strongly typed View() method to return an ActionResult. So, suppose I have

class Edit : ViewPage<Frob>

In my FrobController, I will do something like "return View("Edit", someFrob);". There's no checking going on here, so I have to always manually synchronize the view and controller's use of it. This is suboptimal, and I'm not aware of any built-in fixes.

I added this method to my controller base class:

public ActionResult ViewPage<V, M>(V view, M model)
    where V : ViewPage<M>
    where M : class {
    return View(typeof(V).Name, model);
}

Note: The reason I take a view object that's never used is because, AFAIK, there's no way to get C#'s type inference to work otherwise. If I removed the view parameter, I'd need to specify V explicitly, which also means specifying M explicitly too... sigh.

So now, I can do this:

  return ViewPage(new Views.Frob.Edit(), myFrob);

I'm specifying the exact view (no problem if it gets renamed), and myFrob is typechecked to be the right model type. The ugly side is that I new up a Edit. Alternatively, I could write:

  return ViewPage((Views.Frob.Edit)null, myFrob);

One downside is that the the model must be an exact match. So with a ViewPage>, I cannot pass in a List. I thought this might work:

    public ActionResult ViewPage<V, M, T>(V view, T model)
        where V : ViewPage<M>
        where M : class 
        where T : M {
        return View(typeof(V).Name, model);
    }

But C#'s type inference can't figure it out. The other potential problem is that the name of the view type might not be the right name, as I think it can be overridden by attributes. But that's an easy fix if I run into it.

Questions:

  1. How can I make this syntax cleaner?
  2. What downsides am I missing here?

Edit: With respect to the Controller knowing about the View, it only slightly does. The only thing it gets from the view is the Type, for which it grabs the name. So that's equivalent to passing in the string name. And the strongly typed model, which must match or it'll fail. So it doesn't really know too much about the View -- its just a trick to get the compiler to catch errors.

+2  A: 

The first problem I see is that you've now made your Controller aware of the View. That's a line you should never cross.

Craig Stuntz
How is it not aware of the View when it does View("foo", strongModel)?
MichaelGG
Also, I must say I don't see the downside of "crossing that line". My controllers do almost no work, pretty much just push everything off to the WCF layer where all the real logic is.
MichaelGG
Michael, "foo" doesn't make the controller aware of the model, because it doesn't even know what view engine will be used. Controllers, generally, don't get to decide that. The separation of concerns question is more than I can address in a comment.
Craig Stuntz
OK, yes, I understand that technically, no, they aren't aware. For any sufficiently complex view though, they must be implicitly aware to know which ViewState to set and which model object to pass. Boxing the model and pretending it's now separate seems disingenuous.
MichaelGG
A: 

Take a look at this example: http://oddiandeveloper.blogspot.com/2008/11/strongly-typed-view-names.html

You can call yout View like this:

return View(typeof(Views.en.Home.about), AboutModel);
Roberto Barros
That's ok, but the AboutModel isn't typechecked.
MichaelGG
A: 

I'm still scratching my head as to what you are gaining by doing this, not saying you are wrong. Ayende does the same thing on an almost daily basis to me :-) Anyway the cleanest syntax I could come up with is this:

public ActionResult Test() {
    return View<Views.Module1.Test, string>("Hello All");
}


protected ActionResult View<TView, TModel>(TModel model)
    where TView : ViewPage<TModel>
    where TModel : class {
     return View(typeof(TView).Name, model);
}

You sacrifice the type inference but get your full compile time type checking and you don't have to new up an unused object.

Mike Glenn
The gains are: 1. I specify the right view, and the compiler checks this. It's not just an opaque string which may or may not be related to any view. 2. The Model for that specific view is checked as well. (Hence, I can rename and rework views as needed without worry.)
MichaelGG
you might consider passing in an "object" parameter and use reflection to look up the views generic type and compare it to your model's type. Then if you are unit testing your actions you'd get an exception then. This allows you to pass only the generic view type. Depends on what syntax you want.
Mike Glenn
A: 

There's an article about strongly-typed view here: http://dylanbeattie.blogspot.com/2008/05/strongly-typed-view-references-with.html

ajma