前言
运营部门一直对公司官网SEO有意见,认为做得不好(说得好像运营做不好都是seo似的)。为此两部门老大还闹到CEO那去了。
也因为这事,工作计划终于排上日程。沟通一番后得知有如下几点需求:
1.所有链接都得是绝对地址。比如之前是/about.html,现在得变成http://xxx.com/about.html(你妹啊,这样我本地怎么测试)
2.所有目录最后都加/,没有的定久重定向到加/的。比如之前/xxx ,现在得变成http://xxx.com/xxx/
3.图片必须加alt ,width height 属性
分析编码
前些天图片问题已经改完了,现在重点是链接。因为项目链接大多走的Url.Action,所以自然的想到得从这入手。
其实微软已经为我们提供了这个实现,即 @Url.Action("actionName","controllerName","routeValues","protocol","hostName")
全解决方案搜索Url.Action,一千多处。想到前面优化img标签加alt,才五百多处就花了一天半时间,这肯定是不能接受的。
mvc 不是开源了吗,把源码down下来看看,https://git01.codeplex.com/aspnetwebstack
分析源码定位到两个核心的类 UrlHelper WebViewPage
1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Diagnostics.CodeAnalysis; 4 using System.Globalization; 5 using System.Web.Mvc.Properties; 6 using System.Web.Routing; 7 using System.Web.WebPages; 8 9 namespace System.Web.Mvc 10 { 11 public class UrlHelper 12 { 13 /// <summary> 14 /// Key used to signify that a route URL generation request should include HTTP routes (e.g. Web API). 15 /// If this key is not specified then no HTTP routes will match. 16 /// </summary> 17 private const string HttpRouteKey = "httproute"; 18 19 /// <summary> 20 /// Initializes a new instance of the <see cref="UrlHelper"/> class. 21 /// </summary> 22 /// <remarks>The default constructor is intended for use by unit testing only.</remarks> 23 public UrlHelper() 24 { 25 } 26 27 public UrlHelper(RequestContext requestContext) 28 : this(requestContext, RouteTable.Routes) 29 { 30 } 31 32 public UrlHelper(RequestContext requestContext, RouteCollection routeCollection) 33 { 34 if (requestContext == null) 35 { 36 throw new ArgumentNullException("requestContext"); 37 } 38 if (routeCollection == null) 39 { 40 throw new ArgumentNullException("routeCollection"); 41 } 42 RequestContext = requestContext; 43 RouteCollection = routeCollection; 44 } 45 46 public RequestContext RequestContext { get; private set; } 47 48 public RouteCollection RouteCollection { get; private set; } 49 50 public virtual string Action() 51 { 52 return RequestContext.HttpContext.Request.RawUrl; 53 } 54 55 public virtual string Action(string actionName) 56 { 57 return GenerateUrl(null /* routeName */, actionName, null, (RouteValueDictionary)null /* routeValues */); 58 } 59 60 public virtual string Action(string actionName, object routeValues) 61 { 62 return GenerateUrl(null /* routeName */, actionName, null /* controllerName */, TypeHelper.ObjectToDictionary(routeValues)); 63 } 64 65 public virtual string Action(string actionName, RouteValueDictionary routeValues) 66 { 67 return GenerateUrl(null /* routeName */, actionName, null /* controllerName */, routeValues); 68 } 69 70 public virtual string Action(string actionName, string controllerName) 71 { 72 return GenerateUrl(null /* routeName */, actionName, controllerName, (RouteValueDictionary)null /* routeValues */); 73 } 74 75 public virtual string Action(string actionName, string controllerName, object routeValues) 76 { 77 return GenerateUrl(null /* routeName */, actionName, controllerName, TypeHelper.ObjectToDictionary(routeValues)); 78 } 79 80 public virtual string Action(string actionName, string controllerName, RouteValueDictionary routeValues) 81 { 82 return GenerateUrl(null /* routeName */, actionName, controllerName, routeValues); 83 } 84 85 public virtual string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol) 86 { 87 return GenerateUrl(null /* routeName */, actionName, controllerName, protocol, null /* hostName */, null /* fragment */, routeValues, RouteCollection, RequestContext, true /* includeImplicitMvcValues */); 88 } 89 90 public virtual string Action(string actionName, string controllerName, object routeValues, string protocol) 91 { 92 return GenerateUrl(null /* routeName */, actionName, controllerName, protocol, null /* hostName */, null /* fragment */, TypeHelper.ObjectToDictionary(routeValues), RouteCollection, RequestContext, true /* includeImplicitMvcValues */); 93 } 94 95 public virtual string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName) 96 { 97 return GenerateUrl(null /* routeName */, actionName, controllerName, protocol, hostName, null /* fragment */, routeValues, RouteCollection, RequestContext, true /* includeImplicitMvcValues */); 98 } 99 100 public virtual string Content(string contentPath) 101 { 102 return GenerateContentUrl(contentPath, RequestContext.HttpContext); 103 } 104 105 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 106 public static string GenerateContentUrl(string contentPath, HttpContextBase httpContext) 107 { 108 if (String.IsNullOrEmpty(contentPath)) 109 { 110 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentPath"); 111 } 112 113 if (httpContext == null) 114 { 115 throw new ArgumentNullException("httpContext"); 116 } 117 118 if (contentPath[0] == '~') 119 { 120 return UrlUtil.GenerateClientUrl(httpContext, contentPath); 121 } 122 else 123 { 124 return contentPath; 125 } 126 } 127 128 //REVIEW: Should we have an overload that takes Uri? 129 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Needs to take same parameters as HttpUtility.UrlEncode()")] 130 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")] 131 public virtual string Encode(string url) 132 { 133 return HttpUtility.UrlEncode(url); 134 } 135 136 private string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues) 137 { 138 return GenerateUrl(routeName, actionName, controllerName, routeValues, RouteCollection, RequestContext, true /* includeImplicitMvcValues */); 139 } 140 141 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 142 public static string GenerateUrl(string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) 143 { 144 string url = GenerateUrl(routeName, actionName, controllerName, routeValues, routeCollection, requestContext, includeImplicitMvcValues); 145 146 if (url != null) 147 { 148 if (!String.IsNullOrEmpty(fragment)) 149 { 150 url = url + "#" + fragment; 151 } 152 153 if (!String.IsNullOrEmpty(protocol) || !String.IsNullOrEmpty(hostName)) 154 { 155 Uri requestUrl = requestContext.HttpContext.Request.Url; 156 protocol = (!String.IsNullOrEmpty(protocol)) ? protocol : Uri.UriSchemeHttp; 157 hostName = (!String.IsNullOrEmpty(hostName)) ? hostName : requestUrl.Host; 158 159 string port = String.Empty; 160 string requestProtocol = requestUrl.Scheme; 161 162 if (String.Equals(protocol, requestProtocol, StringComparison.OrdinalIgnoreCase)) 163 { 164 port = requestUrl.IsDefaultPort ? String.Empty : (":" + Convert.ToString(requestUrl.Port, CultureInfo.InvariantCulture)); 165 } 166 167 url = protocol + Uri.SchemeDelimiter + hostName + port + url; 168 } 169 } 170 171 return url; 172 } 173 174 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 175 public static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) 176 { 177 if (routeCollection == null) 178 { 179 throw new ArgumentNullException("routeCollection"); 180 } 181 182 if (requestContext == null) 183 { 184 throw new ArgumentNullException("requestContext"); 185 } 186 187 RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues); 188 189 VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues); 190 if (vpd == null) 191 { 192 return null; 193 } 194 195 string modifiedUrl = UrlUtil.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath); 196 return modifiedUrl; 197 } 198 199 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")] 200 public virtual bool IsLocalUrl(string url) 201 { 202 // TODO this should call the System.Web.dll API once it gets added to the framework and MVC takes a dependency on it. 203 return RequestExtensions.IsUrlLocalToHost(RequestContext.HttpContext.Request, url); 204 } 205 206 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 207 public virtual string RouteUrl(object routeValues) 208 { 209 return RouteUrl(null /* routeName */, routeValues); 210 } 211 212 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 213 public virtual string RouteUrl(RouteValueDictionary routeValues) 214 { 215 return RouteUrl(null /* routeName */, routeValues); 216 } 217 218 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 219 public virtual string RouteUrl(string routeName) 220 { 221 return RouteUrl(routeName, (object)null /* routeValues */); 222 } 223 224 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 225 public virtual string RouteUrl(string routeName, object routeValues) 226 { 227 return RouteUrl(routeName, routeValues, null /* protocol */); 228 } 229 230 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 231 public virtual string RouteUrl(string routeName, RouteValueDictionary routeValues) 232 { 233 return RouteUrl(routeName, routeValues, null /* protocol */, null /* hostName */); 234 } 235 236 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 237 public virtual string RouteUrl(string routeName, object routeValues, string protocol) 238 { 239 return GenerateUrl(routeName, null /* actionName */, null /* controllerName */, protocol, null /* hostName */, null /* fragment */, TypeHelper.ObjectToDictionary(routeValues), RouteCollection, RequestContext, false /* includeImplicitMvcValues */); 240 } 241 242 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 243 public virtual string RouteUrl(string routeName, RouteValueDictionary routeValues, string protocol, string hostName) 244 { 245 return GenerateUrl(routeName, null /* actionName */, null /* controllerName */, protocol, hostName, null /* fragment */, routeValues, RouteCollection, RequestContext, false /* includeImplicitMvcValues */); 246 } 247 248 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 249 public virtual string HttpRouteUrl(string routeName, object routeValues) 250 { 251 return HttpRouteUrl(routeName, TypeHelper.ObjectToDictionary(routeValues)); 252 } 253 254 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")] 255 public virtual string HttpRouteUrl(string routeName, RouteValueDictionary routeValues) 256 { 257 if (routeValues == null) 258 { 259 // If no route values were passed in at all we have to create a new dictionary 260 // so that we can add the extra "httproute" key. 261 routeValues = new RouteValueDictionary(); 262 routeValues.Add(HttpRouteKey, true); 263 } 264 else 265 { 266 // Copy the dictionary to add the extra "httproute" key used by all Web API routes to 267 // disambiguate them from other MVC routes. 268 routeValues = new RouteValueDictionary(routeValues); 269 if (!routeValues.ContainsKey(HttpRouteKey)) 270 { 271 routeValues.Add(HttpRouteKey, true); 272 } 273 } 274 275 return GenerateUrl(routeName, 276 actionName: null, 277 controllerName: null, 278 protocol: null, 279 hostName: null, 280 fragment: null, 281 routeValues: routeValues, 282 routeCollection: RouteCollection, 283 requestContext: RequestContext, 284 includeImplicitMvcValues: false); 285 } 286 } 287 }
1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Diagnostics.CodeAnalysis; 4 using System.Globalization; 5 using System.IO; 6 using System.Web.Mvc.Properties; 7 using System.Web.WebPages; 8 9 namespace System.Web.Mvc 10 { 11 public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild 12 { 13 private ViewDataDictionary _viewData; 14 private DynamicViewDataDictionary _dynamicViewData; 15 private HttpContextBase _context; 16 private HtmlHelper<object> _html; 17 private AjaxHelper<object> _ajax; 18 19 public override HttpContextBase Context 20 { 21 // REVIEW why are we forced to override this? 22 get { return _context ?? ViewContext.HttpContext; } 23 set { _context = value; } 24 } 25 26 public HtmlHelper<object> Html 27 { 28 get 29 { 30 if (_html == null && ViewContext != null) 31 { 32 _html = new HtmlHelper<object>(ViewContext, this); 33 } 34 return _html; 35 } 36 set 37 { 38 _html = value; 39 } 40 } 41 42 public AjaxHelper<object> Ajax 43 { 44 get 45 { 46 if (_ajax == null && ViewContext != null) 47 { 48 _ajax = new AjaxHelper<object>(ViewContext, this); 49 } 50 return _ajax; 51 } 52 set 53 { 54 _ajax = value; 55 } 56 } 57 58 public object Model 59 { 60 get { return ViewData.Model; } 61 } 62 63 internal string OverridenLayoutPath { get; set; } 64 65 public TempDataDictionary TempData 66 { 67 get { return ViewContext.TempData; } 68 } 69 70 public UrlHelper Url { get; set; } 71 72 public dynamic ViewBag 73 { 74 get 75 { 76 if (_dynamicViewData == null) 77 { 78 _dynamicViewData = new DynamicViewDataDictionary(() => ViewData); 79 } 80 return _dynamicViewData; 81 } 82 } 83 84 public ViewContext ViewContext { get; set; } 85 86 [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewPage gets its ViewDataDictionary object.")] 87 public ViewDataDictionary ViewData 88 { 89 get 90 { 91 if (_viewData == null) 92 { 93 SetViewData(new ViewDataDictionary()); 94 } 95 return _viewData; 96 } 97 set { SetViewData(value); } 98 } 99 100 protected override void ConfigurePage(WebPageBase parentPage) 101 { 102 var baseViewPage = parentPage as WebViewPage; 103 if (baseViewPage == null) 104 { 105 // TODO : review if this check is even necessary. 106 // When this method is called by the framework parentPage should already be an instance of WebViewPage 107 // Need to review what happens if this method gets called in Plan9 pointing at an MVC view 108 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, parentPage.VirtualPath)); 109 } 110 111 // Set ViewContext and ViewData here so that the layout page inherits ViewData from the main page 112 ViewContext = baseViewPage.ViewContext; 113 ViewData = baseViewPage.ViewData; 114 InitHelpers(); 115 } 116 117 public override void ExecutePageHierarchy() 118 { 119 // Change the Writer so that things like Html.BeginForm work correctly 120 TextWriter oldWriter = ViewContext.Writer; 121 ViewContext.Writer = Output; 122 123 base.ExecutePageHierarchy(); 124 125 // Overwrite LayoutPage so that returning a view with a custom master page works. 126 if (!String.IsNullOrEmpty(OverridenLayoutPath)) 127 { 128 Layout = OverridenLayoutPath; 129 } 130 131 // Restore the old View Context Writer 132 ViewContext.Writer = oldWriter; 133 } 134 135 public virtual void InitHelpers() 136 { 137 // Html and Ajax helpers are lazily initialized since they are not directly visible to a Razor page. 138 // In order to ensure back-compat, in the event that this instance gets re-used, we'll reset these 139 // properties so they get reinitialized the very next time they get accessed. 140 Html = null; 141 Ajax = null; 142 Url = new UrlHelper(ViewContext.RequestContext); 143 } 144 145 protected virtual void SetViewData(ViewDataDictionary viewData) 146 { 147 _viewData = viewData; 148 } 149 } 150 }
上文中Url 实际上是UrlHelper 类型 在WebViewPage 中虚方法 InitHelpers 完成实例化。
转到UrlHelper看看,Action有九个重载。而项目中只用到了如下三个:
1 public virtual string Action(string actionName, string controllerName) 2 { 3 return GenerateUrl(null /* routeName */, actionName, controllerName, (RouteValueDictionary)null /* routeValues */); 4 } 5 6 public virtual string Action(string actionName, string controllerName, object routeValues) 7 { 8 return GenerateUrl(null /* routeName */, actionName, controllerName, TypeHelper.ObjectToDictionary(routeValues)); 9 } 10 11 public virtual string Action(string actionName, string controllerName, RouteValueDictionary routeValues) 12 { 13 return GenerateUrl(null /* routeName */, actionName, controllerName, routeValues); 14 }
上文有提到一个可以生成绝对地址的重载
1 public virtual string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName) 2 { 3 return GenerateUrl(null /* routeName */, actionName, controllerName, protocol, hostName, null /* fragment */, routeValues, RouteCollection, RequestContext, true /* includeImplicitMvcValues */); 4 }
也就是说,我只要自定义一个UrlHelper实现项目中重载版本间接调用这个生成绝对地址的重载版本即可。
Url 定义在WebViewPage中,意味着WebViewPage也得自定义。可问题是自定义如何用起来呢?搜索一番找到文章结尾三篇文章从中得到了我要答案。
编译发布。整站都浏览了一遍,只要走Url.Action的都是绝对地址了。 nice!无缝对接啊。
总结:
因为生成的绝对地址随iis域名端口自动变化,不会出现本地开发环境也是生产环境地址没法测试问题。项目几乎没什么变动,只需要修改下配置文件即可,无缝对接!
最终版核心代码
using System; using System.Web.Mvc; using System.Web.WebPages; namespace SF.MVC.Core { public abstract class WebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel> { public new CustomUrlHelper Url { get; set; } public override void InitHelpers() { base.InitHelpers(); Url = new CustomUrlHelper(ViewContext.RequestContext); } } public abstract class WebViewPage : WebViewPage<dynamic> { } }
1 using System; 2 using System.Globalization; 3 using System.Web.Mvc; 4 using System.Web.Routing; 5 using System.Web.WebPages; 6 using System.Collections.Generic; 7 8 namespace SF.MVC.Core 9 { 10 public class CustomUrlHelper : UrlHelper 11 { 12 public CustomUrlHelper(RequestContext requestContext) 13 : base(requestContext, RouteTable.Routes) 14 { 15 } 16 17 public new string Action(string actionName, string controllerName) 18 { 19 var hostName = RequestContext.HttpContext.Request.Url.Host; 20 return GenerateUrl(null, actionName, controllerName, null, hostName, null, (RouteValueDictionary)null, RouteCollection, RequestContext, true); 21 } 22 23 public new string Action(string actionName, string controllerName, object routeValues) 24 { 25 var hostName = RequestContext.HttpContext.Request.Url.Host; 26 return GenerateUrl(null, actionName, controllerName, null, hostName, null, new RouteValueDictionary(routeValues), RouteCollection, RequestContext, true); 27 } 28 29 public new string RouteUrl(string routeName) 30 { 31 var hostName = RequestContext.HttpContext.Request.Url.Host; 32 return GenerateUrl(routeName, null, null, null, hostName, null, (RouteValueDictionary)null, RouteCollection, RequestContext, false); 33 34 } 35 36 public new static string GenerateUrl(string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) 37 { 38 string url = GenerateUrl(routeName, actionName, controllerName, routeValues, routeCollection, requestContext, includeImplicitMvcValues); 39 40 if (url != null) 41 { 42 if (!IsContain(url)) 43 url += "/"; 44 45 if (!String.IsNullOrEmpty(fragment)) 46 { 47 url = url + "#" + fragment; 48 } 49 50 if (!String.IsNullOrEmpty(protocol) || !String.IsNullOrEmpty(hostName)) 51 { 52 Uri requestUrl = requestContext.HttpContext.Request.Url; 53 protocol = (!String.IsNullOrEmpty(protocol)) ? protocol : Uri.UriSchemeHttp; 54 hostName = (!String.IsNullOrEmpty(hostName)) ? hostName : requestUrl.Host; 55 56 string port = String.Empty; 57 string requestProtocol = requestUrl.Scheme; 58 59 if (String.Equals(protocol, requestProtocol, StringComparison.OrdinalIgnoreCase)) 60 { 61 port = requestUrl.IsDefaultPort ? String.Empty : (":" + Convert.ToString(requestUrl.Port, CultureInfo.InvariantCulture)); 62 } 63 64 url = protocol + Uri.SchemeDelimiter + hostName + port + url; 65 } 66 } 67 68 return url; 69 } 70 71 /// <summary> 72 /// 判断字符串中是否包含某部分 73 /// </summary> 74 /// <param name="input"></param> 75 /// <returns></returns> 76 private static bool IsContain(string input) 77 { 78 if (string.IsNullOrWhiteSpace(input)) return false; 79 80 if (input == "/") 81 { 82 return true; 83 } 84 else if(input.Contains("articledetial")) 85 { 86 return true; 87 } 88 else if (input.Contains("special")) 89 { 90 return true; 91 } 92 else if(input.EndsWith(".html", StringComparison.CurrentCultureIgnoreCase)) 93 { 94 return true; 95 } 96 97 return false; 98 } 99 100 } 101 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Web.Mvc; 6 7 namespace SF.MVC.Core 8 { 9 public class PermanentRedirectFilter : ActionFilterAttribute 10 { 11 12 /// <summary> 13 /// 301永久重定向 14 /// </summary> 15 /// <param name="filterContext"></param> 16 public override void OnActionExecuting(ActionExecutingContext filterContext) 17 { 18 string url = filterContext.HttpContext.Request.Url.AbsolutePath; 19 20 if (!url.EndsWith("/")) 21 { 22 filterContext.HttpContext.Response.AddHeader("Location", url + "/"); 23 filterContext.HttpContext.Response.Status = "301 Moved Permanently"; 24 filterContext.HttpContext.Response.StatusCode = 301; 25 26 } 27 28 } 29 } 30 31 }
Views/Web.config如下节点:
<system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="SF.MVC.Core.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Optimization"/> <add namespace="System.Web.Routing" /> <add namespace="SF.MVC.Core" /> </namespaces> </pages> </system.web.webPages.razor>
后记:
人算不如天算,本机、测试环境都测试通过了,没想到生产环境做了负载均衡,有端口号反而杯具了。 这样就会出现有时需要有时不需要问题,没办法只能加个配置项来控制了。
1 using System; 2 using System.Globalization; 3 using System.Web.Mvc; 4 using System.Web.Routing; 5 using System.Web.WebPages; 6 using System.Collections.Generic; 7 using MSP.Common.Tools; 8 9 namespace SF.MVC.Core 10 { 11 public class CustomUrlHelper : UrlHelper 12 { 13 /// <summary> 14 /// 是否需要加端口号 15 /// </summary> 16 private static bool IsNeedProtocol = false; 17 18 public CustomUrlHelper(RequestContext requestContext) 19 : base(requestContext, RouteTable.Routes) 20 { 21 ConfigManager config = new ConfigManager(); 22 config.LoadConfig("common"); 23 24 string configValue = config.GetConfig("NeedProtocol") == null ? "false" : config.GetConfig("NeedProtocol").Value; 25 26 bool.TryParse(configValue, out IsNeedProtocol); 27 28 } 29 30 public new string Action(string actionName, string controllerName) 31 { 32 var hostName = RequestContext.HttpContext.Request.Url.Host; 33 return GenerateUrl(null, actionName, controllerName, null, hostName, null, (RouteValueDictionary)null, RouteCollection, RequestContext, true); 34 } 35 36 public new string Action(string actionName, string controllerName, object routeValues) 37 { 38 var hostName = RequestContext.HttpContext.Request.Url.Host; 39 return GenerateUrl(null, actionName, controllerName, null, hostName, null, new RouteValueDictionary(routeValues), RouteCollection, RequestContext, true); 40 } 41 42 public new string RouteUrl(string routeName) 43 { 44 var hostName = RequestContext.HttpContext.Request.Url.Host; 45 return GenerateUrl(routeName, null, null, null, hostName, null, (RouteValueDictionary)null, RouteCollection, RequestContext, false); 46 47 } 48 49 public new static string GenerateUrl(string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) 50 { 51 string url = GenerateUrl(routeName, actionName, controllerName, routeValues, routeCollection, requestContext, includeImplicitMvcValues); 52 53 if (url != null) 54 { 55 if (!IsContain(url)) 56 url += "/"; 57 58 if (!String.IsNullOrEmpty(fragment)) 59 { 60 url = url + "#" + fragment; 61 } 62 63 if (!String.IsNullOrEmpty(protocol) || !String.IsNullOrEmpty(hostName)) 64 { 65 Uri requestUrl = requestContext.HttpContext.Request.Url; 66 protocol = (!String.IsNullOrEmpty(protocol)) ? protocol : Uri.UriSchemeHttp; 67 hostName = (!String.IsNullOrEmpty(hostName)) ? hostName : requestUrl.Host; 68 69 string port = String.Empty; 70 string requestProtocol = requestUrl.Scheme; 71 72 if (IsNeedProtocol && String.Equals(protocol, requestProtocol, StringComparison.OrdinalIgnoreCase)) 73 { 74 port = requestUrl.IsDefaultPort ? String.Empty : (":" + Convert.ToString(requestUrl.Port, CultureInfo.InvariantCulture)); 75 } 76 77 url = protocol + Uri.SchemeDelimiter + hostName + port + url; 78 } 79 } 80 81 return url; 82 } 83 84 /// <summary> 85 /// 判断字符串中是否包含某部分 86 /// </summary> 87 /// <param name="input"></param> 88 /// <returns></returns> 89 private static bool IsContain(string input) 90 { 91 if (string.IsNullOrWhiteSpace(input)) return false; 92 93 if (input == "/") 94 { 95 return true; 96 } 97 else if(input.Contains("articledetial")) 98 { 99 return true; 100 } 101 else if (input.Contains("special")) 102 { 103 return true; 104 } 105 else if(input.EndsWith(".html", StringComparison.CurrentCultureIgnoreCase)) 106 { 107 return true; 108 } 109 110 return false; 111 } 112 113 } 114 }
代码中构造函数读取配置您需要改写成自己的实现逻辑。
参考:
Changing Base Type Of A Razor View