20

MVC 6 ile çalışmak için this sample RouteBase implementation dönüştürmeyi deniyorum. the example in the Routing project'u izleyerek çoğunu çözdüm, ancak nasıl geri döneceğimi anladım Eşzamansız Task yöntemden. Aslına bakarsan gerçekten umursamıyorum (bu cevabı sağlayabilecek herkese tezahürat edin), şimdilik sadece işe yaramak istiyorum.Özel bir IRouter'ı ASP.NET 5 (vNext) içinde gerçekleştirme MVC 6

Giden yolları çalıştırıyorum (ActionLink, rota değerlerini girdiğimde iyi çalışıyor). Sorun, RouteAsync yöntemiyle ilgilidir.

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

Tüm yöntem, bir eşleşme olduğunda sonuna kadar çalışır. Ancak, yürütülürken, olması gerektiği gibi, CustomPage denetleyicisinin Details yöntemini çağırmaz. Sadece tarayıcıda boş bir beyaz sayfa alıyorum. this post yapılan ve boş sayfaya Hello there yazar gibi

Ben WriteAsync hattını eklendi, ancak MVC (bu aksamadan çalıştı önceki sürümlerinde) benim denetleyicisi aramıyor neden anlayamıyorum. Ne yazık ki, bu gönderi, bir IRouter veya INamedRouter'un nasıl uygulanacağı haricinde, yönlendirmenin her bölümünü kapsıyordu.

RouteAsync yöntem işlevini nasıl yapabilirim?

Bütün CustomRoute Uygulama

services.AddTransient<ICustomRoute, CustomRoute>(); 

MVC Rota Yapılandırma durumda

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

ben Beta 5, DNX 4.5.1 ve DNX Core 5 kullanıyorum konularda

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 Kaydı.

Çözüm Ben burada öğrenilen bilgilere dayalı URL 2 yönlü haritalama in this answer için basit birincil anahtar için kullanılabilecek bir jenerik çözüm yarattı. Birincil anahtarın denetleyicisi, eylemi, veri sağlayıcısı ve veri türü, MVC 6 yönlendirmesine bağlanırken belirtilebilir.

cevap

7

@opiants adlı kullanıcı, sorunun şu anda RouteAsync yönteminiz ile ilgili hiçbir şey yapmamak olduğunu söyledi. MVC bir iç hedefe IRouter ile TemplateRoute kullanan Varsayılan olarak

: niyetinizi bir kontrolör eylem yöntemini çağırarak sonuna kadar ise

, varsayılan MVC yolları daha aşağıdaki bir yaklaşım kullanabilirsiniz. RouteAsync'de, TemplateRoute iç IRouter'e delege edecektir. Bu iç yönlendirici, varsayılan builder extensions tarafından MvcRouteHandler olarak ayarlanmaktadır. Senin durumunda , iç hedef olarak bir IRouter ekleyerek başlayın:

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

Sonra zaten routes.DefaultHandler olarak belirlendi MvcRouteHandler olarak bu hedef, ayarlamak için başlangıç ​​güncelleyin:

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

Son olarak, MvcRouteHandler olan IRouter içini çağırmak için AsyncRoute yönteminizi güncelleyin. Bu yöntemin uygulanmasını TemplateRoute kılavuzda kullanabilirsiniz. Ben hızlı bir şekilde bu yaklaşımı kullanılmış ve aşağıdaki gibi bir yöntem değiştirmiş:

RC2 aspnet Yönlendirme artık etrafında TemplateRoute benziyor

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

Güncelleme RC2.

Geçmişi araştırdım ve daha büyük bir refactoringin parçası olarak commit 36180abRouteBase olarak yeniden adlandırıldı.

+0

Evet, içsel bir Kızılötesi'ye sahip olmayı düşündüm ama buna ihtiyacın olduğunu düşünmüyorum. Context.IsHandled öğesinin false değerine ayarlanması ve erken dönmesi, bir sonraki kayıtlı IRouter'a geçmesine ve sonuçta route'lara geri dönmesine neden olur.DefaultHandler (MvcRouteHandler olan) – Dealdiane

+0

Hiçbir yol eşleşme yoksa DefaultHandler'ın kullanılacağından emin misiniz? ? Kod bakmak için sadece ['MapRoute'] (https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/RouteBuilderExtensions.cs) uzantı yöntemi için kullanıldığı görülüyor MVC yolları bir iç MvcRouteHandler –

+0

ile TemplateRoute kullanılarak eklenir. Ayrıca kontrol edin ['RouteBuilder.Build'] (https://github.com/aspnet/Routing/blob/dev/src/Microsoft.AspNet.Routing/RouteBuilder.cs) Sadece tanımlanmış olan her yolu ekleyecektir, ancak varsayılan işleyici değil –

2

Bunun neden bir nedeni, neden RouteAsync yönteminde hiçbir şey yapmıyor olmanızdır. Bir başka neden de MVC 6'da yönlendirme işleminin önceki MVC yönlendirmesinin nasıl çalıştığına göre çok farklı olmasıdır. Bu nedenle, MVC 6'yı ele alan çok az makale olduğu için referans olarak source code'u kullanarak sıfırdan yazmanız daha iyi olacaktır. .

EDIT: @Daniel J.G. Cevap bundan daha mantıklıdır, bu yüzden mümkünse kullanın. Bu başkasının kullanım durumunu karşılayabilir, ben de buradan ayrılıyorum.

beta7'u kullanarak çok basit bir IRouter uygulamasıdır. Bu çalışmalı, ancak muhtemelen boşlukları doldurmanız gerekecektir.Sen denetleyicileri ve eylemleri page != null kaldırmak ve aşağıdaki kodla değiştirin ve değiştirme gerekir:

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; 

Eğer GetRequiredService uzantısını çözmek için Microsoft.Framework.DependencyInjection ad boşluğuna başvuru ekleyin emin olun. Bundan sonra

, IRouter kayıt olarak başına aşağıda:

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

Sonra sadece

services.AddSingleton<CustomRoute>(); 

Başka 'süpürge' yaklaşımı muhtemelen farklı bir uygulamasını oluşturmak olacaktır, sizin IOC o kayıt IActionSelector.

+0

Kapat, ancak puro yok. 'IActionContextAccessor' yaratma konusundaki saçmalığı kaldırdıktan sonra özelliklerinden birini belirledikten sonra, yöntemi başarılı bir şekilde çağırmayı başardım. Ancak, şimdi boş bir başvuru istisnası alıyorum. Yığın izinin ilk 2 satırı şunlardır: 'Microsoft.AspNet.Mvc.UrlHelper..ctor (IScopedInstance'1 contextAccessor, IActionSelector actionSelector)' ve 'lambda_method (Kapatma, ServiceProvider)'. Bir böcek olabileceğini düşünmeye başlıyor. Çalıştırıp yapamayacağımı ve Microsoft'a rapor edemediğimi görmek için Beta 5'ten Beta 7'ye yükselteceğim. – NightOwl888

+0

Üzgünüz, sorunuzu dikkatlice okumadım ve beta 7 üzerinde olduğunuzu varsaydık.Yukarıdaki kod beta 7'de test edildi. Beta 5 üzerinde çalışmadığından emin olun. Rotalara eklemeyle ilgili düzenlememe bakın.Routes.Insert de. – Dealdiane

İlgili konular