2012-04-03 22 views
5

He estado tratando de encontrar una buena manera de manejar los modelos de nuestros sitios web Asp.net MVC cuando se tienen propiedades comunes para todas las páginas. Estas propiedades se mostrarán en el diseño (página maestra). Estoy usando una clase "BaseModel" que contiene esas propiedades y mi diseño utiliza este modelo base como modelo.Asp.net MVC Modelo para ver y diseño

Cada otro modelo hereda de ese BaseModel y cada uno tiene propiedades específicas relativas a la vista que representa. Como habrás adivinado, mis Modelos son en realidad Modelos de Vista, incluso si eso no es muy relevante aquí.

que han intentado diferentes maneras de inicializar los valores de la BaseModel

  1. Por "mano" en todas las vistas
  2. Tener un controlador de base que tiene un método virtual de inicialización para hacerlo (controlador de manera específica se puede poner en práctica específica comportamiento común para exemple)
  3. Tener una base controlelr que OnActionExecuting override para llamar al método de inicialización
  4. Uso de una clase de ayuda para hacerlo fuera del controlador
  5. utilizando un modelo de fábrica

Pero ninguno de los que realmente me atrae:

  1. me parece evidente, pero seco es una razón suficiente para justificar que (en realidad nunca he tratado de esa solución en absoluto, Solo lo estoy poniendo para poder repetir ese punto en el último punto).
  2. No me gusta porque significa que cada vez que se agrega un nuevo Controlador, necesita saber que tiene que heredarlo del BaseController y que necesita llamar al método Initialize, sin mencionar que si su controlador ha anulado la base, para llamar a la base de todos modos para mantener los valores.
  3. ver el siguiente punto
  4. y 3. son una variación del mismo tema, pero eso realmente no ayuda con los problemas de la segunda solución.
  5. Mi favorito hasta ahora, pero ahora tengo que pasar algunas variables más para establecer esos valores. Me gusta para la inversión de la dependencia. Pero si quiero proporcionar valores de la sesión, necesito pasarlos explícitamente, por ejemplo, luego vuelvo a la primera casilla, ya que tengo que proporcionarlos a mano (sean referencias o mediante una interfaz de cualquier tipo)

Por supuesto, (casi) todas esas soluciones funcionan, pero estoy buscando una mejor manera de hacerlo.

Al escribir esta pregunta, encontré tal vez una nueva ruta, la builder pattern que también podría funcionar, pero las implementaciones pueden convertirse rápidamente en una carga también, ya que podemos tener docenas de vistas y controladores.

Con gusto tomaré cualquier recomendación seria/pista/consejo/patrones/sugerencia!

actualización

Gracias a @EBarr me ocurrió otra solución, utilizando un ActionFilterAttribute (no código de producción, lo hicieron en 5 minutos):

public class ModelAttribute : ActionFilterAttribute 
{ 
    public Type ModelType { get; private set; } 

    public ModelAttribute(string typeName) : this(Type.GetType(typeName)) { } 

    public ModelAttribute(Type modelType) 
    { 
     if(modelType == null) { throw new ArgumentNullException("modelType"); } 

     ModelType = modelType; 
     if (!typeof(BaseModel).IsAssignableFrom(ModelType)) 
     { 
      throw new ArgumentException("model type should inherit BaseModel"); 
     } 
    } 

    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     var model = ModelFactory.GetModel(ModelType); 

     var foo = filterContext.RequestContext.HttpContext.Session["foo"] as Foo; 

     model.Foo = foo; 
     model.Bar = somevalue; 

     filterContext.Controller.TempData["model"] = model; 
    } 
} 

llamando entonces es muy simple :

[Model(typeof(HomeModel))] 
public ActionResult Index() 
{ 
    var homeModel = TempData["model"] as HomeModel; 

    // Add View Specific stuff 

    return View(homeModel); 
} 

Y me da lo mejor de todos los mundos. El único inconveniente es encontrar una forma adecuada de pasar el modelo a la acción.

Aquí se hace utilizando el objeto TempData, pero también considero actualizar el modelo que se puede encontrar en los ActionParameters.

Todavía estoy tomando cualquier recomendación seria/sugerencia/consejo/patrones/sugerencia para eso, o los puntos anteriores.

Respuesta

1

La idea que me dio @EBarr para usar un filtro de acción funcionaba pero me sentí mal al final, porque no había una manera limpia de recuperar el modelo sin pasar por una viewbag, o los elementos de httpcontext, o algo por el estilo . Además, hizo obligatorio decorar cada acción con su modelo. También hizo que la devolución de datos sea más difícil de manejar. Todavía creo que esta solución tiene sus ventajas y podría ser útil en algunos escenarios específicos.

Así que volví al punto de partida y comencé a buscar más sobre ese tema. Vine a lo siguiente. En primer lugar el problema tiene dos aspectos

  1. inicialización de los datos de las opiniones
  2. haciendo que los datos

bien en busca de más idea, me di cuenta de que yo no estaba mirando el problema desde la perspectiva correcta . Lo estaba viendo desde un POV "Controlador", mientras que el cliente final para el modelo es la vista. También me recordaron que la página Diseño/Máster no es una vista y no debería tener un modelo asociado. Esa idea me puso en lo que parece el camino correcto para mí. Porque significaba que cada parte "dinámica" del Diseño debería manejarse fuera de él. Por supuesto, las secciones parecen el ajuste perfecto para eso, debido a su flexibilidad.

En la solución de prueba que hice, tuve (solo) 4 secciones diferentes, algunas obligatorias, otras no.El problema con las secciones es que debe agregarlas en cada página, lo que puede ser difícil de actualizar/modificar rápidamente. Para solucionar esto, he intentado esto:

public interface IViewModel 
{ 
    KeyValuePair<string, PartialViewData>[] Sections { get; } 
} 

public class PartialViewData 
{ 
    public string PartialViewName { get; set; } 
    public object PartialViewModel { get; set; } 
    public ViewDataDictionary ViewData { get; set; } 
} 

Para exemple, mi modelo para la vista es la siguiente:

public class HomeViewModel : IViewModel 
{ 
    public Article[] Articles { get; set; }    // Article is just a dummy class 
    public string QuickContactMessage { get; set; }  // just here to try things 

    public HomeViewModel() { Articles = new Article[0]; } 

    private Dictionary<string, PartialViewData> _Sections = new Dictionary<string, PartialViewData>(); 
    public KeyValuePair<string, PartialViewData>[] Sections 
    { 
     get { return _Sections.ToArray(); } 
     set { _Sections = value.ToDictionary(item => item.Key, item => item.Value); } 
    } 
} 

Este que éste se inicia en la acción:

public ActionResult Index() 
{ 
    var hvm = ModelFactory.Get<HomeViewModel>(); // Does not much, basicaly a new HomeViewModel(); 

    hvm.Sections = LayoutHelper.GetCommonSections().ToArray(); // more on this just after 
    hvm.Articles = ArticlesProvider.GetArticles(); // ArticlesProvider could support DI 

    return View(hvm); 
} 

LayoutHelper es una propiedad en el controlador (que podría ser DI 'si es necesario):

public class DefaultLayoutHelper 
{ 
    private Controller Controller; 
    public DefaultLayoutHelper(Controller controller) { Controller = controller; } 

    public Dictionary<string, PartialViewData> GetCommonSections(QuickContactModel quickContactModel = null) 
    { 
     var sections = new Dictionary<string, PartialViewData>(); 
     // those calls were made in methods in the solution, I removed it to reduce the length of the answer 
     sections.Add("header", 
        Controller.UserLoggedIn() // simple extension that check if there is a user logged in 
        ? new PartialViewData { PartialViewName = "HeaderLoggedIn", PartialViewModel = new HeaderLoggedInViewModel { Username = "Bishop" } } 
        : new PartialViewData { PartialViewName = "HeaderNotLoggedIn", PartialViewModel = new HeaderLoggedOutViewModel() }); 
     sections.Add("quotes", new PartialViewData { PartialViewName = "Quotes" }); 
     sections.Add("quickcontact", new PartialViewData { PartialViewName = "QuickContactForm", PartialViewModel = model ?? new QuickContactModel() }); 
     return sections; 
    } 
} 

y en las vistas (.cshtml):

@section  quotes { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "quotes").Value); } } 
@section  login { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "header").Value); } } 
@section  footer { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "footer").Value); } } 

La solución actual tiene más código, yo tratado de simplificar para obtener sólo la idea aquí. Todavía está un poco crudo y necesita pulido/manejo de errores, pero con eso puedo definir en mi acción, cuáles serán las secciones, qué modelo usarán y así sucesivamente. Se puede probar fácilmente y configurar DI no debería ser un problema.

Todavía tengo que duplicar las líneas @section en cada vista, lo que parece un poco doloroso (especialmente porque no podemos poner las secciones en una vista parcial).

Estoy buscando en el templated razor delegates para ver si eso no puede reemplazar las secciones.

2

Pasé por casi el mismo proceso que me metí en MVC. Y tienes razón, ninguna de las soluciones se siente tan bien.

Al final utilicé una serie de modelos base. Por diversas razones, tenía algunos tipos diferentes de modelos base, pero la lógica debería aplicarse a un solo tipo de base. La mayoría de mis modelos de vista luego heredaron de una de las bases. Luego, dependiendo de la necesidad/tiempo, llene la porción de base del modelo en ActionExecuting o OnActionExecuted.

Un fragmento de mi código que debe hacer el proceso claro:

if (filterContext.ActionParameters.ContainsKey("model")) { 
    var tempModel = (System.Object)filterContext.ActionParameters["model"]; 

    if (typeof(BaseModel_SuperLight).IsAssignableFrom(tempModel.GetType())) { 
     //do stuff required by light weight model 
    } 

    if (typeof(BaseModel_RegularWeight).IsAssignableFrom(tempModel.GetType())) { 
     //do more costly stuff for regular weight model here 
    } 
} 

Al final, mi patrón no parecía demasiado satisfactoria. Sin embargo, era práctico, flexible y fácil de implementar con diferentes niveles de herencia. También pude inyectar una ejecución previa o posterior al controlador, lo que importaba mucho en mi caso. Espero que esto ayude.

+0

Gracias por su respuesta. Si bien no coincide exactamente con lo que estoy buscando (su solución es una implementación diferente de 3.), realmente me dio una idea. Solo traté de usar un ActionFilterAttribute personalizado que parece ser el truco. Todavía tengo que encontrar una forma "limpia" para devolverle el modelo a la acción (en este momento estoy usando TempData). Actualizaré la pregunta para reflejar eso. –

+0

Sí, es una versión de # 3. Pensé en los atributos, pero eso requería decorar cada controlador/acción (según las necesidades). Mantenerlo en 'OnActionExecuting' y' OnActionExecuted' mantuvo mi código en un único controlador base. En la práctica para cualquier proyecto de MVC más grande, vas a tener un controlador base, así que no me importó mucho. También me permitió ejecutar la lógica en función del tipo de modelo, en lugar de vincular la lógica con la que se estaba ejecutando la acción. – EBarr

+0

RE: Modelo de agarre: en su atributo de filtro de acción va a terminar implementando los mismos eventos (ejecución y ejecución), pero puede sacar el modelo de filterContext, no hay necesidad de devolverlo, simplemente búsquelo y llena lo que necesitas – EBarr

Cuestiones relacionadas