ASP.NET MVC Razor pasar modelo al diseño

97

Lo que veo es una propiedad de diseño de cadena. Pero, ¿cómo puedo pasar un modelo al diseño de forma explícita?

Chico siberiano
fuente
Tengo varias páginas con diferentes modelos pero el mismo diseño
SiberianGuy
2
Esta pregunta de stackoverflow parece responder a lo que está preguntando: stackoverflow.com/questions/13225315/…
Paul
No tengo este problema. Modelestá disponible en _Layout. Estoy usando MVC5.
toddmo

Respuestas:

66

Parece que ha modelado sus modelos de vista un poco mal si tiene este problema.

Personalmente, nunca escribiría una página de diseño. Pero si desea hacer eso, debe tener un modelo de vista base que hereden sus otros modelos de vista y escribir su diseño en el modelo de vista base y sus páginas en el modelo de vista específico una vez.

Mattias Jakobsson
fuente
11
"Personalmente, nunca escribiría una página de diseño". ¿Por qué? Quiero decir, ¿cómo maneja el contenido dinámico lateral que aparece en Todas las páginas? ¿Omite los controladores de la vista? / ¿tal vez quieres usar RenderAction desde el diseño? (Solo lo estoy viendo ahora mismo)
eglasius
52
@eglasius, la solución que utilizo es diferente dependiendo del tipo de contenido del que hablemos. Pero una solución común es usar RenderAction para renderizar partes que necesitan sus propios datos en la página de diseño. La razón por la que no me gusta escribir la página de diseño es que te obligará a heredar siempre un modelo de vista "base" en todos tus modelos de vista específicos. En mi experiencia, esto generalmente no es una muy buena idea y muchas veces tendrás problemas cuando sea demasiado tarde para cambiar el diseño (o tomará mucho tiempo).
Mattias Jakobsson
2
¿Qué sucede si quiero incluir el modelo base por agregación, no por herencia? Una forma perfectamente legítima desde la perspectiva del diseño. Entonces, ¿cómo manejo el diseño?
Fyodor Soikin
4
Tengo 2 soluciones: un modelo genérico para el diseño, por lo que puedo usar MyLayoutModel <MyViewModel> para el modelo de vista, usando RenderPartial con MyViewModel solo en el diseño. O renderice parcialmente las partes de la página usando RenderAction para partes estáticas en caché y llamadas ajax para partes dinámicas. Pero prefiero la primera solución ya que es más amigable con los motores de búsqueda y se puede combinar fácilmente con las actualizaciones de ajax.
Softlion
4
Trabajando en código heredado donde exactamente se ha hecho esto. Es una pesadilla. No escriba sus diseños ... ¡por favor!
79
  1. Agregue una propiedad a su controlador (o controlador base) llamado MainLayoutViewModel (o lo que sea) con el tipo que le gustaría usar.
  2. En el constructor de su controlador (o controlador base), cree una instancia del tipo y configúrelo en la propiedad.
  3. Establecerlo en el campo ViewData (o ViewBag)
  4. En la página Diseño, transmita esa propiedad a su tipo.

Ejemplo: controlador:

public class MyController : Controller
{
    public MainLayoutViewModel MainLayoutViewModel { get; set; }

    public MyController()
    {
        this.MainLayoutViewModel = new MainLayoutViewModel();//has property PageTitle
        this.MainLayoutViewModel.PageTitle = "my title";

        this.ViewData["MainLayoutViewModel"] = this.MainLayoutViewModel;
    }

}

Ejemplo de parte superior de la página de diseño

@{
var viewModel = (MainLayoutViewModel)ViewBag.MainLayoutViewModel;
}

Ahora puede hacer referencia a la variable 'viewModel' en su página de diseño con acceso completo al objeto escrito.

Me gusta este enfoque porque es el controlador el que controla el diseño, mientras que los modelos de vista de página individuales siguen siendo independientes del diseño.

Notas para MVC Core


Mvc Core parece volar el contenido de ViewData / ViewBag al llamar a cada acción la primera vez. Lo que esto significa es que la asignación de ViewData en el constructor no funciona. Sin embargo, lo que sí funciona es usar IActionFiltery hacer exactamente el mismo trabajo en OnActionExecuting. Ponte MyActionFiltertu MyController.

public class MyActionFilter: Attribute, IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var myController= context.Controller as MyController;

            if (myController!= null)
            {
                myController.Layout = new MainLayoutViewModel
                {

                };

                myController.ViewBag.MainLayoutViewModel= myController.Layout;
            }
        }
    }
BlackjacketMack
fuente
1
Ya entiendo ... pero las dinámicas / moldes son bastante fundamentales para las páginas de afeitar. Una cosa que podría hacer es agregar un método estático a MainLayoutViewModel que realiza la conversión por usted (por ejemplo, MainLayoutViewModel.FromViewBag (this.ViewBag)) para que al menos la transmisión se realice en un lugar y pueda manejar mejor las excepciones allí.
BlackjacketMack
@BlackjacketMack Buen enfoque y lo logré usando lo anterior y haciendo algunas modificaciones porque tenía un requisito de diferencia y esto realmente me ayudó, gracias. ¿Podemos lograr lo mismo usando TempData? Si es así, entonces cómo y no, entonces por favor dígame por qué no se puede usar. Gracias de nuevo.
Zaker
2
@User - TempData usa Session y siempre me parece un poco torpe. Tengo entendido que es 'leer una vez', de modo que tan pronto como lo lea, lo elimine de la sesión (o tal vez tan pronto como finalice la solicitud). Es posible que almacene la sesión en Sql Server (o Dynamo Db), así que considere el hecho de que tendría que serializar MasterLayoutViewModel ... no es lo que probablemente desee. Básicamente, configurarlo en ViewData lo almacena en la memoria en un pequeño diccionario flexible, que se ajusta a la factura.
BlackjacketMack
Bastante simple, utilicé su solución, pero soy nuevo en MVC, así que me pregunto si esto se considera una buena práctica. o al menos no una mala?
Karim AG
1
Hola Karim AG, creo que es un poco de ambos. Tiendo a considerar almacenar cosas en ViewData como una mala práctica (es difícil de rastrear, está basado en un diccionario, no está escrito) ... PERO ... escribir todas las propiedades de su diseño en un objeto fuertemente tipado es una gran práctica. Así que me comprometo al decir, está bien, guardemos una cosa allí, pero bloqueemos el resto en un buen ViewModel fuertemente tipado.
BlackjacketMack
30

esto es algo bastante básico, todo lo que necesita hacer es crear un modelo de vista base y asegurarse de que TODO! y me refiero a TODOS! de sus vistas que alguna vez usarán ese diseño, recibirán vistas que usen ese modelo base.

public class SomeViewModel : ViewModelBase
{
    public bool ImNotEmpty = true;
}

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

en _Layout.cshtml:

@model Models.ViewModelBase
<!DOCTYPE html>
  <html>
  and so on...

en el método Index (por ejemplo) en el controlador doméstico:

    public ActionResult Index()
    {
        var model = new SomeViewModel()
        {
        };
        return View(model);
    }

el Index.cshtml:

@model Models.SomeViewModel

@{
  ViewBag.Title = "Title";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="row">

No estoy de acuerdo con que pasar un modelo al _layout sea un error, se puede pasar alguna información del usuario y los datos se pueden completar en la cadena de herencia de los controladores, por lo que solo se necesita una implementación.

obviamente, para un propósito más avanzado, debería considerar crear un contacto estático personalizado mediante inyección e incluir ese espacio de nombres de modelo en _Layout.cshtml.

pero para usuarios básicos esto servirá

Mansión Yakir
fuente
Estoy de acuerdo contigo. Gracias.
Sebastián Guerrero
1
y solo quiero mencionar que también funciona con una interfaz en lugar de la clase base
VladL
27

Una solución común es crear un modelo de vista base que contenga las propiedades utilizadas en el archivo de diseño y luego heredar del modelo base a los modelos utilizados en las páginas respectivas.

El problema con este enfoque es que ahora se ha encerrado en el problema de que un modelo solo puede heredar de otra clase, y tal vez su solución sea tal que no pueda usar la herencia en el modelo que pretendía de todos modos.

Mi solución también comienza con un modelo de vista base:

public class LayoutModel
{
    public LayoutModel(string title)
    {
        Title = title;
    }

    public string Title { get;}
}

Lo que luego uso es una versión genérica de LayoutModel que hereda de LayoutModel, así:

public class LayoutModel<T> : LayoutModel
{
    public LayoutModel(T pageModel, string title) : base(title)
    {
        PageModel = pageModel;
    }

    public T PageModel { get; }
}

Con esta solución he desconectado la necesidad de tener herencia entre el modelo de diseño y el modelo.

Entonces ahora puedo seguir adelante y usar LayoutModel en Layout.cshtml de esta manera:

@model LayoutModel
<!doctype html>
<html>
<head>
<title>@Model.Title</title>
</head>
<body>
@RenderBody()
</body>
</html>

Y en una página puede usar el LayoutModel genérico como este:

@model LayoutModel<Customer>
@{
    var customer = Model.PageModel;
}

<p>Customer name: @customer.Name</p>

Desde su controlador, simplemente devuelve un modelo de tipo LayoutModel:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}
Oskar Sjöberg
fuente
1
¡Bonificación por señalar el problema de la herencia múltiple y cómo lidiar con él! Esta es una mejor respuesta para la escalabilidad.
Brett Spencer
1
La mejor solución en mi opinión. Desde un punto de vista arquitectónico, es escalable y mantenible. Esta es la forma correcta de hacer esto. Nunca me gustó ViewBag o ViewData ... Ambos me parecen hacky.
Jonathan Alfaro
10

¿Por qué no agrega una nueva vista parcial con el controlador específico de i pasando el modelo requerido a la vista parcial y finalmente renderiza la vista parcial mencionada en su Layout.cshtml usando RenderPartial o RenderAction?

Utilizo este método para mostrar la información del usuario que ha iniciado sesión, como el nombre, la imagen de perfil, etc.

Arya Sh
fuente
2
¿Puede explicar esto por favor? Agradecería un enlace a alguna publicación de blog que
describa
Esto puede funcionar, pero ¿por qué sufrir el impacto del rendimiento? Tienes que esperar todo el procesamiento realizado por el controlador, devolver la vista, solo para que el navegador del usuario realice OTRA solicitud para obtener los datos necesarios. ¿Y qué pasa si su diseño depende de los datos para renderizar correctamente? En mi humilde opinión, esta no es una respuesta a esta pregunta.
Brett Spencer
3

Pregunta anterior, pero solo para mencionar la solución para los desarrolladores de MVC5, puede usar la Modelpropiedad igual que en la vista.

La Modelpropiedad tanto en la vista como en el diseño está asociada con el mismo ViewDataDictionaryobjeto, por lo que no tiene que hacer ningún trabajo adicional para pasar su modelo a la página de diseño, y no tiene que declarar @model MyModelNameen el diseño.

Pero tenga @Model.XXXen cuenta que cuando use en el diseño, el menú contextual de intelliSense no aparecerá porque Modelaquí hay un objeto dinámico como ViewBag.

Laz Ziya
fuente
2

Tal vez técnicamente no sea la forma correcta de manejarlo, pero la solución más simple y razonable para mí es simplemente crear una clase e instanciarla en el diseño. Es una excepción única a la forma correcta de hacerlo. Si esto se hace más que en el diseño, entonces debe reconsiderar seriamente lo que está haciendo y tal vez leer algunos tutoriales más antes de avanzar más en su proyecto.

public class MyLayoutModel {
    public User CurrentUser {
        get {
            .. get the current user ..
        }
    }
}

entonces en la vista

@{
    // Or get if from your DI container
    var myLayoutModel = new MyLayoutModel();
}

en .net core, incluso puede omitir eso y usar la inyección de dependencia.

@inject My.Namespace.IMyLayoutModel myLayoutModel

Es una de esas áreas que tiene cierta sombra. Pero dadas las alternativas extremadamente complicadas que estoy viendo aquí, creo que es más que una buena excepción para hacer en nombre de la practicidad. Especialmente si se asegura de mantenerlo simple y se asegura de que cualquier lógica pesada (yo diría que realmente no debería haber ninguna, pero los requisitos difieren) está en otra clase / capa a la que pertenece. Ciertamente es mejor que contaminar TODOS sus controladores o modelos por el simple hecho de tener una sola vista.

computrius
fuente
2

Hay otra forma de archivarlo.

  1. Simplemente implemente la clase BaseController para todos los controladores .

  2. En la BaseControllerclase, cree un método que devuelva una clase Modelo como, por ejemplo.

public MenuPageModel GetTopMenu() 
{    

var m = new MenuPageModel();    
// populate your model here    
return m; 

}
  1. Y en la Layoutpágina puedes llamar a ese métodoGetTopMenu()
@using GJob.Controllers

<header class="header-wrapper border-bottom border-secondary">
  <div class="sticky-header" id="appTopMenu">
    @{
       var menuPageModel = ((BaseController)this.ViewContext.Controller).GetTopMenu();
     }
     @Html.Partial("_TopMainMenu", menuPageModel)
  </div>
</header>
Desarrollador
fuente
0

Supongamos que su modelo es una colección de objetos (o tal vez un solo objeto). Para cada objeto del modelo, haga lo siguiente.

1) Coloque el objeto que desea mostrar en ViewBag. Por ejemplo:

  ViewBag.YourObject = yourObject;

2) Agregue una declaración using en la parte superior de _Layout.cshtml que contiene la definición de clase para sus objetos. Por ejemplo:

@using YourApplication.YourClasses;

3) Cuando haga referencia a yourObject en _Layout, cámbielo. Puede aplicar el yeso por lo que hizo en (2).

HappyPawn8
fuente
-2
public interface IContainsMyModel
{
    ViewModel Model { get; }
}

public class ViewModel : IContainsMyModel
{
    public string MyProperty { set; get; }
    public ViewModel Model { get { return this; } }
}

public class Composition : IContainsMyModel
{
    public ViewModel ViewModel { get; set; }
}

Utilice IContainsMyModel en su diseño.

Resuelto. Regla de interfaces.

Será
fuente
1
No estoy seguro de por qué fue rechazado. El uso de una interfaz, similar a lo que ha hecho aquí, funcionó en mi contexto.
costa
-6

Por ejemplo

@model IList<Model.User>

@{
    Layout="~/Views/Shared/SiteLayout.cshtml";
}

Leer más sobre la nueva directiva @model

Martín Fabik
fuente
Pero, ¿qué pasa si quiero pasar el primer elemento de la colección al modelo de diseño?
SiberianGuy
Debe buscar el primer elemento en su controlador y establecer el modelo en @model Model.User
Martin Fabik
Pero quiero que mi página obtenga IList y Layout, solo el primer elemento
SiberianGuy
Si te he entendido bien, ¿quieres que el modelo sea un IList <SomeThing> y en la vista obtenga el primer elemento de la colección? Si es así, use @ Model.First ()
Martin Fabik
6
El cartel preguntaba cómo pasar un modelo a la página _Layout.cshtml ... no a la vista principal que usa el diseño.
Pure.Krome