我正在尝试将this sample RouteBase implementation转换为使用MVC 6.我已经通过跟随the example in the Routing project解决了大部分问题,但我正在惹恼如何返回异步{{1}从方法。我真的不在乎它是否实际上是异步的(为任何可以提供答案的人欢呼),现在我只是想让它运转起来。
我有外向路由功能(意味着Task
在我输入路由值时工作正常)。问题在于ActionLink
方法。
RouteAsync
当匹配时,整个方法一直运行到最后。但是当它完成执行时,它并没有像它应该那样调用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);
}
控制器的Details
方法。我只是在浏览器中看到一个空白的白页。
我在this post中添加了CustomPage
行,并将WriteAsync
写入空白页面,但我无法理解为什么MVC不会调用我的控制器(在以前的版本中,这没有任何障碍)。不幸的是,除了如何实施Hello there
或IRouter
之外,该帖子涵盖了路由的每个部分。
如何制作INamedRouter
方法功能?
RouteAsync
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;
}
}
services.AddTransient<ICustomRoute, CustomRoute>();
如果重要,我使用// 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
。
解决方案
我创建了一个通用解决方案,可以根据我在这里学到的信息,将一个简单的主键用于URL双向映射in this answer。在将其连接到MVC 6路由时,可以指定主键的控制器,操作,数据提供程序和数据类型。
答案 0 :(得分:7)
正如@opiants所说,问题是你在RouteAsync
方法中什么也没做。
如果您打算最终调用控制器操作方法,则可以使用以下方法而不是默认的MVC路由:
默认情况下,MVC使用a
MvcRouteHandler
内部目标IRouter
。在RouteAsync中,TemplateRoute将 委托内部IRouter。这个内部路由器被设置为 builder extensions 默认TemplateRoute
。 在您的情况下,首先添加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
:
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
中使用该方法的实现作为指导。我很快就使用了这种方法并修改了你的方法如下:
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
在RC2 aspnet路由中看起来RouteBase
不再存在。
我调查了历史记录,并在commit 36180ab中将其重命名为{{3}},作为更大重构的一部分。
答案 1 :(得分:2)
这不起作用的主要原因是因为您没有在RouteAsync
方法中执行任何操作。另一个原因是MVC 6中的路由工作方式与以前的MVC路由工作方式有很大不同,所以你可能最好使用source code作为参考从头开始编写它,因为很少有文章可以解决MVC问题。现在6点。
这是使用 beta7 的非常简单的IRouter
实现。这应该可行,但您可能需要填补空白。您需要删除page != null
并将其替换为以下代码并替换控制器和操作:
if (page == null)
{
// Move to next router
return;
}
// TODO: Replace with correct controller
var controllerType = typeof(HomeController);
// TODO: Replace with correct action
var action = nameof(HomeController.Index);
// This is used to locate the razor view
// Remove the trailing "Controller" string
context.RouteData.Values["Controller"] = controllerType.Name.Substring(0, controllerType.Name.Length - 10);
var actionInvoker = context.HttpContext.RequestServices.GetRequiredService<IActionInvokerFactory>();
var descriptor = new ControllerActionDescriptor
{
Name = action,
MethodInfo = controllerType.GetTypeInfo().DeclaredMethods.Single(m => m.Name == action),
ControllerTypeInfo = controllerType.GetTypeInfo(),
// Setup filters
FilterDescriptors = new List<FilterDescriptor>(),
// Setup DI properties
BoundProperties = new List<ParameterDescriptor>(0),
// Setup action arguments
Parameters = new List<ParameterDescriptor>(0),
// Setup route constraints
RouteConstraints = new List<RouteDataActionConstraint>(0),
// This router will work fine without these props set
//ControllerName = "Home",
//DisplayName = "Home",
};
var accessor = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
accessor.ActionContext = new ActionContext(context.HttpContext, context.RouteData, descriptor);
var actionInvokerFactory = context.HttpContext.RequestServices.GetRequiredService<IActionInvokerFactory>();
var invoker = actionInvokerFactory.CreateInvoker(accessor.ActionContext);
// Render the page
await invoker.InvokeAsync();
// Don't execute the next IRouter
context.IsHandled = true;
return;
请务必添加对Microsoft.Framework.DependencyInjection
命名空间的引用,以解析GetRequiredService
扩展名。
之后,按照以下方式注册IRouter:
app.UseMvc(routes =>
{
// Run before any default IRouter implementation
// or use .Add to run after all the default IRouter implementations
routes.Routes.Insert(0, routes.ServiceProvider.GetRequiredService<CustomRoute>());
// .. more code here ...
});
然后在你的IOC注册,
services.AddSingleton<CustomRoute>();
另一种“更清洁”的方法可能是创建IActionSelector
的不同实现。