asp.net mvc - partialview - Determine el modelo de una vista parcial desde el controlador dentro de MVC
render partial view mvc 5 (5)
Mi problema actual es que tengo una visión parcial de que quiero determinar qué modelo está utilizando.
He tenido que lidiar con algunos escenarios extraños para mi proyecto, así que intentaré describirlo aquí, tal vez alguien pueda ofrecer una mejor manera de hacerlo.
Estoy diseñando algo como la página de Google iGoogle. Una página principal con varios widgets que pueden moverse o configurarse según sea necesario. El sistema actual carga los datos del widget real de forma asincrónica al ver un POST en un controlador dentro de mi aplicación. Ese controlador emitirá una vista parcial a HTML que se puede devolver (y luego cargar en la vista de página JQUERY) o simplemente HTML / JavaScript directo que se almacena en una base de datos.
Esto estaba funcionando bien para mí, tenía un modelo para los widgets que contiene un diccionario de opciones que se describen a través de la base de datos y luego se usan para la vista parcial. El problema vino cuando quería pasar los datos a una vista parcial. La mejor solución que se me ocurrió fue hacer que el controlador determinara qué modelo utiliza la vista parcial en cuestión, tener alguna función que llene el modelo y luego pasarlo, junto con la vista parcial, a la función que lo representará. HTML dentro del controlador.
Me doy cuenta de que este es un escenario extraño para MVC (las capas se mezclan ...) y cualquier consejo sobre el diseño fundamental, o la implementación de este, sería muy apreciado.
Actualmente estoy usando MVC3 / Razor. No dude en hacer cualquier otra pregunta.
¿Qué pasa con un modelo de vista dinámica? Los diseños en MVC3 los usan, y quizás puedas usar algo similar para tus propósitos:
Hice un blog acerca de hacer exactamente esto. Por favor, visite http://blogs.planetcloud.co.uk/mygreatdiscovery/?tag=/widget
Esencialmente construí un sistema de widget similar. Las publicaciones también cubren cómo manejar la configuración de esos widgets. Esto hace uso del soporte dinámico en Mvc3 para que cualquier objeto del modelo pueda pasar a la vista, desde una sola acción del controlador.
Por defecto, todos los widgets tienen una colección de propiedades KVP (creo que esto es lo que tiene el OP). Entonces, para un widget simple, tenemos acceso a esas propiedades desde la vista. Utilicé para un widget que mostraba algunos html (donde el html estaba almacenado en una de esas propiedades).
Sin embargo, para widgets más complejos implementamos IWidgetWithDisplayModel
. Esto nos dice que antes de volver a pasar el widget cargado a la vista, debemos "construir" nuestro modelo de visualización.
Aquí está la acción del controlador que hace eso. Compruebe los mensajes para más detalles.
[HttpGet]
public ActionResult Get(string name)
{
var widget = widgetService.GetWidgetBySystemName(name, true);
if (widget == null)
return Content(string.Format("Widget [{0}] not found!", name));
if (!this.ViewExists(widget.WidgetName))
return Content(string.Format("A template for widget [{0}] was not found.", widget.WidgetName));
if (widget is IWidgetWithDisplayModel) {
(widget as IWidgetWithDisplayModel).CreateDisplayModel();
}
return PartialView(widget.WidgetName, widget);
}
No estoy 100% seguro de que esto es lo que está buscando, pero el atributo [ChildActionOnly]
se puede agregar a un método dentro de su controlador. Eso requiere que el método solo pueda ser llamado desde una partial view
. Luego, puede configurar su vista parcial para ese método que básicamente se parece a uno de sus widgets. Echa un vistazo al ejemplo de MVC Music Store aquí:
Prototipé una posible solución a esto, porque me pareció un problema divertido. Espero que te sea de utilidad.
Modelos
Primero, los modelos. Decidí crear dos ''widgets'', uno para noticias y otro para reloj.
public class NewsModel
{
public string[] Headlines { get; set; }
public NewsModel(params string[] headlines)
{
Headlines = headlines;
}
}
public class ClockModel
{
public DateTime Now { get; set; }
public ClockModel(DateTime now)
{
Now = now;
}
}
Controlador
Mi controlador no sabe nada de las vistas. Lo que hace es devolver un solo modelo, pero ese modelo tiene la capacidad de obtener dinámicamente el modelo correcto como lo requiere la vista.
public ActionResult Show(string widgetName)
{
var selector = new ModelSelector();
selector.WhenRendering<ClockModel>(() => new ClockModel(DateTime.Now));
selector.WhenRendering<NewsModel>(() => new NewsModel("Headline 1", "Headline 2", "Headline 3"));
return PartialView(widgetName, selector);
}
Los delegados se utilizan para que el modelo correcto solo se cree / recupere si realmente se usa.
ModelSelector
El ModelSelector que usa el controlador es bastante simple: solo mantiene una bolsa de delegados para crear cada tipo de modelo:
public class ModelSelector
{
private readonly Dictionary<Type, Func<object>> modelLookup = new Dictionary<Type, Func<object>>();
public void WhenRendering<T>(Func<object> getter)
{
modelLookup.Add(typeof(T), getter);
}
public object GetModel(Type modelType)
{
if (!modelLookup.ContainsKey(modelType))
{
throw new KeyNotFoundException(string.Format("A provider for the model type ''{0}'' was not provided", modelType.FullName));
}
return modelLookup[modelType]();
}
}
Las vistas - solución simple
Ahora, la forma más fácil de implementar una vista sería:
@model MvcApplication2.ModelSelector
@using MvcApplication2.Models
@{
var clock = (ClockModel) Model.GetModel(typeof (ClockModel));
}
<h2>The time is: @clock.Now</h2>
Usted podría terminar aquí y utilizar este enfoque.
Las vistas - Mejor solución
Eso es bastante feo. Quería que mis puntos de vista se vieran así:
@model MvcApplication2.Models.ClockModel
<h2>Clock</h2>
@Model.Now
Y
@model MvcApplication2.Models.NewsModel
<h2>News Widget</h2>
@foreach (var headline in Model.Headlines)
{
<h3>@headline</h3>
}
Para hacer que esto funcionara, tuve que crear un motor de vista personalizado.
Motor de vista personalizada
Cuando se compila una vista de Razor, hereda un ViewPage<T>
, donde T
es el @model
. Así que podemos usar la reflexión para averiguar qué tipo de vista desea la vista y seleccionarla.
public class ModelSelectorEnabledRazorViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var result = base.CreateView(controllerContext, viewPath, masterPath);
if (result == null)
return null;
return new CustomRazorView((RazorView) result);
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
var result = base.CreatePartialView(controllerContext, partialPath);
if (result == null)
return null;
return new CustomRazorView((RazorView)result);
}
public class CustomRazorView : IView
{
private readonly RazorView view;
public CustomRazorView(RazorView view)
{
this.view = view;
}
public void Render(ViewContext viewContext, TextWriter writer)
{
var modelSelector = viewContext.ViewData.Model as ModelSelector;
if (modelSelector == null)
{
// This is not a widget, so fall back to stock-standard MVC/Razor rendering
view.Render(viewContext, writer);
return;
}
// We need to work out what @model is on the view, so that we can pass the correct model to it.
// We can do this by using reflection over the compiled views, since Razor views implement a
// ViewPage<T>, where T is the @model value.
var compiledViewType = BuildManager.GetCompiledType(view.ViewPath);
var baseType = compiledViewType.BaseType;
if (baseType == null || !baseType.IsGenericType)
{
throw new Exception(string.Format("When the view ''{0}'' was compiled, the resulting type was ''{1}'', with base type ''{2}''. I expected a base type with a single generic argument; I don''t know how to handle this type.", view.ViewPath, compiledViewType, baseType));
}
// This will be the value of @model
var modelType = baseType.GetGenericArguments()[0];
if (modelType == typeof(object))
{
// When no @model is set, the result is a ViewPage<object>
throw new Exception(string.Format("The view ''{0}'' needs to include the @model directive to specify the model type. Did you forget to include an @model line?", view.ViewPath));
}
var model = modelSelector.GetModel(modelType);
// Switch the current model from the ModelSelector to the value of @model
viewContext.ViewData.Model = model;
view.Render(viewContext, writer);
}
}
}
El motor de visualización se registra al colocar esto en Global.asax.cs:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new ModelSelectorEnabledRazorViewEngine());
Representación
La vista de mi hogar incluye las siguientes líneas para probarlo todo:
@Html.Action("Show", "Widget", new { widgetName = "Clock" })
@Html.Action("Show", "Widget", new { widgetName = "News" })
Una opción sería ampliar la idea de solicitudes parciales en su aplicación. Steve Sanderson tiene un ejemplo fantástico de esto , aunque la publicación se relaciona con MVC 1 y 2. Creo que aún ayudaría en v3, pero no he investigado v3 para ver si el equipo de MVC implementó su propia versión. En su escenario asíncrono, deberá jugar un poco con la implementación, tal vez cambiar la definición de PartialRequest para aceptar información diferente según sea necesario, pero creo que este podría ser un buen comienzo. El resultado neto sería un mejor aislamiento de las preocupaciones, lo que permitiría a los controladores individuales administrar un tipo particular de parcial y, a su vez, conocer mejor el tipo de modelo con el que desea trabajar.