How to make ASP.NET Routing escape route values?

后端 未结 4 2039
别跟我提以往
别跟我提以往 2020-12-11 19:16

I have an ASP.NET MVC site where I want routes like /{controller}/{id}/{action}/{date}, where \"date\" is the mm/dd/yyyy portion of a date/time. (I\'m

相关标签:
4条回答
  • 2020-12-11 19:47

    I'm guessing that it doesn't automatically url encode it b/c its hard for the html helper to determine if you want to represent a date or if you want to have 3 more fields in the route e.g.

    // 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
    

    Maybe you could change your route to be

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

    That way, it would always work without escaping.

    Otherwise, you could do a URL Encoding of the date within the Html.ActionLink(...) call

    0 讨论(0)
  • 2020-12-11 19:53

    I don't know if this counts as an answer or not, but I always use yyyy-mm-dd format in URIs. Not because slashes are reserved per the RFC (although that's a good reason) but because it is immune to globalization issues when converting to/from a string. DateTime.Parse() "just works" with this format, even if someone sets the server locale to somewhere in Eastern Europe.

    0 讨论(0)
  • 2020-12-11 19:57

    I had the same problem because client codes could include / : and all sorts of characters. This is how I solved it: http://blog.peterlesliemorris.com/archive/2010/11/19/asp-mvc-encoding-route-values.aspx

    This is what you need to do in your web app.

    //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(.....);
    

    And this is the extension source code

    //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;
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-11 20:08

    You can not use a forward slash in a route value in ASP.NET MVC. Even if it is URL-encoded it wont work.

    slash in url

    There is only a solution if you use ASP.NET 4.0

    0 讨论(0)
提交回复
热议问题