Есть ли способ сделать недействительной 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());
    }
}

Хитрая проблема действительно!

Другие вопросы по тегам