Thursday, April 25, 2013

Where do I put view logic with Razor?

So I have been using MVC3 with Razor as a view engine. I started putting a lot of logic in the cshtml file. I didn't necessarily think this was the right thing. I talked to my team and the consensus was that view logic should go in the ViewModel object. Two main reasons were given for this.

  1. Combining code and markup is ugly. Due to a visual studio bug and our tabbing system that was especially true. Remember that ugly code is hard to read code.
  2. It is easier to test straight code than Razor's cshtml.

The next page I worked on I decided to go this way. I had a situation where I was rendering a table. For one column sometimes I wanted to render a link and sometimes I wanted to render text. Before I would have put an if in the cshtml, but I tried moving the code into the ViewModel. I instantly ran into the issue of using HtmlHelper. I had no access to this class. I toyed with the idea of creating one, but I didn't have a pointer to the controller which is needed to create an HtmlHelper. I could have passed a pointer to the controller into the ViewModel, but it looked like I was quickly generating an unacceptably hacky solution.

Another smell I quickly generated was that I started using region tags. Now I like the region tag, but people who hate that tag and say that it exists to cover up bad design have a point.

So I decided to go another way. I decided to put view logic where it is supposed to go, the WebViewPage. One nice thing about this is that I can access HtmlHelper easily. Now this leaves me with a lot of objects. I have a Controller, a Handler, a Model, a ViewModel, a View, and now ViewLogic. But since most people believe lots of small, clearly defined objects are better than big ones with potentially multiple functions I think this is reasonable.

Here is the basic process:

  1. Create a ViewLogic class. The class signature should be  public abstract class XXXViewLogic<TModel> : WebViewPage<TModel> where TModel : XXXViewModel
  2. Remove your @model in the cshtml and replace with @inherits XXXViewLogic<XXXViewModel>
You can now access methods in the ViewLogic class just by calling them in the cshtml. You can keep html markup out of your ViewModel. Your view logic is in a testable class and is nicely isolated from business logic and from your models. You can also easily attach breakpoints to the view logic class which can be handy.

I really like MVC and am fairly neutral on Razor, but I think many decisions made were to put distance between MVC and WebForms. It seems to me that a lot of useful stuff was tossed out because it was too WebFormsy. Having a code behind for view logic fits extremely well into MVC as does ViewState. Ironically, both fit better into MVC than into WebForms, but aren't common patterns in WebForms.

No comments:

Post a Comment