答案是没有合理的方法可以做到这一点,即使您找到一种方法也不是一个好习惯。
解决问题的方法不正确
基本上,过去的MVC版本的路由配置旨在像DI配置一样工作-
也就是说,您将所有内容放在组合根目录中,然后在运行时使用该配置。问题是您
可以 在运行时将对象推送到配置中(很多人这样做),这不是正确的方法。
现在,配置已由真正的DI容器替换,此方法将不再起作用。现在只能在应用程序启动时完成注册步骤。
正确的方法
超越
Route类在过去的MVC版本中所做的工作来自定义路由的正确方法是继承Routebase或Route。
AspNetCore(以前称为MVC
6)具有类似的抽象,IRouter和INamedRouter扮演相同的角色。与其前身很像,
IRouter只有两种方法可以实现。
namespace Microsoft.AspNet.Routing{ public interface IRouter { // Derives a virtual path (URL) from a list of route values VirtualPathData GetVirtualPath(VirtualPathContext context); // Populates route data (including route values) based on the // request Task RouteAsync(RouteContext context); }}在此接口中,您可以实现路由的2向性质-用于路由值的URL和用于URL的路由值。
一个例子: CachedRoute<TPrimaryKey>
这是一个跟踪和缓存主键到URL的1-1映射的示例。它是通用的,我已经测试它工作的主键是否
int还是
Guid。
有一个必须插入的可插入部分,可以
ICachedRouteDataProvider在其中实现对数据库的查询。您还需要提供控制器和操作,因此此路由足够通用,可以通过使用多个实例将多个数据库查询映射到多个操作方法。
using Microsoft.AspNetCore.Routing;using Microsoft.Extensions.Caching.Memory;using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Threading.Tasks;public class CachedRoute<TPrimaryKey> : IRouter{ private readonly string _controller; private readonly string _action; private readonly ICachedRouteDataProvider<TPrimaryKey> _dataProvider; private readonly IMemoryCache _cache; private readonly IRouter _target; private readonly string _cacheKey; private object _lock = new object(); public CachedRoute( string controller, string action, ICachedRouteDataProvider<TPrimaryKey> dataProvider, IMemoryCache cache, IRouter target) { if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException("controller"); if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullException("action"); if (dataProvider == null) throw new ArgumentNullException("dataProvider"); if (cache == null) throw new ArgumentNullException("cache"); if (target == null) throw new ArgumentNullException("target"); _controller = controller; _action = action; _dataProvider = dataProvider; _cache = cache; _target = target; // Set Defaults CacheTimeoutInSeconds = 900; _cacheKey = "__" + this.GetType().Name + "_GetPageList_" + _controller + "_" + _action; } public int CacheTimeoutInSeconds { get; set; } 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 id that matches. TPrimaryKey id; //If this returns false, that means the URI did not match if (!GetPageList().TryGetValue(requestPath, out id)) { return; } //Invoke MVC controller/action var routeData = context.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-pre // this information. routeData.Values["controller"] = _controller; routeData.Values["action"] = _action; // This will be the primary key of the database row. // It might be an integer or a GUID. routeData.Values["id"] = id; await _target.RouteAsync(context); } public VirtualPathData GetVirtualPath(VirtualPathContext context) { VirtualPathData result = null; string virtualPath; if (TryFindMatch(GetPageList(), context.Values, out virtualPath)) { result = new VirtualPathData(this, virtualPath); } return result; } private bool TryFindMatch(IDictionary<string, TPrimaryKey> pages, IDictionary<string, object> values, out string virtualPath) { virtualPath = string.Empty; TPrimaryKey id; object idObj; object controller; object action; if (!values.TryGetValue("id", out idObj)) { return false; } id = SafeConvert<TPrimaryKey>(idObj); values.TryGetValue("controller", out controller); values.TryGetValue("action", out action); // The logic here should be the inverse of the logic in // RouteAsync(). 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(_action) && controller.Equals(_controller)) { // The 'OrDefault' case returns the default value of the type you're // iterating over. For value types, it will be a new instance of that type. // Since KeyValuePair<TKey, TValue> is a value type (i.e. a struct), // the 'OrDefault' case will not result in a null-reference exception. // Since TKey here is string, the .Key of that new instance will be null. virtualPath = pages.FirstOrDefault(x => x.Value.Equals(id)).Key; if (!string.IsNullOrEmpty(virtualPath)) { return true; } } return false; } private IDictionary<string, TPrimaryKey> GetPageList() { IDictionary<string, TPrimaryKey> pages; if (!_cache.TryGetValue(_cacheKey, out pages)) { // only allow one thread to poplate the data lock (_lock) { if (!_cache.TryGetValue(_cacheKey, out pages)) { pages = _dataProvider.GetPageToIdMap(); _cache.Set(_cacheKey, pages, new MemoryCacheEntryOptions() { Priority = CacheItemPriority.NeverRemove, AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(this.CacheTimeoutInSeconds) }); } } } return pages; } private static T SafeConvert<T>(object obj) { if (typeof(T).Equals(typeof(Guid))) { if (obj.GetType() == typeof(string)) { return (T)(object)new Guid(obj.ToString()); } return (T)(object)Guid.Empty; } return (T)Convert.ChangeType(obj, typeof(T)); }}CmsCachedRouteDataProvider
这就是数据提供程序的实现,基本上是您需要在CMS中执行的操作。
public interface ICachedRouteDataProvider<TPrimaryKey>{ IDictionary<string, TPrimaryKey> GetPageToIdMap();}public class CmsCachedRouteDataProvider : ICachedRouteDataProvider<int>{ public IDictionary<string, int> GetPageToIdMap() { // Lookup the pages in DB return (from page in DbContext.Pages select new KeyValuePair<string, int>( page.Url.TrimStart('/').TrimEnd('/'), page.Id) ).ToDictionary(pair => pair.Key, pair => pair.Value); }}用法
在这里,我们在默认路由之前添加路由,并配置其选项。
// Add MVC to the request pipeline.app.UseMvc(routes =>{ routes.Routes.Add( new CachedRoute<int>( controller: "Cms", action: "Index", dataProvider: new CmsCachedRouteDataProvider(), cache: routes.ServiceProvider.GetService<IMemoryCache>(), target: routes.DefaultHandler) { CacheTimeoutInSeconds = 900 }); 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?}");});这就是要旨。您仍然可以改善一点。
例如,我个人将使用工厂模式并将存储库注入到构造器中,
CmsCachedRouteDataProvider而不是
DbContext在各处进行硬编码。



