20

this sample RouteBase implementationをMVC 6で使用するように変換しようとしています。私はそのほとんどをthe example in the Routing projectに従っていますが、復帰方法が遅れていますこのメソッドの非同期Task私は実際に非同期であるかどうかは気にしません(その答えを提供できる人に歓声をあげます)。今は機能したいだけです。ASP.NET 5(vNext)MVC 6でカスタムIRouterを実装する

私は、ルートが機能しています(ルート値を入力すると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); 
} 

マッチするものがある場合、メソッド全体が最後まで実行されます。しかし、実行が完了すると、それはCustomPageコントローラのDetailsメソッドを呼び出しません。私はちょうどブラウザの白い空白のページを取得します。

this postで行われ、それは空白のページへHello thereを書き込みますが、MVCは(以前のバージョンでは、これが滞りなく働いていた)私のコントローラを呼び出していない理由を私は理解できないたように私はWriteAsync行を追加しました。残念ながら、その投稿はIRouterまたはINamedRouterの実装方法を除いてルーティングのすべての部分をカバーしていました。

RouteAsyncメソッドはどのように機能させることができますか?

全体CustomRoute実装

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登録

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 5DNX 4.5.1DNX Core 5を使用しています重要な場合。

ソリューションは、私がここで学習した情報に基づいてURL 2ウェイマッピングin this answerへの単純な主キーのために使用することができる汎用的なソリューションを作成しました。主キーのコントローラ、アクション、データプロバイダ、およびデータ型は、MVC 6ルーティングに配線するときに指定できます。

答えて

7

@opiantsが述べたように、問題はあなたのメソッドで何もしていないということです。 MVCは、内部ターゲットIRouterTemplateRoute を使用してデフォルトで

:あなたの意図は、コントローラのアクションメソッドを呼び出して終了する場合

は、デフォルトMVCのルートよりも、次のアプローチを使用することができます。 RouteAsyncでは、TemplateRouteは 内部IRouterに委譲します。この内部ルータは、 MvcRouteHandler のデフォルト値のbuilder extensionsに設定されています。あなたのケースでは 、あなたの内側のターゲットとして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; 
    } 

そして、すでにroutes.DefaultHandlerとして設定されているMvcRouteHandler、としてその目標を設定し、起動を更新:

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

最後に、内部のIRouterMvcRouteHandler)を呼び出すようにAsyncRouteメソッドを更新します。その方法の実装をTemplateRouteで使用することができます。私はすぐにこのアプローチを使用して、次のようにあなたの方法を変更した:

はRC2のASPNETルーティングでは、もはや周りにある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。

私は歴史を調査し、より大きなリファクタリングの一部としてcommit 36180abRouteBaseと名前を変更しました。

+0

ええ、私も内部のIRouterを持っていることを考えましたが、私はあなたがそれを必要とは思わない。 context.IsHandledをfalseに設定して早期に戻すと、次に登録されたIRouterに移動し、最終的にroutes.DefaultHandler(これはMvcRouteHandler)にフォールバックします。 – Dealdiane

+0

一致するルートがない場合はDefaultHandlerが使用されますか?コードを見ると、['MapRoute'](https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/RouteBuilderExtensions.cs)拡張メソッドのためだけに使用されているようですMVCルートは、内部MvcRouteHandlerを持つTemplateRouteを使用して追加されます –

+0

['RouteBuilder.Build'](https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/RouteBuilder.cs)もチェックしてください。定義済みの各ルートを追加するだけですが、デフォルトのハンドラは追加されません –

2

それが機能しない主な理由は、RouteAsyncメソッドで何もしていないためです。もう1つの理由は、MVC 6でルーティングがどのように動作するかは、以前のMVCルーティングがどのように機能していたかであり、その時点でMVC 6に取り組んでいる記事がほとんどないので、source codeを参考にして、 。

EDIT:@Daniel J.G.答えはこれよりもはるかに意味があるので、可能ならばそれを使用してください。これは他人のユースケースに合うかもしれませんので、ここでこれを残しておきます。

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; 

あなたがGetRequiredService拡張子を解決するためにMicrosoft.Framework.DependencyInjection名前空間への参照を追加していることを確認します。その後

、以下のとおり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 ... 
}); 

それからちょうど

services.AddSingleton<CustomRoute>(); 

、あなたのIOCに別の「クリーナー」アプローチは、おそらくの異なる実装を作成するだろうと登録 IActionSelector

+0

閉じるが、シガーはない。 'IActionContextAccessor'を作成することについてナンセンスを取り除いてから、そのプロパティの1つを取得して取得した後、メソッドを正常に呼び出せました。しかし、今はnull参照例外が発生しています。スタックトレースの最初の2行は 'Microsoft.AspNet.Mvc.UrlHelper..ctor(IScopedInstance'1 contextAccessor、IActionSelector actionSelector)'と 'lambda_method(Closure、ServiceProvider)'です。バグがあるかもしれないと考え始めました。私はBeta 5からBeta 7にアップグレードして、それを動作させることができるかどうかを確認し、そうでない場合はMicrosoftに報告します。 – NightOwl888

+0

あなたの質問を慎重に読んでおらず、あなたがベータ版であると仮定しました7。上記のコードはベータ7でテストされています。ベータ5では動作しません。ルートに追加することについて私の編集を参照してください。ルート。同様に挿入してください。 – Dealdiane

関連する問題