Модель ASP.NET MVC, связывающая отношения внешнего ключа

StackOverflow https://stackoverflow.com/questions/662651

  •  20-08-2019
  •  | 
  •  

Вопрос

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

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

Моя сигнатура метода действия выглядит следующим образом:

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

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

Это было полезно?

Решение

Вот мое мнение: это настраиваемая связующая модель, которая при запросе 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-контейнер и свой собственный тип репозитория.

Другие советы

Конечно, у каждого автомобиля есть только один производитель.Если это так, то у вас должно быть поле «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)

Возможно, это поздно, но для этого вы можете использовать специальную связку моделей.Обычно я делал бы это так же, как @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))%>

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top