tag route page net mvc for asp c# asp.net-mvc asp.net-mvc-routing asp.net-core asp.net-core-mvc

c# - route - select asp-for asp-items



Implementación de un IRouter personalizado en ASP.NET 5(vNext) MVC 6 (2)

Como dijo @opiants, el problema es que no está haciendo nada en su método RouteAsync .

Si su intención es terminar llamando a un método de acción del controlador, podría usar el siguiente enfoque que las rutas MVC predeterminadas:

Por defecto, MVC usa un TemplateRoute con un IRouter destino IRouter . En RouteAsync, el TemplateRoute delegará al IRouter interno. Este enrutador interno se está configurando como MvcRouteHandler por las extensiones de constructor predeterminadas. En su caso, comience agregando un IRouter como su objetivo interno:

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

Luego actualice su inicio para establecer ese objetivo como el MvcRouteHandler , que ya se ha establecido como 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?}"); });

Finalmente, actualice su método AsyncRoute para llamar al IRouter interno, que sería el MvcRouteHandler . Puede usar la implementación de ese método en TemplateRoute como guía. Rápidamente he usado este enfoque y he modificado su método de la siguiente manera:

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

Actualizar RC2

Parece que TemplateRoute ya no existe en RC2 aspnet Routing.

Investigué la historia, y se renombró a RouteBase en commit 36180ab como parte de una refactorización más grande.

Estoy intentando convertir esta implementación de RouteBase de muestra para trabajar con MVC 6. He trabajado la mayor parte de la misma siguiendo el ejemplo en el proyecto de Enrutamiento , pero me estoy preguntando cómo devolver la Task asíncrona desde el método. Realmente no me importa si en realidad es asíncrono (saludos a cualquiera que pueda brindar esa respuesta), por ahora solo quiero que funcione.

Tengo las rutas salientes funcionando (lo que significa que ActionLink funciona bien cuando pongo los valores de ruta). El problema está con el método 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); }

Todo el método se ejecuta hasta el final cuando hay una coincidencia. Pero cuando termina de ejecutarse, no llama al método de Details del controlador CustomPage , como debería. Acabo de recibir una página en blanco en el navegador.

WriteAsync línea WriteAsync como se hizo en esta publicación y escribe Hello there en la página en blanco, pero no puedo entender por qué MVC no está llamando a mi controlador (en versiones anteriores esto funcionó sin problemas). Desafortunadamente, esa publicación cubrió cada parte del enrutamiento, excepto cómo implementar un IRouter o INamedRouter .

¿Cómo puedo hacer que el método RouteAsync funcione?

Implementación completa de 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 Registro

services.AddTransient<ICustomRoute, CustomRoute>();

Configuración de ruta 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?}"); });

En caso de que importe, estoy usando Beta 5 , DNX 4.5.1 y DNX Core 5 .

Solución

Creé una solución genérica que se puede usar para una clave primaria simple para la asignación de URL de 2 vías en esta respuesta, en base a la información que aprendí aquí. El controlador, la acción, el proveedor de datos y el tipo de datos de la clave principal se pueden especificar al cablearlo en el enrutamiento MVC 6.


La razón principal por la que eso no funciona es porque no estás haciendo nada en el método RouteAsync . Otra razón es que la forma en que funciona el enrutamiento en MVC 6 es muy diferente a la forma en que funcionó el enrutamiento anterior de MVC, por lo que probablemente sea mejor escribirlo desde cero utilizando el código fuente como referencia, ya que hay muy pocos artículos que abordan MVC 6 en el momento.

EDITAR: la respuesta de @Daniel JG tiene mucho más sentido que esto, así que úselo si es posible. Esto podría encajar en el caso de uso de otra persona, así que lo dejo aquí.

Aquí hay una implementación muy simple de IRouter usando beta7 . Esto debería funcionar pero es probable que tengas que rellenar los huecos. Deberá eliminar la page != null y reemplazarla con el código a continuación y reemplazar los controladores y las acciones:

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;

Asegúrese de agregar una referencia al espacio de nombres Microsoft.Framework.DependencyInjection para resolver la extensión GetRequiredService .

Después de eso, registre el IRouter como se muestra a continuación:

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

Entonces simplemente registra eso en tu COI,

services.AddSingleton<CustomRoute>();

Otro enfoque ''más limpio'' probablemente sería crear una implementación diferente de IActionSelector .