我有一个 ASP.NET MVC 3 应用程序,它使用自定义属性为模型属性创建选择控件,这些属性可以在运行时从外部数据源填充。问题是我的EditorTemplate
输出似乎在应用程序级别缓存,因此当它们的数据源更改时,我的下拉列表不会更新,直到应用程序池被回收。
我还输出了ActionCache
绑定到ViewContext.HttpContext
对象的 MVC 3 的内容,如 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
生命周期。虽然存在一些小问题,但该问题似乎与实现中的 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
,但我只是仔细检查了,LINQSelect
方法只接受Func
对象。
有什么额外的建议吗?