Я понял, это возможно!
Чтобы уточнить, мне нужно было: преобразовать строку или массив строк (из идентификаторов) в структуру JSON для значения моего скрытого поля, затем при обратной отправке десериализовать JSON в скрытом поле и преобразовать структуру обратно в простое строка или строковый массив (из идентификаторов) для свойства объекта моего домена.
Шаг 1. Создание помощника HTML
Я уже сделал это, но только для того, чтобы принять мой собственный тип AutoCompleteModel. Мне нужен был один для строки и Enumerable типа string.
Все, что я сделал, это сгенерировал мои структуры Person из значения свойства и сериализовал их в JSON для скрытого поля, используемого Autocompleter (это пример помощника string
, у меня также есть почти идентичный один для IEnumerable<string>
):
public static MvcHtmlString AutoComplete<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, string>> idProp)
where TModel : class
{
TModel model = htmlHelper.ViewData.Model;
string id = idProp.Compile().Invoke(model);
string propertyName = idProp.GetPropertyName();
Person[] people = new Person[] {
new Person() { ID = id }
};
// Don't name the textbox the same name as the property,
// otherwise the value will be whatever the textbox is,
// if you care.
MvcHtmlString textBox = htmlHelper.TextBox(propertyName + "_ac", string.Empty);
// For me, the JSON is the value I want to postback
MvcHtmlString hidden = htmlHelper.Hidden(propertyName, new JavaScriptSerializer().Serialize(people));
return MvcHtmlString.Create(
"<span class=\"AutoComplete\">" +
textBox.ToHtmlString() +
hidden.ToHtmlString() +
"</span>");
}
Использование: @Html.AutoComplete(model => model.ID)
Шаг 2. Создание пользовательского связующего для модели
Суть моей проблемы заключалась в том, что мне нужно, чтобы это связыватель применялось только к определенным свойствам, а это были строки или массивы строк.
Я был вдохновлен этой статьей , потому что она использовала Generics. Я решил, эй, мы можем просто спросить людей, на какую недвижимость они хотят подать заявку на переплет.
public class AutoCompleteBinder<T> : DefaultModelBinder
where T : class
{
private IEnumerable<string> PropertyNames { get; set; }
public AutoCompleteBinder(params Expression<Func<T, object>>[] idProperties)
{
this.PropertyNames = idProperties.Select(x => x.GetPropertyName());
}
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (submittedValue != null && this.PropertyNames.Contains(propertyDescriptor.Name))
{
string json = submittedValue.AttemptedValue;
Person[] people = new JavaScriptSerializer().Deserialize<Person[]>(json);
if (people != null && people.Any())
{
string[] IDs = people.Where(x => !string.IsNullOrEmpty(x.ID)).Select(x => x.ID).ToArray();
bool isArray = bindingContext.ModelType != typeof(string) &&
(bindingContext.ModelType == typeof(string[]) ||
bindingContext.ModelType.HasInterface<IEnumerable>());
if (IDs.Count() == 1 && !isArray)
return IDs.First(); // return string
else if (IDs.Count() > 0 && isArray)
return IDs.ToArray(); // return string[]
else
return null;
}
else
{
return null;
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
GetPropertyName()
(перевести выражение LINQ в строку, т.е. m => m.ID
= ID
) и HasInterface()
- это всего лишь два служебных метода, которые у меня есть.
Шаг 3: Регистрация
Зарегистрируйте подшивку объектов вашего домена и их свойства в Application_Start
:
ModelBinders.Binders.Add(typeof(Employee), new AutoCompleteBinder<Employee>(e => e.ID, e => e.TeamIDs));
Просто немного раздражает необходимость регистрировать подшивку для определенных свойств, но это не конец света и обеспечивает приятный, плавный опыт работы с моим автозаполнением.
Любые комментарии приветствуются.