20

Ich versuche, this sample RouteBase implementation zu konvertieren, um mit MVC 6 zu arbeiten. Ich habe das meiste davon ausgearbeitet, indem ich the example in the Routing project folgte, aber ich stolpere wie ich zurückkomme die asynchrone Task von der Methode. Es ist mir wirklich egal, ob es wirklich asynchron ist (Prost für jeden, der diese Antwort geben kann), denn jetzt will ich es nur funktionieren lassen.Ein angepasster IRouter in ASP.NET 5 (vNext) MVC 6

Ich habe die ausgehenden Routen funktioniert (dh ActionLink funktioniert gut, wenn ich die Route Werte). Das Problem ist mit der Methode.

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

Die gesamte Methode läuft bis zum Ende, wenn es eine Übereinstimmung gibt. Aber wenn es fertig ausgeführt wird, ruft es nicht die Details Methode des CustomPage Controllers auf, wie es sollte. Ich bekomme nur eine leere weiße Seite im Browser.

Ich habe die WriteAsync Linie wie in this post getan wurde und es Hello there auf die leere Seite schreibt, aber ich kann nicht verstehen, warum MVC meine Controller Aufruf nicht (in früheren Versionen dieses ohne Probleme gearbeitet). Leider umfasste dieser Beitrag jeden Teil des Routings, außer der Implementierung eines IRouter oder INamedRouter.

Wie kann ich die Methode RouteAsync funktionieren lassen?

Entire CustomRoute Implementierung

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 Registrierung

services.AddTransient<ICustomRoute, CustomRoute>(); 

MVC Routen Konfiguration

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

Falls es darauf ankommt, ich bin mit Beta 5, DNX 4.5.1 und DNX Core 5.

Lösung

habe ich eine generische Lösung, die für eine einfache Primärschlüssel URL 2-Wege-Mapping in this answer auf der Grundlage der Informationen verwendet werden kann, die ich hier gelernt. Der Controller, die Aktion, der Datenprovider und der Datentyp des Primärschlüssels können beim Anschluss an das MVC 6-Routing angegeben werden.

Antwort

7

Wie @opiants sagte, ist das Problem, dass Sie nichts in Ihrer RouteAsync Methode tun.

Wenn Ihre Absicht ist es, eine Controller-Aktion-Methode, um am Ende rief, können Sie den folgenden Ansatz als die Standard-MVC-Routen verwenden:

standardmäßig MVC ein IRouter TemplateRoute mit einem inneren Ziel verwendet. In RouteAsync delegiert die TemplateRoute Delegate an den inneren IRouter. Dieser innere Router wird als MvcRouteHandler standardmäßig builder extensions festgelegt. In Ihrem Fall starten, indem Sie ein IRouter als inneres Ziel Zugabe:

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

Ihren Start aktualisiert dann das gewählte Ziel als MvcRouteHandler zu setzen, die bereits als routes.DefaultHandler eingestellt wurde:

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

Schließlich aktualisieren Sie Ihre AsyncRoute-Methode, um die innere IRouter aufzurufen, die die MvcRouteHandler wäre. Sie können die Implementierung dieser Methode in TemplateRoute als Leitfaden verwenden.

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

aktualisieren RC2

wie TemplateRoute Sieht nicht mehr um in RC2 aspnet Routing: Ich habe schnell diesen Ansatz und verändert Ihre Methode wie folgt verwendet.

Ich untersuchte die Geschichte, und es wurde RouteBase in commit 36180ab als Teil eines größeren Refactoring umbenannt.

+0

Ja, ich habe darüber nachgedacht, einen inneren IRouter zu haben, aber ich glaube nicht, dass du das brauchst. Wenn context.IsHandle auf false gesetzt wird und früh zurückkehrt, wird es zum nächsten registrierten IRouter verschoben und fällt schließlich auf routes.DefaultHandler zurück (was der MvcRouteHandler ist) – Dealdiane

+0

Sind Sie sicher, dass der DefaultHandler verwendet wird, wenn keine Route eine Übereinstimmung hat? ? Mit Blick auf den Code scheint es nur für die Erweiterungsmethode ['MapRoute'] (https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/RouteBuilderExtensions.cs) verwendet zu werden Die MVC-Routen werden mithilfe von TemplateRoute mit einem internen MvcRouteHandler –

+0

hinzugefügt. Überprüfen Sie auch ['RouteBuilder.Build'] (https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/RouteBuilder.cs). das wird nur jede definierte Routen hinzufügen, aber nicht die Standard-Handler –

2

Hauptgrund, warum das nicht funktioniert, ist, weil Sie nichts in der Methode tun. Ein weiterer Grund ist, dass das Routing in MVC 6 sehr anders ist als beim vorherigen MVC-Routing. Daher ist es wahrscheinlich besser, es mit source code als Referenz zu schreiben, da es derzeit nur wenige Artikel gibt, die MVC 6 angehen .

EDIT: @Daniel J.G. Antwort macht viel mehr Sinn als das, also nutze das wenn möglich. Dies könnte für den Anwendungsfall einer anderen Person passen, also verlasse ich das hier.

Hier ist eine sehr einfache IRouter Implementierung mit beta7. Dies sollte funktionieren, aber Sie müssen wahrscheinlich die Lücken füllen.Sie verlassen nun die page != null und ersetzen Sie es mit dem Code entfernen müssen unten und ersetzen Sie die Controller und Aktionen:

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; 

Stellen Sie sicher, einen Verweis auf die die GetRequiredService Erweiterung zu lösen Microsoft.Framework.DependencyInjection Namespace hinzuzufügen.

Danach registrieren Sie den IRouter nach unten:

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

Dann registrieren nur, dass in Ihrem IOC,

services.AddSingleton<CustomRoute>(); 

Ein weiterer ‚sauberere‘ Ansatz wäre wahrscheinlich eine andere Implementierung erstellen zu IActionSelector.

+0

Schließen, aber keine Zigarre. Nachdem ich den Unsinn über das Erstellen von'ActionContextAccessor' entfernt hatte, nur um eine seiner Eigenschaften zu setzen und dann zu bekommen, konnte ich die Methode erfolgreich aufrufen. Jetzt bekomme ich jedoch eine Null-Referenz-Ausnahme. Die ersten zwei Zeilen des Stack-Trace sind: 'Microsoft.AspNet.Mvc.UrlHelper..ctor (IScopedInstance'1 contextAccessor, IActionSelector actionSelector)' und 'lambda_method (Closure, ServiceProvider)'. Beginnen zu denken, dass es einen Fehler geben könnte. Ich werde von Beta 5 auf Beta 7 umsteigen, um zu sehen, ob ich es zum Laufen bringen kann und wenn nicht, melde es an Microsoft. – NightOwl888

+0

Sorry hat deine Frage nicht sorgfältig gelesen und angenommen, dass du in der Beta 7 bist.Der Code oben wurde in Beta 7 getestet. Ziemlich sicher, dass es nicht auf Beta 5 funktioniert. Siehe meine Bearbeitung über das Hinzufügen zu den Routen.Routes.Insert auch. – Dealdiane

Verwandte Themen