How to access controller methods from a view in ASP.NET MVC

This question has been posed in several places.  One of the places is on StackOverflow here.  The use case here is that my controller has important state or an important function, and I’d like to leverage it from the view.  It’s not appropriate to factor out to an html helper or other view-based utility class.  It is something unique to the controller.  It would be very easy to just pass the controller over to the view so that the view could make use of its methods, but there is a lot of discussion why that is “dirty.” 

For the lay-programmer, that solution would function just fine.  For the professional, the MVC pattern was chosen to keep the view away from the business of knowing the storyboard flow of an application.  That is the responsibility of the controller; therefore, the pattern states that the controller knows the view, but the view does not know the controller.

In ASP.NET MVC, the controller passes an object called a ViewBag to the view.  As an aside, it is really interesting that when ASP.NET MVC 1 was being developed, many .Net developers were using MonoRail, which was another MVC implementation for ASP.NET.  It’s passed object from the controller to the view was called PropertyBag, and many lobbied for the use of the word “bag”, but ViewData was the first term used.  The new ViewBag object comes to us in ASP.NET MVC 3 as a dynamic object.

Desired use case

Let’s consider a page that lists out a series of odd numbers.  We want to alert the user when there is an odd number that is not prime.  Here is the screen.

image

The view for this screen looks like this.

 

image

Notice that inside my loop, I am actually passing the enumerated value of the view model to a method on the ViewBag.  Remember that ViewBag doesn’t come with any methods like this.  It is an empty dynamic object until we define something on it. 

 

Solution

It appears that we have defined a new method on ViewBag called GetWarning().  This new method is the gateway that we longed for to take us back to the controller.  Let’s look at the code for the controller, and we will see that the controller provides the logic for deciding what warning message to display next to the number, if any.

image

There are several things to look at.  First, we define a property, not a method on the ViewBag.  It is a Func, so it can be invoked because it is a method being passed as an object thanks to delegates/closures.  The definition of the method that is passed takes in a number and makes use of a method that belongs to the controller.  Because closures/delegates have access to the scope in which they are defined, our GetContextAwareWarning method can access the IsPrime method even when the method is invoked way over in the view.

Now, we can give the view everything it needs to function without ever being tempted to pass a reference to the controller itself. 

 

Comments

Michael J. Ryan said on 7.30.2011 at 12:08 AM

In all honesty, I don't see how, even in your use case, it is necessary to break the veil between controller and view.. you could just as easily predetermine the result of GetContextAwareWarning with the results you are passing to the view as a List(Tuple(int, string)) ...

Sean Feldman said on 7.30.2011 at 8:50 AM

It's a good attempt, thought I tend to agree with Michael. View should get the data. The fact that you can pass a method from controller decorated as a property on a viewbag doesn't change the fact that this is controller logic. If this is sheer presentation logic, maybe there's a place for this method on the view.

Jeffrey Palermo said on 7.30.2011 at 9:09 AM

Of course, I agree with you both. In these cases, I would find a way to divorce this logic from the controller or for the controller to pass _all_ the data needed by the view.

There are those out there who resort to passing the controller to the view. This is a step away from doing that.

Marcus Swope said on 7.30.2011 at 1:01 PM

What's wrong with defining the GetWarning method on the model?

Something like:

public class PrimeNumberModel

{

public int[] Numbers { get; set; }

public string GetWarning (int number)

{

...prime number logic...

}

}

I know that you are presenting a contrived example, but it seems that this is more of a view/model concern, and not a controller concern considering it doesn't have anything to do with controlling application flow or view resolution. Just a thought....

I am excited, though, that you are posting more dev-centric stuff, keep it up! :)

StarTrekRedneck said on 7.31.2011 at 11:23 PM

I like Jeffrey's comment about removing the logic from the controller entirely. I only use controllers for translating between http requests and .net objects/method calls. No logic outside of that function whatsoever. However, I appreciate Jeffrey's acceptance of using "hacks" for casual websites. Sometimes we're building a sky scraper, and sometimes we're just building a dog house.

Dane Morgridge said on 8.01.2011 at 8:18 AM

Why not just use a helper?

Jeffrey Palermo said on 8.01.2011 at 2:19 PM

@Dane,

I think that would be a good option. That would allow moving the logic away from the controller.

For those in the habit of sending the controller to the view, this is a method to stop doing that. In my own work, I don't use this technique (you won't find it in my book, either - http://bit.ly/aspnetmvc3inaction)

emet said on 9.23.2011 at 9:14 PM

ViewModel anyone?