ASP.NET MVC Custom Route Constraints and Dependenc

2020-07-11 08:36发布

问题:

On my ASP.NET MVC 3 App, I have a route constraint defined like below:

public class CountryRouteConstraint : IRouteConstraint {

    private readonly ICountryRepository<Country> _countryRepo;

    public CountryRouteConstraint(ICountryRepository<Country> countryRepo) {
        _countryRepo = countryRepo;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {

        //do the database look-up here

        //return the result according the value you got from DB
        return true;
    }
}

I am using Ninject as IoC container on my app which implements IDependencyResolver and I registered my dependency:

    private static void RegisterServices(IKernel kernel) {

        kernel.Bind<ICountryRepository<Country>>().
            To<CountryRepository>();
    }    

How can I use this route constraint with a dependency injection friendly manner?

EDIT

I cannot find a way to pass this dependency on unit test:

[Fact]
public void country_route_should_pass() {

    var mockContext = new Mock<HttpContextBase>();
    mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns("~/countries/italy");

    var routes = new RouteCollection();
    TugberkUgurlu.ReservationHub.Web.Routes.RegisterRoutes(routes);

    RouteData routeData = routes.GetRouteData(mockContext.Object);

    Assert.NotNull(routeData);
    Assert.Equal("Countries", routeData.Values["controller"]);
    Assert.Equal("Index", routeData.Values["action"]);
    Assert.Equal("italy", routeData.Values["country"]);
}

回答1:

routes.MapRoute(
    "Countries",
    "countries/{country}",
    new { 
        controller = "Countries", 
        action = "Index" 
    },
    new { 
        country = new CountryRouteConstraint(
            DependencyResolver.Current.GetService<ICountryRepository<Country>>()
        ) 
    }
);


回答2:

While the approach @Darin suggested works, the dependencies injected need to stay alive for the entire life of the application. If the scope of the dependency is in request scope, for example, then it will work for the first request then not for every request after that.

You can get around this by using a very simple DI wrapper for your route constraints.

public class InjectedRouteConstraint<T> : IRouteConstraint where T : IRouteConstraint
{
private IDependencyResolver _dependencyResolver { get; set; }
public InjectedRouteConstraint(IDependencyResolver dependencyResolver)
{
    _dependencyResolver = dependencyResolver;
}

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
    return _dependencyResolver.GetService<T>().Match(httpContext, route, parameterName, values, routeDirection);
}
}

then create your routes like this

var _dependencyResolver = DependencyResolver.Current; //Get this from private variable that you can override when unit testing

routes.MapRoute(
  "Countries",
  "countries/{country}",
  new { 
      controller = "Countries", 
      action = "Index" 
  },
  new { 
      country = new InjectedRouteConstraint<CountryRouteConstraint>(_dependencyResolver);
  }
);

EDIT: tried to make it testable.



回答3:

You can try to use property injection and the IDependencyResolver

public class CountryRouteConstraint : IRouteConstraint {
    [Inject]
    public ICountryRepository<Country> CountryRepo {get;set;}
}

Not all IoC containers play well with this; Ninject works.

I'm not sure whether this will work, can't test this atm unfortunately.

Another option is to use a service locator instead, where you make a static object available that is responsible for retrieving an implementation of the interface.