- ASP.NET MVC 2 Cookbook
- Andrew Siemer, Richard Kimber
- 1116字
- 2025-03-31 05:30:57
In this recipe, we will implement our own controller factory, which will allow us to utilize the structure map at the very top of the request cycle of the MVC framework. This will allow us to get rid of any object initialization that we might otherwise have to do directly in a controller. This in turn means that our controllers can conform to a dependency injection style of development, which will make them considerably easier to test.
The first thing we need to do to create a StructureMap
controller factory is to download StructureMap here: http://structuremap.sourceforge.net/Default.htm. We will also use NBuilder to make our example a bit more of a real-world scenario. You can get NBuilder here: http://nbuilder.org/. Also, both of these tools are in the dependencies folder.
Next, I will list some code that we need in our solution prior to starting this recipe. We create a ProductRepository
to get our products from the database, a ProductService
to apply business logic to the list of products, and a Product
definition to pass the data around. After that, we will need to wire up our HomeController to pass a list of products to our view. Here are those classes:
Product.cs: public class Product { public int ProductID { get; set; } public string Name { get; set; } public double Cost { get; set; } } ProductRepository.cs: public class ProductRepository { public List<Product> GetProducts() { return Builder<Product>.CreateListOfSize(50).Build().ToList(); } } ProductService.cs: public class ProductService { public List<Product> GetProducts() { return new ProductRepository().GetProducts(); } } HomeController.cs: controller factorycontroller factoryimplementing, for using with StructureMap... public ActionResult Index() { ViewData["Message"] = "Welcome to ASP.NET MVC!"; ViewData["Products"] = new ProductService().GetProducts(); return View(); } ... Index.aspx: ... <% foreach (var product in ((List<Product>)ViewData["Products"])) { %> <%= product.ProductID %> <%= product.Name %> <%= String.Format("{0:C}", product.Cost) %><br /> <% } %> ...
With this code in place, we can run our project and we should see a list of products down the home page. We will also notice that our code is heavily coupled all over the place. Now let's turn to the recipe and refactor in our StructureMap
controller factory and shed these dependencies.
- Create a new ASP.NET MVC project.
- Add a reference to NBuilder and StructureMap.
- Then we need to extract an interface from our
ProductRepository
. You can use ReSharper to do this for you by clicking on theProductRepository
classname and pressing Ctrl + Shift + R, which will cause the refactor this menu to pop up, then choose Extract Interface. Or you can simply create a new interface file and define the interface forProductRepository
. Either way, you want to end up with anIProductRepository
in yourModels
folder.IProductRepository.cs:
public interface IProductRepository { List<Product> GetProducts(); }
- Next, we need to do the same thing for our
ProductService
and extract an interface namedIProductService
.IProductService.cs:
public interface IProductService { List<Product> GetProducts(); }
- Once we have created our interfaces, we can then set our
ProductRepository
andProductService
to derive from those interfaces.ProductRepository.cs:
public class ProductRepository : IProductRepository
ProductService.cs:
public class ProductService : IProductService
- Now that we have classes that are defined by their interface, we can plug
StructureMap
into the flow of our program. We already added a reference toStructureMap
to the project. Now we need to create aRegistry
class namedProjectRegistry
to our project. This is where we tellStructureMap
which types we want to use when we ask it to give us a class by interface.ProjectRegistry.cs:
using StructureMap.Configuration.DSL; ... public class ProjectRegistry : Registry { public ProjectRegistry() { ForRequestedType<IProductRepository>().TheDefault.Is. OfConcreteType<ProductRepository>(); ForRequestedType<IProductService>().TheDefault.Is. OfConcreteType<ProductService>(); } }
- Then we need to create a bootstrapper class named
RegisterProject
that we will use to load our registry classes (we have only one...but we might have more as a project grows).RegisterProject.cs:
using StructureMap; ... public class RegisterProject { public RegisterProject() { ObjectFactory.Initialize(x => { x.AddRegistry(new ProjectRegistry()); //add more registry classes here if you need too //x.AddRegistry(new DataRegistry()); }); } }
- Now that we have our registry and bootstrapper created, we can wire
StructureMap
the rest of the way into our application. We do this in theGlobal.asax.cs
file in theApplication_Start
method.Global.asax.cs:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); new RegisterProject(); }
- Now that
StructureMap
is wired in, you can build and run your project. We are not yet usingStructureMap
, but we want to make sure that everything works at this point. If you did something wrong you will quickly find out! - Assuming that all is good...we can now move on to refactoring our dependencies out of our application in favor of allowing
StructureMap
to handle them instead. We can start in ourProductService
. We will move the instantiation of theProductRepository
up and out of theProductService
like this.ProductService.cs:
public class ProductService : IProductService { private IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public List<Product> GetProducts() { return _productRepository.GetProducts(); } }
- Next, we need to push the usage of the
ProductService
up and out of the HomeController. Do that like this.HomeController.cs:
public class HomeController : Controller { private IProductService _productService; public HomeController(IProductService productService) { _productService = productService; } public ActionResult Index() { ViewData["Message"] = "Welcome to ASP.NET MVC!"; ViewData["Products"] = _productService.GetProducts(); return View(); } ...
- Now we can add a new class to our
Models
directory calledStructureMapController
. This class will be responsible for loading our dependencies into the constructor of each of our controllers.StructureMapController.cs:
public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance( RequestContext requestContext, Type controllerType) { if(controllerType == null) return base.GetControllerInstance(requestContext, controllerType); return ObjectFactory.GetInstance(controllerType) as Controller; } }
- In order for our
StructureMapController
to be used though, we will need to wire it into MVC. We will do this in theGlobal.asax.cs
file as well.Global.asax.cs:
protected void Application_Start() { ControllerBuilder.Current.SetControllerFactory( new Models.StructureMapControllerFactory()); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); new RegisterProject(); }
- Now you can hit F5 and see
StructureMap
do its magic.
In order to make this work, we have created a custom controller factory that is derived from DefaultControllerFactory
. This requires that we have a GetControllerInstance
method that returns an instance of IController
. Inside this method, we are checking to see if we know the requested ControllerType
or not. If we don't know what was requested, then we return control to MVC. If we do know the Controller Type
though, then we ask StructureMap
to go get a configured instance of the requested controller. The benefit here is that when StructureMap
gets an instance of something, it will also inject appropriate instances of other classes into the constructor of the requested controller. This is what allows us to push our dependencies up and out of our code and request that the dependencies be provided to us via the constructor of our classes.