自定义WebViewPage,实现Url.Action生成绝对地址

前言

运营部门一直对公司官网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 }
UrlHelper
  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 }
WebViewPage

 上文中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>
    {
    }

}
CustomWebViewPage
  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 }
CustomUrlHelper
 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 }
301永久重定向

 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>
View Code

后记:

人算不如天算,本机、测试环境都测试通过了,没想到生产环境做了负载均衡,有端口号反而杯具了。 这样就会出现有时需要有时不需要问题,没办法只能加个配置项来控制了。

  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 }
修改后CustomUrlHelper

代码中构造函数读取配置您需要改写成自己的实现逻辑。

参考:

Changing Base Type Of A Razor View

Create Your Own Custom ViewWebPage for ASP.NET MVC

MVC中自定义ViewPage和WebViewPage

原文地址:https://www.cnblogs.com/lonny/p/CustomWebViewPage-CustomUrlHelper-GenerateAbsoluteAddress.html