代码之家  ›  专栏  ›  技术社区  ›  Seth Petry-Johnson

如何使ASP.NET路由转义路由值?

  •  5
  • Seth Petry-Johnson  · 技术社区  · 15 年前

    我有一个ASP.NET MVC站点,我想要像这样的路由 /{controller}/{id}/{action}/{date} ,其中“日期”是 MM/DD/YYYY 日期/时间的一部分。(我处理的是时间维度的数据,所以我需要一个ID和一个时间点来进行大多数操作)

    这条路线很简单:

    routes.MapRoute(
        "TimeDimensionedRoute",
        "{controller}/{id}/{action}/{date}",
        new { controller = "Iteration", action = "Index", id = String.Empty, date = String.Empty }
    );
    

    此路线正确映射” /foo/100/编辑/01%2f21%2f2010 “达到所需的动作。 更新:这是不正确的。这个路线不对,我错了。请参阅已接受答案中链接的相关问题。

    我的问题是,当我使用html.actionlink()生成此路由的链接时, 不是URL编码日期 最后我得到了无效的URL,比如“ /foo/100/编辑/01/21/2010 “。

    有没有办法让路由基础设施为我编码值?我必须手动对传递给HTML助手的数据进行URL编码,这似乎是错误的。

    4 回复  |  直到 12 年前
        1
  •  0
  •   Community CDub    8 年前

    在ASP.NET MVC中,不能在路由值中使用正斜杠。即使它是URL编码的,也无法工作。

    slash in url

    只有在使用ASP.NET 4.0时才有解决方案

        2
  •  4
  •   TJB    15 年前

    我猜它不会自动对其进行URL编码,HTML助手很难确定您是想表示日期,还是想在路由中多加3个字段,例如。

    // Here's what you're seeing
    /Foo  /100  /Edit  /10/21/2010/
    // 4 route values
    
    // But there's know way to know you don't want this
    /Foo  /100  /Edit  /10  /21  /2010/
    // 6 route values
    

    也许你可以改变你的路线

    ...
    "{controller}/{id}/{action}/{month}/{day}/{year}",
    ...
    

    这样,它就可以一直工作而不逃逸。

    否则,你可以做一个 URL Encoding Html.ActionLink(...) 呼叫

        3
  •  2
  •   Craig Stuntz    15 年前

    我不知道这算不算回答,但我 总是 使用 yyyy-mm-dd 在URI中设置格式。不是因为斜线是按RFC保留的(尽管这是一个很好的原因),而是因为当转换为字符串/从字符串转换为字符串时,斜线不受全球化问题的影响。 DateTime.Parse() “只适用于”这种格式,即使有人将服务器区域设置为东欧的某个地方。

        4
  •  1
  •   Peter Morris    12 年前

    我也有同样的问题,因为客户机代码可以包括/:和各种字符。我就是这样解决的: http://blog.peterlesliemorris.com/archive/2010/11/19/asp-mvc-encoding-route-values.aspx

    这是您在Web应用程序中需要做的。

    //1: Register a custom value provider in global.asax.cs
    protected void Application_Start()
    {
      EncodedRouteValueProviderFactory.Register();
      ...
    }
    
    //2: Use the following code in your views instead of Html.ActionLink
    //this will ensure that all values before the ? query string part of your
    //URL are properly encoded
    
    <%: Html.EncodedActionLink(.....) %>
    //3: Use this special redirect action when redirecting from a method
    return this.EncodedActionLink(.....);
    

    这是扩展源代码

    //EncodedActionLinkExtensions.cs
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Web.Routing;
    
    namespace System.Web.Mvc.Html
    {
      public static class EncodedActionLinkExtensions
      {
        public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action)
        {
          return htmlHelper.EncodedActionLink(linkText, action, (object)null);
        }
    
        public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName)
        {
          return htmlHelper.EncodedActionLink(linkText, action, controllerName, (object)null);
        }
    
        public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, object explicitRouteValues)
        {
          object routeValueObj;
          if (!htmlHelper.ViewContext.RequestContext.RouteData.Values.TryGetValue("controller", out routeValueObj))
            throw new InvalidOperationException("Could not determine controller");
    
          string controllerName = (string)routeValueObj;
          return htmlHelper.EncodedActionLink(linkText, action, controllerName, explicitRouteValues);
        }
    
        public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName, object explicitRouteValues)
        {
          return htmlHelper.EncodedActionLink(linkText, action, controllerName, new RouteValueDictionary(explicitRouteValues));
        }
    
        public static MvcHtmlString EncodedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controllerName, RouteValueDictionary explicitRouteValues)
        {
          string url = EncodedUrlHelper.GenerateUrl(
            htmlHelper.ViewContext.RequestContext,
            controllerName, action, explicitRouteValues);
          string result = string.Format("<a href=\"{0}\">{1}</a>", url, linkText);
          return MvcHtmlString.Create(result);
        }
      }
    }
    
    
    //EncodedRedirectToRouteExtensions.cs
    using System.Web.Routing;
    namespace System.Web.Mvc
    {
      public static class EncodedRedirectToRouteExtensions
      {
        public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName)
        {
          return controller.EncodedRedirectToAction(
            actionName,
            (string)null, //controllerName,
            (RouteValueDictionary)null //routeValues
            );
        }
    
        public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, object routeValues)
        {
          return controller.EncodedRedirectToAction(
            actionName,
            (string)null, //controllerName,
            new RouteValueDictionary(routeValues)
            );
        }
    
        public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, RouteValueDictionary routeValues)
        {
          return controller.EncodedRedirectToAction(
            actionName,
            (string)null, //controllerName,
            routeValues
            );
        }
    
        public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName)
        {
          return controller.EncodedRedirectToAction(
            actionName,
            controllerName,
            (RouteValueDictionary)null //routeValues
            );
        }
    
        public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName, object routeValues)
        {
          return controller.EncodedRedirectToAction(
            actionName,
            controllerName,
            new RouteValueDictionary(routeValues)
            );
        }
    
        public static EncodedRedirectToRouteResult EncodedRedirectToAction(this IController controller, string actionName, string controllerName, RouteValueDictionary routeValues)
        {
          RouteValueDictionary dictionary;
          if (routeValues != null)
            dictionary = new RouteValueDictionary(routeValues);
          else
            dictionary = new RouteValueDictionary();
          dictionary["controller"] = controllerName;
          dictionary["action"] = actionName;
    
          var result = new EncodedRedirectToRouteResult(dictionary);
          return result;
        }
    
      }
    }
    
    //EncodedRedirectToRouteResult.cs
    using System.Web.Mvc;
    using System.Web.Routing;
    namespace System.Web.Mvc
    {
      public class EncodedRedirectToRouteResult : ActionResult
      {
        readonly string RouteName;
        readonly RouteValueDictionary RouteValues;
    
        public EncodedRedirectToRouteResult(RouteValueDictionary routeValues)
          : this(null, routeValues)
        {
        }
    
        public EncodedRedirectToRouteResult(string routeName, RouteValueDictionary routeValues)
        {
          RouteName = routeName ?? "";
          RouteValues = routeValues != null ? routeValues : new RouteValueDictionary();
        }
    
        public override void ExecuteResult(ControllerContext context)
        {
          string url = EncodedUrlHelper.GenerateUrl(context.RequestContext, null, null, RouteValues);
          context.Controller.TempData.Keep();
          context.HttpContext.Response.Redirect(url, false);
        }
      }
    }
    
    //EncodedRouteValueProvider.cs
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using System.Web.Routing;
    using System.Reflection;
    namespace System.Web.Mvc
    {
      public class EncodedRouteValueProvider : IValueProvider
      {
        readonly ControllerContext ControllerContext;
        bool Activated = false;
    
        public EncodedRouteValueProvider(ControllerContext controllerContext)
        {
          ControllerContext = controllerContext;
        }
    
        public bool ContainsPrefix(string prefix)
        {
          if (!Activated)
            DecodeRouteValues();
          return false;
        }
    
        public ValueProviderResult GetValue(string key)
        {
          if (!Activated)
            DecodeRouteValues();
          return null;
        }
    
        void DecodeRouteValues()
        {
          Activated = true;
          var route = (Route)ControllerContext.RouteData.Route;
          string url = route.Url;
          var keysToDecode = new HashSet<string>();
          var regex = new Regex(@"\{.+?\}");
          foreach (Match match in regex.Matches(url))
            keysToDecode.Add(match.Value.Substring(1, match.Value.Length - 2));
          foreach (string key in keysToDecode)
          {
            object valueObj = ControllerContext.RequestContext.RouteData.Values[key];
            if (valueObj == null)
              continue;
            string value = valueObj.ToString();
            value = UrlValueEncoderDecoder.DecodeString(value);
            ControllerContext.RouteData.Values[key] = value;
            ValueProviderResult valueProviderResult = ControllerContext.Controller.ValueProvider.GetValue(key);
            if (valueProviderResult == null)
              continue;
            PropertyInfo attemptedValueProperty = valueProviderResult.GetType().GetProperty("AttemptedValue");
            attemptedValueProperty.SetValue(valueProviderResult, value, null);
            PropertyInfo rawValueProperty = valueProviderResult.GetType().GetProperty("RawValue");
            rawValueProperty.SetValue(valueProviderResult, value, null);
          }
        }
    
      }
    }
    
    //EncodedRouteValueProviderFactory.cs
    namespace System.Web.Mvc
    {
      public class EncodedRouteValueProviderFactory : ValueProviderFactory
      {
        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
          return new EncodedRouteValueProvider(controllerContext);
        }
    
        public static void Register()
        {
          ValueProviderFactories.Factories.Insert(0, new EncodedRouteValueProviderFactory());
        }
      }
    }
    
    //EncodedUrlHelper.cs
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Web.Mvc;
    namespace System.Web.Routing
    {
      public static class EncodedUrlHelper
      {
        public static string GenerateUrl(
          RequestContext requestContext, 
          string controllerName,
          string action,
          RouteValueDictionary explicitRouteValues)
        {
          if (requestContext == null)
            throw new ArgumentNullException("RequestContext");
    
          var newRouteValues = RouteHelper.GetRouteValueDictionary(
            requestContext, controllerName, action, explicitRouteValues);
          var route = RouteHelper.GetRoute(requestContext, controllerName, action, newRouteValues);
          string url = route.Url;
          //Replace the {values} in the main part of the URL with request values
          var regex = new Regex(@"\{.+?\}");
          url = regex.Replace(url,
            match =>
            {
              string key = match.Value.Substring(1, match.Value.Length - 2);
              object value;
              if (!newRouteValues.TryGetValue(key, out value))
                throw new ArgumentNullException("Cannot reconcile value for key: " + key);
              string replaceWith;
              if (value == UrlParameter.Optional)
                replaceWith = "";
              else
                replaceWith = UrlValueEncoderDecoder.EncodeObject(value);
              explicitRouteValues.Remove(key);
              return replaceWith;
            });
    
          //2: Add additional values after the ?
          explicitRouteValues.Remove("controller");
          explicitRouteValues.Remove("action");
          var urlBuilder = new StringBuilder();
          urlBuilder.Append("/" + url);
          string separator = "?";
          foreach (var kvp in explicitRouteValues)
          {
            if (kvp.Value != UrlParameter.Optional)
            {
              urlBuilder.AppendFormat("{0}{1}={2}", separator, kvp.Key, kvp.Value == null ? "" : HttpUtility.UrlEncode(kvp.Value.ToString()));
              separator = "&";
            }
          }
          return urlBuilder.ToString();
        }
      }
    }
    
    //RouteHelper.cs
    namespace System.Web.Routing
    {
      public static class RouteHelper
      {
        public static RouteValueDictionary GetRouteValueDictionary(
          RequestContext requestContext,
          string controllerName,
          string action,
          RouteValueDictionary explicitRouteValues)
        {
          var newRouteValues = new RouteValueDictionary();
          var route = GetRoute(requestContext, controllerName, action, explicitRouteValues);
          MergeValues(route.Defaults, newRouteValues);
          MergeValues(requestContext.RouteData.Values, newRouteValues);
          if (explicitRouteValues != null)
            MergeValues(explicitRouteValues, newRouteValues);
          if (controllerName != null)
            newRouteValues["controller"] = controllerName;
          if (action != null)
            newRouteValues["action"] = action;
          return newRouteValues;
        }
    
        public static Route GetRoute(
          RequestContext requestContext,
          string controllerName,
          string action,
          RouteValueDictionary explicitRouteValues
          )
        {
          var routeValues = new RouteValueDictionary(requestContext.RouteData.Values);
          if (explicitRouteValues != null)
            MergeValues(explicitRouteValues, routeValues);
          if (controllerName != null)
            routeValues["controller"] = controllerName;
          if (action != null)
            routeValues["action"] = action;
          var virtualPath = RouteTable.Routes.GetVirtualPath(requestContext, routeValues);
          return (Route)virtualPath.Route;
        }
    
        static void MergeValues(RouteValueDictionary routeValues, RouteValueDictionary result)
        {
          foreach (var kvp in routeValues)
          {
            if (kvp.Value != null)
              result[kvp.Key] = kvp.Value;
            else
            {
              object value;
              if (!result.TryGetValue(kvp.Key, out value))
                result[kvp.Key] = null;
            }
          }
        }
      }
    }
    
    //UrlValueEncoderDecoder.cs
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    using System.Text.RegularExpressions;
    namespace System.Web.Mvc
    {
      public static class UrlValueEncoderDecoder
      {
        static HashSet<char> ValidChars;
    
        static UrlValueEncoderDecoder()
        {
          string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";
          ValidChars = new HashSet<char>(chars.ToCharArray());
        }
    
        public static string EncodeObject(object value)
        {
          if (value == null)
            return null;
          return EncodeString(value.ToString());
        }
    
        public static string EncodeString(string value)
        {
          if (value == null)
            return null;
          var resultBuilder = new StringBuilder();
          foreach (char currentChar in value.ToCharArray())
            if (ValidChars.Contains(currentChar))
              resultBuilder.Append(currentChar);
            else
            {
              byte[] bytes = System.Text.UnicodeEncoding.UTF8.GetBytes(currentChar.ToString());
              foreach (byte currentByte in bytes)
                resultBuilder.AppendFormat("${0:x2}", currentByte);
            }
          string result = resultBuilder.ToString();
          //Special case, use + for spaces as it is shorter and spaces are common
          return result.Replace("$20", "+");
        }
    
        public static string DecodeString(string value)
        {
          if (value == null)
            return value;
          //Special case, change + back to a space
          value = value.Replace("+", " ");
          var regex = new Regex(@"\$[0-9a-fA-F]{2}");
          value = regex.Replace(value,
            match =>
            {
              string hexCode = match.Value.Substring(1, 2);
              byte byteValue = byte.Parse(hexCode, NumberStyles.AllowHexSpecifier);
              string decodedChar = System.Text.UnicodeEncoding.UTF8.GetString(new byte[] { byteValue });
              return decodedChar;
            });
          return value;
        }
      }
    }