【问题标题】:Is there a way to invalidate the ASP.NET MVC 3 ActionCache有没有办法使 ASP.NET MVC 3 ActionCache 失效
【发布时间】:2012-12-14 15:39:34
【问题描述】:

我有一个 ASP.NET MVC 3 应用程序,它使用自定义属性为模型属性创建选择控件,这些属性可以在运行时从外部数据源填充。问题是我的EditorTemplate 输出似乎在应用程序级别缓存,因此当它们的数据源更改时,我的下拉列表不会更新,直到应用程序池被回收。

我还输出了绑定到 ViewContext.HttpContext 对象的 MVC 3 ActionCache 的内容,如 System.Web.Mvc.Html.TemplateHelpers.cs:95 中的 MVC 3 源代码所示。

  • 动作缓存 GUID:adf284af-01f1-46c8-ba15-ca2387aaa8c4
  • 动作缓存集合类型: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 生命周期。虽然存在一些小问题,但该问题似乎与 SelectProvider 实现中的 LINQ 表达式中涉及回调的奇怪行为有关。

这里是相关代码。

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 对象。

还有其他建议吗?

【问题讨论】:

  • 这应该只是单个 HTTP 请求上下文中的问题。动作缓存存储在 HttpContextBase.Items 中,其中"is used to share data between a module and a handler during an HTTP request"。你能提供 ISelectorProvider 的实际实现吗?您是否可能正在缓存模型元数据或 SelectList 数据?
  • 你是对的。我已经确认我的简化示例按照发布的方式工作。我正试图进一步隔离这个问题。我们的项目使用 EntityFramework 从数据库中查询,并使用 Ninject 作为 DI 框架。
  • 您是否已确认您为 DbContext 使用了正确的(按请求)对象范围?
  • 它不是,但现在 DbContext 被创建为 .InRequestScope(),但在某个地方很早就被处理掉了。感谢您的帮助。
  • repository.GetTable()和repository.Table有什么区别?

标签: asp.net-mvc-3 razor mvc-editor-templates asp.net-mvc-views


【解决方案1】:

问题解决了!

我终于有机会重新审视这个问题。根本原因与LINQActionCacheObjectContext无关,而是与when attribute constructors are called有关。

如图所示,我的自定义SelectAttribute 类在其构造函数中调用DependencyResolver.Current.GetService 以创建ISelectProvider 类的实例。但是,ASP.NET MVC 框架会扫描一次程序集以查找自定义元数据属性,并在应用程序范围内保留对它们的引用。如链接问题中所述,访问 Attribute 会触发其构造函数。

因此,构造函数只运行一次,而不是在每个请求上运行,as I had assumed。这意味着实际上只有一个被实例化的 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());
    }
}

确实是个棘手的问题!

【讨论】:

    猜你喜欢
    • 2011-05-28
    • 2010-10-14
    • 2018-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    相关资源
    最近更新 更多