【问题标题】:First call to Url.Action on a page is slow在页面上首次调用 Url.Action 很慢
【发布时间】:2012-08-10 10:49:14
【问题描述】:

我有一个相当简单的 ASP.MVC 视图的性能问题。

这是一个应该几乎是即时的登录页面,但大约需要半秒钟。

经过大量挖掘后,问题似乎在于第一次调用 Url.Action - 大约需要 450 毫秒(根据 MiniProfiler),但这似乎非常慢。

Url.Action 的后续调用耗时

无论我使用Url.Action("action", "controller") 还是Url.Action("action"),这都是一致的,但如果我使用Url.Content("~/controller/action"),这似乎不会发生。当我拨打Html.BeginForm("action") 时也会发生这种情况。

有人知道是什么原因造成的吗?

source 的深入研究表明RouteCollection.GetVirtualPath 可能是罪魁祸首,因为Url.ActionHtml.BeginForm 都有这种情况。但是,肯定到处都在使用它吗?我的意思是,½ 秒太慢了。

我有 20 条左右的自定义路由(这是一个相当大的应用程序,带有一些旧版 WebForms 页面),但即便如此,时间似乎也太慢了。

有什么解决办法吗?

【问题讨论】:

    标签: asp.net-mvc performance urlhelper route-constraint


    【解决方案1】:

    发现问题,与路由表有关(干杯基里尔)。

    基本上我们有很多看起来像这样的路线:

    string[] controllers = GetListOfValidControllers();
    
    routes.MapRoute(
        name: GetRouteName(),
        url: subfolder + "/{controller}/{action}/{id}",
        defaults: new { action = "Index", id = UrlParameter.Optional },
        constraints: new { controller = "(" + string.Join("|", controllers) + ")" });
    

    原来the Regex check is very slow,慢得令人痛苦。所以我用IRouteConstraint 的实现替换了它,它只检查HashSet

    然后我更改了地图路线调用:

    routes.MapRoute(
        name: GetRouteName(),
        url: subfolder + "/{controller}/{action}/{id}",
        defaults: new { action = "Index", id = UrlParameter.Optional },
        constraints: new { controller = new HashSetConstraint(controllers) });
    

    我还将RegexConstraint mentioned in that linked article 用于更复杂的事情 - 包括很多这样的调用(因为我们有旧版 WebForm 页面):

    routes.IgnoreRoute(
        url: "{*allaspx}", 
        constraints: new { allaspx = new RegexConstraint( @".*\.as[pmh]x(/.*)?") });
    

    这两个简单的更改完全解决了问题; Url.ActionHtml.BeginForm 现在花费的时间可以忽略不计(即使有很多路线)。

    【讨论】:

      【解决方案2】:

      在我看来,您的问题是编译视图。您需要在构建时预编译视图,这个问题就会消失。 details here

      【讨论】:

      • 这不会预编译视图,只是在构建后编译它们,因此您会遇到构建错误而不是运行时错误。而且它没有任何区别 - 我在第一次 Url.Action 呼叫时仍然看到 450 毫秒左右。
      • 使用 ASPNet_Compiler.exe 可以进行正确的预编译(请参阅 msdn.microsoft.com/en-us/library/ms229863(v=vs.80).aspx),但即使没有,我看到 Url.Action 的时间也很疯狂 - 几乎就像它正在进行全面反思以找到每次页面运行时的控制器操作。
      • 你能展示你的 RegisterRoutes(来自 global.asax)吗?它可以利用您的时间。
      • 我查看框架的来源。在这种情况下没有反射,但是 GetVirtualPath 方法可能很慢。
      • 我看到Html.BeginForm 以同样的方式变慢,它也调用RouteCollection.GetVirtualPath - 这在所有地方都很流行,所以我不确定为什么它在这里特别慢。我不能分享RegisterRoutes - 这是一个相当大的应用程序,但有很多自定义路线。
      【解决方案3】:
          public class RegexConstraint : IRouteConstraint, IEquatable<RegexConstraint>
           {
          Regex regex;
          string pattern;
      
          public RegexConstraint(string pattern, RegexOptions options = RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase)
          {
              regex = new Regex(pattern, options);
              this.pattern = pattern;
          }
      
          public bool Match(System.Web.HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
          {
              object val;
              values.TryGetValue(parameterName, out val);
              string input = Convert.ToString(val, CultureInfo.InvariantCulture);
              return regex.IsMatch(input);
          }
      
          public string Pattern
          {
              get
              {
                  return pattern;
              }
          }
      
          public RegexOptions RegexOptions
          {
              get
              {
                  return regex.Options;
              }
          }
      
          private string Key
          {
              get
              {
                  return regex.Options.ToString() + " | " + pattern;
              }
          }
      
          public override int GetHashCode()
          {
              return Key.GetHashCode();
          }
      
          public override bool Equals(object obj)
          {
              var other = obj as RegexConstraint;
              if (other == null) return false;
              return Key == other.Key;
          }
      
          public bool Equals(RegexConstraint other)
          {
              return this.Equals((object)other);
          }
      
          public override string ToString()
          {
              return "RegexConstraint (" + Pattern + ")";
          }
      }
      

      【讨论】:

      【解决方案4】:

      与从 IHttpModule 下载相比,我已将其剥离为“基本”...将单个文件设置到内存中并从操作中下载它。由于某种原因(可能是 MVC 管道加载、路由),IHttpModule 更快(对于小文件,例如产品列表图像)。我没有在路由中使用正则表达式(这会更慢)。在 IHttpModule 中,我达到的速度与 URL 指向驱动器上的文件相同(当然,如果文件在驱动器上,但不在 URL 指向的驱动器位置)。

      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true">
           <add name="ImagesHandler" type="NsdMupWeb.ImagesHttpModule" />
        </modules>
      </system.webServer>
      
      
      //Code is made for testing
      public class ImagesHttpModule : IHttpModule
      {
          public void Dispose()
          {
          }
      
          public void Init(HttpApplication context)
          {
              context.BeginRequest += Context_BeginRequest;
          }
      
          private void Context_BeginRequest(object sender, EventArgs e)
          {
              var app = (HttpApplication)sender;
              if (app.Request.CurrentExecutionFilePathExtension.Length > 0)
              {
                  var imagePathFormated = "/image/";
                  var imagesPath = app.Request.ApplicationPath.TrimEnd('/') + imagePathFormated;
                  if (app.Request.CurrentExecutionFilePath.StartsWith(imagesPath))
                  {
                      var path = app.Request.CurrentExecutionFilePath.Remove(0, imagesPath.Length);
                      var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                      if (parts.Length > 1)
                      {
                          var ms = new MemoryStream();
                          Stream stream;
      
                          stream = System.IO.File.OpenRead(@"C:\Programming\Sindikat\Main\NsdMupWeb\Files\Cached\imageFormatProductList\1b1e2671-a365-4a87-97ba-063cf51ac34e.jpg");
                          var ctx = ((HttpApplication)sender).Context;
                          ctx.Response.ContentType = MimeMapping.GetMimeMapping(parts[1]);
                          ctx.Response.Headers.Add("last-modified", new DateTime(2000, 01, 01).ToUniversalTime().ToString("R"));
      
                          byte[] buffer = new byte[stream.Length / 2];
                          stream.Read(buffer, 0, buffer.Length);
                          ctx.Response.BinaryWrite(buffer);
      
                          buffer = new byte[stream.Length - buffer.Length];
                          stream.Read(buffer, 0, buffer.Length);
                          ctx.Response.BinaryWrite(buffer);
                          ctx.Response.End();
                      }
                  }
              }
          }
      }
      

      【讨论】:

      • 嗨,谢谢,但这个问题已经有了答案 - 我们早在 2012 年就找到了解决方案,这是由于用于解析路由的正则表达式。
      • 好的,但它更快,它可能会帮助其他需要它的人更快,例如对于图像不在驱动器上的相同 URL 中的项目列表。甚至没有一条路线的新 MVC 项目也没有那么快。
      • 接受的答案应用于整个路由表,这个调用返回一个静态文件。是的,对于要重写到静态文件的路由的特定情况,这比 MVC 应用程序更快,但我的用例是 MVC 应用程序,我怀疑还有更快的方法可以再次路由到静态文件。
      • 我也想留在 MVC 中,但找不到更快的解决方案。 MVC 是在此之上的一个附加层,它几乎没有那么快。
      猜你喜欢
      • 1970-01-01
      • 2015-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-03
      相关资源
      最近更新 更多