【发布时间】: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