我怎样才能做一个虚拟路由/重定向基于ASP.NET VNEXT MVC6给出的路径? [英] How can I do a virtual route/redirect based on the path given in ASP.NET VNEXT MVC6?

查看:98
本文介绍了我怎样才能做一个虚拟路由/重定向基于ASP.NET VNEXT MVC6给出的路径?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个网站,以不同的路径暴露出几个API,每个特定于应用程序的一部分控制器处理,如 example.com/Api/Controller/Action?param1=stuff ,其中控制器的变化,但操作相当稳定。

I have a website that exposes several APIs at different paths, each handled by a controller specific to a section of the application, like example.com/Api/Controller/Action?param1=stuff, where Controller changes, but Actions remain fairly consistent.

我有一个调用这些API的几个集成器件。的问题是,这些集成的设备不能容易地改变,并且具体控制欲它们指向将需要在未来发生变化。

I have several integrated devices that call these APIs. The issue is that these integrated devices cannot be changed easily, and the specific controller I want them to point to will need to change in the future.

我的计划是使用类似虚拟重定向,所有的设备将要求一个固定的URL像 example.com/Api/VRedirect/ {}的DeviceID /方法名?参数1 =测试

My plan is to use something like a virtual redirect, where all the devices would call a fixed URL like example.com/Api/VRedirect/{deviceId}/MethodName?param1=test

根据 DEVICEID 的值,用于将改变(基于某些数据库查询逻辑)的实际控制人。

Depending on the value of deviceId, the actual Controller that is used would change (based on some database lookup logic).

因此​​,例如,如果 DEVICEID 1234被抬起头来,返回示例,呼吁 example.com/Api/VRedirect/1234/测试?参数1 =测试将调用相当于 example.com/Api/Example/Test?param1=test 直接

So for example, if the deviceId 1234 gets looked up and returns "Example", calling example.com/Api/VRedirect/1234/Test?param1=test would be the equivalent of calling example.com/Api/Example/Test?param1=test directly.

到目前为止,我还没有发现很好地贯彻这种方式,我已经接近的唯一方法是通过使用自定义路由:

So far I have found no way of implementing this properly, the only way I have come close is by using custom routing:

app.UseMvc(routes => {
    routes.MapRoute(
                    name: "RedirectRoute",
                    template: "Api/VRedirect/{deviceId}/{*subAction}",
                    defaults: new { controller = "BaseApi", action = "VRedirect"});
);

用重定向操作:

public IActionResult VRedirect(string deviceId, string subAction) {
        string controllerName = "Example"; // Database lookup based off deviceId
        return Redirect(string.Format("/Api/{0}/{1}", controllerName, subAction));
    }

本部分适用于GET请求,但不会因为它放弃任何和所有的POST数据在所有的岗位工作。

This partially works for GET requests, but doesn't work at all for POST because it discards any and all POST data.

有什么办法来实现这样的事情?我怀疑我可能要编写自定义的路由器,但我不知道从哪里开始。

Is there any way to implement something like this? I suspect I might have to write a custom router but I'm not sure where to start.

更新:
我已成功使用默认路由器通过简单地在一个循环中添加路由每个设备来完成所需的行为:

Update: I have managed to accomplish the desired behaviour using the default router by simply adding a route for each device in a loop:

app.UseMvc(routes => {
    Dictionary<string, string> deviceRouteAssignments = new Dictionary<string, string>();
    // TODO: Get all these assignments from a database
    deviceRouteAssignments.Add("12345", "ExampleControllerName");
    foreach (var thisAssignment in deviceRouteAssignments) {
        routes.MapRoute(
            name: "DeviceRouteAssignment_" + thisAssignment.Key,
            template: "Api/VRedirect/" + thisAssignment.Key + "/{action}",
            defaults: new { controller = thisAssignment.Value });
        }
    }
}

然而,这有几个明显的局限性,如在应用程序启动只有被更新的路由。路由的数量巨大的性能下降可能是一个问题,但是我测试过万的路线没有发现任何可感知的减速。

However this has a few obvious limitations, such as the routes only being updated upon application startup. Performance degradation for a huge number of routes may be an issue, however I've tested 10,000 routes didn't notice any perceivable slowdown.

推荐答案

首先,如果这些限制是静态的,不改,或者不经常改变,我不会看它们对每个请求,而是在应用程序启动,然后缓存在HttpContext.Cache或Redis的或一些其他缓存机制,将允许你绕过这一下对每个请求的数据。如果他们能定期更新,设置一个时限,在缓存驱逐重新加载新的条目。

First, if these constraints are static and don't change, or don't change often, I would not look them up on each request but rather on application startup, and then cache the data in HttpContext.Cache or Redis or some other caching mechanism that would allow you to bypass this look on each request. If they can be periodically updated, setup a time limit and on cache eviction reload the new set of entries.

记住,你有更多的路线,你必须在最坏的情况下更数据库查询。所以,即使你需要做一个查找每个请求,一个更好的解决方案是更新每个请求缓存。

Remember, the more routes you have, the more database lookups you'll have in the worst case situation. So even if you need to do a lookup each request, a better solution would be to update the cache on each request.

不过,如果你绝对要做到这一点在每个约束每个请求,那么你可以简单地做到这一点:

However, if you absolutely have to do this on each request in each constraint, then you can simply do this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddEntityFramework(Configuration)
        .AddSqlServer()
        .AddDbContext<VRouterDbContextt>();
    //...
 }

// Note: I added the DbContext here (and yes, this does in fact work)...
public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
     ILoggerFactory loggerfactory, VRouterDbContext context)
{
    // ....

     app.UseMvc(routes =>
     {
         routes.MapRoute(
             name: "VRoute_" + "Example",
             template: "Api/VRouter/{deviceId}/{action}",
             defaults: new { controller = "Example"},
             constraints: new { deviceId = new VRouterConstraint(context, "Example")}
        });
}

public class VRouterConstraint : IRouteConstraint {
    public VRouterConstraint (VRouterDbContext context, string controllerId) {
       this.DbContext = context;
       this.ControllerId = controllerId;
    }

    private VRouterDbContext DbContext {get; set;}
    public string ControllerId{ get; set; }

    public bool Match(HttpContext httpContext, IRouter route, string routeKey, 
        IDictionary<string, object> values, RouteDirection routeDirection) {
        object deviceIdObject;
        if (!values.TryGetValue(routeKey, out deviceIdObject)) {
            return false;
        }

        string deviceId = deviceIdObject as string;
        if (deviceId == null) {
            return false;
        }

        bool match = DbContext.DeviceServiceAssociations
            .AsNoTracking()
            .Where(o => o.ControllerId == this.ControllerId)
            .Any(o => o.AssoicatedDeviceId == deviceId);
        return match;
    }
}

所以,这是一个相当简单的方法来提供一个存储库注入到您手动创建RouteConstraints。

So this is a rather simple way to provide an injected repository to your manually created RouteConstraints.

有,然而,在那的DbContext必须活在应用程序的生命,这是不是DbContexts真的打算如何过一个小问题。 DbContexts没有经过工厂自己比处置环境本身等进行清理,所以基本上都会成长和壮大其内存使用量随着时间的推移..虽然这可能会在这种情况下受到限制,如果你总是查询相同的数据集

There is, however, a small issue in that the DbContext must live for the life of the application and that is not really how DbContexts are intended to live. DbContexts have no facility to clean up after themselves other than disposal of the context itself, so it will basically grow and grow its memory usage over time.. although that will likely be limited in this case if you are always querying the same sets of data.

这是因为你的路由约束是在应用程序启动时创建的,活在应用程序的生命,你的环境已经到创建的约束时,虽然有周围的一些方法,以及注射(,他们可能不会是,例如,你可以做到这将注入一个工厂,创建上​​下文,而不是优化的最佳解决方案要么...,但现在你绕过容器生命周期管理。你也可以使用服务定位,有时你不这样做没有太多的选择中..但我留到最后的手段)。

This is because your Route Constraints are created at app startup, and live for the life of the application, and your contexts have to be injected when the constraints are created (although there are some ways around that as well, they may not be the best solution either... for instance You could do an optimization which would inject a factory that creates your context instead, but now you're bypassing the containers lifetime management. You could also use service location, which sometimes you don't have much choice in.. but I leave that for a last resort).

这就是为什么它是更好的查询在启动数据库和缓存中的数据,而不是做这些类型的查询对每个请求。

This is why it's much better to query the database at startup and cache the data than to do these kinds of queries on each request.

不过,如果你的罚款与上下文(将只有一个)生活应用的生活,那么这是一个非常简单的解决方案。

However, if you're fine with the context (there will be only one) living for the life of the app, then this is a very easy solution.

此外,你真的应该使用接口分离原则,以及减少依赖性。这仍产生实际VRouterDbContext依赖关系,因此它不能容易地嘲笑和测试...所以添加一个接口代替

Also, you really should use the Interface Segregation Principle as well to reduce dependencies. This still creates a dependency on the actual VRouterDbContext, so it can't be easily mocked and tested... so add an interface instead.

这篇关于我怎样才能做一个虚拟路由/重定向基于ASP.NET VNEXT MVC6给出的路径?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆