3

我正在尝试创建一个自定义ModelMetadataProvider来为 JQuery UI 自动完成小部件提供不显眼的属性。

我有一个看起来像这样的自定义属性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class AutocompleteAttribute : Attribute, IMetadataAware
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.TemplateHint = "Autocomplete";
    }
}

和一个如下所示的编辑器模板:

@{
    var attributes = new RouteValueDictionary
    {
        {"class", "text-box single-line"},
        {"autocomplete", "off"},
        {"data-autocomplete-url", "UrlPlaceholder" },
    };
}

@Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)

我有一个视图模型,其类型属性string包括AutocompleteAttribute以下内容:

public class MyViewModel
{
    [Autocomplete]
    public string MyProperty { get; set; }
}

当我在我的视图中使用这个 viewModel 时,我检查生成的 html 并且我得到一个<input>具有如下属性的标签:data-autocomplete-url="UrlPlaceholder".

我接下来要做的是能够在我的视图中指定使用我的 viewModel 的 URL,如下所示:

@model MyViewModel

@{ ViewBag.Title = "Create item"; }

@Html.AutoCompleteUrlFor(p => p.MyProperty, UrlHelper.GenerateUrl(null, "Autocomplete", "Home", null, Html.RouteCollection, Html.ViewContext.RequestContext, true))

// Other stuff here...

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

我的AutoCompleteForUrl助手只是将生成的 URL 保存在字典中,使用属性名称作为键。

接下来我创建了一个自定义ModelMetadataProvider并使用这行代码在 global.asax 中注册它ModelMetadataProviders.Current = new CustomModelMetadataProvider();

我想要做的是将要由 JQuery UI 自动完成小部件使用的 URL 插入到要metadata.AdditionalValues由自动完成编辑器模板使用的字典中。

我的自定义ModelMetadataProvider看起来像这样:

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<System.Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

        if (metadata.TemplateHint == "Autocomplete")
        {
            string url;
            if(htmlHelpers.AutocompleteUrls.TryGetValue(metadata.propertyName, out url)
            {
                metadata.AdditionalValues["AutocompleteUrl"] = url;
            }
        }

        return metadata;
    }
}

我更新的编辑器模板如下所示:

@{
    object url;

    if (!ViewContext.ViewData.ModelMetadata.TryGetValue("AutocompleteUrl", out url))
    {
        url = "";
    }

    var attributes = new RouteValueDictionary
    {
        {"class", "text-box single-line"},
        {"autocomplete", "off"},
        {"data-autocomplete-url", (string)url },
    };
}

@Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)

问题是,TemplateHint在我的自定义模型元数据提供程序中,该属性永远不会等于“自动完成”,因此我生成 URL 的逻辑永远不会被调用。我会认为此时该属性将被设置为TemplateHint我称之为CreateMetadata.DataAnnotationsModelMetadataProvider

这是我可以确认的:

  • CustomModelMetadataProvider正确注册,因为它包含正在调用的其他代码。
  • 由于生成的 Html 包含一个名为"data-autocomplete-url".
  • 如果我在自动完成模板中放置一个断点,Visual Studio 会转到调试器。

那么任何人都可以为我解释一下吗?我对ModelMetadataProvider系统有什么误解?

4

1 回答 1

3

在查看了 ASP.NET MVC 3 源代码后,我发现这是因为该方法是在应用于模型的任何属性CreateMetadata的方法之前调用的。OnMetadataCreatedIMetadataAware

我找到了一种替代解决方案,可以让我做我想做的事。

首先我更新了我的AutocompleteAttribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class AutocompleteAttribute : Attribute, IMetadataAware
{
    public const string Key = "autocomplete-url";
    internal static IDictionary<string, string> Urls { get; private set; }

    static AutocompleteAttribute()
    {
        Urls = new Dictionary<string, string>();
    }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.TemplateHint = "Autocomplete";

        string url;

        if (Urls.TryGetValue(metadata.PropertyName, out url))
        {
            metadata.AdditionalValues[Key] = url;
            Urls.Remove(metadata.PropertyName);
        }
    }
}

我在视图中设置 url 的 Html 辅助方法如下所示:

public static IHtmlString AutocompleteUrlFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string url)
{
    if (string.IsNullOrEmpty(url))
        throw new ArgumentException("url");

    var property = ModelMetadata.FromLambdaExpression(expression, html.ViewData).PropertyName;
    AutocompleteAttribute.Urls[property] = url;

    return MvcHtmlString.Empty;
}

然后我在编辑器模板中要做的就是:

@{
    object url;
    ViewData.ModelMetadata.AdditionalValues.TryGetValue(AutocompleteAttribute.Key, out url);

    var attributes = new RouteValueDictionary
    {
        {"class", "text-box single-line"},
        {"autocomplete", "off"},
        { "data-autocomplete-url", url },
    };
}

@Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)
于 2013-04-17T20:01:11.507 回答