我有一个ASP.NET MVC 3应用程序,它使用自定义属性为模型属性创建选择控件,这些控件可以在运行时从外部数据源填充。问题是我的EditorTemplate
输出似乎在应用程序级别进行了缓存,因此我的下拉列表在数据源发生更改时不会更新,直到应用程序池被回收。
我还输出了绑定到ActionCache
对象的MVC 3 ViewContext.HttpContext
的内容,如System.Web.Mvc.Html.TemplateHelpers.cs中的MVC 3源代码所示: 95。
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
个对象。
还有其他建议吗?
答案 0 :(得分:0)
解决了问题!
我终于有机会重新访问这个问题了。根本原因与LINQ
,ActionCache
或ObjectContext
无关,而是与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());
}
}
确实很棘手!