Первый вызов Url.Action на странице медленный

У меня проблема с производительностью при довольно простом представлении ASP.MVC.

Это страница входа в систему, которая должна быть почти мгновенной, но занимает около половины секунды.

После долгих копаний похоже, что проблема заключается в первом вызове Url.Action - это занимает около 450 мс (по данным MiniProfiler), но это кажется безумно медленным.

Последующие звонки Url.Action принимают <1 мс, что больше соответствует тому, что я ожидал.

Это соответствует ли я Url.Action("action", "controller") или же Url.Action("action"), но, кажется, не произойдет, если я использую Url.Content("~/controller/action"), Это также происходит, когда я звоню Html.BeginForm("action"),

Кто-нибудь знает, что это вызывает?

Копание в источнике предполагает, что RouteCollection.GetVirtualPath может быть виновником, так как это является общим для обоих Url.Action а также Html.BeginForm, Однако, конечно, это используется повсеместно? Я имею в виду, ½ секунды слишком медленно.

У меня есть около 20 пользовательских маршрутов (это довольно большое приложение с некоторыми устаревшими страницами WebForms), но даже тогда времена кажутся слишком медленными.

Есть идеи как это исправить?

3 ответа

Решение

Проблема найдена, и это с таблицами маршрутизации (ура Кирилл).

В основном у нас есть много маршрутов, которые выглядят примерно так:

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) + ")" });

Оказывается, проверка Regex очень медленная, мучительно медленная. Поэтому я заменил его реализацией 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, упомянутый в этой связанной статье, для чего-то более сложного - включая множество таких вызовов (потому что у нас есть устаревшие страницы WebForm):

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

Эти два простых изменения полностью решают проблему; Url.Action а также Html.BeginForm теперь занимает незначительное количество времени (даже с большим количеством маршрутов).

    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 + ")";
    }
}

Мне кажется, что ваша проблема заключается в составлении взглядов. Вам нужно предварительно скомпилировать представления о сборке, и эта проблема исчезнет. подробности здесь

Я разделил его до "голого"... установил один файл в память и загрузил его из экшена по сравнению с загрузкой из IHttpModule. IHttpModule намного быстрее (для небольших файлов, например, изображений списка продуктов) по какой-то причине (вероятно, загрузка конвейера MVC, маршрутизация). У меня нет регулярного выражения, используемого в маршрутизации (это еще больше замедляет его). В 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();
                }
            }
        }
    }
}
Другие вопросы по тегам