1

我有一个 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对象。

有什么额外的建议吗?

4

1 回答 1

0

问题解决了!

我终于有机会重新审视这个问题。根本原因与LINQ、 theActionCache或 the无关ObjectContext,而是与何时调用属性构造函数有关。

如图所示,我的自定义SelectAttribute类在其构造函数中调用DependencyResolver.Current.GetService以创建ISelectProvider该类的实例。但是,ASP.NET MVC 框架会扫描一次程序集以查找自定义元数据属性,并在应用程序范围内保留对它们的引用。如链接问题中所述,访问 aAttribute会触发其构造函数。

因此,构造函数只运行一次,而不是像我假设的那样在每个请求上运行。这意味着实际上只有一个被实例化的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 回答