问题描述
我正在尝试转换这个示例RouteBase实现与 MVC 6 一起工作.我已经按照 路由项目中的示例,但我对如何从该方法返回异步 Task
感到困惑.我真的不在乎它是否真的是异步的(为任何可以提供答案的人欢呼),现在我只想让它运行.
I am attempting to convert this sample RouteBase implementation to work with MVC 6. I have worked out most of it by following the example in the Routing project, but I am getting tripped up on how to return the asynchronous Task
from the method. I really don't care if it actually is asynchronous (cheers to anyone who can provide that answer), for now I just want to get it functioning.
我的传出路由正常运行(这意味着当我输入路由值时 ActionLink
工作正常).问题出在 RouteAsync
方法上.
I have the outgoing routes functioning (meaning ActionLink
works fine when I put in the route values). The problem is with the RouteAsync
method.
public Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
// Get the page that matches.
var page = GetPageList()
.Where(x => x.VirtualPath.Equals(requestPath))
.FirstOrDefault();
// If we got back a null value set, that means the URI did not match
if (page != null)
{
var routeData = new RouteData();
// This doesn't work
//var routeData = new RouteData(context.RouteData);
// This doesn't work
//routeData.Routers.Add(this);
// This doesn't work
//routeData.Routers.Add(new MvcRouteHandler());
// TODO: You might want to use the page object (from the database) to
// get both the controller and action, and possibly even an area.
// Alternatively, you could create a route for each table and hard-code
// this information.
routeData.Values["controller"] = "CustomPage";
routeData.Values["action"] = "Details";
// This will be the primary key of the database row.
// It might be an integer or a GUID.
routeData.Values["id"] = page.Id;
context.RouteData = routeData;
// When there is a match, the code executes to here
context.IsHandled = true;
// This test works
//await context.HttpContext.Response.WriteAsync("Hello there");
// This doesn't work
//return Task.FromResult(routeData);
// This doesn't work
//return Task.FromResult(context);
}
// This satisfies the return statement, but
// I'm not sure it is the right thing to return.
return Task.FromResult(0);
}
当有匹配时,整个方法会一直运行到最后.但是当它完成执行时,它不会调用 CustomPage
控制器的 Details
方法,因为它应该.我只是在浏览器中看到一个空白页面.
The entire method runs all the way through to the end when there is a match. But when it is done executing, it doesn't call the Details
method of the CustomPage
controller, as it should. I just get a blank white page in the browser.
我在 并将 Hello there
写入空白页面,但我不明白为什么 MVC 不调用我的控制器(在以前的版本中)这很顺利).不幸的是,那篇文章涵盖了路由的每个部分,除了如何实现 IRouter
或 INamedRouter
.
I added the WriteAsync
line as was done in this post and it writes Hello there
to the blank page, but I can't understand why MVC isn't calling my controller (in previous versions this worked without a hitch). Unfortunately, that post covered every part of routing except for how to implement an IRouter
or INamedRouter
.
如何使 RouteAsync
方法起作用?
How can I make the RouteAsync
method function?
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class PageInfo
{
// VirtualPath should not have a leading slash
// example: events/conventions/mycon
public string VirtualPath { get; set; }
public int Id { get; set; }
}
public interface ICustomRoute : IRouter
{ }
public class CustomRoute : ICustomRoute
{
private readonly IMemoryCache cache;
private object synclock = new object();
public CustomRoute(IMemoryCache cache)
{
this.cache = cache;
}
public Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
// Get the page that matches.
var page = GetPageList()
.Where(x => x.VirtualPath.Equals(requestPath))
.FirstOrDefault();
// If we got back a null value set, that means the URI did not match
if (page != null)
{
var routeData = new RouteData();
// TODO: You might want to use the page object (from the database) to
// get both the controller and action, and possibly even an area.
// Alternatively, you could create a route for each table and hard-code
// this information.
routeData.Values["controller"] = "CustomPage";
routeData.Values["action"] = "Details";
// This will be the primary key of the database row.
// It might be an integer or a GUID.
routeData.Values["id"] = page.Id;
context.RouteData = routeData;
context.IsHandled = true;
}
return Task.FromResult(0);
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
VirtualPathData result = null;
PageInfo page = null;
// Get all of the pages from the cache.
var pages = GetPageList();
if (TryFindMatch(pages, context.Values, out page))
{
result = new VirtualPathData(this, page.VirtualPath);
context.IsBound = true;
}
return result;
}
private bool TryFindMatch(IEnumerable<PageInfo> pages, IDictionary<string, object> values, out PageInfo page)
{
page = null;
int id;
object idObj;
object controller;
object action;
if (!values.TryGetValue("id", out idObj))
{
return false;
}
id = Convert.ToInt32(idObj);
values.TryGetValue("controller", out controller);
values.TryGetValue("action", out action);
// The logic here should be the inverse of the logic in
// GetRouteData(). So, we match the same controller, action, and id.
// If we had additional route values there, we would take them all
// into consideration during this step.
if (action.Equals("Details") && controller.Equals("CustomPage"))
{
page = pages
.Where(x => x.Id.Equals(id))
.FirstOrDefault();
if (page != null)
{
return true;
}
}
return false;
}
private IEnumerable<PageInfo> GetPageList()
{
string key = "__CustomPageList";
IEnumerable<PageInfo> pages;
// Only allow one thread to poplate the data
if (!this.cache.TryGetValue(key, out pages))
{
lock (synclock)
{
if (!this.cache.TryGetValue(key, out pages))
{
// TODO: Retrieve the list of PageInfo objects from the database here.
pages = new List<PageInfo>()
{
new PageInfo() { Id = 1, VirtualPath = "somecategory/somesubcategory/content1" },
new PageInfo() { Id = 2, VirtualPath = "somecategory/somesubcategory/content2" },
new PageInfo() { Id = 3, VirtualPath = "somecategory/somesubcategory/content3" }
};
this.cache.Set(key, pages,
new MemoryCacheEntryOptions()
{
Priority = CacheItemPriority.NeverRemove,
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
});
}
}
}
return pages;
}
}
自定义路由 DI 注册
services.AddTransient<ICustomRoute, CustomRoute>();
MVC 路由配置
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.Routes.Add(routes.ServiceProvider.GetService<ICustomRoute>());
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
以防万一,我正在使用 Beta 5
、DNX 4.5.1
和 DNX Core 5
.
In case it matters I am using Beta 5
, DNX 4.5.1
and DNX Core 5
.
我创建了一个通用解决方案,可用于 URL 2 向映射的简单主键 在这个答案中 基于我在这里学到的信息.将主键连接到 MVC 6 路由时,可以指定主键的控制器、操作、数据提供者和数据类型.
推荐答案
正如@opants 所说,问题在于您在 RouteAsync
方法中什么也没做.
As @opiants said, the problem is that you are doing nothing in your RouteAsync
method.
如果您打算最终调用控制器操作方法,您可以使用以下方法而不是默认的 MVC 路由:
If your intention is to end up calling a controller action method, you could use the following approach than the default MVC routes:
默认情况下 MVC 使用TemplateRoute代码>
带有内部目标 IRouter
.在 RouteAsync 中,TemplateRoute 将委托给内部 IRouter.这个内部路由器被设置为MvcRouteHandler
默认情况下 builder扩展.在您的情况下,首先添加一个 IRouter
作为您的内部目标:
public class CustomRoute : ICustomRoute
{
private readonly IMemoryCache cache;
private readonly IRouter target;
private object synclock = new object();
public CustomRoute(IMemoryCache cache, IRouter target)
{
this.cache = cache;
this.target = target;
}
然后更新您的启动以将该目标设置为 MvcRouteHandler
,该目标已设置为 routes.DefaultHandler
:
Then update your startup to set that target as the MvcRouteHandler
, which has already been set as routes.DefaultHandler
:
app.UseMvc(routes =>
{
routes.Routes.Add(
new CustomRoute(routes.ServiceProvider.GetRequiredService<IMemoryCache>(),
routes.DefaultHandler));
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
最后,更新您的 AsyncRoute 方法以调用内部 IRouter
,也就是 MvcRouteHandler
.您可以在 TemplateRoute
作为指南.我很快就使用了这种方法并将您的方法修改如下:
Finally, update your AsyncRoute method to call the inner IRouter
, which would be the MvcRouteHandler
. You can use the implementation of that method in TemplateRoute
as a guide. I have quickly used this approach and modified your method as follows:
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
// Get the page that matches.
var page = GetPageList()
.Where(x => x.VirtualPath.Equals(requestPath))
.FirstOrDefault();
// If we got back a null value set, that means the URI did not match
if (page == null)
{
return;
}
//Invoke MVC controller/action
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Routers.Add(this.target);
// TODO: You might want to use the page object (from the database) to
// get both the controller and action, and possibly even an area.
// Alternatively, you could create a route for each table and hard-code
// this information.
newRouteData.Values["controller"] = "CustomPage";
newRouteData.Values["action"] = "Details";
// This will be the primary key of the database row.
// It might be an integer or a GUID.
newRouteData.Values["id"] = page.Id;
try
{
context.RouteData = newRouteData;
await this.target.RouteAsync(context);
}
finally
{
// Restore the original values to prevent polluting the route data.
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
更新 RC2
看起来像 TemplateRoute
不再出现在 RC2 aspnet 路由中.
Looks like TemplateRoute
is no longer around in RC2 aspnet Routing.
我调查了历史,并将其重命名为 在 提交 36180ab 作为更大重构的一部分.
I investigated the history, and it was renamed RouteBase
in commit 36180ab as part of a bigger refactoring.
这篇关于在 ASP.NET 5 (vNext) MVC 6 中实现自定义 IRouter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!