在ASP.NET 5 Imlementing定制IRouter(vNext)MVC 6 [英] Imlementing a Custom IRouter in ASP.NET 5 (vNext) MVC 6
问题描述
我试图转换<一个href=\"http://stackoverflow.com/questions/31934144/multiple-levels-in-mvc-custom-routing/31958586#31958586\">this样品RouteBase实施通过以下<带MVC 6.工作我已经做了大部分href=\"https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs\">the例如,在路由项目,但我正在逐渐绊倒如何从该方法返回异步工作
。我真的不关心,如果它实际上是异步的(欢呼的人谁可以提供答案),现在我只想把它发挥作用。
我即将离任的路由功能(即 ActionLink的
当我把在路由值正常工作)。问题是与 RouteAsync
方法。
公共任务RouteAsync(RouteContext上下文)
{
VAR requestPath = context.HttpContext.Request.Path.Value; 如果(string.IsNullOrEmpty(requestPath)及!&放大器; requestPath [0] =='/')
{
//修剪前导斜杠
requestPath = requestPath.Substring(1);
} //获取相匹配的页面。
VAR页= GetPageList()
。凡(X =&GT; x.VirtualPath.Equals(requestPath))
.FirstOrDefault(); //如果我们回到一个空值设置,这意味着URI不匹配
如果(页面!= NULL)
{
VAR的RouteData =新的RouteData(); //这不起作用
// VAR的RouteData =新的RouteData(context.RouteData); //这不起作用
//routeData.Routers.Add(this); //这不起作用
//routeData.Routers.Add(new MvcRouteHandler()); // TODO:您可能希望使用页面对象(从数据库)来
//取得两个控制器和动作,甚至可能的区域。
//或者,您可以创建为每个表和硬code路线
// 此信息。
routeData.Values [控制器] =CustomPage;
routeData.Values [行动] =详细信息; //这将是数据库行的主键。
//这可能是一个整数或GUID。
routeData.Values [ID] = page.Id; context.RouteData =的RouteData; //当有匹配,code执行在这里
context.IsHandled = TRUE; //这个测试工作
//等待context.HttpContext.Response.WriteAsync(你好); //这不起作用
//返回Task.FromResult(的RouteData); //这不起作用
//返回Task.FromResult(背景);
} //这满足了return语句,但
//我不知道它是返回正确的事情。
返回Task.FromResult(0);
}
整个方法运行一路过关斩将到最后的时候有一个匹配。但是,当它执行完毕,它不叫详细信息
的 CustomPage
控制器的方法,因为它应该。我只是得到一个空白页面在浏览器。
我添加了 WriteAsync
行是在的这个帖子并将其写入你好
来的空白页,但我不明白为什么MVC是不是要求我的控制器(以previous版本,这个工作很顺利)。不幸的是,该职位涵盖除了如何实现路由的每一个部分的 IRouter
或 INamedRouter
。
我怎样才能让 RouteAsync
方法函数?
整个CustomRoute实施
使用Microsoft.AspNet.Routing;
使用Microsoft.Framework.Caching.Memory;
使用系统;
使用System.Collections.Generic;
使用System.Linq的;
使用System.Threading.Tasks;公共类PageInfo
{
// VirtualPath不应该有一个斜线
//例如:活动/会议/ mycon
公共字符串VirtualPath {搞定;组; }
公众诠释标识{搞定;组; }
}公共接口ICustomRoute:IRouter
{}
公共类CustomRoute:ICustomRoute
{
私人只读IMemoryCache缓存;
私有对象的SyncLock =新的对象(); 公共CustomRoute(IMemoryCache缓存)
{
this.cache =高速缓存;
} 公共任务RouteAsync(RouteContext上下文)
{
VAR requestPath = context.HttpContext.Request.Path.Value; 如果(string.IsNullOrEmpty(requestPath)及!&放大器; requestPath [0] =='/')
{
//修剪前导斜杠
requestPath = requestPath.Substring(1);
} //获取相匹配的页面。
VAR页= GetPageList()
。凡(X =&GT; x.VirtualPath.Equals(requestPath))
.FirstOrDefault(); //如果我们回到一个空值设置,这意味着URI不匹配
如果(页面!= NULL)
{
VAR的RouteData =新的RouteData(); // TODO:您可能希望使用页面对象(从数据库)来
//取得两个控制器和动作,甚至可能的区域。
//或者,您可以创建为每个表和硬code路线
// 此信息。
routeData.Values [控制器] =CustomPage;
routeData.Values [行动] =详细信息; //这将是数据库行的主键。
//这可能是一个整数或GUID。
routeData.Values [ID] = page.Id; context.RouteData =的RouteData;
context.IsHandled = TRUE;
} 返回Task.FromResult(0);
} 公共VirtualPathData GetVirtualPath(VirtualPathContext上下文)
{
VirtualPathData结果= NULL;
PageInfo页= NULL; //获取所有缓存中的网页。
变种页= GetPageList(); 如果(TryFindMatch(页,context.Values,走出页))
{
结果=新VirtualPathData(这一点,page.VirtualPath);
context.IsBound = TRUE;
} 返回结果;
} 私人布尔TryFindMatch(IEnumerable的&LT; PageInfo&GT;页,IDictionary的&LT;字符串对象&gt;值,出PageInfo页)
{
页= NULL;
INT ID;
反对idObj;
对象控制器;
对象操作; 如果(!values.TryGetValue(ID,出idObj))
{
返回false;
} ID = Convert.ToInt32(idObj);
values.TryGetValue(控制器,走出控制器);
values.TryGetValue(行动,出于行动); //这里的逻辑应的逻辑在逆
// GetRouteData()。所以,我们匹配相同的控制器,操作和ID。
//如果我们有更多的路由值在那里,我们将采取所有这些
在该步骤期间//考虑。
如果(action.Equals(详细信息)及和放大器; controller.Equals(CustomPage))
{
页=页
。凡(X =&GT; x.Id.Equals(ID))
.FirstOrDefault();
如果(页面!= NULL)
{
返回true;
}
}
返回false;
} 私人的IEnumerable&LT; PageInfo&GT; GetPageList()
{
字符串键=__CustomPageList;
IEnumerable的&LT; PageInfo&GT;页; //只允许一个线程poplate数据
如果(!this.cache.TryGetValue(键,出来的网页))
{
锁(为SyncLock)
{
如果(!this.cache.TryGetValue(键,出来的网页))
{
// TODO:从这里数据库中取出PageInfo对象的列表。
页=新的List&LT; PageInfo&GT;()
{
新PageInfo(){ID = 1,VirtualPath =somecategory / somesubcategory /内容1},
新PageInfo(){ID = 2,VirtualPath =somecategory / somesubcategory /内容2},
新PageInfo(){ID = 3,VirtualPath =somecategory / somesubcategory / content3}
}; this.cache.Set(键,页面,
新MemoryCacheEntryOptions()
{
优先级= CacheItemPriority.NeverRemove,
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
});
}
}
} 返回页面;
}
}
CustomRoute DI注册
services.AddTransient&LT; ICustomRoute,CustomRoute&GT;();
MVC路由配置
//添加到MVC的请求管道。
app.UseMvc(路线=&GT;
{
routes.Routes.Add(routes.ServiceProvider.GetService&所述; ICustomRoute&GT;()); routes.MapRoute(
名称:默认,
模板:{控制器= HOME} / {行动=指数} / {?ID}); //取消注释以下行添加路由用于移植的Web API 2控制器。
// routes.MapWebApiRoute(DefaultApi,API / {控制器} / {ID}?);
});
在情况下,重要的我使用 5测试版
, DNX 4.5.1
和 DNX Core 5的
。
解决方案
我创建的可用于一个简单的主键的URL 2路映射<一个一个通用的解决方案href=\"http://stackoverflow.com/questions/32565768/change-route-collection-of-mvc6-after-startup#32586837\">in这个答案的基础上我在这里学到的信息。主键的控制器,动作,数据提供者和数据类型可以接线入MVC 6路由时,可以指定。
块引用>解决方案由于@opiants说,问题是,你正在做什么在你的
RouteAsync
方法。如果你的意图是最终调用一个控制器的操作方法,你可以用下面的办法不是默认的MVC路线:
在默认情况下MVC使用
<一href=\"https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs\"><$c$c>TemplateRoute$c$c>
与内部目标IRouter
。在RouteAsync的TemplateRoute会
委托给内部IRouter。该内路由器被设置为
<一href=\"https://github.com/aspnet/Mvc/blob/465b4ce0df0ae92928b8ff2e4673f829cbbaa982/src/Microsoft.AspNet.Mvc.Core/Actions/MvcRouteHandler.cs\"><$c$c>MvcRouteHandler$c$c>
由默认的builder
扩展
块引用>在你的情况下,通过添加启动
IRouter
为你内心的目标:公共类CustomRoute:ICustomRoute
{
私人只读IMemoryCache缓存;
私人只读IRouter目标;
私有对象的SyncLock =新的对象(); 公共CustomRoute(IMemoryCache缓存,IRouter目标)
{
this.cache =高速缓存;
this.target =目标;
}然后更新您的启动来设置目标为
MvcRouteHandler
,它已经被设置为routes.DefaultHandler
app.UseMvc(路线=&GT;
{
routes.Routes.Add(
新CustomRoute(routes.ServiceProvider.GetRequiredService&LT; IMemoryCache&GT;()
routes.DefaultHandler)); routes.MapRoute(
名称:默认,
模板:{控制器= HOME} / {行动=指数} / {?ID}); //取消注释以下行添加路由用于移植的Web API 2控制器。
// routes.MapWebApiRoute(DefaultApi,API / {控制器} / {ID}?);
});最后,更新您的AsyncRoute方法来调用内部
IRouter
,这将是MvcRouteHandler
。您可以使用该方法在<一实施href=\"https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs\"><$c$c>TemplateRoute$c$c>作为指导。我也很快采用这种方法和修改你的方法如下:公共异步任务RouteAsync(RouteContext上下文)
{
VAR requestPath = context.HttpContext.Request.Path.Value; 如果(string.IsNullOrEmpty(requestPath)及!&放大器; requestPath [0] =='/')
{
//修剪前导斜杠
requestPath = requestPath.Substring(1);
} //获取相匹配的页面。
VAR页= GetPageList()
。凡(X =&GT; x.VirtualPath.Equals(requestPath))
.FirstOrDefault(); //如果我们回到一个空值设置,这意味着URI不匹配
如果(页面== NULL)
{
返回;
}
//调用MVC控制器/动作
VAR oldRouteData = context.RouteData;
VAR newRouteData =新的RouteData(oldRouteData);
newRouteData.Routers.Add(this.target); // TODO:您可能希望使用页面对象(从数据库)来
//取得两个控制器和动作,甚至可能的区域。
//或者,您可以创建为每个表和硬code路线
// 此信息。
newRouteData.Values [控制器] =CustomPage;
newRouteData.Values [行动] =详细信息; //这将是数据库行的主键。
//这可能是一个整数或GUID。
newRouteData.Values [ID] = page.Id; 尝试
{
context.RouteData = newRouteData;
等待this.target.RouteAsync(背景);
}
最后
{
//还原污染的路由数据的原始值以prevent。
如果(!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}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.I have the outgoing routes functioning (meaning
ActionLink
works fine when I put in the route values). The problem is with theRouteAsync
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); }
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 theCustomPage
controller, as it should. I just get a blank white page in the browser.I added the
WriteAsync
line as was done in this post and it writesHello 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 anIRouter
orINamedRouter
.How can I make the
RouteAsync
method function?Entire CustomRoute Implementation
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; } }
CustomRoute DI Registration
services.AddTransient<ICustomRoute, CustomRoute>();
MVC Route Configuration
// 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?}"); });
In case it matters I am using
Beta 5
,DNX 4.5.1
andDNX Core 5
.Solution
I created a generic solution that can be used for a simple primary key to URL 2-way mapping in this answer based on the information I learned here. The controller, action, data provider, and datatype of the primary key can be specified when wiring it into MVC 6 routing.
解决方案As @opiants said, the problem is that you are doing nothing in your
RouteAsync
method.If your intention is to end up calling a controller action method, you could use the following approach than the default MVC routes:
By default MVC uses a
TemplateRoute
with an inner targetIRouter
. In RouteAsync, the TemplateRoute will delegate to the inner IRouter. This inner router is being set as theMvcRouteHandler
by the default builder extensionsIn your case, start by adding an
IRouter
as your inner target: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; }
Then update your startup to set that target as the
MvcRouteHandler
, which has already been set asroutes.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?}"); });
Finally, update your AsyncRoute method to call the inner
IRouter
, which would be theMvcRouteHandler
. You can use the implementation of that method inTemplateRoute
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; } } }
这篇关于在ASP.NET 5 Imlementing定制IRouter(vNext)MVC 6的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!