• NopCommerce源码架构详解对seo友好Url的路由机制实现源码分析


    seo友好Url的路由机制实现源码分析

    NopCommerce源码架构详解-对seo友好Url的路由机制实现源码分析。

    内容

    可能你刚开始看nop源码不太清楚一个Url对应具体的Controller是哪一个,因为Nop自身用了对seo友好的Url,它对路由进行了一些重写。我希望同学们通过我的这个文章对Nop路由有更深入的了解,以后也可以通过借鉴Nop的思路自己实现一个对SEO友好的Url路由。

    相关类的类图如下:

    2020-04-23-18-03-53

    下面是相关功能主要类:

    1、Nop.Web.Framework.mvc.Routes.IRoutePublisher和IRouteProvider,注册路由的共用接口。

    IRoutePublisher:

    public interface IRoutePublisher
    {
    /// <summary>
    /// Register routes
    /// </summary>
    /// <param name="routes">Routes</param>
    void RegisterRoutes(RouteCollection routeCollection);
    }

    IRouteProvider:

    public interface IRouteProvider
    {
    void RegisterRoutes(RouteCollection routes);
     
    int Priority { get; }
    }

    2、RouteProvider,实现接口IRouteProvider,注册一些核心路由规则,如首页、登录、注册、购物车等等。

    using System.Web.Mvc;
    using System.Web.Routing;
    using Nop.Web.Framework.Localization;
    using Nop.Web.Framework.Mvc.Routes;
     
    namespace Nop.Web.Infrastructure
    {
    public partial class RouteProvider : IRouteProvider
    {
    public void RegisterRoutes(RouteCollection routes)
    {
    //We reordered our routes so the most used ones are on top. It can improve performance.
     
    //home page
    routes.MapLocalizedRoute("HomePage",
    "",
    new { controller = "Home", action = "Index" },
    new[] { "Nop.Web.Controllers" });
     
    //widgets
    //we have this route for performance optimization because named routes are MUCH faster than usual Html.Action(...)
    //and this route is highly used
    routes.MapRoute("WidgetsByZone",
    "widgetsbyzone/",
    new { controller = "Widget", action = "WidgetsByZone" },
    new[] { "Nop.Web.Controllers" });
     
    //login
    routes.MapLocalizedRoute("Login",
    "login/",
    new { controller = "Customer", action = "Login" },
    new[] { "Nop.Web.Controllers" });
    //register
    routes.MapLocalizedRoute("Register",
    "register/",
    new { controller = "Customer", action = "Register" },
    new[] { "Nop.Web.Controllers" });
    //logout
    routes.MapLocalizedRoute("Logout",
    "logout/",
    new { controller = "Customer", action = "Logout" },
    new[] { "Nop.Web.Controllers" });
     
    //shopping cart
    routes.MapLocalizedRoute("ShoppingCart",
    "cart/",
    new { controller = "ShoppingCart", action = "Cart" },
    new[] { "Nop.Web.Controllers" });
    //wishlist
    routes.MapLocalizedRoute("Wishlist",
    "wishlist/{customerGuid}",
    new { controller = "ShoppingCart", action = "Wishlist", customerGuid = UrlParameter.Optional },
    new[] { "Nop.Web.Controllers" });
     
    //customer
    routes.MapLocalizedRoute("CustomerInfo",
    "customer/info",
    new { controller = "Customer", action = "Info" },
    new[] { "Nop.Web.Controllers" });
    routes.MapLocalizedRoute("CustomerAddresses",
    "customer/addresses",
    new { controller = "Customer", action = "Addresses" },
    new[] { "Nop.Web.Controllers" });
    routes.MapLocalizedRoute("CustomerOrders",
    "customer/orders",
    new { controller = "Customer", action = "Orders" },
    new[] { "Nop.Web.Controllers" });
    routes.MapLocalizedRoute("CustomerReturnRequests",
    "customer/returnrequests",
    new { controller = "Customer", action = "ReturnRequests" },
    new[] { "Nop.Web.Controllers" });
    routes.MapLocalizedRoute("CustomerDownloadableProducts",
    "customer/downloadableproducts",
    new { controller = "Customer", action = "DownloadableProducts" },
    new[] { "Nop.Web.Controllers" });
     
    //省略其它路由注册....
     
    //page not found
    routes.MapLocalizedRoute("PageNotFound",
    "page-not-found",
    new { controller = "Common", action = "PageNotFound" },
    new[] { "Nop.Web.Controllers" });
    }
     
    public int Priority
    {
    get
    {
    return 0;
    }
    }
    }
    }

    3、Nop.Web.Infrastructure.GenericUrlRouteProvider,同样的实现接口IRoutePublisher。这个RouteProvider定义了一些对SEO友好的路由。

    using System.Web.Routing;
    using Nop.Web.Framework.Localization;
    using Nop.Web.Framework.Mvc.Routes;
    using Nop.Web.Framework.Seo;
     
    namespace Nop.Web.Infrastructure
    {
    public partial class GenericUrlRouteProvider : IRouteProvider
    {
    public void RegisterRoutes(RouteCollection routes)
    {
    //generic URLs
    routes.MapGenericPathRoute("GenericUrl",
    "{generic_se_name}",
    new {controller = "Common", action = "GenericUrl"},
    new[] {"Nop.Web.Controllers"});
     
    //define this routes to use in UI views (in case if you want to customize some of them later)
    routes.MapLocalizedRoute("Product",
    "{SeName}",
    new { controller = "Product", action = "ProductDetails" },
    new[] {"Nop.Web.Controllers"});
     
    routes.MapLocalizedRoute("Category",
    "{SeName}",
    new { controller = "Catalog", action = "Category" },
    new[] { "Nop.Web.Controllers" });
     
    routes.MapLocalizedRoute("Manufacturer",
    "{SeName}",
    new { controller = "Catalog", action = "Manufacturer" },
    new[] { "Nop.Web.Controllers" });
     
    routes.MapLocalizedRoute("Vendor",
    "{SeName}",
    new { controller = "Catalog", action = "Vendor" },
    new[] { "Nop.Web.Controllers" });
     
    routes.MapLocalizedRoute("NewsItem",
    "{SeName}",
    new { controller = "News", action = "NewsItem" },
    new[] { "Nop.Web.Controllers" });
     
    routes.MapLocalizedRoute("BlogPost",
    "{SeName}",
    new { controller = "Blog", action = "BlogPost" },
    new[] { "Nop.Web.Controllers" });
     
    routes.MapLocalizedRoute("Topic",
    "{SeName}",
    new { controller = "Topic", action = "TopicDetails" },
    new[] { "Nop.Web.Controllers" });
    }
     
    public int Priority
    {
    get
    {
    //it should be the last route
    //we do not set it to -int.MaxValue so it could be overriden (if required)
    return -1000000;
    }
    }
    }
    }

    4、Nop.Web.Framework.Localization.LocalizedRoute,它采用基类System.Web.Routing.Route,为了实现路由本地化。它提供一些属性和方法为获取到真正的路由做准备。它重写了基类Route两方法,GetRouteData和GetVirtualPath。

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
    if (DataSettingsHelper.DatabaseIsInstalled() && this.SeoFriendlyUrlsForLanguagesEnabled)
    {
    string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
    string applicationPath = httpContext.Request.ApplicationPath;
    if (virtualPath.IsLocalizedUrl(applicationPath, false))
    {
    string rawUrl = httpContext.Request.RawUrl;
    var newVirtualPath = rawUrl.RemoveLanguageSeoCodeFromRawUrl(applicationPath);
    if (string.IsNullOrEmpty(newVirtualPath))
    newVirtualPath = "/";
    newVirtualPath = newVirtualPath.RemoveApplicationPathFromRawUrl(applicationPath);
    newVirtualPath = "~" + newVirtualPath;
    httpContext.RewritePath(newVirtualPath, true);
    }
    }
    RouteData data = base.GetRouteData(httpContext);
    return data;
    }
     
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
    VirtualPathData data = base.GetVirtualPath(requestContext, values);
     
    if (data != null && DataSettingsHelper.DatabaseIsInstalled() && this.SeoFriendlyUrlsForLanguagesEnabled)
    {
    string rawUrl = requestContext.HttpContext.Request.RawUrl;
    string applicationPath = requestContext.HttpContext.Request.ApplicationPath;
    if (rawUrl.IsLocalizedUrl(applicationPath, true))
    {
    data.VirtualPath = string.Concat(rawUrl.GetLanguageSeoCodeFromUrl(applicationPath, true), "/",
    data.VirtualPath);
    }
    }
    return data;
    }

    5、Nop.Web.Framework.Seo.GenericPathRoute,这个类是真正把友好的Url解析到我们在RouteProvider配置好的友好的路由规则。它继承了类Nop.Web.Framework.Localization.LocalizedRoute。

    类GenericPathRoute核心代码如下:

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
    RouteData data = base.GetRouteData(httpContext);
    if (data != null && DataSettingsHelper.DatabaseIsInstalled())
    {
    var urlRecordService = EngineContext.Current.Resolve<IUrlRecordService>();
    var slug = data.Values["generic_se_name"] as string;
    //performance optimization.
    //we load a cached verion here. it reduces number of SQL requests for each page load
    var urlRecord = urlRecordService.GetBySlugCached(slug);//查询url对应的路由规则
    //comment the line above and uncomment the line below in order to disable this performance "workaround"
    //var urlRecord = urlRecordService.GetBySlug(slug);
    if (urlRecord == null)
    {
    data.Values["controller"] = "Common";
    data.Values["action"] = "PageNotFound";
    return data;
    }
    //ensre that URL record is active
    if (!urlRecord.IsActive)
    {
    //URL record is not active. let's find the latest one
    var activeSlug = urlRecordService.GetActiveSlug(urlRecord.EntityId, urlRecord.EntityName, urlRecord.LanguageId);
    if (!string.IsNullOrWhiteSpace(activeSlug))
    {
    //the active one is found
    var webHelper = EngineContext.Current.Resolve<IWebHelper>();
    var response = httpContext.Response;
    response.Status = "301 Moved Permanently";
    response.RedirectLocation = string.Format("{0}{1}", webHelper.GetStoreLocation(false), activeSlug);
    response.End();
    return null;
    }
    else
    {
    data.Values["controller"] = "Common";
    data.Values["action"] = "PageNotFound";
    return data;
    }
    }
     
    //ensure that the slug is the same for the current language
    //otherwise, it can cause some issues when customers choose a new language but a slug stays the same
    var workContext = EngineContext.Current.Resolve<IWorkContext>();
    var slugForCurrentLanguage = SeoExtensions.GetSeName(urlRecord.EntityId, urlRecord.EntityName, workContext.WorkingLanguage.Id);
    if (!String.IsNullOrEmpty(slugForCurrentLanguage) &&
    !slugForCurrentLanguage.Equals(slug, StringComparison.InvariantCultureIgnoreCase))
    {
    //we should make not null or "" validation above because some entities does not have SeName for standard (ID=0) language (e.g. news, blog posts)
    var webHelper = EngineContext.Current.Resolve<IWebHelper>();
    var response = httpContext.Response;
    //response.Status = "302 Found";
    response.Status = "302 Moved Temporarily";
    response.RedirectLocation = string.Format("{0}{1}", webHelper.GetStoreLocation(false), slugForCurrentLanguage);
    response.End();
    return null;
    }
     
    //处理URL并动态赋值真正的Controller的相关信息
    switch (urlRecord.EntityName.ToLowerInvariant())
    {
    case "product":
    {
    data.Values["controller"] = "Product";
    data.Values["action"] = "ProductDetails";
    data.Values["productid"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    case "category":
    {
    data.Values["controller"] = "Catalog";
    data.Values["action"] = "Category";
    data.Values["categoryid"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    case "manufacturer":
    {
    data.Values["controller"] = "Catalog";
    data.Values["action"] = "Manufacturer";
    data.Values["manufacturerid"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    case "vendor":
    {
    data.Values["controller"] = "Catalog";
    data.Values["action"] = "Vendor";
    data.Values["vendorid"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    case "newsitem":
    {
    data.Values["controller"] = "News";
    data.Values["action"] = "NewsItem";
    data.Values["newsItemId"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    case "blogpost":
    {
    data.Values["controller"] = "Blog";
    data.Values["action"] = "BlogPost";
    data.Values["blogPostId"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    case "topic":
    {
    data.Values["controller"] = "Topic";
    data.Values["action"] = "TopicDetails";
    data.Values["topicId"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    default:
    {
    //no record found
     
    //generate an event this way developers could insert their own types
    EngineContext.Current.Resolve<IEventPublisher>()
    .Publish(new CustomUrlRecordEntityNameRequested(data, urlRecord));
    }
    break;
    }
    }
    return data;
    }

    可以看到上面通过获取路由中变量generic_se_name的值,然后通过这个值查询这个url对应的路由规则。Nop把这个对应信息存在表UrlRecord里面,如下图:

    2020-04-23-18-04-01

    比如,我们在前台访问:http://localhost:15536/books,其实generic_se_name的值就为books,然后会找到字段Slug的值为books的记录。接着进行处理Url的Switch语句:

    switch (urlRecord.EntityName.ToLowerInvariant())
    {
    //....省略其它代码
    case "category":
    {
    data.Values["controller"] = "Catalog";
    data.Values["action"] = "Category";
    data.Values["categoryid"] = urlRecord.EntityId;
    data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    //....省略其它代码
    }

    可以看到请求url:http://localhost:15536/books,真正执行的是Catalog中的Category方法。

    6、RoutePublisher,实现接口IRoutePublisher,通过typeFinder.FindClassesOfType查找项目中所有实现了接口IRouteProvider的类,并依次注册其里面的路由。

    public virtual void RegisterRoutes(RouteCollection routes)
    {
    var routeProviderTypes = typeFinder.FindClassesOfType<IRouteProvider>();
    var routeProviders = new List<IRouteProvider>();
    foreach (var providerType in routeProviderTypes)
    {
    //Ignore not installed plugins
    var plugin = FindPlugin(providerType);
    if (plugin != null && !plugin.Installed)
    continue;
     
    var provider = Activator.CreateInstance(providerType) as IRouteProvider;
    routeProviders.Add(provider);
    }
    routeProviders = routeProviders.OrderByDescending(rp => rp.Priority).ToList();
    routeProviders.ForEach(rp => rp.RegisterRoutes(routes));
    }

    在程序启动的时候就会注册路由,依赖注入在Nop.Web.Framework.DependencyRegistrar类中有下面的代码把接口IRoutePublisher用类RoutePublisher来注册:

    builder.RegisterType<RoutePublisher>().As<IRoutePublisher>().SingleInstance();

    最后在类MvcApplication中的会调用routePublisher注册所有路由规则到MVC框架中:

     
    //register custom routes (plugins, etc)
    var routePublisher = EngineContext.Current.Resolve<IRoutePublisher>();
    routePublisher.RegisterRoutes(routes);
  • 相关阅读:
    Linux基础命令---arch
    JSON漫谈
    django中外键关联表的查询随笔
    <django中render_to_response的可选参数和使用方法>
    有趣的Redis:缓存被我写满了,该怎么办?
    2020全球C++及系统软件技术大会成功落下帷幕
    AWS 宣布创建 Elasticsearch 和 Kibana 分支
    Flutter开发指南之理论篇:Dart语法05(单线程模型,事件循环模型,Isolate)
    自定义注解!绝对是程序员装逼的利器!!
    Java8 Stream
  • 原文地址:https://www.cnblogs.com/Alex80/p/16092057.html
Copyright © 2020-2023  润新知