2012-03-16 14 views
5

Al construir un modelo de vista en el MVC3 de asp.net, ¿dónde debería ir el código para crear una instancia de los objetos de ese modelo de vista? Lo estoy haciendo principalmente en el controlador en este momento, aparte del código para consultar la base de datos. Aquí es un ejemplo en el código:¿A dónde debería ir el código para construir modelos de vista?

Ver Modelo:

public class WorkListVM 
{ 
    //list for employees 
    [Display(Name = "Select A Employee")] 
    [Required] 
    public int? EmployeeId { get; set; } 
    public GenericSelectList EmployeeList { get; set; } 
} 

Código de control:

 //build view model 
     var vm = new WorkListVM(); 

     //build employee list 
     vm.EmployeeList = new GenericSelectList(0,"-- Select Employee --"); 
     var employees = new List<Employee>(); 
     using (var gr = new GenericRepo<Employee>()) 
     { 
      employees = gr.Get().ToList(); 
     } 
     foreach(var employee in employees) 
     { 
      var gl = new GenericListItem(); 
      gl.Id = employee.EmployeeId; 
      gl.DisplayFields = employee.FirstName + " " + employee.LastName; 
      vm.EmployeeList.Values.Add(gl); 
     } 

Genérico lista de selección es una clase simple para contener los datos que va en el ayudante @html.dropdownfor 's SelectList. Construyo estas listas de selección y también construyo configuraciones de datos similares para ver modelos dentro del código del controlador. El controlador que aloja este código tiene un total de 109 líneas de código, por lo que no es enorme ni está fuera de control. Sin embargo, siempre me esfuerzo por reducir la redundancia y, a veces, el código en //build employee list termina siendo copiado (uf, odio copiar y pegar) en otros controladores.

¿Hay un lugar mejor para tener este código? ¿Debería estar usando el patrón de fábrica para construir los datos para estas listas de selección/otros objetos de datos de visualización?

EDITAR

Gracias por toda su ayuda. Esto es lo que terminé haciendo. Acabé por hacer un método dentro de la clase selecta lista genérica muy similar a la .ToSelectList (...) sugerido por Richard y Jesse:

public class GenericSelectList 
{ 
    public List<GenericListItem> Values { get; set; } 
    public int StartValue { get; set; } 
    public string Message { get; set; } 

    public GenericSelectList(int StartValue = 0, string Message = "select") 
    { 
     Values = new List<GenericListItem>(); 
     this.StartValue = StartValue; 
     this.Message = Message; 
    } 

    public void BuildValues<T>(List<T> items, Func<T, int> value, Func<T, string> text) where T : class 
    { 
     this.Values = items.Select(f => new GenericListItem() 
     { 
      Id = value(f), 
      DisplayFields = text(f) 
     }).ToList(); 
    } 
} 
+0

Si devuelve su viewmodel a una vista, entonces el código entra en el controlador. –

+0

@ 白 ジ ェ ス ス ス - Sí, al final de este método actionresult es 'return View (vm);'. La vista está fuertemente tipeada '@model WorkListVM'. Parte de este código se usa varias veces (en diferentes controladores). ¿No tendría sentido tener una fábrica que envuelva la creación de estos objetos de viewmodel para reducir la redundancia? –

Respuesta

2

Si la lógica de negocio que va en la creación de un modelo de vista es bastante complejo I generalmente extraer en un método de ayuda que puedo probar independientemente del controlador.

Sin embargo, aparte de eso, su creación del modelo de vista está perfectamente bien dentro del controlador tal como está. Como ya se ha señalado, cómo generar la lista de selección podría hacerse mucho más simple (por no mencionar reutilizable).

Aquí es una extensión ToSelectList de IEnumerable, junto con un ejemplo de uso:

public static List<SelectListItem> ToSelectList<T>(this IEnumerable<T> enumerable, Func<T, string> value, Func<T, string> text, string defaultOption) 
{ 
    var items = enumerable.Select(f => new SelectListItem() 
              { 
               Text = text(f) , 
               Value = value(f) 
              }).ToList(); 

    if (!string.IsNullOrEmpty(defaultOption)) 
    { 
        items.Insert(0, new SelectListItem() 
         { 
          Text = defaultOption, 
          Value = string.Empty 
         }); 
    } 

    return items; 
} 

Dentro de su modelo de vista se podría añadir una propiedad de este modo:

IEnumerable<SelectListItem> Employees { get; set; } 

Dentro de su controlador (i' estoy asumiendo que tu repositorio está volviendo IEnumberable):

var employees = new IEnumerable<Employee>(); 
using (var gr = new GenericRepo<Employee>()) 
{ 
    employees = gr.Get(); 
} 

vm.Employees = employees.ToSelectList(x=>x.FirstName + " " + x.LastName, x=>x.Id, "-- Select Employee --") 

Y después de instalar su lista desplegable en la vista se vería algo así como:

@Html.DropDownListFor(model => model.EmployeeId, Model.employees) 
+0

+1 - Las mentes geniales piensan igual;) ¡Y me salvaste una traducción! – RichardW1001

+0

Extensión muy bien escrita, gracias por la entrada en este tema :) –

+0

@Jesse - Tu idea para una extensión fue buena, pero decidí convertirla en un método dentro de la clase que uso para listas de selección que funcionó muy bien. Ver mi edición para la implementación. ¡Gracias de nuevo! –

2

Si su objetivo es evitar código redundante, se debe extraer el común partes en métodos de ayuda. En la forma más simple, puede usar métodos estáticos para esto.

La construcción de modelos de vista en el controlador es generalmente el enfoque correcto, excepto si hay razones especiales en contra.

Puede estructurar su código de la manera que desee. Utilice técnicas estándar para hacer frente a la complejidad, como los métodos de ayuda y (con moderación) abstracciones. No hay necesidad de ir complejo cuando lo simple es suficiente.

2

1 - Puede agregar un método de extensión a IEnumerable<Employee> o IQueryable<Employee> que devuelve su GenericSelectList. Ventajas: puede reutilizar contra cualquier colección de empleados, es decir, una lista filtrada y una sintaxis de llamadas bastante agradable; puede personalizar cómo se forma la lista de selección caso por caso. Desventajas: tendrá que escribir uno de estos métodos para cada tipo.

2 - Puede cambiar ese método de extensión para trabajar con un genérico, que funciona en contra de IEnumerable<T> y produce un GenericSetList basado en las entradas Expression. Ventajas: su método ahora es realmente genérico, es decir, escriba una vez y vuelva a usarlo. Puede hacer esto de tal manera que se combine con 1 para que su función 1-por-clase use el genérico para guardar la duplicación. Desventajas: asume la comodidad en el uso de expresiones y similares.

3 - Puede tener métodos de fábrica que devuelvan ViewModels.

Estas cosas también pueden funcionar en combinación, y deberían ayudar a eliminar el código de copiar/pegar y promover la reutilización y la capacidad de prueba.

Editar - aquí hay una forma en que no. 2 podría implementarse (en VB pero la traducción a C# es trivial).Luego agregaría sobrecargas para las permutaciones útiles de las entradas.

Imports System.Runtime.CompilerServices 
Imports System.Linq.Expressions 

Module IQueryableExtensions 

    <Extension()> 
    Public Function ToSelectList(Of T)(source As IEnumerable(Of T), nameExpression As Expression(Of Func(Of T, String)), valueExpression As Expression(Of Func(Of T, String)), selectedExpression As Expression(Of Func(Of T, Boolean)), additionalItems As IEnumerable(Of SelectListItem)) As IEnumerable(Of SelectListItem) 
     Return additionalItems.Union(
      source.Select(Function(x) New SelectListItem With { 
           .Text = nameExpression.Compile.Invoke(x), 
           .Value = valueExpression.Compile.Invoke(x), 
           .Selected = selectedExpression.Compile.Invoke(x) 
          } 
        ) 
       ) 
    End Function 

End Module 

Ejemplo de uso:

Dim People As New List(Of Person) From {New Person With {.Name = "Richard", .ID = 1}} 

    Dim sl = People.ToSelectList(Function(p) p.Name, 
           Function(p) p.ID, 
           Function(p) p.ID = 1, 
           {New SelectListItem With {.Value = 0, 
                  .Text = "Please Select A Person"}}) 
+0

Podría hacer # 2, en el sentido de que agregaría una respuesta genérica para seleccionar la generación de listas. Sin embargo, requeriría reflexión. ¿Sería imprudente usar la reflexión en el repositorio? –

+0

¿Estás diciendo reflexión porque quieres incluir el nombre de clase? Hay otras formas de hacerlo, dame un segundo y publicaré una muestra. – RichardW1001

+0

@TravisJ - allí tienes - una posible implementación sin reflexión. – RichardW1001

1

No sé si este es el camino correcto, pero lo uso y me ayuda a mantener el control de todo.

un controlador en mi opinión hace cosas como sesiones de actualización, cookies y luego devuelve una vista. Nunca lo uso para clasificar datos o crear objetos para enviar a la vista.

(podría hacer trampa si es el que está de un trazador de líneas: p)

todo ese tipo de cosas que añadir a una clase de ayuda. Cada vez que siento que se copian y pegan, creo un nuevo método en mi ayudante y llamo a eso.

plus, obtienes una agradable sensación de bienestar cuando, tres días después, necesitas utilizar el método y todo está esperando.

Martyn

+1

hombre ... escribo demasiado lento, ¡2 respuestas mientras estoy tocando! – SmithMart

+0

Así que tal vez debería simplemente hacer un método de ayuda genérico usando la reflexión para administrar la creación de estas listas de selección ... –

Cuestiones relacionadas