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

时间:2010-02-01 03:41:17

标签: asp.net-mvc url-routing asp.net-mvc-routing

我有一个ASP.NET MVC网站,我想要/{controller}/{id}/{action}/{date}之类的路由,其中​​“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()为此路线生成链接时,不会对日期进行网址编码,最终会出现无效的网址,例如“ /富/ 100 /编辑/ 01 /二千〇一十分之二十一”。

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

4 个答案:

答案 0 :(得分:4)

我猜它不会自动对其进行url编码b / c很难让html助手确定你是想表示一个日期,还是想要在路线中再增加三个字段,例如

// 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}",
...

这样,它总能在没有逃避的情况下发挥作用。

否则,您可以在Html.ActionLink(...)电话

中执行URL Encoding日期

答案 1 :(得分:2)

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

答案 2 :(得分:1)

我遇到了同样的问题,因为客户端代码可能包含/:和各种字符。这就是我解决它的方式: http://blog.peterlesliemorris.com/archive/2010/11/19/asp-mvc-encoding-route-values.aspx

这是您在网络应用中需要做的事情。

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

答案 3 :(得分:0)

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

slash in url

如果使用ASP.NET 4.0,则只有一个解决方案