有没有办法使ASP.NET MVC 3 ActionCache无效

时间:2012-12-14 15:39:34

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

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

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

  • 操作缓存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。< / p>

有没有办法清除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个对象。

还有其他建议吗?

1 个答案:

答案 0 :(得分:0)

解决了问题!

我终于有机会重新访问这个问题了。根本原因与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());
    }
}
确实很棘手!