开发者

Asp.net MVC RouteBase and IoC

I am 开发者_Python百科creating a custom route by subclassing RouteBase. I have a dependency in there that I'd like to wire up with IoC. The method GetRouteData just takes HttpContext, but I want to add in my unit of work as well....somehow.

I am using StructureMap, but info on how you would do this with any IoC framework would be helpful.


Well, here is our solution. Many little details may be omitted but overall idea is here. This answer may be a kind of offtop to original question but it describes the general solution to the problem.

I'll try to explain the part that is responsible for plain custom HTML-pages that are created by users at runtime and therefore can't have their own Controller/Action. So the routes should be either somehow built at runtime or be "catch-all" with custom IRouteConstraint.

First of all, lets state some facts and requirements.

  • We have some data and some metadata about our pages stored in DB;
  • We don't want to generate a (hypothetically) whole million of routes for all of existing pages beforehand (i.e. on Application startup) because something can change during application and we don't want to tackle with pushing the changes to global RouteCollection;

So we do it this way:

1. PageController

Yes, special controller that is responsible for all our content pages. And there is the only action that is Display(int id) (actually we have a special ViewModel as param but I used an int id for simplicity.

The page with all its data is resolved by ID inside that Display() method. The method itself returns either ViewResult (strongly typed after PageViewModel) or NotFoundResult in case when page is not found.

2. Custom IRouteConstraint

We have to somewhere define if the URL user actually requested refers to one of our custom pages. For this we have a special IsPageConstraint that implements IRouteConstraint interface. In the Match() method of our constraint we just call our PageRepository to check whether there is a page that match our requested URL. We have our PageRepository injected by StructureMap. If we find the page then we add that "id" parameter (with the value) to the RouteData dictionary and it is automatically bound to PageController.Display(int id) by DefaultModelBinder.

But we need a RouteData parameter to check. Where we get that? Here comes...

3. Route mapping with "catch-all" parameter

Important note: this route is defined in the very end of route mappings list because it is very general, not specific. We check all our explicitly defined routes first and then check for a Page (that is easily changeable if needed).

We simply map our route like this:

routes.MapRoute("ContentPages", 
                "{*pagePath}", 
                new { controller = "Page", action = "Display" }
                new { pagePath = new DependencyRouteConstraint<IsPageConstraint>() });

Stop! What is that DependencyRouteConstraint thing appeared in mapping? Well, thats what does the trick.

4. DependencyRouteConstraint<TConstraint> class

This is just another generic implementation of IRouteConstraint which takes the "real" IRouteConstraint (IsPageConstraint) and resolves it (the given TConstraint) only when Match() method called. It uses dependency injection so our IsPageConstraint instance has all actual dependencies injected!

Our DependencyRouteConstraint then just calls the dependentConstraint.Match() providing all the parameters thus just delegating actual "matching" to the "real" IRouteConstraint.

Note: this class actually has the dependency on ServiceLocator.

Summary

That way we have:

  • Our Route clear and clean;
  • The only class that has a dependency on Service Locator is DependencyRouteConstraint;
  • Any custom IRouteConstraint uses dependency injection whenever needed;
  • ???
  • PROFIT!

Hope this helps.


So, the problem is:

  • Route must be defined beforehand, during Application startup
  • Route's responsibility is to map the incoming URL pattern to the right Controller/Action to perform some task on request. And visa versa - to generate links using that mapping data. Period. Everything else is "Single Responsibility Principle" violation which actually led to your problem.
  • But UoW dependencies (like NHibernate ISession, or EF ObjectContext) must be resolved at runtime.

And that is why I don't see the children of RouteBase class as a good place for some DB work dependency. It makes everything closely coupled and non-scalable. It is actually impossible to perform Dependency Injection.

From now (I guess there is some kind of already working system) you actually have just one more or less viable option that is:

  • To use Service Locator pattern: resolve your UoW instance right inside the GetRouteData method (use CommonServiceLocator backed by StructureMap IContainer). That is simple but not really nice thing because this way you get the dependency on static Service Locator itself in your Route.

With CSL you have to just call inside GetRouteData:

var uow = ServiceLocator.Current.GetService<IUnitOfWork>();

or with just StructureMap (without CSL facade):

var uow = ObjectFactory.GetInstance<IUnitOfWork>();

and you're done. Quick and dirty. And the keyword is "dirty" actually :)

Sure, there is much more flexible solution but it needs a few architectural changes. If you provide more details on exactly what data you get in your routes I can try to explain how we solved our Pages routing problem (using DI and custom IRouteConstraint).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜