ASP.NET MVC - публикация формы с настраиваемыми полями разных типов данных - PullRequest
9 голосов
/ 10 декабря 2010

В моем веб-приложении ASP.NET MVC 2 я позволяю пользователям создавать настраиваемые поля ввода различных типов данных, чтобы расширить нашу базовую форму ввода.Хотя это сложно, создать форму ввода из набора настраиваемых полей достаточно просто.

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

Типичная форма ввода может выглядеть примерно так:

  • Поле моей даты: [элемент управления вводом даты]
  • Поле моего текста: [поле ввода текста]
  • Поле моего файла: [элемент управления загрузкой файла]
  • Поле моего номера: [управление числовым вводом]
  • Поле моего текста 2: [поле ввода текста]
  • и т. Д. *

ИдеиЯ думал о:

  • Отправка всего в виде строк (кроме файловых входов, которые должны быть обработаны специально).
  • Использование модели ссвойство "object" и попытка привязки к нему (если это вообще возможно).
  • Отправка запроса json на мой контроллер с данными, правильно закодированными и попытка его проанализировать.
  • Обработка вручнуюколлекция форм в моем контроллере после действия - конечно, вариант, но яХотелось бы избежать этого.

Кто-нибудь сталкивался с подобной проблемой раньше?Если да, то как вы решили это?

Обновление:

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

Обновление 2:

Спасибо ARM и smartcaveman;вы оба дали хорошее руководство о том, как это можно сделать.Я обновлю этот вопрос своим окончательным решением, как только оно будет реализовано.

Ответы [ 3 ]

1 голос
/ 17 декабря 2010

Вот так я бы начал подходить к вопросу.Привязка пользовательской модели будет довольно легко построить на основе свойства FormKey (которое может определяться индексом и / или меткой, в зависимости от).

public class CustomFormModel
{
    public string FormId { get; set; }
    public string Label { get; set; }
    public CustomFieldModel[] Fields { get; set; }
}
public class CustomFieldModel
{
    public DataType DateType { get; set; } //  System.ComponentModel.DataAnnotations
    public string FormKey { get; set; }
    public string Label { get; set; }
    public object Value { get; set; }
}
public class CustomFieldModel<T> : CustomFieldModel
{
    public new T Value { get; set; }
}

Кроме того, я заметил, что один из комментариев ниже имелотфильтрованная модель связующей системы.Джимми Богард из Automapper сделал очень полезную публикацию об этом методе в http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx, а затем исправил в http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx.Это очень помогло мне в создании пользовательских связывателей моделей.

Обновление

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

public interface IFormComponent
{
    //  the id on the html form field.  In the case of a composite Id, that doesn't have a corresponding 
    //  field you should still use something consistent, since it will be helpful for model binding
    //  (For example, a CompositeDateField appearing as the third field in the form should have an id 
    //  something like "frmId_3_date" and its child fields would be "frmId_3_date_day", "frmId_3_date_month",
    //  and "frmId_3_date_year".
    string FieldId { get; }

    //  the human readable field label
    string Label { get; }

    //  some functionality may require knowledge of the 
    //  Parent component.  For example, a DayField with a value of "30"
    //  would need to ask its Parent, a CompositeDateField
    //  for its MonthField's value in order to validate
    //  that the month is not "February"
    IFormComponent Parent { get; }

    //  Gets any child components or null if the 
    //  component is a leaf component (has no children).
    IList<IFormComponent> GetChildren();

    //  For leaf components, this method should accept the AttemptedValue from the value provider
    //  during Model Binding, and create the appropriate value.  
    //  For composites, the input should be delimited in someway, and this method should parse the 
    //  string to create the child components.  
    void BindTo(string value);

    //  This method should parse the Children or Underlying value to the 
    //  default used by your business models.  (e.g. a CompositeDateField would 
    //  return a DateTime.  You can get type safety by creating a FormComponent<TValue>
    //  which would help to avoid issues in binding.
    object GetValue();

    //  This method would render the field to the http response stream.
    //  This makes it easy to render the forms simply by looping through 
    //  the array.  Implementations could extend this for using an injected 
    //  formatting 
    void Render(TextWriter writer);
} 

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

public interface IForm : IFormComponent
{
    Guid FormId { get; }
    void Add(IFormComponent component);
}
public interface IFormRepository
{
    IForm GetForm(Guid id);
}
public class CustomFormModelBinder : IModelBinder   
{
    private readonly IFormRepository _repository;
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        ValueProviderResult result;
        if(bindingContext.ValueProvider.TryGetValue("_customFormId", out result))
        {
            var form = _repository.GetForm(new Guid(result.AttemptedValue));
            var fields = form.GetChildren();
            //  loop through the fields and bind their values 
            return form;
        }
        throw new Exception("Form ID not found.");
    }
}

Очевидно, что весь код здесь только для того, чтобы понять суть, и его необходимо будет завершить и очистить для фактического использования.,Кроме того, даже если это будет выполнено, это будет связано только с реализацией интерфейса IForm, а не со строго типизированным бизнес-объектом.(Преобразование его в словарь и создание строго типизированного прокси с использованием Castle DictionaryAdapter не будет огромным шагом, но, поскольку ваши пользователи динамически создают формы на сайте, в вашем решении, вероятно, нет строго типизированной модели иэто не имеет значения).Надеюсь, это поможет больше.

1 голос
/ 10 декабря 2010

Взгляните на то, что я сделал здесь: MVC2 Действие для работы с несколькими моделями и посмотрите, сможете ли вы встать на правильный путь.

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

Удачи, не стесняйтесь задавать дополнительные вопросы.

Edit:

Ваш метод, который выполняет работу, должен выглядеть примерно так:

private/public void SaveCustomFields(var formId, FormCollection collection) //var as I don't know what type you are using to Id the form.
{
    var binders = this.binders.select(b => b.CanHandle(collection)); //I used IOC to get my list of IBinder objects
    // Method 1:    
    binders.ForEach(b => b.Save(formId, collection)); //This is the execution implementation.
    // Method 2:
    var commands = binders.Select(b => b.Command(formId, collection));
    commands.ForEach(c => c.Execute());    
}

public DateBinder : IBinder //Example binder
{
    public bool CanHandle(FormCollection collection)
    {
        return (null != collection["MyDateField"]); //Whatever the name of this field is.
    }

    //Method 1
    public void Save(var formId, FormCollection collection)
    {
        var value = DateTime.Parse(collection["MyDateField"]);
        this.someLogic.Save(formId, value); //Save the value with the formId, or however you wish to save it.
    }
    //Method 2
    public Command Command(var formId, FormCollection collection)
    {
        //I haven't done command pattern before so I'm not sure exactly what to do here.
        //Sorry that I can't help further than that.
    }
}
0 голосов
/ 11 декабря 2010

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

Возможно, эти статьи помогут вам:

http://www.gregshackles.com/2010/03/templated-helpers-and-custom-model-binders-in-asp-net-mvc-2/

http://www.singingeels.com/Articles/Model_Binders_in_ASPNET_MVC.aspx

Более конкретно, я бы, вероятно, взял в качестве аргумента контроллера пользовательский класс со всеми включенными «базовыми» свойствами.Класс мог бы, например, включать словарь, связывающий имя каждого поля либо с просто объектом, либо с интерфейсом, который вы реализуете один раз для каждого типа данных, что упрощает последующую обработку данных.

/ Victor

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...