Imlementing eine Benutzerdefinierte IRouter in ASP.NET 5 (vNext) MVC 6
Ich bin versucht zu konvertieren dieses Beispiel RouteBase Umsetzung arbeiten mit MVC-6. Ich habe die meisten davon durch den folgenden das Beispiel in der Routing-Projekt, aber ich bin immer stolperte auf wie die Rückkehr der asynchronen Task
von der Methode. Ich weiß wirklich nicht egal, ob es sich tatsächlich asynchron ist (prost, wer kann vorsehen, dass Antwort), für jetzt möchte ich nur, es funktioniert.
Habe ich die ausgehenden Strecken funktionieren (also ActionLink
funktioniert gut, wenn ich in der route-Werten). Das problem ist mit der RouteAsync
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 den ganzen Weg durch bis zum Ende, wenn es eine übereinstimmung gibt. Aber wenn es fertig ist die Ausführung, es nicht an die Details
Methode der CustomPage
controller, wie es sein sollte. Bekomme ich nur eine leere weiße Seite im browser.
Habe ich den WriteAsync
Linie wie in dieser Beitrag und es schreibt Hello there
auf die leere Seite, aber ich kann nicht verstehen, warum MVC nicht anrufen, mein controller (in früheren Versionen klappte dies ohne Probleme). Leider, dass die post unter jedem Teil der routing-außer implementieren Sie eine IRouter
oder INamedRouter
.
Wie kann ich machen das RouteAsync
Methode Funktion?
Gesamte CustomRoute Umsetzung
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-Route-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 wichtig ist ich bin mit Beta 5
, DNX 4.5.1
und DNX Core 5
.
Lösung
Erstellte ich eine generische Lösung, die verwendet werden kann für einen einfachen Primärschlüssel URL 2-Wege-mapping in dieser Antwort auf der Grundlage der Informationen, die ich hier gelernt. Der controller, action, data provider und dem Datentyp des Primärschlüssels angegeben werden können, wenn die Verkabelung es in MVC-6-routing.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Als @opiants gesagt, das problem ist, dass Sie tun nichts in Ihrem
RouteAsync
Methode.Wenn Ihre Absicht ist, um am Ende den Aufruf einer controller-action-Methode, könnten Sie den folgenden Ansatz als die Standard-MVC-Routen:
Dann aktualisieren Sie Ihre Systemstart zu setzen, das Ziel als
MvcRouteHandler
, die bereits alsroutes.DefaultHandler
:Schließlich, aktualisieren Sie Ihre AsyncRoute-Methode aufrufen, um die innere
IRouter
, das wäre derMvcRouteHandler
. Sie können die Umsetzung dieser Methode inTemplateRoute
als Leitfaden. Ich habe schnell verwendet diesen Ansatz und modifizierte Ihre Methode wie folgt:Update RC2
Sieht aus wie
TemplateRoute
nicht mehr um in RC2 aspnet-Routing.Untersuchte ich die Geschichte, und es wurde umbenannt
RouteBase
in commit 36180ab als Teil einer größeren Umgestaltung.MapRoute
- Erweiterung-Methode, also das MVC-Routen Hinzugefügt werden, die mithilfe TemplateRoute mit einer inneren MvcRouteHandlerRouteBuilder.Build
, die einfach fügen Sie jede festgelegte Routen, aber nicht die Standard-handlerMvcRouteHandler
war nicht Teil der Gleichung, wie es war in den Versionen der Vergangenheit. Nicht zu erwähnen, es schien nicht viel Sinn zu re-implementieren Sie alles, was es bereits tut. Ich werde es mal ausprobieren.IRouter
Schnittstelle, die Sie implementieren müssen, um Sie beim schreiben einer benutzerdefinierten route mit benutzerdefinierte routing-Logik. Also im Grunde, um Ihre benutzerdefinierte routing-Logik, aber immer noch Delegierter in der MVC pipeline für die Zustellung der Anforderung, die Sie haben, um eineIRouter
-, Wickel anderenIRouter
die passiert werden dieMvcRouteHandler
... Nicht sicher, wenn ich erklärte mich selbst! 🙂TemplateRoute
Klasse war die Antwort basiert. Seit mehreren Routen kann ausgewertet werden, wenn Ihre route ändert sich die route, die Daten aber nicht bis Ende der Bearbeitung der Anforderung, dann stellen Sie sicher, verlassen Sie den Weg der Daten, wie es vorher war.Primäre Grund, warum das nicht funktioniert ist, weil Sie nicht etwas zu tun in der
RouteAsync
Methode. Ein weiterer Grund ist, dass, wie das routing funktioniert in MVC 6 ist sehr unterschiedlich zu dem, wie die bisherigen MVC routing gearbeitet, also bist du wahrscheinlich besser dran es zu schreiben, von Grund auf, mit der source code als Referenz, da gibt es sehr wenige Artikel, die Bekämpfung MVC 6 im moment.EDIT: @Daniel, J. G. Antwort macht viel mehr Sinn, als diese so zu verwenden, dass, wenn möglich. Diese passen könnte jemand anderes use-case-also ich gehe dieses hier.
Hier ist ein sehr einfaches
IRouter
Umsetzung mit beta7. Dies sollte funktionieren, aber Sie werden wahrscheinlich brauchen, um die Lücken zu füllen. Sie müssen entfernen Sie diepage != null
und ersetzen Sie es mit den folgenden code und ersetzen Sie den Controller und Aktionen:Stellen Sie sicher, Sie fügen Sie einen Verweis auf die
Microsoft.Framework.DependencyInjection
namespace zu beheben, dieGetRequiredService
Erweiterung.Danach registrieren Sie die IRouter wie nachstehend erklärt:
Dann melde dich doch einfach, dass in Ihrem IOC,
Andere 'Reiniger' - Ansatz würde wahrscheinlich zu einer anderen Umsetzung der
IActionSelector
.IActionContextAccessor
nur zu setzen und erhalten dann eine seiner Eigenschaften war, konnte ich die Methode aufrufen erfolgreich. Aber jetzt bin ich immer ein null-Verweis-Ausnahme. Ersten 2 Zeilen der stack-trace sind:Microsoft.AspNet.Mvc.UrlHelper..ctor(IScopedInstance
1 contextAccessor, IActionSelector actionSelector)` undlambda_method(Closure , ServiceProvider )
. Angefangen zu denken, es könnte ein Fehler sein. Ich werde das upgrade von Beta 5 auf Beta 7 zu sehen, ob ich das hinbekomme und wenn nicht, wird ein Bericht an Microsoft.