Search code examples
c#castle-windsordatabase-schemalifecycleasp.net-web-api-routing

At what point in a Web API app can I intercept the URI arguments and route the calls accordingly?


Note: This question is indeed somewhat similar to this one, but I think I can put it in a simpler and more specific way.

I'm using Castle Windsor to intercept the URI passed to my Web API app to register the appropriate concrete class to the Controller's constructor.

What I want to be able to do is pass a "site number" on the URI, perhaps always as either the first or last arg. IOW, for site 42, instead of

http://localhost:28642/api/platypi/GetAll

...it would be:

http://localhost:28642/api/platypi/42/GetAll

-or:

http://localhost:28642/api/platypi/GetAll/42

When my Web API app first "sees"/intercepts the URI, I want to note that site number so that I can assign the desired concrete Repository to be registered by Castle Windsor. I want to be able to do this:

public class RepositoriesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        if (siteNum == 42)
        {
            container.Register(
            Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository42>().LifestylePerWebRequest(),
                Component.For<IInventoryItemRepository>().ImplementedBy<InventoryItemRepository42>().LifestylePerWebRequest(),
            . . .
        }
        else if (siteNum = 77)
        {
            container.Register(
        Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository77>().LifestylePerWebRequest(),
                Component.For<IInventoryItemRepository>().ImplementedBy<InventoryItemRepository77>().LifestylePerWebRequest(),
            . . .
        }

In this way I can give site 42 its data, site 77 its data (each site uses a different database that share a common schema).

So: At which point in my Web API app's lifecycle can I hijack the URI, so as to assign the appropriate val to the global siteNum variable, so that it has been assigned before the IWindsorInstaller method runs?

UPDATE

Thanks to Mr. Slate, but if I were to do that, would this Controller code:

public DepartmentsController(IDepartmentRepository deptsRepository)
{
    if (deptsRepository == null)
    {
        throw new ArgumentNullException("deptsRepository");
    }
    _deptsRepository = deptsRepository;
}

...become:

public DepartmentsController(IDepartmentRepository deptsRepository)
{
    if (deptsRepository == null)
    {
        throw new ArgumentNullException("deptsRepository");
    }
    _deptsRepository = deptsRepository(siteNum);
}

...or???

And I'm still left with the problem of where do I intercept the incoming URI before Castle Windsor / the Controller gets it, so that I can set the appropriate value to the global / siteNum var?


Solution

  • There are a number of extensions points that you can use to achieve this, personally I use this one for a similar result.

    Create a custom model binder by extending IModelBinder something like this:

    public class SiteManagerModelBinder : IModelBinder
        {
            #region IModelBinder Members
    
            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                if (bindingContext.Model != null)
                {
                    throw new InvalidOperationException("Cannot update instances");
                }
    
                // Apply your condition to determine if site number is in Url.
                if (controllerContext.RouteData.Values['siteNum']!=null)
                {
                   // probably want to resolve this from container just hard coding as example, assumption is that SiteManager, does the repository bits for you.
                   return new SiteManager((int)controllerContext.RouteData.Values['siteNum']);
                }
    
                return null;
            }
    
            #endregion
        }
    

    Okay so now we jus tneed to register our new ModelBinder:

        protected void Application_Start()
        { 
            ModelBinders.Binders.Add(typeof(SiteManager), new SiteManagerModelBinder ());
    

    Okay and now in our controller all we do is add the SiteManager as a parameter of any Action and it will get filled in by our ModelBinder.

    public class DepartmentsController: Controller {
    
        public ActionResult AnyAction(SiteManager siteManager, int whateverElse, ViewModel model)
        {
    
        }
    
    }