ASP.Net Core 2.0:无需请求即可创建 UrlHelper

Posted

技术标签:

【中文标题】ASP.Net Core 2.0:无需请求即可创建 UrlHelper【英文标题】:ASP.Net Core 2.0: Creating UrlHelper without request 【发布时间】:2017-09-07 12:14:34 【问题描述】:

我正在为后台工作人员创建一个 UrlHelper 以创建回调 url,这意味着它不是正常请求的一部分,我可以通过 DI 请求它。

在 ASP.Net 5 中,我可以只创建一个 HttpRequest 并为其提供与构建应用程序时相同的 HttpConfiguration,但在 ASP.Net Core 2.0 中,UrlHelper 依赖于完整的 ActionContext,这有点难以制作。

我有一个可以工作的原型,但它使用了一种令人讨厌的 hack 将路线数据偷运出应用程序启动过程。有没有更好的方法来做到这一点?

public class Capture

    public IRouter Router  get; set; 


public static class Ext

    // Step 1: Inject smuggler when building web host
    public static IWebHostBuilder SniffRouteData(this IWebHostBuilder builder)
    
        return builder.ConfigureServices(svc => svc.AddSingleton<Capture>());
    

    // Step 2: Swipe the route data in application startup
    public static IApplicationBuilder UseMvcAndSniffRoutes(this IApplicationBuilder app)
    
        var capture = app.ApplicationServices.GetRequiredService<Capture>();
        IRouteBuilder capturedRoutes = null;
        app.UseMvc(routeBuilder => capturedRoutes = routeBuilder);
        capture.Router = capturedRoutes?.Build();
        return app;
    

    // Step 3: Build the UrlHelper using the captured routes and webhost
    public static IUrlHelper GetStaticUrlHelper(this IWebHost host, string baseUri)
        => GetStaticUrlHelper(host, new Uri(baseUri));
    public static IUrlHelper GetStaticUrlHelper(this IWebHost host, Uri baseUri)
    
        HttpContext httpContext = new DefaultHttpContext()
        
            RequestServices = host.Services,
            Request =
                
                    Scheme = baseUri.Scheme,
                    Host = HostString.FromUriComponent(baseUri),
                    PathBase = PathString.FromUriComponent(baseUri),
                ,
        ;

        var captured = host.Services.GetRequiredService<Capture>();
        var actionContext = new ActionContext
        
            HttpContext = httpContext,
            RouteData = new RouteData  Routers =  captured.Router ,
            ActionDescriptor = new ActionDescriptor(),
        ;
        return new UrlHelper(actionContext);
    


// Based on dotnet new webapi

public class Program

    public static void Main(string[] args)
    
        BuildWebHost(args);//.Run();
    

    public static IWebHost BuildWebHost(string[] args)
    
        var captured = new Capture();
        var webhost = WebHost.CreateDefaultBuilder(args)
            .SniffRouteData()
            .UseStartup<Startup>()
            .Build();

        var urlHelper = webhost.GetStaticUrlHelper("https://my.internal.service:48923/somepath");
        Console.WriteLine("YO! " + urlHelper.Link(nameof(ValuesController), null));
        return webhost;
    


public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    
        services.AddMvc();
    

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, Capture capture)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
        

        app.UseMvcAndSniffRoutes();
    


[Route("api/[controller]", Name = nameof(ValuesController))]
public class ValuesController : Controller

    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    
        return new string[]  "value1", "value2" ;
    

    // etc

【问题讨论】:

这可能是个糟糕的主意,但你不能保存 any 在请求期间创建的 UrlHelper 以便继续从后台线程使用它吗?那么,您会在第一个请求发出之后启动该线程吗? 你有机会检查一下吗? ***.com/questions/37322076/… @poke,这可能行得通,但它有明显的缺点,所以我只会把它作为最后的手段。 @dmitry-pavlov 不,虽然它将由 DI 实例化,但它在路由处理程序中初始化,特别是 here 和 here。因此,如果您不在其中一个中间件已经运行的请求管道中,那么 IActionContextAccessor.ActionContext 将为空。我试过这个:) 可能有点晚了,但我的问题和你的差不多。我需要在正常工作中发送电子邮件。感谢您的解决方案,为了更加“墨守成规”或“按部就班”地遵循 asp.net 理念,我想知道您是否可以从您的后台工作人员那里调用您的应用程序中的 web api url。这样,您将拥有一个 http 上下文。这只有在您可以在主线程服务器上工作时才有效 【参考方案1】:

浏览资源似乎没有更少的hacky解决方案。

在 UseMvc() 方法中,正在构建的 IRouter 对象是 passed to the RouterMiddleware,stores it in a private field 并仅将其公开给请求。因此,反射将是您唯一的其他选择,这显然已不再适用。

但是,如果您只需要使用 IUrlHelper.Content() 生成静态路径,则不需要将路由器作为 default implementation won't use it。在这种情况下,您可以像这样创建助手:

var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var urlHelper = new UrlHelper(actionContext);

【讨论】:

这与我得出的结论相同,这导致了我的问题中的解决方法。关于 Content() 的好点,但我们想链接到一个命名的控制器动作。感谢您证实这一点:)我的下一步可能是尝试在 Github 上制定一个问题以使这更容易。 我们也可能没有 httpcontext! @亚当西蒙【参考方案2】:

使用ASP.NET Core 2.2 releasing today,他们添加了一个LinkGenerator 类,听起来它可以解决这个问题(tests 看起来很有希望)。我很想尝试一下,但由于我目前没有积极从事我需要这个的项目,所以它必须等待一段时间。但我很乐观,可以将此标记为新答案。

【讨论】:

路由捕获技巧似乎也不再有效,所以现在这是唯一可用的选项。 LinkGenerator 不容易被嘲笑为 IUrlHelper,所以它确实会产生一些问题。

以上是关于ASP.Net Core 2.0:无需请求即可创建 UrlHelper的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net Core 2.0 如何获取中间件中的所有请求标头? [复制]

ASP.NET Core 2.0 : 八.图说管道

如何从 ASP.NET Core 2.0 中的自定义中间件请求身份验证

在控制器中更改 ASP.NET Core 2.0 标识连接字符串

创建返回 Serilog LoggerConfiguration 的方法(Asp.Net Core 2.0)

ASP.NET Core 2.0/Razor Pages - 如何在请求之间将数据保存在 NonFactors MVC6 网格中?