カスタム属性を使用して、実行時に外部データソースから入力できるモデルプロパティの選択コントロールを作成するASP.NETMVC3アプリケーションがあります。問題は、EditorTemplate
出力がアプリケーションレベルでキャッシュされているように見えるため、データソースが変更されても、アプリケーションプールがリサイクルされるまでドロップダウンリストが更新されないことです。
System.Web.Mvc.Html.TemplateHelpers.cs:95のMVC 3ソースコードに示されているようにActionCache
、オブジェクトにバインドされているMVC3のコンテンツも出力しました。ViewContext.HttpContext
- アクションキャッシュGUID
adf284af-01f1-46c8-ba15-ca2387aaa8c4
:: - アクションキャッシュコレクションタイプ:
System.Collections.Generic.Dictionary``2[System.String,System.Web.Mvc.Html.TemplateHelpers+ActionCacheItem]
- アクションキャッシュディクショナリキー:
EditorTemplates/Select
したがって、Select
エディターテンプレートは確実にキャッシュされているように見えます。これにより、TemplateHelper.ExecuteTemplate
メソッドは2回目の呼び出しではなく、常にキャッシュされた値を返します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
、ダブルチェックしただけで、LINQSelect
メソッドはFunc
オブジェクトのみを取得します。
追加の提案はありますか?