Получение большей "детализации" от MVC Mini Profiler
Если это окажется полезным для всех, я с радостью превращу его в вики-сообщество сообщества.
У меня есть несколько медленных страниц в приложении MVC3, и, поскольку в моем коде было мало времени выполнения, я хотел посмотреть, могу ли я узнать больше о том, что заняло так много времени. Не то, чтобы я преуспел, но я получил немного больше мудрости на этом пути.
Здесь ничего нет, что не очевидно никому с опытом MVC. В принципе, я создал свой собственный ActionFilterAttribute, который выглядит так:
public class ProfilerAttribute : ActionFilterAttribute
{
IDisposable actionStep = null;
IDisposable resultStep = null;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
actionStep = MiniProfiler.Current.Step("OnActionExecuting " + ResultDescriptor(filterContext));
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (actionStep != null)
{
actionStep.Dispose();
actionStep = null;
}
base.OnActionExecuted(filterContext);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
resultStep = MiniProfiler.Current.Step("OnResultExecuting " + ResultDescriptor(filterContext));
base.OnResultExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (resultStep != null)
{
resultStep.Dispose();
resultStep = null;
}
base.OnResultExecuted(filterContext);
}
private string ResultDescriptor(ActionExecutingContext filterContext)
{
return filterContext.ActionDescriptor.ControllerDescriptor.ControllerName + "." + filterContext.ActionDescriptor.ActionName;
}
private string ResultDescriptor(ResultExecutingContext filterContext)
{
var values = filterContext.RouteData.Values;
return String.Format("{0}.{1}", values["controller"], values["action"]);
}
Кажется, что это работает хорошо, и в моем случае я узнал, что большую часть времени фактически тратится на часть выполнения ResultExecuting, а не внутри моих действий.
Однако у меня есть некоторые вопросы об этом подходе.
1) Является ли это безопасным для пользователя способом? Я предполагаю, что нет, так как actionfilter создается только один раз, в методе RegisterGlobalFilters() в Global.asax.cs. Если сразу появятся два запроса, actionStep и resultStep будут бесполезны. Это правда? Если да, может ли кто-то, кто знает больше, чем я, способствовал бы умному способу справиться с этим? Работает для меня во время локального профилирования машины, но, вероятно, не так много развертывается на сервере с несколькими людьми, делающими запросы одновременно.
2) Есть ли способ получить более полное представление о процессе выполнения результата? Или я должен просто согласиться с тем, что визуализация представления и т.д. Требует времени? В моем собственном приложении я гарантирую, что весь доступ к базе данных закончен до того, как мой метод действия закончен (с использованием NHibernate Profiler в моем случае), и я хотел бы, чтобы мои представления были тонкими и простыми; Однако любое понимание того, что замедляет рендеринг, все равно может быть полезным. Думаю, использование Mini Profiler в моих объектах модели появилось бы здесь, если бы был выполнен какой-то медленный код с моей стороны.
3) Методы ResultDescriptor, вероятно, являются злыми и ядовитыми. Они работали на меня в моих тестах, но, вероятно, их нужно было заменить чем-то более надежным. Я просто пошел с первыми версиями, которые дали мне что-то наполовину полезное.
Любые другие комментарии к этому также будут очень желанными, даже если они "Это плохая идея, иди умереть в одиночку".
Ответы
Ответ 1
Это выглядит как классная идея. Я считаю, что это НЕ - безопасный способ делать вещи.
Вы можете связать его с HttpContext.Items
следующим образом
HttpContext.Items.Add("actionstep", actionStep);
HttpContext.Items.Add("resultstep", resultStep);
И затем извлеките его аналогичным образом
actionStep = HttpContext.Items["actionstep"];
resultStep = HttpContext.Items["resultstep"];
Очевидно, что вы ставите свои собственные проверки на нули и т.д.
HttpContext
для каждого пользователя/запроса отличается.
Что нужно помнить о HttpContext.Current.Session.SessionID
, которое я иногда забываю, что это SessionId текущего HTTP-запроса (т.е. он меняется каждый раз, когда вы нажимаете F5 или иным образом создаете новый запрос). Другая важная вещь, которую следует помнить, заключается в том, что, хотя в любой момент времени все значения HttpContext.Current.Session.SessionID
обязательно уникальны (т.е. по одному для каждого пользователя или запроса), их можно повторно использовать, поэтому не думайте о них как о GUID, которые используется только один раз.
Ответ 2
В сборке MiniProfiler уже есть атрибут фильтра действий, который выполняет профилирование для действий. Он находится в пространстве имен StackExchange.Profiling.MVCHelpers и называется ProfilingActionFilter. Вы можете расширить его, чтобы также просмотреть свои представления.
Он использует тот же подход, что и описанный @Dommer, но вместо того, чтобы хранить IDisposable напрямую, он хранит Stack в HttpContext.Current.Items. Вы можете сделать то же самое для представлений.
Вот код для профилирования действия:
/// <summary>
/// This filter can be applied globally to hook up automatic action profiling
///
/// </summary>
public class ProfilingActionFilter : ActionFilterAttribute
{
private const string stackKey = "ProfilingActionFilterStack";
/// <summary>
/// Happens before the action starts running
///
/// </summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (MiniProfiler.Current != null)
{
Stack<IDisposable> stack = HttpContext.Current.Items[(object) "ProfilingActionFilterStack"] as Stack<IDisposable>;
if (stack == null)
{
stack = new Stack<IDisposable>();
HttpContext.Current.Items[(object) "ProfilingActionFilterStack"] = (object) stack;
}
MiniProfiler current = MiniProfiler.Current;
if (current != null)
{
RouteValueDictionary dataTokens = filterContext.RouteData.DataTokens;
string str1 = !dataTokens.ContainsKey("area") || string.IsNullOrEmpty(dataTokens["area"].ToString()) ? "" : (string) dataTokens["area"] + (object) ".";
string str2 = Enumerable.Last<string>((IEnumerable<string>) filterContext.Controller.ToString().Split(new char[1] { '.' })) + ".";
string actionName = filterContext.ActionDescriptor.ActionName;
stack.Push(MiniProfilerExtensions.Step(current, "Controller: " + str1 + str2 + actionName, ProfileLevel.Info));
}
}
base.OnActionExecuting(filterContext);
}
/// <summary>
/// Happens after the action executes
///
/// </summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
Stack<IDisposable> stack = HttpContext.Current.Items[(object) "ProfilingActionFilterStack"] as Stack<IDisposable>;
if (stack == null || stack.Count <= 0) return;
stack.Pop().Dispose();
}
}
Надеюсь на эту помощь.
Ответ 3
Вы можете просто обернуть метод ExecuteCore на контроллере.:)