Модель ASP.NET MVC привязки внешнего ключа

Можно ли связать отношения внешнего ключа в моей модели с вводом формы?

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

Моя подпись метода действия выглядит так:

public JsonResult Save(int id, [Bind(Include="Name, Description, Manufacturer")]Car car)

Форма публикует значения Имя, Описание и Производитель, где Производитель — это первичный ключ типа int. Имя и описание устанавливаются правильно, но не производитель, что имеет смысл, поскольку связыватель модели не знает, что такое поле PK. Означает ли это, что мне придется написать собственный IModelBinder, чтобы он знал об этом? Я не уверен, как это будет работать, поскольку мои репозитории доступа к данным загружаются через контейнер IoC в каждом конструкторе Controller.


person roryf    schedule 19.03.2009    source источник


Ответы (3)


Вот мое мнение: это пользовательское связывание модели, которое при запросе GetPropertyValue проверяет, является ли свойство объектом из моей сборки модели, и имеет ли IRepository‹> зарегистрированный в моем NInject IKernel. Если он может получить IRepository из Ninject, он использует его для получения объекта внешнего ключа.

public class ForeignKeyModelBinder : System.Web.Mvc.DefaultModelBinder
{
    private IKernel serviceLocator;

    public ForeignKeyModelBinder( IKernel serviceLocator )
    {
        Check.Require( serviceLocator, "IKernel is required" );
        this.serviceLocator = serviceLocator;
    }

    /// <summary>
    /// if the property type being asked for has a IRepository registered in the service locator,
    /// use that to retrieve the instance.  if not, use the default behavior.
    /// </summary>
    protected override object GetPropertyValue( ControllerContext controllerContext, ModelBindingContext bindingContext,
        PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder )
    {
        var submittedValue = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
        if ( submittedValue == null )
        {
            string fullPropertyKey = CreateSubPropertyName( bindingContext.ModelName, "Id" );
            submittedValue = bindingContext.ValueProvider.GetValue( fullPropertyKey );
        }

        if ( submittedValue != null )
        {
            var value = TryGetFromRepository( submittedValue.AttemptedValue, propertyDescriptor.PropertyType );

            if ( value != null )
                return value;
        }

        return base.GetPropertyValue( controllerContext, bindingContext, propertyDescriptor, propertyBinder );
    }

    protected override object CreateModel( ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType )
    {
        string fullPropertyKey = CreateSubPropertyName( bindingContext.ModelName, "Id" );
        var submittedValue = bindingContext.ValueProvider.GetValue( fullPropertyKey );
        if ( submittedValue != null )
        {
            var value = TryGetFromRepository( submittedValue.AttemptedValue, modelType );

            if ( value != null )
                return value;
        }

        return base.CreateModel( controllerContext, bindingContext, modelType );
    }

    private object TryGetFromRepository( string key, Type propertyType )
    {
        if ( CheckRepository( propertyType ) && !string.IsNullOrEmpty( key ) )
        {
            Type genericRepositoryType = typeof( IRepository<> );
            Type specificRepositoryType = genericRepositoryType.MakeGenericType( propertyType );

            var repository = serviceLocator.TryGet( specificRepositoryType );
            int id = 0;
#if DEBUG
            Check.Require( repository, "{0} is not available for use in binding".FormatWith( specificRepositoryType.FullName ) );
#endif
            if ( repository != null && Int32.TryParse( key, out id ) )
            {
                return repository.InvokeMethod( "GetById", id );
            }
        }

        return null;
    }

    /// <summary>
    /// perform simple check to see if we should even bother looking for a repository
    /// </summary>
    private bool CheckRepository( Type propertyType )
    {
        return propertyType.HasInterface<IModelObject>();
    }

}

вы, очевидно, могли бы заменить Ninject для вашего контейнера DI и вашего собственного типа репозитория.

person Dave Thieben    schedule 04.06.2010
comment
Очень полезный пример! Одна идея, которую я могу предложить, заключается в использовании интерфейса IModelBinderProvider для нацеливания этого связывателя модели на ваши типы моделей вместо проверки внутри связывателя. Брэд Уилсон написал об этом здесь . - person Derek Morrison; 02.05.2011
comment
да, это было бы здорово. Однако я еще не обновился до MVC3. - person Dave Thieben; 03.05.2011

Конечно, у каждого автомобиля есть только один производитель. Если это так, то у вас должно быть поле ManufacturerID, к которому вы можете привязать значение выбора. То есть ваш выбор должен иметь имя производителя в качестве текста и идентификатор в качестве значения. В вашем сохраненном значении привяжите ManufacturerID, а не производителя.

<%= Html.DropDownList( "ManufacturerID",
        (IEnumerable<SelectListItem>)ViewData["Manufacturers"] ) %>

С участием

ViewData["Manufacturers"] = db.Manufacturers
                              .Select( m => new SelectListItem
                                            {
                                               Text = m.Name,
                                               Value = m.ManufacturerID
                                            } )
                               .ToList();

И

public JsonResult Save(int id,
                       [Bind(Include="Name, Description, ManufacturerID")]Car car)
person tvanfosson    schedule 19.03.2009
comment
Если модель построена с использованием POCO, наличие свойства ManufacturerID в Car мне кажется неправильным. Действительно ли это предпочтительный способ решения такой привязки модели? - person Jørn Schou-Rode; 08.08.2009
comment
Я не уверен, что ты имеешь в виду. Обычно у меня есть поле внешнего ключа, чтобы связать сущности автомобиля и производителя. Это довольно стандартно, чтобы это было поле идентификатора. Я предполагаю, что вы могли бы не показывать это поле в своей модели, но вы, конечно, могли бы. Я обычно использую LINQtoSQL, и я могу заверить вас, что в сущности Car будет как свойство ManufacturerID, так и связанная с ним сущность производителя. - person tvanfosson; 08.08.2009

Может быть, это поздно, но для этого вы можете использовать специальную привязку модели. Обычно я делаю это так же, как @tvanofosson, но у меня был случай, когда я добавлял UserDetails в таблицы AspNetMembershipProvider. Поскольку я также использую только POCO (и сопоставляю его с EntityFramework), я не хотел использовать идентификатор, потому что это не было оправдано с точки зрения бизнеса, поэтому я создал модель только для добавления/регистрации пользователей. Эта модель имела все свойства для пользователя, а также свойство роли. Я хотел связать текстовое имя роли с ее представлением RoleModel. Это в основном то, что я сделал:

public class RoleModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string roleName = controllerContext.HttpContext.Request["Role"];

        var model = new RoleModel
                          {
                              RoleName = roleName
                          };

        return model;
    }
}

Затем мне пришлось добавить в Global.asax следующее:

ModelBinders.Binders.Add(typeof(RoleModel), new RoleModelBinder());

И использование в представлении:

<%= Html.DropDownListFor(model => model.Role, new SelectList(Model.Roles, "RoleName", "RoleName", Model.Role))%>

Я надеюсь, это поможет вам.

person Maksymilian Majer    schedule 08.04.2010
comment
Я повторно разместил это как вопрос выпадающий список не выбирает правильное значение "> stackoverflow.com/questions/3642870/. - person nfplee; 04.09.2010