ASP.NET MVC - привязка пользовательской модели по типу интерфейса

Я не уверен, ожидается ли такое поведение или нет, но кажется, что привязка пользовательской модели не работает, когда привязка назначается типу интерфейса. Кто-нибудь с этим экспериментировал?

public interface ISomeModel {}
public class SomeModel : ISomeModel {}

public class MvcApplication : HttpApplication {
    protected void Application_Start(object sender, EventArgs e) {
        ModelBinders.Binders[typeof(ISomeModel)] = new MyCustomModelBinder();
    }
}

С помощью приведенного выше кода, когда я привязываюсь к модели типа SomeModel, MyCustomModelBinder никогда не выполняется; однако, если я изменю приведенный выше код и заменю typeof(ISomeModel) на typeof(SomeModel) и отправлю ту же самую форму, MyCustomModelBinder будет вызван должным образом. Это кажется правильным?


Изменить

Я снова оказался в этом затруднительном положении через год после того, как изначально задал этот вопрос, и теперь у меня есть работающее решение. Спасибо, Мэтт Хидингер!

http://www.matthidinger.com/archive/2011/08/16/An-inheritance-aware-ModelBinderProvider-in-MVC-3.aspx


person Nathan Taylor    schedule 04.06.2010    source источник


Ответы (3)


Я не уверен, что это напрямую связано, но да, есть вещи, о которых вам нужно подумать при использовании привязки модели и интерфейсов ... Я столкнулся с аналогичными проблемами с привязкой модели по умолчанию, но это может не быть напрямую связано в зависимости от того, как ты делаешь что-то ...

Взгляните на следующее: ASP.net MVC v2 - Проблемы с привязкой модели отладки - ОШИБКА? ASP.net MVC v2 - Проблемы с привязкой модели отладки - ОШИБКА?

person anthonyv    schedule 04.06.2010
comment
Моя структура наследования немного отличается от вашей, но похоже, что это может быть связанной проблемой. Я должен думать, что система привязки модели может сделать вывод, что тип, реализующий интерфейс, также должен быть предметом привязки модели, назначенной этому интерфейсу, если нет привязки другого типа к более конкретному типу, чем интерфейс (тот же класс, или базовый класс). Думаю, я понимаю, почему это серая зона. - person Nathan Taylor; 04.06.2010
comment
Это тоже было у меня чувство ... Я ожидал, что это просто сработает, но оказалось, что при работе с интерфейсами таким образом в целом с asp.net MVC (хотя это строго не ограничение MVC, которое они могли бы разрешить, если бы захотели ) вы, вероятно, столкнетесь с проблемами / неожиданным поведением, если не будете думать об этих проблемах интерфейса ... отсюда серая зона ... - person anthonyv; 04.06.2010

Я экспериментировал с этой проблемой и нашел своего рода решение. Я создал класс InterfaceModelBinder:

public class InterfaceModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        ModelBindingContext context = new ModelBindingContext(bindingContext);
        var item = Activator.CreateInstance(
            Type.GetType(controllerContext.RequestContext.HttpContext.Request.Form["AssemblyQualifiedName"]));

        Func<object> modelAccessor = () => item;
        context.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(),
            bindingContext.ModelMetadata.ContainerType, modelAccessor, item.GetType(), bindingContext.ModelName);

        return base.BindModel(controllerContext, context);
    }
}

Что я зарегистрировал в моем Application_Start так:

ModelBinders.Binders.Add(typeof(IFormSubmission), new InterfaceModelBinder.Models.InterfaceModelBinder());

Интерфейс и конкретная реализация выглядят так:

public interface IFormSubmission
{
}

public class ContactForm : IFormSubmission
{
    public string Name
    {
        get;
        set;
    }

    public string Email
    {
        get;
        set;
    }

    public string Comments
    {
        get;
        set;
    }
}

Единственным недостатком всего этого подхода (как вы, возможно, уже догадались) является то, что мне нужно откуда-то получить AssemblyQualifiedName, и в этом примере оно сохраняется как скрытое поле на стороне клиента, например:

<%=Html.HiddenFor(m => m.GetType().AssemblyQualifiedName) %>

Я не уверен, что недостатки раскрытия имени типа клиенту стоят потери преимуществ этого подхода. Подобное действие может обрабатывать все отправленные мной формы:

[HttpPost]
public ActionResult Process(IFormSubmission form)
{
    if (ModelState.IsValid)
    {
        FormManager manager = new FormManager();
        manager.Process(form);
    }

    //do whatever you want
}

Есть мысли по поводу этого подхода?

person Nathan Anderson    schedule 03.07.2010
comment
Похоже, это поможет, но, как вы сказали, имя сборки в вашем типе похоже на то, что вы напрашиваетесь на проблемы. В настоящее время я не уверен в правильном варианте использования его злонамеренно, но мне это кажется странным. - person Nathan Taylor; 04.07.2010
comment
Возможно, решением этой проблемы было бы хеширование или шифрование имени типа на клиенте, а затем определение этого значения в привязке модели. - person Nathan Anderson; 04.07.2010
comment
Просто то, на что я потратил час, стоит упомянуть другим - префикс модели, похоже, не используется в этой реализации, поскольку он не был настроен на новый контекст привязки. Поэтому я предлагаю использовать исходный параметр bindingContext, в нем новые ModelMetadata, работает как шарм. - person andkorsh; 21.11.2019

Вдруг появляется решение MVC3:

http://www.matthidinger.com/archive/2011/08/16/An-inheritance-aware-ModelBinderProvider-in-MVC-3.aspx

person Nathan Taylor    schedule 19.10.2011