Есть ли способ сделать недействительной ASPCNET MVC 3 ActionCache
У меня есть приложение ASP.NET MVC 3, которое использует настраиваемые атрибуты для создания элементов управления выбором для свойств модели, которые можно заполнять из внешних источников данных во время выполнения. Проблема в том, что мой EditorTemplate
выходные данные выглядят кэшированными на уровне приложения, поэтому мои раскрывающиеся списки не обновляются при изменении источника данных до тех пор, пока пул приложений не будет переработан.
Я также вывел содержимое MVC 3 ActionCache
это связано с ViewContext.HttpContext
объект, как показано в исходном коде MVC 3 в System.Web.Mvc.Html.TemplateHelpers.cs:95.
- GUID кэша действий:
adf284af-01f1-46c8-ba15-ca2387aaa8c4
: - Тип коллекции Action Cache:
System.Collections.Generic.Dictionary``2[System.String,System.Web.Mvc.Html.TemplateHelpers+ActionCacheItem]
- Ключи словаря кэша действий:
EditorTemplates/Select
Похоже, что Select
шаблон редактора определенно кэшируется, что приведет к TemplateHelper.ExecuteTemplate
метод всегда возвращать кэшированное значение вместо вызова ViewEngineResult.View.Render
второй раз.
Есть ли способ очистить MVC ActionCache или иным образом заставить механизм представления Razor всегда повторно визуализировать определенные шаблоны?
Для справки, Вот соответствующие компоненты платформы:
public interface ISelectProvider
{
IEnumerable<SelectListItem> GetSelectList();
}
public class SelectAttribute : Attribute, IMetadataAware
{
private readonly ISelectProvider _provider;
public SelectAttribute(Type type)
{
_provider = DependencyResolver.Current.GetService(type) as ISelectProvider;
}
public void OnMetadataCreated(ModelMetadata modelMetadata)
{
modelMetadata.TemplateHint = "Select";
modelMetadata.AdditionalValues.Add("SelectListItems", SelectList);
}
public IEnumerable<SelectListItem> SelectList
{
get
{
return _provider.GetSelectList();
}
}
}
Далее, есть пользовательский шаблон редактора в ~\Views\Shared\EditorTemplates\Select.cshtml
,
@model object
@{
var selectList = (IEnumerable<SelectListItem>)ViewData.ModelMetadata.AdditionalValues["SelectListItems"];
foreach (var item in selectList)
{
item.Selected = (item != null && Model != null && item.Value.ToString() == Model.ToString());
}
}
@Html.DropDownListFor(s => s, selectList)
Наконец, у меня есть модель представления, выберите класс поставщика и простое представление.
/** Providers/MySelectProvider.cs **/
public class MySelectProvider : ISelectProvider
{
public IEnumerable<SelectListItem> GetSelectList()
{
foreach (var item in System.IO.File.ReadAllLines(@"C:\Test.txt"))
{
yield return new SelectListItem() { Text = item, Value = item };
}
}
}
/** Models/ViewModel.cs **/
public class ViewModel
{
[Select(typeof(MySelectProvider))]
public string MyProperty { get; set; }
}
/** Views/Controller/MyView.cshtml **/
@model ViewModel
@using (Html.BeginForm())
{
@Html.EditorForModel()
<input type="submit" value="Submit" />
}
** РЕДАКТИРОВАТЬ **
Основываясь на предложениях в комментарии, я начал более внимательно смотреть на ObjectContext
жизненный цикл. В то время как были некоторые незначительные проблемы, проблема, кажется, изолирована к странному поведению, вовлекающему обратный вызов в выражении LINQ в SelectProvider
реализация.
Вот соответствующий код.
public abstract class SelectProvider<R, T> : ISelectProvider
where R : class, IQueryableRepository<T>
{
protected readonly R repository;
public SelectProvider(R repository)
{
this.repository = repository;
}
public virtual IEnumerable<SelectListItem> GetSelectList(Func<T, SelectListItem> func, Func<T, bool> predicate)
{
var ret = new List<SelectListItem>();
foreach (T entity in repository.Table.Where(predicate).ToList())
{
ret.Add(func(entity));
}
return ret;
}
public abstract IEnumerable<SelectListItem> GetSelectList();
}
public class PrinterSelectProvider : SelectProvider<IMyRepository, MyEntityItem>
{
public PrinterSelectProvider()
: base(DependencyResolver.Current.GetService<IMyRepository>())
{
}
public override IEnumerable<SelectListItem> GetSelectList()
{
// Create a sorted list of items (this returns stale data)
var allItems = GetSelectList(
x => new SelectListItem()
{
Text = x.DisplayName,
Value = x.Id.ToString()
},
x => x.Enabled
).OrderBy(x => x.Text);
// Do the same query, but without the callback
var otherItems = repository.Table.Where(x => x.Enabled).ToList().Select(x => new SelectListItem()
{
Text = x.DisplayName,
Value = x.Id.ToString()
}).OrderBy(x => x.Text);
System.Diagnostics.Trace.WriteLine(string.Format("Query 1: {0} items", allItems.Count()));
System.Diagnostics.Trace.WriteLine(string.Format("Query 2: {0} items", otherItems.Count()));
return allItems;
}
}
И, захваченный вывод из System.Diagnostics.Trace
является
Query 1: 2 items
Query 2: 3 items
Я не уверен, что здесь может пойти не так. Я считал, что Select
может понадобиться Expressions
, но я просто дважды проверил и LINQ Select
метод только берет Func
объекты.
Какие-нибудь дополнительные предложения?
1 ответ
Задача решена!
У меня наконец-то появилась возможность повторно посетить этот выпуск. Коренная причина не имеет ничего общего с LINQ
, ActionCache
, или ObjectContext
, скорее это было связано с тем, когда вызывались конструкторы атрибутов.
Как показано, мой обычай SelectAttribute
классовые звонки DependencyResolver.Current.GetService
в своем конструкторе, чтобы создать экземпляр ISelectProvider
учебный класс. Однако платформа ASP.NET MVC сканирует сборки на наличие пользовательских атрибутов метаданных один раз и сохраняет ссылку на них в области приложения. Как объясняется в связанном вопросе, доступ к Attribute
вызывает его конструктор.
Итак, конструктор запускался только один раз, а не на каждый запрос, как я предполагал. Это означало, что на самом деле был только один, кэшированный экземпляр PrinterSelectProvider
экземпляр класса, который был разделен между всеми запросами.
Я решил проблему, изменив SelectAttribute
класс как это:
public class SelectAttribute : Attribute, IMetadataAware
{
private readonly Type type;
public SelectAttribute(Type type)
{
this.type = type;
}
public void OnMetadataCreated(ModelMetadata metadata)
{
// Instantiate the select provider on-demand
ISelectProvider provider = DependencyResolver.Current.GetService(type) as ISelectProvider;
modelMetadata.TemplateHint = "Select";
modelMetadata.AdditionalValues.Add("SelectListItems", provider.GetSelectList());
}
}
Хитрая проблема действительно!