在ASP.NET 5 Imlementing定制IRouter(vNext)MVC 6 [英] Imlementing a Custom IRouter in ASP.NET 5 (vNext) MVC 6

查看:188
本文介绍了在ASP.NET 5 Imlementing定制IRouter(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
  与内部目标 IRouter 。在RouteAsync的TemplateRoute会
  委托给内部IRouter。该内路由器被设置为
  <一href=\"https://github.com/aspnet/Mvc/blob/465b4ce0df0ae92928b8ff2e4673f829cbbaa982/src/Microsoft.AspNet.Mvc.Core/Actions/MvcRouteHandler.cs\"><$c$c>MvcRouteHandler
  由默认的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作为指导。我也很快采用这种方法和修改你的方法如下:

 公共异步任务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 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);
}

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.

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.

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 and DNX 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 target IRouter. In RouteAsync, the TemplateRoute will delegate to the inner IRouter. This inner router is being set as the MvcRouteHandler by the default builder extensions

In 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 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?}");
});

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;
        }
    }
}

这篇关于在ASP.NET 5 Imlementing定制IRouter(vNext)MVC 6的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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