1

カスタム属性を使用して、実行時に外部データソースから入力できるモデルプロパティの選択コントロールを作成する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オブジェクトのみを取得します。

追加の提案はありますか?

4

1 に答える 1

0

問題が解決しました!

私はついにこの問題を再検討する機会がありました。根本的な原因は、、、、またはとは関係がなくLINQ属性コンストラクターが呼び出されるタイミングに関連していました。ActionCacheObjectContext

示されているように、私のカスタムSelectAttributeクラスはDependencyResolver.Current.GetService、そのコンストラクターを呼び出して、クラスのインスタンスを作成しますISelectProvider。ただし、ASP.NET MVCフレームワークは、カスタムメタデータ属性のアセンブリを1回スキャンし、それらへの参照をアプリケーションスコープに保持します。リンクされた質問で説明されているように、Attributeトリガーにアクセスするとコンストラクターがトリガーされます。

したがって、コンストラクターは、私が想定したように、各要求ではなく、1回だけ実行されました。つまり、インスタンス化されたクラスのキャッシュされたインスタンスは、実際には1つだけであり、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());
    }
}

確かにトリッキーな問題!

于 2013-06-06T12:55:26.283 に答える