Integrated StructureMap container for the MvcServiceLocator in ASP.NET MVC 3
ASP.NET MVC 3 just released a few days ago. In this release, Microsoft team published some features very cool. I really love that, because we shall code easier. See release note for more information. And in this post, I would like to explore about MvcServiceLocator, one of features very interesting in this release. So why I want to know about that? Because in the past project in codeplex http://nma.codeplex.com, I used to CommonServiceLocator from Microsoft, and I must to customize it for integrated with StructureMap. Now, I don’t need making like that, because ASP.NET MVC team has already integrated it into core of ASP.NET MVC 3. That enough to talking, I only care about coding because I don’t want to talk more but not practice.
When ASP.NET MVC 3 released, I read the series articles from Brad Wilson http://bradwilson.typepad.com/blog/2010/07/service-location-pt1-introduction.html. It is really useful articles for me. And he mentioned to MvcServiceLocator in ASP.NET MVC 3, and implemented the custom ServiceLocator for Unity. And after few hours to searching on internet, I also found the article from Maarten Balliauw about integrated the custom ServiceLocator for MEF. I really like the StructureMap, so in this post, I will pay attention to custom the ServiceLocator for StructureMap.
The first thing, you need to start is download the StructureMap from http://structuremap.github.com, and download the ASP.NET MVC 3 at http://go.microsoft.com/fwlink/?LinkID=157073 . After enough tools, we shall start for my post.
To save time for all of you, I will list the agenda as below:
-
Build the StructureMapControllerFactory
-
Build StructureMapServiceLocator
-
Build the Registry of StructureMap
-
Build the heart of application
-
Repository for Persistence Ignorance
-
Adding information to Global.asax (request from Itzik)
-
Controller for getting request from user
-
Build the View
Build the StructureMapControllerFactory
public class StructureMapControllerFactory : IControllerFactory
{
private readonly IContainer _container;
private readonly IControllerFactory _innerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="StructureMapControllerFactory"/> class.
/// </summary>
/// <param name="container">The container.</param>
public StructureMapControllerFactory(IContainer container)
: this(container, new DefaultControllerFactory())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StructureMapControllerFactory"/> class.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="innerFactory">The inner factory.</param>
public StructureMapControllerFactory(IContainer container, IControllerFactory innerFactory)
{
_container = container;
_innerFactory = innerFactory;
}
/// <summary>
/// Creates the specified controller by using the specified request context.
/// </summary>
/// <param name="requestContext">The request context.</param>
/// <param name="controllerName">The name of the controller.</param>
/// <returns>The controller.</returns>
public IController CreateController(RequestContext requestContext, string controllerName)
{
try
{
return _container.GetInstance<IController>(controllerName.ToLowerInvariant());
}
catch (Exception)
{
return _innerFactory.CreateController(requestContext, controllerName);
}
}
/// <summary>
/// Releases the specified controller.
/// </summary>
/// <param name="controller">The controller.</param>
public void ReleaseController(IController controller)
{
GC.SuppressFinalize(controller);
}
}
Build StructureMapServiceLocator
public class StructureMapServiceLocator : IMvcServiceLocator
{
const string HttpContextKey = "__StructureMapServiceLocator_Container";
private readonly IMvcServiceLocator _defaultLocator;
private readonly IContainer _container;
/// <summary>
/// Initializes a new instance of the <see cref="StructureMapServiceLocator"/> class.
/// </summary>
public StructureMapServiceLocator()
{
var structureMapServiceLocator = MvcServiceLocator.Current as StructureMapServiceLocator;
if (structureMapServiceLocator != null)
{
_container = structureMapServiceLocator.Container;
}
_defaultLocator = MvcServiceLocator.Default;
}
/// <summary>
/// Initializes a new instance of the <see cref="StructureMapServiceLocator"/> class.
/// </summary>
/// <param name="container">The container.</param>
public StructureMapServiceLocator(IContainer container)
: this(container, MvcServiceLocator.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StructureMapServiceLocator"/> class.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="defaultLocator">The default locator.</param>
public StructureMapServiceLocator(IContainer container, IMvcServiceLocator defaultLocator)
{
_container = container;
_defaultLocator = defaultLocator;
}
/// <summary>
/// Gets the container.
/// </summary>
/// <value>The container.</value>
protected Container Container
{
get
{
if (!HttpContext.Current.Items.Contains(HttpContextKey))
{
HttpContext.Current.Items.Add(HttpContextKey, Container);
}
return (Container)HttpContext.Current.Items[HttpContextKey];
}
}
/// <summary>
/// Releases the specified instance.
/// </summary>
/// <param name="instance">The instance.</param>
public void Release(object instance)
{
if (instance != null)
{
GC.SuppressFinalize(instance);
}
}
/// <summary>
/// Resolves the specified service type.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <param name="key">The key.</param>
/// <returns></returns>
private object Resolve(Type serviceType, string key = null)
{
var instance = _container.GetInstance(serviceType);
if (instance != null)
{
return instance;
}
var dedaultInstance = _defaultLocator.GetInstance(serviceType, key);
if (dedaultInstance != null)
{
return dedaultInstance;
}
throw new ActivationException(string.Format("Could not resolve service type {0}.", serviceType.FullName));
}
/// <summary>
/// Resolves all.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <returns></returns>
private IEnumerable<object> ResolveAll(Type serviceType)
{
var instances = _container.GetAllInstances(serviceType).Cast<IEnumerable<object>>();
if (instances.Count() > 0)
{
return instances;
}
var defaultInstances = _defaultLocator.GetAllInstances(serviceType);
if (defaultInstances != null)
{
return defaultInstances;
}
throw new ActivationException(string.Format("Could not resolve service type {0}.", serviceType.FullName));
}
/// <summary>
/// Gets all instances.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <returns></returns>
public IEnumerable<object> GetAllInstances(Type serviceType)
{
return ResolveAll(serviceType);
}
/// <summary>
/// Gets all instances.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <returns></returns>
public IEnumerable<TService> GetAllInstances<TService>()
{
var instances = ResolveAll(typeof(TService));
return from TService instance in instances select instance;
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <param name="key">The key.</param>
/// <returns></returns>
public object GetInstance(Type serviceType, string key)
{
return Resolve(serviceType, key);
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <returns></returns>
public object GetInstance(Type serviceType)
{
return Resolve(serviceType);
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <param name="key">The key.</param>
/// <returns></returns>
public TService GetInstance<TService>(string key)
{
return (TService)Resolve(typeof(TService), key);
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <returns></returns>
public TService GetInstance<TService>()
{
return (TService)Resolve(typeof(TService));
}
/// <summary>
/// Gets the service object of the specified type.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <returns>
/// A service object of type <paramref name="serviceType"/>.-or- null if there is no service object of type <paramref name="serviceType"/>.
/// </returns>
public object GetService(Type serviceType)
{
return Resolve(serviceType);
}
}
Build the Registry of StructureMap
public class CustomRegistry : Registry
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomRegistry"/> class.
/// </summary>
public CustomRegistry()
{
// These need for MvcServiceLocator in ASP.NET MVC 3
For<IMvcServiceLocator>().Singleton().Use<StructureMapServiceLocator>();
For<IControllerFactory>().Singleton().Use<DefaultControllerFactory>();
// register all services here
For<ICategoryDataProvider>().Use<CategoryDataProvider>();
For<ICategoryRepository>().Use<StubCategoryRepository>();
For<HomeController>().Use<HomeController>();
}
}
Build the heart of application
public class Category : EntityBase
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public virtual string Name { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Category"/> class.
/// </summary>
public Category()
{
}
}
Repository for Persistence Ignorance
public interface ICategoryRepository : ICommandRepository<Category>, IQueryRepository<Category>
{
}
public class StubCategoryRepository : ICategoryRepository
{
private ICategoryDataProvider _categoryDataProvider;
/// <summary>
/// Initializes a new instance of the <see cref="StubCategoryRepository"/> class.
/// </summary>
/// <param name="categoryDataProvider">The category data provider.</param>
public StubCategoryRepository(ICategoryDataProvider categoryDataProvider)
{
_categoryDataProvider = categoryDataProvider;
}
/// <summary>
/// Initializes a new instance of the <see cref="StubCategoryRepository"/> class.
/// </summary>
public StubCategoryRepository()
: this(new CategoryDataProvider())
{
}
/// <summary>
/// Gets the by id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public Category GetById(int id)
{
DBC.Contract.Assert(_categoryDataProvider != null, "CategoryDataProvider is null");
return _categoryDataProvider.GetCategoryList().SingleOrDefault(x => x.Id == id);
}
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
public IEnumerable<Category> GetAll()
{
DBC.Contract.Assert(_categoryDataProvider != null, "CategoryDataProvider is null");
return _categoryDataProvider.GetCategoryList();
}
.....
}
Adding information to Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ObjectFactory.Initialize(x => x.AddRegistry(new CustomRegistry()));
MvcServiceLocator.SetCurrent(new StructureMapServiceLocator(ObjectFactory.Container, MvcServiceLocator.Default));
var factory = new StructureMapControllerFactory(ObjectFactory.Container);
ControllerBuilder.Current.SetControllerFactory(factory);
}
Controller for getting request from user
public class HomeController : Controller
{
/// <summary>
/// Gets or sets the category repository.
/// </summary>
/// <value>The category repository.</value>
private ICategoryRepository CategoryRepository { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="HomeController"/> class.
/// </summary>
public HomeController()
: this(MvcServiceLocator.Current.GetInstance(typeof(ICategoryRepository)) as StubCategoryRepository)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HomeController"/> class.
/// </summary>
/// <param name="categoryRepository">The category repository.</param>
public HomeController(ICategoryRepository categoryRepository)
{
CategoryRepository = categoryRepository;
}
/// <summary>
/// Indexes this instance.
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
ViewModel.Message = "List of categories";
Contract.Assert(CategoryRepository != null, "CategoryRepository is null");
return View(CategoryRepository.GetAll());
}
/// <summary>
/// Details the specified id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public ActionResult Detail(int id)
{
ViewModel.Message = "List of categories";
Contract.Assert(CategoryRepository != null, "CategoryRepository is null");
return View(CategoryRepository.GetById(id));
}
................
}
Build the View
Index.cshtml
@inherits System.Web.Mvc.WebViewPage<IList<CustomStructureMapServiceLocator.Models.Entity.Category>>
@{
View.Title = "Category List Page";
LayoutPage = "~/Views/Shared/_Layout.cshtml";
}
<h2>@View.Message</h2>
<ul>
@foreach(var c in Model) {
<li>
@Html.ActionLink(c.Name, "Detail", new { id = c.Id })
</li>
}
</ul>
Detail.cshtml
@inherits System.Web.Mvc.WebViewPage<CustomStructureMapServiceLocator.Models.Entity.Category>
@{
View.Title = "Category Detail";
LayoutPage = "~/Views/Shared/_Layout.cshtml";
}
<h2>Detail information for [@Model.Name]</h2>
<ul>
<li>Id: @Model.Id</li>
<li>Name: @Model.Name</li>
<li>Is deleted: @Model.IsDelete</li>
<li>Created date: @Model.CreatedDate</li>
<li>Additional information: @Model.Description</li>
</ul>I hope this post will enjoy for the fan of StructureMap. Goodbye and enjoy your coding.