A common problem many developers face when creating ASP.NET MVC applications, is when you need some data in your Master Page (I know they're called "Layout" pages in Razor, but from here on out I'll be referring to them as Master Pages). The problem comes up when you need to have some dynamic data in the Master Page itself (think of a Shopping Cart example, where you want to display the current count of items in the cart) but since we ever only send data from the Controllers to the Views themselves, how does the Master Page get the data that it needs.
I know there are many solutions out there, and I'm not saying any of them are good or bad, I'm just here to show another way of doing it, using Dependency Injection. For this example, I'll be demoing a use case of where we want to display my last 5 tweets on the Master Page. This is the final result:
The right hand side under Index is the Index View, but on the left hand side, the "left nav" is where the twitter feed sits, and that data we want to display on every page, and will therefore come from the master page.
In ASP.NET MVC 3, Dependency Injection has become very easy. Now all you need to do is to implement your own IDependencyResolver, and let the framework know to use your custom resolver when it needs to build up Controllers and such. Here's a typical implementation using Unity:
public class UnityDependencyResolver : IDependencyResolver
{
private readonly IUnityContainer _unityContainer;
public UnityDependencyResolver(IUnityContainer unityContainer)
{
_unityContainer = unityContainer;
}
public object GetService(Type serviceType)
{
if (!_unityContainer.IsRegistered(serviceType))
{
if (serviceType.IsAbstract || serviceType.IsInterface)
{
return null;
}
}
return _unityContainer.Resolve(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _unityContainer.ResolveAll(serviceType);
}
}
Not much to explain on this one, except that with Unity there's no "TryResolve" (actually there is in the Prism Codeplex project, but all that does is wrap a try/catch) so therefore we need to make sure we can resolve the type before we actually call Resolve.
Now let's start building on top of that. I created an interface called IMasterPageDataRetriever. It's responsibility is simply to get data that the Master Page needs. In this case, it'll call out to a Service that calls Twitter to retrieve my last 5 tweets (incidentally, NuGet is AWESOME for this kind of stuff. I just typed in "Twitter" in the Search box in Nuget, and picked the one that looked the most popular "TweetSharp". Cool stuff).
public interface IMasterPageDataRetriever
{
object GetMasterPageData();
}
Here's the implementation:
public class MasterPageDataSetter : IMasterPageDataRetriever
{
private readonly ITweetRetriever _tweetRetriever;
public MasterPageDataSetter(ITweetRetriever tweetRetriever)
{
_tweetRetriever = tweetRetriever;
}
public object GetMasterPageData()
{
return new TweetViewModel(_tweetRetriever.GetTweets("@brooklynDev", 5));
}
}
public class Tweet
{
public string UserName { get; set; }
public string Message { get; set; }
public DateTime Timestamp { get; set; }
}
public interface ITweetRetriever
{
IEnumerable<Tweet> GetTweets(string userName, int maxAmount);
}
public class TweetRetriever : ITweetRetriever
{
public IEnumerable<Tweet> GetTweets(string userName, int maxAmount)
{
var service = new TwitterService();
var result = service.ListTweetsOnSpecifiedUserTimeline(userName, maxAmount);
return result.Select(r => new Tweet
{
Message = r.TextAsHtml,
Timestamp = r.CreatedDate,
UserName = userName
});
}
}
public class TweetViewModel
{
public TweetViewModel(IEnumerable<Tweet> tweets)
{
this.Tweets = tweets.Select(t => t.Message);
this.UserName = tweets.First().UserName;
}
public string UserName { get; set; }
public IEnumerable<string> Tweets { get; set; }
}
Not much code, but the point here is, I have an implementation of an IMasterPageDataRetriever that gets injected with an ITweetRetriever, calls that TweetRetriever and that returns a ViewModel that has the tweets in it. So far this is all pretty straight forward, very common stuff that you see in all apps that use loose coupling.
The questions now however, is how does the Master Page get a hold of this TweetViewModel? The trick is in the Dependency Resolver. Here's the updated code, I'll do my best to explain:
public class UnityDependencyResolver : IDependencyResolver
{
private readonly IUnityContainer _unityContainer;
public UnityDependencyResolver(IUnityContainer unityContainer)
{
_unityContainer = unityContainer;
}
public object GetService(Type serviceType)
{
if(!_unityContainer.IsRegistered(serviceType))
{
if(serviceType.IsAbstract || serviceType.IsInterface)
{
return null;
}
}
var result = _unityContainer.Resolve(serviceType);
var controller = result as ControllerBase;
if(controller == null)
{
return result;
}
try
{
var masterPageDataSetter = _unityContainer.Resolve<IMasterPageDataRetriever>();
controller.ViewBag.MasterPageModel = masterPageDataSetter.GetMasterPageData();
return controller;
}
catch
{
return result;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _unityContainer.ResolveAll(serviceType);
}
}
What's happening here is that instead of just returning what we got out of the container, we first check if it's a Controller (if it can be assigned to a ControllerBase). If it is, we cast it to a ControllerBase, at which point we use the ViewBag property, and set the data we get from our IMasterPageDataRetriever (pulled out of the container) to a (dynamic) property on the ViewBag called "MasterPageModel". In order for this to work, you need to make sure you're correctly configuring your container:
In my Global.asax I have this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
DependencyResolver.SetResolver(new UnityDependencyResolver(GetConfiguredContainer()));
}
private IUnityContainer GetConfiguredContainer()
{
var container = new UnityContainer();
container.RegisterType<ITweetRetriever, TweetRetriever>();
container.RegisterType<IMasterPageDataRetriever, MasterPageDataSetter>();
return container;
}
I'm registering all the dependencies I need, including the IMasterPageDataRetriever.
Now, in order to use this data, here's what we do in our Master Page (I'm using the Razor view engine, but this can obviously be applied to any View Engine):
First, I created a Partial View that will actually display the Tweets. It's strongly typed to the TweetViewModel type:
@model MasterPageModelDemo.Web.Models.TweetViewModel
<h4>@this.Model.UserName</h4>
<ul>
@foreach(var tweet in this.Model.Tweets)
{
<li>@Html.Raw(tweet)</li>
}
</ul>
Then, in my Master Page:
@{Html.RenderPartial("TweetViewPartial", (object)ViewBag.MasterPageModel);}
Make sure to cast it to an object, because if not, .Net will complain that it doesn't have an overload that takes dynamic, since the ViewBag is dynamic.
When you put it all together, we get this:
The only tricky bit here, is to unit test this so that we can make sure we never accidentally mess up our DependencyResolver and no longer setting the MasterPageModel property. The hard part about this particular unit test, is that we need a UnityContainer. The problem is, we want to mock a ControllerBase, and an IMasterPageDataRetriever. Now remember, when mocking objects, the Mocking framework (in this case I'll be using Moq) creates Proxy objects with no types at runtime. For one reason or another, Unity doesn't play nice with Proxy types, so therefore, we're stuck using our own implementation of IUnityContainer. It's not bad because there's really only two things we need to give implementations for:
public class TestContainer : IUnityContainer
{
private readonly IMasterPageDataRetriever _masterPageDataRetriever;
private readonly ControllerBase _controllerBase;
public TestContainer(IMasterPageDataRetriever masterPageDataRetriever, ControllerBase controllerBase)
{
_masterPageDataRetriever = masterPageDataRetriever;
_controllerBase = controllerBase;
}
public IUnityContainer AddExtension(UnityContainerExtension extension)
{
throw new NotImplementedException();
}
public object BuildUp(Type t, object existing, string name, params ResolverOverride[] resolverOverrides)
{
throw new NotImplementedException();
}
public object Configure(Type configurationInterface)
{
throw new NotImplementedException();
}
public IUnityContainer CreateChildContainer()
{
throw new NotImplementedException();
}
public IUnityContainer Parent
{
get
{
throw new NotImplementedException();
}
}
public IUnityContainer RegisterInstance(Type t, string name, object instance, LifetimeManager lifetime)
{
throw new NotImplementedException();
}
public IUnityContainer RegisterType(Type from, Type to, string name, LifetimeManager lifetimeManager, params InjectionMember[] injectionMembers)
{
throw new NotImplementedException();
}
public IEnumerable<ContainerRegistration> Registrations
{
get
{
return Enumerable.Empty<ContainerRegistration>();
}
}
public IUnityContainer RemoveAllExtensions()
{
throw new NotImplementedException();
}
public object Resolve(Type t, string name, params ResolverOverride[] resolverOverrides)
{
if (t == typeof(IMasterPageDataRetriever))
{
return _masterPageDataRetriever;
}
if ((typeof(ControllerBase)).IsAssignableFrom(t))
{
return _controllerBase;
}
throw new InvalidOperationException();
}
public IEnumerable<object> ResolveAll(Type t, params ResolverOverride[] resolverOverrides)
{
throw new NotImplementedException();
}
public void Teardown(object o)
{
}
public void Dispose()
{
}
}
Basically, we take in an instance of an IMasterPageDataRetriever, and a ControllerBase. In the resolve method, we just pass the correct one back, based on what's being asked from it. Here's the actual test (I'm using NUnit, again, NuGet is awesome for adding all these references!!)
[TestFixture]
public class Tests
{
[Test]
public void TestMasterPageDataGetsSetByDependencyResolver()
{
const string testData = "Test Data";
var masterDataRetrieverMoq = new Mock<IMasterPageDataRetriever>();
masterDataRetrieverMoq.Setup(m => m.GetMasterPageData()).Returns(() => testData);
var masterDataRetireverObject = masterDataRetrieverMoq.Object;
var controllerMoq = new Mock<ControllerBase>();
var controllerObject = controllerMoq.Object;
var unityDependencyResolver = new UnityDependencyResolver(new TestContainer(masterDataRetireverObject, controllerObject));
var result = unityDependencyResolver.GetService(controllerObject.GetType()) as ControllerBase;
Assert.AreEqual(result.ViewBag.MasterPageModel, testData);
}
}
Here, we're mocking out the IMasterPageDataRetriever, and making it return a string "Test Data". After registering that instance with our fake container, and then calling GetService on the UnityDependencyResolver, the Mocked ControllerBase ViewBag should have the "Test Data" set.
Like I said in the beginning, this is far from the only way of doing it, it's just that I like this approach because you don't need to force your ViewModels from inheriting some base view model, you don't need to have a custom ControllerBase, you just need to massage your Dependency Injection code a bit, and then forget it's ever there. Also, as I've shown, it's still testable, which is always a good thing! Enjoy!
5 comments:
Hello!
Thanks for the very useful topic. But what if I need to pass some parameter into partial view, how to do that? For example I want to have several partial views on my layout page, for twitters from different users. It would be great to use the same partial view but I don't imagine how to pass parameter like username, for example, "@brooklynDev", to it?
Thanks
@Electric: Any data that you need in your view, can be set in the MasterPageDataSetter. e.g. In your example, where you have multiple instances of the partial with different twitter handles, well where are the different twitter handles coming from? Let's say it came from some database, then your MasterPageDataSetter would need to be injected with an "ITwitterHandlesRepository" or something like that, and then you set all that data to the ViewBag property. The point I'm making is, any data you need in the view, you can have the appropriate services injected to the MasterPageDataSetter and then feed it along. Makes sense?
Thanks for the answer. But I don't need to set up any data into MasterPageDataSetter, I need to set up them onto page itself, for example something like that:
@{Html.RenderPartial("TweetViewPartial", (object)ViewBag.MasterPageModel, new ViewDataDictionary { { "twitId", "brooklynDev" } });}
Then partial view will access it. But I need it access somewhere before, and I suppose MasterPageDateSetter will not able to access ViewBag at all. So how could I pass this parameter to process it before partial view itself? Unity resolver can access but the dictionary is always null.
Thanks
Hi I am new to MVC 3 and struggling with the same model in master page issue. with unity.mvc3 there is only only bootstrapper.cs which is added by the package itlself. How do I use this solution in that?
Great sample, mister Friedman, great !!
Post a Comment