2010-12-10 15 views
9

En mi aplicación web ASP.NET MVC 2, que permite a los usuarios crear campos de entrada personalizada de los diferentes tipos de datos para extender nuestro formulario básico de entrada. Si bien es complicado, crear el formulario de entrada desde una colección de campos personalizados es lo suficientemente directo.ASP.NET MVC - publicar un formulario con campos personalizados de diferentes tipos de datos

Sin embargo, ahora estoy al punto donde quiero manejar la publicación de esta forma y no estoy seguro de cuál es la mejor manera de manejar esto sería. Normalmente, utilizaríamos modelos de entrada fuertemente tipados que se vincularán a las diversas entradas de tipo estáticamente disponibles en el formulario. Sin embargo, no sé cómo hacer esto con una cantidad variable de campos de entrada que representan diferentes tipos de datos.

Un formulario de entrada representativa podría ser algo como:

  • Mi campo de fecha: [fecha de tiempo de entrada de control]
  • Mi campo de texto: [texto de entrada campo]
  • Mi archivo campo: [carga de archivo control]
  • Mi campo de número: [control de entrada numérica]
  • Mi campo de texto 2: [campo de entrada de texto]
  • etc ...

ideas que he pensado son:

  • Envío de todo como cadenas (excepto para las entradas de archivos , que necesitaría ser manejado especialmente).
  • Utilizando un modelo con una propiedad "objeto" y de intentar unirse a que (si esto es posible).
  • Enviando una solicitud json a mi controlador con los datos codificados correctamente y tratando de analizar eso.
  • el procesamiento manual de la colección forma en mi acción posterior del controlador - ciertamente una opción, pero me gustaría evitar esto.

¿Alguien abordó un tema como este antes? Si es así, ¿cómo lo resolvió?

Actualización:

Mi forma de "base" se maneja en otra área de entrada de todos juntos, por lo que una solución no tiene que tener en cuenta cualquier tipo de magia inheritence para esto. Solo me interesa manejar los campos personalizados en esta interfaz, no los de "base".

Actualización 2:

Gracias a ARM y smartcaveman; ambos brindaron una buena guía sobre cómo se podría hacer esto. Actualizaré esta pregunta con mi solución final una vez que se haya implementado.

Respuesta

1

Así es como comenzaría a abordar el problema. Un encuadernador de modelo personalizado sería bastante fácil de construir en función de la propiedad FormKey (que podría estar determinada por el índice y/o la etiqueta, según corresponda).

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; } 
} 

Además, noté que uno de los comentarios a continuación tenía un sistema de encuadernación modelo filtrado. Jimmy Bogard de Automapper hizo una publicación muy útil acerca de este método al http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx, y luego la revisó en http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx. Ha sido muy útil para mí en la construcción de carpetas de modelo personalizado.

actualización

me di cuenta de que la pregunta mal interpretado, y que él estaba pidiendo específicamente cómo manejar publicación de la forma "con un número variable de campos de entrada que representan diferentes tipos de datos". Creo que la mejor manera de hacerlo es usar una estructura similar a la anterior pero aprovechar el Composite Pattern. Básicamente, deberá crear una interfaz como IFormComponent e implementarla para cada tipo de datos que se representará. He escrito y comentado un ejemplo de interfaz para ayudar a explicar cómo esto se lograría:

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); 
} 

Estoy asumiendo que los formularios personalizados se puede acceder a través de algún tipo de identificación que puede estar contenido como un parámetro de forma. Con esa suposición, el encuadernador modelo y el proveedor podrían verse más o menos así.

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."); 
    } 
} 

Obviamente, todo el código aquí es sólo para conseguir el punto a través, y tendría que ser completado y limpiado por el uso real. Además, incluso si se completa, esto solo se vincularía a una implementación de la interfaz IForm, no a un objeto comercial fuertemente tipado.(No sería un gran paso convertirlo a un diccionario y construir un proxy fuertemente tipado usando Castle DictionaryAdapter, pero como sus usuarios están creando dinámicamente los formularios en el sitio, probablemente no haya un modelo fuertemente tipado en su solución y esto es irrelevante). Espero que esto ayude más.

+0

Gracias por los comentarios, muy perspicaces. – DanP

1

echar un vistazo a lo que hice aquí: MVC2 Action to handle multiple models y ver si se puede obtener en el camino correcto.

Si utiliza un FormCollection como uno de sus parámetros para su acción, puede ir a través de esa colección de formularios buscando bits de datos aquí o allí para vincular esos valores a lo que sea y luego guardar los datos. Lo más probable es que necesite aprovechar los patrones de estrategia y comando para que esto funcione.

La mejor de las suertes, no dude en hacer preguntas de seguimiento.

Editar:

Su método que hace el trabajo debe ser algo como esto:

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

Gracias por esta información, su enfoque parece muy interesante. Probablemente tendré algunos seguimientos para ti el lunes. – DanP

+0

ARM; Estaría encantado de premiarte con la recompensa si estuvieras dispuesto a publicar/compartir detalles de implementación más relevantes. – DanP

+0

La parte más importante de esto es IUIWrapper.CanHandle (querrá usar un Select en lugar de SingleOrDefault para obtener múltiples contenedores). El método CanHandle toma un FormCollection e intenta obtener un elemento de colección (var X = collection ["SomeValue"]; return X! = Null;) que determinará si existe un elemento de colección de formulario particular. Una vez que tenga su colección de envoltorios, cada envoltura tendrá un comando para guardar ese elemento en particular en su repositorio y luego simplemente ejecutar a través de la colección de comandos para almacenar los datos en su repositorio. De nuevo, no dude en preguntar seguimiento. – ARM

0

yo creo que una de las mejores opciones es crear una carpeta de modelo personalizado, lo que hace que sea posible tiene una lógica personalizada detrás de escena y sigue siendo un código muy personalizable.

Tal vez estos artículos pueden ayudarle a:

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

Más específicamente probablemente me tome como argumento controlador de una clase personalizada con toda la "base" de las propiedades incluidas. La clase podría entonces, por ejemplo, incluir un diccionario que vincule el nombre de cada campo con un solo objeto o una interfaz que implemente una vez para cada tipo de datos, lo que simplifica el procesamiento posterior de los datos.

/Victor

Cuestiones relacionadas