using System;
using System.Web;
using System.Web.Routing;
using Nop.Core;
using Nop.Core.Data;
using Nop.Core.Infrastructure;
using Nop.Services.Events;
using Nop.Services.Seo;
using Nop.Web.Framework.Localization;
namespace Nop.Web.Framework.Seo
{
/// <summary>
/// Provides properties and methods for defining a SEO friendly route, and for getting information about the route.
/// </summary>
public partial class GenericPathRoute : LocalizedRoute
{
#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 GenericPathRoute(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 GenericPathRoute(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 GenericPathRoute(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 GenericPathRoute(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)
{
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);
//comment the line above and uncomment the line below in order to disable this performance "workaround"
//var urlRecord = urlRecordService.GetBySlug(slug);
if (urlRecord == null)
{
//no URL record found
//var webHelper = EngineContext.Current.Resolve<IWebHelper>();
//var response = httpContext.Response;
//response.Status = "302 Found";
//response.RedirectLocation = webHelper.GetStoreLocation(false);
//response.End();
//return 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))
{
//no active slug found
//var webHelper = EngineContext.Current.Resolve<IWebHelper>();
//var response = httpContext.Response;
//response.Status = "302 Found";
//response.RedirectLocation = webHelper.GetStoreLocation(false);
//response.End();
//return null;
data.Values["controller"] = "Common";
data.Values["action"] = "PageNotFound";
return data;
}
//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;
}
//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;
}
//process URL
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;
}
#endregion
}
}
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.RemoveLanguageSeoCodeFromRawUrl(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 (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; } 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 } }
using System.Runtime.CompilerServices; namespace System.Web.Routing { // // 摘要: // 提供用于定义路由及获取路由相关信息的属性和方法。 [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public class Route : RouteBase { // // 摘要: // 使用指定的 URL 模式和处理程序类初始化 System.Web.Routing.Route 类的新实例。 // // 参数: // url: // 路由的 URL 模式。 // // routeHandler: // 处理路由请求的对象。 public Route(string url, IRouteHandler routeHandler); // // 摘要: // 使用指定的 URL 模式、默认参数值和处理程序类初始化 System.Web.Routing.Route 类的新实例。 // // 参数: // url: // 路由的 URL 模式。 // // defaults: // 用于 URL 中缺失的任何参数的值。 // // routeHandler: // 处理路由请求的对象。 public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler); // // 摘要: // 使用指定的 URL 模式、默认参数值、约束和处理程序类初始化 System.Web.Routing.Route 类的新实例。 // // 参数: // url: // 路由的 URL 模式。 // // defaults: // 要在 URL 不包含所有参数时使用的值。 // // constraints: // 一个用于指定 URL 参数的有效值的正则表达式。 // // routeHandler: // 处理路由请求的对象。 public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler); // // 摘要: // 使用指定的 URL 模式、默认参数值、约束、自定义值和处理程序类初始化 System.Web.Routing.Route 类的新实例。 // // 参数: // url: // 路由的 URL 模式。 // // defaults: // 要在 URL 不包含所有参数时使用的值。 // // constraints: // 一个用于指定 URL 参数的有效值的正则表达式。 // // dataTokens: // 传递到路由处理程序但未用于确定该路由是否匹配特定 URL 模式的自定义值。 这些值会传递到路由处理程序,以便用于处理请求。 // // routeHandler: // 处理路由请求的对象。 public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler); // // 摘要: // 获取或设置为 URL 参数指定有效值的表达式的词典。 // // 返回结果: // 一个包含参数名称和表达式的对象。 public RouteValueDictionary Constraints { get; set; } // // 摘要: // 获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。 // // 返回结果: // 一个包含自定义值的对象。 public RouteValueDictionary DataTokens { get; set; } // // 摘要: // 获取或设置要在 URL 不包含所有参数时使用的值。 // // 返回结果: // 一个包含参数名称和默认值的对象。 public RouteValueDictionary Defaults { get; set; } // // 摘要: // 获取或设置处理路由请求的对象。 // // 返回结果: // 处理请求的对象。 public IRouteHandler RouteHandler { get; set; } // // 摘要: // 获取或设置路由的 URL 模式。 // // 返回结果: // 用于匹配路由和 URL 的模式。 // // 异常: // T:System.ArgumentException: // 以下任一值: 以 ~ 或 / 开头的值。 包含 ? 字符的值。 “全部捕捉”参数不在末尾。 // // T:System.Exception: // 没有使用分隔符或文字常量分隔 URL 分段。 public string Url { get; set; } // // 摘要: // 返回有关所请求路由的信息。 // // 参数: // httpContext: // 一个对象,封装有关 HTTP 请求的信息。 // // 返回结果: // 一个对象,其中包含路由定义中的值。 public override RouteData GetRouteData(HttpContextBase httpContext); // // 摘要: // 返回与路由关联的 URL 的相关信息。 // // 参数: // requestContext: // 一个对象,封装有关所请求的路由的信息。 // // values: // 一个包含路由参数的对象。 // // 返回结果: // 一个包含与路由关联的 URL 的相关信息的对象。 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); // // 摘要: // 确定参数值是否与该参数的约束匹配。 // // 参数: // httpContext: // 一个对象,封装有关 HTTP 请求的信息。 // // constraint: // 用于测试 parameterName 的正则表达式或对象。 // // parameterName: // 要测试的参数的名称。 // // values: // 要测试的值。 // // routeDirection: // 一个指定 URL 路由是否处理传入请求或构造 URL 的值。 // // 返回结果: // 如果参数值与约束匹配,则为 true;否则为 false。 // // 异常: // T:System.InvalidOperationException: // constraint 不是包含正则表达式的字符串。 protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection); } }
using System; using System.Web.Mvc; using System.Web.Routing; namespace Nop.Web.Framework.Seo { public static class GenericPathRouteExtensions { //Override for localized route public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url) { return MapGenericPathRoute(routes, name, url, null /* defaults */, (object)null /* constraints */); } public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults) { return MapGenericPathRoute(routes, name, url, defaults, (object)null /* constraints */); } public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints) { return MapGenericPathRoute(routes, name, url, defaults, constraints, null /* namespaces */); } public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, string[] namespaces) { return MapGenericPathRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces); } public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces) { return MapGenericPathRoute(routes, name, url, defaults, null /* constraints */, namespaces); } public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) { if (routes == null) { throw new ArgumentNullException("routes"); } if (url == null) { throw new ArgumentNullException("url"); } var route = new GenericPathRoute(url, new MvcRouteHandler()) { Defaults = new RouteValueDictionary(defaults), Constraints = new RouteValueDictionary(constraints), DataTokens = new RouteValueDictionary() }; if ((namespaces != null) && (namespaces.Length > 0)) { route.DataTokens["Namespaces"] = namespaces; } routes.Add(name, route); return route; } } }