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

时间秒杀一切 提交于 2019-12-11 02:27:28

问题


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?


回答1:


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)
    {

    }

}



回答2:


I'd change the constructor on DepartmentRepository() to pass the site num and use that to get the connectionstring. Then in your webconfig create a connectionstring for each site.




回答3:


Here is a different way you could try rather using Castle Windsor which might be better:

Create a repository factory like this:

public interface IRepositoryFactory
{
    Repository CreateRepositoryById(int id);
}

Create a component selector to select the correct repository based on the name like this:

public class RepositoryFactorySelector : DefaultTypedFactoryComponentSelector
{
    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        return (method.Name.EndsWith("ById") && arguments.Length >= 1 && arguments[0] is int)
                   ? string.Format("Repository{0}", arguments[0]) 
                   : base.GetComponentName(method, arguments);
    }
}

Register your repositories like this:

public class RepositoryInstaller : IWindsorInstaller
{
    #region IWindsorInstaller Members

    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly().BasedOn<Repository>().Configure(c=>c.Named(c.Implementation.Name)));

        container.Register(
            Component.For<RepositoryFactorySelector, ITypedFactoryComponentSelector>().LifestyleSingleton(),
            Component.For<IRepositoryFactory>().AsFactory(c => c.SelectedWith<RepositoryFactorySelector>()));
    }

    #endregion
}

As long as Windsor is loading your controllers using a constroller factory you can now do this:

public class HomeController : Controller
{
    public IRepositoryFactory RepositoryFactory { get; set; }

    public ActionResult Index(int siteNum)
    {
        var repository = RepositoryFactory.CreateRepositoryById(siteNum);

        // tada!

        return View();
    }

}

For reference my global.asax.cs:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        Container = new WindsorContainer();
        Container.AddFacility<TypedFactoryFacility>(); 

        Container.Install(FromAssembly.This()); 

        var controllerFactory = new WindsorControllerFactory(Container.Kernel);
        ControllerBuilder.Current.SetControllerFactory(controllerFactory);
    }

    public WindsorContainer Container;
}

And my repositories:

public abstract class Repository
{
}

public class Repository1 : Repository
{ 
}

public class Repository2 : Repository
{ 
}


来源:https://stackoverflow.com/questions/21416185/at-what-point-in-a-web-api-app-can-i-intercept-the-uri-arguments-and-route-the-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!