ModelMetadata TemplateHint всегда нулевой
Я пытаюсь создать кастом 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)
У меня есть viewModel со свойством типа string
это включает в себя AutocompleteAttribute
как это:
public class MyViewModel
{
[Autocomplete]
public string MyProperty { get; set; }
}
Когда я использую эту модель представления в моем представлении, я проверяю сгенерированный HTML, и я получаю <input>
тег с таким атрибутом: data-autocomplete-url="UrlPlaceholder"
,
Далее я хочу указать URL-адрес в моем представлении, который использует мою viewModel, следующим образом:
@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();
,
Я хочу вставить URL-адрес, который будет использоваться виджетом автозаполнения пользовательского интерфейса JQuery, в 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
система?
1 ответ
Изучив исходный код ASP.NET MVC 3, я обнаружил, что причина этого в том, что CreateMetadata
метод вызывается до OnMetadataCreated
метод любого IMetadataAware
атрибуты, которые применяются к модели.
Я нашел альтернативное решение, которое позволяет мне делать то, что я хотел.
Прежде всего, я обновил свой 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);
}
}
}
и мой вспомогательный метод Html для установки URL в моих представлениях выглядит так:
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)