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

Если у меня есть следующее строго типизированное представление:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<XXX.DomainModel.Core.Locations.Location>" %>

Где Location - абстрактный класс.

И у меня есть следующий контроллер, который принимает строго типизированную модель через POST:

[HttpPost]
public ActionResult Index(Location model)

Я получаю сообщение об ошибке "Не удается создать абстрактный класс"

Что, конечно, имеет смысл. Однако - я не уверен, что лучшее решение здесь.

У меня есть много конкретных типов (около 8), и это представление, где вы можете редактировать только свойства абстрактного класса.

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

[HttpPost]
public ActionResult Index(City model)
{
   UpdateLocationModel(model);
   return View(model);
}

[HttpPost]
public ActionResult Index(State model)
{
   UpdateLocationModel(model);
   return View(model);
}

и т. д.

А потом:

[NonAction]
private void UpdateLocationModel (Location model)
{
   // ..snip - update model
}

Но это также не работает, MVC жалуется, что методы действий неоднозначны (также имеет смысл).

Что мы делаем? Можем ли мы просто не привязываться к абстрактной модели?

3 ответа

Решение

Как насчет написания пользовательского связывателя модели для этого абстрактного класса:

public class CustomBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        // TODO: based on some request parameter choose the proper child type
        // to instantiate here
        return new Child();
    }
}

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

Вы также можете создать общий ModelBinder, который работает для всех ваших абстрактных моделей. Мое решение требует, чтобы вы добавили скрытое поле в ваше представление с именем "ModelTypeName" со значением, установленным для имени конкретного типа, который вы хотите. Однако должна быть возможность сделать это умнее и выбрать конкретный тип, сопоставив свойства типа с полями в представлении.

В вашем файле Global.asax.cs в Application_Start ():

ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

CustomModelBinder:

public class CustomModelBinder2 : DefaultModelBinder 
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var modelType = bindingContext.ModelType;
        if (modelType.IsAbstract)
        {
            var modelTypeValue = controllerContext.Controller.ValueProvider.GetValue("ModelTypeName");
            if (modelTypeValue == null)
                throw new Exception("View does not contain ModelTypeName");

            var modelTypeName = modelTypeValue.AttemptedValue;

            var type = modelType.Assembly.GetTypes().SingleOrDefault(x => x.IsSubclassOf(modelType) && x.Name == modelTypeName);

            if (type != null)
            {
                var instance= bindingContext.Model ?? base.CreateModel(controllerContext, bindingContext, type);
                bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, type);
            }
        }
        return base.BindModel(controllerContext, bindingContext);
    }
}

Просто, чтобы бросить это там - меня очень интересует, что могут ответить другие, но это то, что я в итоге сделал в случае, когда у меня была похожая ситуация;

По сути, я не использовал класс модели в качестве параметра в методе Action, вместо этого передав FormCollection и тестирование пары известных дискриминаторов, чтобы выяснить, какой тип создавать / редактировать, а затем использовать TryUpdateModel оттуда.

Казалось, что может быть лучше, но я никогда не думал об этом больше.

Другие вопросы по тегам