• 学习Nop中Routes的使用


    1. 映射路由

    大型MVC项目为了扩展性,可维护性不能向一般项目在Global中RegisterRoutes的方法里面映射路由。这里学习一下Nop是如何做的。

    Global.cs  . 通过IOC容器取得IRoutePublisher实例

    public static void RegisterRoutes(RouteCollection routes)
            {
                routes.IgnoreRoute("favicon.ico");
                routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
                
                //register custom routes (plugins, etc)
                var routePublisher = EngineContext.Current.Resolve<IRoutePublisher>();
                routePublisher.RegisterRoutes(routes);
                
                routes.MapRoute(
                    "Default", // Route name
                    "{controller}/{action}/{id}", // URL with parameters
                    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                    new[] { "Nop.Web.Controllers" }
                );
            }

    IRoutePublisher.cs 只有一个方法

    public interface IRoutePublisher
        {
            void RegisterRoutes(RouteCollection routeCollection);
        }

    实现类

    public class RoutePublisher : IRoutePublisher
        {
            private readonly ITypeFinder _typeFinder;
    
            public RoutePublisher(ITypeFinder typeFinder)
            {
                this._typeFinder = typeFinder;
            }
    
            public void RegisterRoutes(RouteCollection routes)
            {
                var routeProviderTypes = _typeFinder.FindClassesOfType<IRouteProvider>();
                var routeProviders = new List<IRouteProvider>();
                foreach (var providerType in routeProviderTypes)
                {
                    var provider = Activator.CreateInstance(providerType) as IRouteProvider;
                    routeProviders.Add(provider);
                }
                routeProviders = routeProviders.OrderByDescending(rp => rp.Priority).ToList();
                routeProviders.ForEach(rp => rp.RegisterRoutes(routes));
            }
        }

    ITypeFinder之前已经介绍过。点击查看 ,搜索项目中所有的IRouteProvider实现。然后根据优先级排序,最后调用RegisterRoutes依次映射

    image

    竟然有这么多,大多都在Plugin程序集中为插件映射路由。随便打开一个。

    public partial class RouteProvider : IRouteProvider
        {
            public void RegisterRoutes(RouteCollection routes)
            {
                routes.MapRoute("Plugin.Payments.AuthorizeNet.Configure",
                     "Plugins/PaymentAuthorizeNet/Configure",
                     new { controller = "PaymentAuthorizeNet", action = "Configure" },
                     new[] { "Nop.Plugin.Payments.AuthorizeNet.Controllers" }
                );
    
                routes.MapRoute("Plugin.Payments.AuthorizeNet.PaymentInfo",
                     "Plugins/PaymentAuthorizeNet/PaymentInfo",
                     new { controller = "PaymentAuthorizeNet", action = "PaymentInfo" },
                     new[] { "Nop.Plugin.Payments.AuthorizeNet.Controllers" }
                );
            }
            public int Priority
            {
                get
                {
                    return 0;
                }
            }
        }

    好处不用说了。模块化方便复用。

    2.自定义Route

    LocalizedRoute.cs

    using System.Web;
    using System.Web.Routing;
    using Nop.Core.Data;
    using Nop.Core.Domain.Localization;
    using Nop.Core.Infrastructure;
    
    namespace Nop.Web.Framework.Localization
    {
        /// <summary>
        /// Provides properties and methods for defining a localized route, and for getting information about the localized route.
        /// </summary>
        public class LocalizedRoute : Route
        {
            #region Fields
    
            private bool? _seoFriendlyUrlsForLanguagesEnabled;
    
            #endregion
    
            #region Constructors
    
            /// <summary>
            /// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern and handler class.
            /// </summary>
            /// <param name="url">The URL pattern for the route.</param>
            /// <param name="routeHandler">The object that processes requests for the route.</param>
            public LocalizedRoute(string url, IRouteHandler routeHandler)
                : base(url, routeHandler)
            {
            }
    
            /// <summary>
            /// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern, handler class and default parameter values.
            /// </summary>
            /// <param name="url">The URL pattern for the route.</param>
            /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
            /// <param name="routeHandler">The object that processes requests for the route.</param>
            public LocalizedRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
                : base(url, defaults, routeHandler)
            {
            }
    
            /// <summary>
            /// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern, handler class, default parameter values and constraints.
            /// </summary>
            /// <param name="url">The URL pattern for the route.</param>
            /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
            /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
            /// <param name="routeHandler">The object that processes requests for the route.</param>
            public LocalizedRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
                : base(url, defaults, constraints, routeHandler)
            {
            }
    
            /// <summary>
            /// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern, handler class, default parameter values, 
            /// constraints,and custom values.
            /// </summary>
            /// <param name="url">The URL pattern for the route.</param>
            /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
            /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
            /// <param name="dataTokens">Custom values that are passed to the route handler, but which are not used to determine whether the route matches a specific URL pattern. The route handler might need these values to process the request.</param>
            /// <param name="routeHandler">The object that processes requests for the route.</param>
            public LocalizedRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
                : base(url, defaults, constraints, dataTokens, routeHandler)
            {
            }
    
            #endregion
    
            #region Methods
    
            /// <summary>
            /// Returns information about the requested route.
            /// </summary>
            /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
            /// <returns>
            /// An object that contains the values from the route definition.
            /// </returns>
            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))
                    {
                        //In ASP.NET Development Server, an URL like "http://localhost/Blog.aspx/Categories/BabyFrog" will return 
                        //"~/Blog.aspx/Categories/BabyFrog" as AppRelativeCurrentExecutionFilePath.
                        //However, in II6, the AppRelativeCurrentExecutionFilePath is "~/Blog.aspx"
                        //It seems that IIS6 think we're process Blog.aspx page.
                        //So, I'll use RawUrl to re-create an AppRelativeCurrentExecutionFilePath like ASP.NET Development Server.
    
                        //Question: should we do path rewriting right here?
                        string rawUrl = httpContext.Request.RawUrl;
                        var newVirtualPath = rawUrl.RemoveLocalizedPathFromRawUrl(applicationPath);
                        if (string.IsNullOrEmpty(newVirtualPath))
                            newVirtualPath = "/";
                        newVirtualPath = newVirtualPath.RemoveApplicationPathFromRawUrl(applicationPath);
                        newVirtualPath = "~" + newVirtualPath;
                        httpContext.RewritePath(newVirtualPath, true);
                    }
                }
                RouteData data = base.GetRouteData(httpContext);
                return data;
            }
    
            /// <summary>
            /// Returns information about the URL that is associated with the route.
            /// </summary>
            /// <param name="requestContext">An object that encapsulates information about the requested route.</param>
            /// <param name="values">An object that contains the parameters for a route.</param>
            /// <returns>
            /// An object that contains information about the URL that is associated with the route.
            /// </returns>
            public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
            {
                VirtualPathData data = base.GetVirtualPath(requestContext, values);
                
                if (DataSettingsHelper.DatabaseIsInstalled() && this.SeoFriendlyUrlsForLanguagesEnabled)
                {
                    if (data != null)
                    {
                        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;
            }
    
            public virtual void ClearSeoFriendlyUrlsCachedValue()
            {
                _seoFriendlyUrlsForLanguagesEnabled = null;
            }
    
            #endregion
    
            #region Properties
    
            protected bool SeoFriendlyUrlsForLanguagesEnabled
            {
                get
                {
                    if (!_seoFriendlyUrlsForLanguagesEnabled.HasValue)
                        _seoFriendlyUrlsForLanguagesEnabled = EngineContext.Current.Resolve<LocalizationSettings>().SeoFriendlyUrlsForLanguagesEnabled;
    
                    return _seoFriendlyUrlsForLanguagesEnabled.Value;
                }
            }
    
            #endregion
        }
    }

    继承Route并且实现GetRouteData, GetVirtualPath这2个方法。从返回的参数可以知道一个是解析URL,一个是生成URL。是一个双向的过程。

    注意 if (DataSettingsHelper.DatabaseIsInstalled() && this.SeoFriendlyUrlsForLanguagesEnabled)  这个判断条件,启用了地区化友好SEO。才进行URL转换。

    简单说就是 http://localhost:2619/en  《=》http://localhost:2619/  的双向转换。 en,ch之类的就是网站设置的语言了。

    2。MVC路由测试

    先看单元测试

    映射路由

    new Nop.Web.Infrastructure.RouteProvider().RegisterRoutes(RouteTable.Routes);
    [Test]
            public void Boards_routes()
            {
                "~/boards/".ShouldMapTo<BoardsController>(c => c.Index());
                //TODO add support for optional parameters in 'ShouldMapTo' method (such as in ~/boards/activediscussions/ or ~/boards/topic/11/). The same is about issue is in the other route test methods
                //"~/boards/activediscussions/".ShouldMapTo<BoardsController>(c => c.ActiveDiscussions(0));
                //"~/boards/activediscussionsrss/".ShouldMapTo<BoardsController>(c => c.ActiveDiscussionsRss(0));
                "~/boards/postedit/1".ShouldMapTo<BoardsController>(c => c.PostEdit(1));
                "~/boards/postdelete/2".ShouldMapTo<BoardsController>(c => c.PostDelete(2));
                "~/boards/postcreate/3".ShouldMapTo<BoardsController>(c => c.PostCreate(3, null));
                "~/boards/postcreate/4/5".ShouldMapTo<BoardsController>(c => c.PostCreate(4, 5));
                "~/boards/topicedit/6".ShouldMapTo<BoardsController>(c => c.TopicEdit(6));
                "~/boards/topicdelete/7".ShouldMapTo<BoardsController>(c => c.TopicDelete(7));
                "~/boards/topiccreate/8".ShouldMapTo<BoardsController>(c => c.TopicCreate(8));
                "~/boards/topicmove/9".ShouldMapTo<BoardsController>(c => c.TopicMove(9));
                "~/boards/topicwatch/10".ShouldMapTo<BoardsController>(c => c.TopicWatch(10));
                //"~/boards/topic/11/".ShouldMapTo<BoardsController>(c => c.Topic(11, 1));
                //"~/boards/topic/11/test-topic-slug".ShouldMapTo<BoardsController>(c => c.Topic(11, 1));
                "~/boards/topic/11/test-topic-slug/page/2".ShouldMapTo<BoardsController>(c => c.Topic(11, 2));
                "~/boards/forumwatch/12".ShouldMapTo<BoardsController>(c => c.ForumWatch(12));
                "~/boards/forumrss/13".ShouldMapTo<BoardsController>(c => c.ForumRss(13));
                //"~/boards/forum/14/".ShouldMapTo<BoardsController>(c => c.Forum(14, 1));
                //"~/boards/forum/14/test-forum-slug".ShouldMapTo<BoardsController>(c => c.Forum(14, 1));
                "~/boards/forum/14/test-forum-slug/page/2".ShouldMapTo<BoardsController>(c => c.Forum(14, 2));
                "~/boards/forumgroup/15/".ShouldMapTo<BoardsController>(c => c.ForumGroup(15));
                "~/boards/forumgroup/15/test-forumgroup-slug/".ShouldMapTo<BoardsController>(c => c.ForumGroup(15));
                //"~/boards/search/".ShouldMapTo<BoardsController>(c => c.Search(null, null, null, null, null, 1));
            }

    看来这里的代码。膜拜作者吧。。之前一直不知道如何测试Route,最多是装个Routedebug什么的

    接下来进行解析匹配

    /// <summary>
            /// Asserts that the route matches the expression specified.  Checks controller, action, and any method arguments
            /// into the action as route values.
            /// </summary>
            /// <typeparam name="TController">The controller.</typeparam>
            /// <param name="routeData">The routeData to check</param>
            /// <param name="action">The action to call on TController.</param>
            public static RouteData ShouldMapTo<TController>(this RouteData routeData, Expression<Func<TController, ActionResult>> action)
                where TController : Controller
            {
                routeData.ShouldNotBeNull("The URL did not match any route");
    
                //check controller
                routeData.ShouldMapTo<TController>();
    
                //check action
                var methodCall = (MethodCallExpression)action.Body;
                string actualAction = routeData.Values.GetValue("action").ToString();
    
                string expectedAction = methodCall.Method.ActionName();
                actualAction.AssertSameStringAs(expectedAction);
    
                //check parameters
                for (int i = 0; i < methodCall.Arguments.Count; i++)
                {
                    ParameterInfo param = methodCall.Method.GetParameters()[i];
                    bool isReferenceType = !param.ParameterType.IsValueType;
                    bool isNullable = isReferenceType ||
                        (param.ParameterType.UnderlyingSystemType.IsGenericType && param.ParameterType.UnderlyingSystemType.GetGenericTypeDefinition() == typeof(Nullable<>));
    
                    string controllerParameterName = param.Name;
                    bool routeDataContainsValueForParameterName = routeData.Values.ContainsKey(controllerParameterName);
                    object actualValue = routeData.Values.GetValue(controllerParameterName);
                    object expectedValue = null;
                    Expression expressionToEvaluate = methodCall.Arguments[i];
    
                    // If the parameter is nullable and the expression is a Convert UnaryExpression, 
                    // we actually want to test against the value of the expression's operand.
                    if (expressionToEvaluate.NodeType == ExpressionType.Convert
                        && expressionToEvaluate is UnaryExpression)
                    {
                        expressionToEvaluate = ((UnaryExpression)expressionToEvaluate).Operand;
                    }
    
                    switch (expressionToEvaluate.NodeType)
                    {
                        case ExpressionType.Constant:
                            expectedValue = ((ConstantExpression)expressionToEvaluate).Value;
                            break;
    
                        case ExpressionType.New:
                        case ExpressionType.MemberAccess:
                            expectedValue = Expression.Lambda(expressionToEvaluate).Compile().DynamicInvoke();
                            break;
                    }
    
                    if (isNullable && (string)actualValue == String.Empty && expectedValue == null)
                    {
                        // The parameter is nullable so an expected value of '' is equivalent to null;
                        continue;
                    }
    
                    // HACK: this is only sufficient while System.Web.Mvc.UrlParameter has only a single value.
                    if (actualValue == UrlParameter.Optional ||
                        (actualValue != null && actualValue.ToString().Equals("System.Web.Mvc.UrlParameter")))
                    {
                        actualValue = null;
                    }
    
                    if (expectedValue is DateTime)
                    {
                        actualValue = Convert.ToDateTime(actualValue);
                    }
                    else
                    {
                        expectedValue = (expectedValue == null ? expectedValue : expectedValue.ToString());
                    }
    
                    string errorMsgFmt = "Value for parameter '{0}' did not match: expected '{1}' but was '{2}'";
                    if (routeDataContainsValueForParameterName)
                    {
                        errorMsgFmt += ".";
                    }
                    else
                    {
                        errorMsgFmt += "; no value found in the route context action parameter named '{0}' - does your matching route contain a token called '{0}'?";
                    }
                    actualValue.ShouldEqual(expectedValue, String.Format(errorMsgFmt, controllerParameterName, expectedValue, actualValue));
                }
    
                return routeData;
            }

    这里又看到了表达式树强大的地方。关于表达式树具体使用请搜索博客园,  这里通过routeData和解析action来判断请求和action是否一致。

    总结:通过学习Nop中路由的一些使用。掌握了一些很有用的Route的使用。这些模块今后也能根据需要加入到自己的网站中。

    参考:

    两篇关于自定义路由:http://www.cnblogs.com/john-connor/archive/2012/05/03/2478821.html 

    http://www.cnblogs.com/ldp615/archive/2011/12/05/asp-net-mvc-elegant-route.html

  • 相关阅读:
    Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1288372549423476738' for key 'PRIMARY'
    环形数组循环
    less命令
    ln命令
    Vue中$refs的理解
    cut命令
    除数博弈
    find命令
    file命令
    最长公共前缀
  • 原文地址:https://www.cnblogs.com/miku/p/2706276.html
Copyright © 2020-2023  润新知