Первый вызов 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();
}
}
}
}
}