Pase datos al diseño que son comunes a todas las páginas


124

Tengo un sitio web que tiene una página de diseño. Sin embargo, esta página de diseño tiene datos que todos los modelos de páginas deben proporcionar, como el título de la página, el nombre de la página y la ubicación en la que realmente estamos para un ayudante HTML que realicé y que realizó alguna acción. Además, cada página tiene sus propias propiedades de modelos de vista.

¿Cómo puedo hacer esto? Parece que es una mala idea escribir un diseño, pero ¿cómo paso estas informaciones?


10
Para cualquiera que lea las respuestas aquí, consulte stackoverflow.com/a/21130867/706346 donde verá una solución mucho más simple y ordenada que cualquier cosa publicada aquí.
Avrohom Yisroel

55
@AvrohomYisroel buena sugerencia. Sin embargo, prefiero el enfoque de @Colin Bacon porque es de tipo fuerte y no en el ViewBag. Quizás una cuestión de preferencias. Sin embargo, votó por su comentario
JP Hellemons

para mvc 5 vea esta respuesta: stackoverflow.com/a/46783375/5519026
Laz Ziya

Respuestas:


142

Si se le requiere que pase las mismas propiedades a cada página, sería inteligente crear un modelo de vista base que sea utilizado por todos sus modelos de vista. Su página de diseño puede tomar este modelo base.

Si se requiere lógica detrás de estos datos, esto se debe poner en un controlador base que sea utilizado por todos sus controladores.

Hay muchas cosas que puede hacer, el enfoque importante es no repetir el mismo código en varios lugares.

Editar: actualización de los comentarios a continuación

Aquí hay un ejemplo simple para demostrar el concepto.

Cree un modelo de vista base del que todos los modelos de vista heredarán.

public abstract class ViewModelBase
{
    public string Name { get; set; }
}

public class HomeViewModel : ViewModelBase
{
}

Su página de diseño puede tomar esto como su modelo.

@model ViewModelBase
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Test</title>
    </head>
    <body>
        <header>
            Hello @Model.Name
        </header>
        <div>
            @this.RenderBody()
        </div>
    </body>
</html>

Finalmente establezca los datos en el método de acción.

public class HomeController
{
    public ActionResult Index()
    {
        return this.View(new HomeViewModel { Name = "Bacon" });
    }
}

12
Pero los datos se usan en el diseño. ¿Cómo puedo pasar los datos al diseño?
Rushino

2
¡Perfecto! Vi mi error Olvidé pasar el modelo a la vista ... qué error tan lamentable. ¡Gracias!
Rushino

77
El problema con este enfoque es que a veces no todas las vistas tienen un ViewModel, por lo que esto no funcionará en ese caso: O /
Cacho Santa

16
Pero, ¿no requeriría esto que cada controlador y cada acción incluyan el código {Name = "Bacon"}? ¿Y si quisiera agregar otra propiedad a ViewModelBase, tendría que ir a cada controlador y cada acción y agregar el código para completar esa propiedad? Usted mencionó "Si se requiere lógica [...], esto debe colocarse en un controlador base [...]". ¿Cómo funcionaría esto para eliminar este código repetido en cada controlador y cada acción?
Lee

55
@Lee Si se trata de datos comunes en todas las páginas, un controlador base es donde colocaría esto. Sus controladores luego heredan de este controlador base. por ej public class HomeController : BaseController. De esta manera, el código común solo necesita escribirse una vez y puede aplicarse a todos los controladores.
Colin Bacon

73

Utilicé RenderAction html helper para la maquinilla de afeitar en el diseño.

@{
   Html.RenderAction("Action", "Controller");
 }

Lo necesitaba para una cadena simple. Entonces mi acción devuelve una cadena y la escribe fácilmente a la vista. Pero si necesita datos complejos, puede devolver PartialViewResult y model.

 public PartialViewResult Action()
    {
        var model = someList;
        return PartialView("~/Views/Shared/_maPartialView.cshtml", model);
    }

Solo necesita poner su modelo a partir de la vista parcial '_maPartialView.cshtml' que creó

@model List<WhatEverYourObjeIs>

Luego puede usar datos en el modelo en esa vista parcial con html.


18
¡Esta es, de lejos, la mejor respuesta!
gingerbreadboy

@gingerbreadboy estuvo de acuerdo en que promueve una buena encapsulación y una separación de preocupaciones.
A-Dubb

35

Otra opción es crear una clase LayoutModel separada con todas las propiedades que necesitará en el diseño y luego insertar una instancia de esta clase en ViewBag. Yo uso el método Controller.OnActionExecuting para llenarlo. Luego, al comienzo del diseño, puede extraer este objeto de ViewBag y continuar accediendo a este objeto fuertemente tipado.


1
Esto en realidad suena como la solución menos dolorosa, ¿hay alguna desventaja? +1
formatc

2
Definitivamente la mejor solución y no veo inconvenientes.
Wiktor Zychla

77
No veo lo que esto te está dando. Si tiene una clase con todas las propiedades que necesita para el diseño, ¿por qué molestarse en agregarla a ViewBag solo para tener que volver a lanzarla? Utilice el modelo en la vista de diseño, aún puede completar el modelo OnActionExecuting. El uso de ViewBag también significa que pierde seguridad de tipo en su controlador, lo que nunca es bueno.
Colin Bacon

3
Lo que esto me da es la capacidad de agregar un modelo para el diseño, sin tener que reestructurar todos los modelos para heredar del modelo único "super" en todos los métodos de todos los controladores, en un proyecto que ya existe. Si está comenzando desde cero, puede optar por derivar todos sus modelos de la raíz común.
DenNukem

55
@ColinBacon otra ventaja de esta opción es que sus acciones no necesitan tener siempre modelos de vista. Además, diría que los desarrolladores que necesitan saber que siempre deben heredar sus modelos de vista de una base es una desventaja.
Josh Noe

28

Presumiblemente, el caso de uso principal para esto es obtener un modelo base para la vista de todas (o la mayoría de) las acciones del controlador.

Teniendo en cuenta eso, he usado una combinación de varias de estas respuestas, respaldo primario en la respuesta de Colin Bacon.

Es correcto que esto siga siendo la lógica del controlador porque estamos completando un modelo de vista para volver a una vista. Por lo tanto, el lugar correcto para poner esto es en el controlador.

Queremos que esto suceda en todos los controladores porque lo usamos para la página de diseño. Lo estoy usando para vistas parciales que se representan en la página de diseño.

También queremos el beneficio adicional de un ViewModel fuertemente tipado

Por lo tanto, he creado un BaseViewModel y BaseController. Todos los controladores ViewModels heredarán de BaseViewModel y BaseController respectivamente.

El código:

BaseController

public class BaseController : Controller
{
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var model = filterContext.Controller.ViewData.Model as BaseViewModel;

        model.AwesomeModelProperty = "Awesome Property Value";
        model.FooterModel = this.getFooterModel();
    }

    protected FooterModel getFooterModel()
    {
        FooterModel model = new FooterModel();
        model.FooterModelProperty = "OMG Becky!!! Another Awesome Property!";
    }
}

Tenga en cuenta el uso de OnActionExecuted tomado de esta publicación SO

HomeController

public class HomeController : BaseController
{
    public ActionResult Index(string id)
    {
        HomeIndexModel model = new HomeIndexModel();

        // populate HomeIndexModel ...

        return View(model);
    }
}

BaseViewModel

public class BaseViewModel
{
    public string AwesomeModelProperty { get; set; }
    public FooterModel FooterModel { get; set; }
}

HomeViewModel

public class HomeIndexModel : BaseViewModel
{

    public string FirstName { get; set; }

    // other awesome properties
}

Pie de página

public class FooterModel
{
    public string FooterModelProperty { get; set; }
}

Layout.cshtml

@model WebSite.Models.BaseViewModel
<!DOCTYPE html>
<html>
<head>
    < ... meta tags and styles and whatnot ... >
</head>
<body>
    <header>
        @{ Html.RenderPartial("_Nav", Model.FooterModel.FooterModelProperty);}
    </header>

    <main>
        <div class="container">
            @RenderBody()
        </div>

        @{ Html.RenderPartial("_AnotherPartial", Model); }
        @{ Html.RenderPartial("_Contact"); }
    </main>

    <footer>
        @{ Html.RenderPartial("_Footer", Model.FooterModel); }
    </footer>

    < ... render scripts ... >

    @RenderSection("scripts", required: false)
</body>
</html>

_Nav.cshtml

@model string
<nav>
    <ul>
        <li>
            <a href="@Model" target="_blank">Mind Blown!</a>
        </li>
    </ul>
</nav>

Espero que esto ayude.


2
Utilicé este enfoque, pero prefiero heredar de una interfaz en lugar de una clase base. Así lo hice: var model = filterContext.Controller.ViewData.Model como IBaseViewModel if (model! = Null) {model.AwesomeModelProperty = "Awesome Property Value"; }
Tom Gerken

2
gran respuesta, preferí esta a todas las demás.
Jynn

1
Gran respuesta, pero tengo una pregunta. "¿Qué sucede si tengo algunas vistas que no tienen ViewModels ...?"
Isma Haro

Intenté esto pero en la Acción de índice, OnActionExecuted llena el FooterModel y luego se crea un nuevo HomeIndexModel con un FooterModel nulo :(
SteveCav

1
@drizzie: en su controlador base, el modelo es una variable local en el método Filter: var model = filterContext.Controller.ViewData.Model como BaseViewModel. No entiendo cómo MVC entiende que esta variable local es la misma que el modelo que HomeController está enviando para ver.
Hooman Bahreini

9

No tiene que meterse con acciones o cambiar el modelo, solo use un controlador base y emita el controlador existente desde el contexto de la vista de diseño.

Cree un controlador base con los datos comunes deseados (título / página / ubicación, etc.) e inicialización de acciones ...

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Asegúrese de que cada controlador use el controlador base ...

public class UserController:_BaseController {...

Transmita el controlador base existente desde el contexto de vista en su _Layout.cshmlpágina ...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

Ahora puede consultar los valores en su controlador base desde su página de diseño.

@myController.MyCommonValue

ACTUALIZAR

También puede crear una extensión de página que le permita usar this.

//Allows typed "this.Controller()." in cshtml files
public static class MyPageExtensions {
    public static _BaseController Controller(this WebViewPage page) => Controller<_BaseController>(page);
    public static T Controller<T>(this WebViewPage page) where T : _BaseController => (T)page.ViewContext.Controller;
}

Entonces solo debe recordar usar this.Controller()cuando desee el controlador.

@{
    var myController = this.Controller(); //_BaseController
}

o controlador específico que hereda de _BaseController...

@{
    var myController = this.Controller<MyControllerType>();
}

¿Cuál es el equivalente de esto en .net core? Como ViewContext.Controller no está presente y hay algún cambio en la cadena de herencia
Jayanth Thyagarajan

4

si desea pasar un modelo completo, vaya así en el diseño:

@model ViewAsModelBase
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8"/>
    <link href="/img/phytech_icon.ico" rel="shortcut icon" type="image/x-icon" />
    <title>@ViewBag.Title</title>
    @RenderSection("styles", required: false)    
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    @RenderSection("scripts", required: false)
    @RenderSection("head", required: false)
</head>
<body>
    @Html.Action("_Header","Controller", new {model = Model})
    <section id="content">
        @RenderBody()
    </section>      
    @RenderSection("footer", required: false)
</body>
</html>

y agregue esto en el controlador:

public ActionResult _Header(ViewAsModelBase model)

4

No creo que ninguna de estas respuestas sea lo suficientemente flexible para una gran aplicación de nivel empresarial. No soy un fanático del uso excesivo de ViewBag, pero en este caso, por flexibilidad, haría una excepción. Esto es lo que haría ...

Debe tener un controlador base en todos sus controladores. Agregue sus datos de diseño OnActionExecuting en su controlador base (o OnActionExecuted si desea diferir eso) ...

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext     
        filterContext)
    {
        ViewBag.LayoutViewModel = MyLayoutViewModel;
    }
}

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View(homeModel);
    }
}

Luego, en su _Layout.cshtml, extraiga su ViewModel de la ViewBag ...

@{
  LayoutViewModel model = (LayoutViewModel)ViewBag.LayoutViewModel;
}

<h1>@model.Title</h1>

O...

<h1>@ViewBag.LayoutViewModel.Title</h1>

Hacer esto no interfiere con la codificación de los controladores de su página o los modelos de vista.


Me gusta tu idea, pero ¿qué pasa si tienes un MyLayoutViewModelcreado dinámicamente, cómo puedo pasar algunos parámetros al OnActionExecutingmétodo?
Rajmond Burgaj 01 de

1
Uh, todavía necesitas base.OnActionExecuting(filterContext)tu OnActionExecutingmétodo !!!
ErikE

4

Crear una vista base que represente el modelo de vista Diseño es un enfoque terrible. Imagine que desea tener un modelo que represente la navegación definida en el diseño. ¿Lo harías CustomersViewModel : LayoutNavigationViewModel? ¿Por qué? ¿Por qué debería pasar los datos del modelo de navegación a través de cada modelo de vista individual que tiene en la solución?

El modelo de vista Diseño debe ser dedicado, por sí solo y no debe forzar al resto de los modelos de vista a depender de él.

En cambio, puede hacer esto, en su _Layout.cshtmlarchivo:

@{ var model = DependencyResolver.Current.GetService<MyNamespace.LayoutViewModel>(); }

Lo más importante, no necesitamos hacerlo new LayoutViewModel()y obtendremos todas las dependencias que se LayoutViewModelhayan resuelto por nosotros.

p.ej

public class LayoutViewModel
{
    private readonly DataContext dataContext;
    private readonly ApplicationUserManager userManager;

    public LayoutViewModel(DataContext dataContext, ApplicationUserManager userManager)
    {
    }
}

¿Dónde llenas este modelo? ¿En un BaseController también?
ndberg

Me imagino que sería una buena idea para un Scopedobjeto de modelo de diseño en ASP..Net Core también.
James Wilkins

No quisiera que la vista fuera a buscar una dependencia. Eso definitivamente no es "MVC". Service Locator es un antipatrón .
Jiveman

Contrariamente a la creencia popular, el Localizador de servicios no es un antipatrón y, en realidad, esto no tiene nada que ver con MVC, ¿estás lanzando palabras de moda @Jiveman? blog.gauffin.org/2012/09/service-locator-is-not-an-anti-pattern
hyankov

El punto principal de Jgauffin en ese artículo parece ser que el término "antipatrón" no debe aplicarse al Localizador de servicios, ya que puede haber al menos algunos usos válidos de SL. Un punto justo. Sin embargo, como es evidente en algunos de sus propios comentarios de discusión, sugiere que si bien SL puede ser un enfoque válido al construir bibliotecas y marcos, no se recomienda necesariamente al construir aplicaciones (lo que consideraría que la pregunta de OP y esta discusión aquí son girando alrededor).
Jiveman

3

Otras respuestas han cubierto casi todo sobre cómo podemos pasar el modelo a nuestra página de diseño. Pero he encontrado una manera de usar la cual puede pasar variables a su página de diseño dinámicamente sin usar ningún modelo o vista parcial en su diseño. Digamos que tiene este modelo:

public class SubLocationsViewModel
{
    public string city { get; set; }
    public string state { get; set; }
}

Y desea obtener ciudad y estado dinámicamente. Por ej.

en su index.cshtml puede poner estas dos variables en ViewBag

@model  MyProject.Models.ViewModel.SubLocationsViewModel
@{
    ViewBag.City = Model.city;
    ViewBag.State = Model.state;
}

Y luego en tu layout.cshtml puedes acceder a esas variables de viewbag

<div class="text-wrap">
    <div class="heading">@ViewBag.City @ViewBag.State</div>
</div>

esto funciona muy bien, @stun_Gravy ¿hay una falla al usar ViewBag para pasar datos como rol o nivel de acceso de usuario?
3not3

3

Hay otra forma de manejar esto. Quizás no sea la forma más limpia desde el punto de vista arquitectónico, pero evita mucho dolor involucrado con las otras respuestas. Simplemente inyecte un servicio en el diseño de Razor y luego llame a un método que obtenga los datos necesarios:

@inject IService myService

Luego, más tarde en la vista de diseño:

@if (await myService.GetBoolValue()) {
   // Good to go...
}

Una vez más, no es limpio en términos de arquitectura (obviamente, el servicio no debe inyectarse directamente en la vista), pero hace el trabajo.


¿No es la forma más limpia? Estoy en desacuerdo. Creo que esto es lo más limpio posible: el objeto se pasa del lugar donde se creó directamente al lugar donde desea que esté, sin "contaminar" a otros controladores con elementos que no necesitan ver. Usar @injectes la mejor solución, en mi opinión.
dasblinkenlight

1
Pensando en esto más, tal vez tengas razón. El hecho de que este método evite tanto dolor es una señal de que tal vez sea la forma más limpia. He estado trabajando en una aplicación ASP.NET Core muy grande y estoy usando este patrón para cosas como la lógica de navegación, datos de encabezado que se encuentran en la mayoría de las páginas, cosas así. He evitado tanto dolor al hacerlo de esta manera.
Andrew

2

También puede hacer uso de RenderSection , que le ayuda a inyectar sus Modeldatos en la _Layoutvista.

Usted puede inyectar View Modeldatos, Json, Script, CSS, HTMLetc.

En este ejemplo, estoy inyectando Jsonde mi Indexvista a Layoutvista.

Index.chtml

@section commonLayoutData{

    <script>

        var products = @Html.Raw(Json.Encode(Model.ToList()));

    </script>

    }

_Layout.cshtml

@RenderSection("commonLayoutData", false)

Esto elimina la necesidad de crear una Base separada View Model.

La esperanza ayuda a alguien.


1
Solución perfecta cuando solo necesita renderizar algo específico para pocas vistas.
Kunal

1

lo que hice es muy simple y funciona

Declare la propiedad estática en cualquier controlador o puede hacer una clase de datos con valores estáticos si lo desea así:

public static username = "Admin";
public static UserType = "Administrator";

Los controladores pueden actualizar estos valores en función de las operaciones. luego puedes usarlos en tu _Diseño

En _layout.cshtml

@project_name.Controllers.HomeController.username
@project_name.Controllers.HomeController.UserType

1
Siempre es útil agregar alguna explicación a su respuesta, para que sea más clara y comprensible. Lea stackoverflow.com/help/how-to-answer .
32cupo

0

¿Por qué nadie ha sugerido métodos de extensión en ViewData?

Opción 1

Me parece, con mucho, la solución menos intrusiva y más simple al problema. Sin cadenas codificadas. Sin restricciones impuestas. Sin codificación mágica. Sin código complejo

public static class ViewDataExtensions
{
    private const string TitleData = "Title";
    public static void SetTitle<T>(this ViewDataDictionary<T> viewData, string value) => viewData[TitleData] = value;
    public static string GetTitle<T>(this ViewDataDictionary<T> viewData) => (string)viewData[TitleData] ?? "";
}

Establecer datos en la página

ViewData.SetTitle("abc");

Opcion 2

Otra opción, facilitando la declaración de campo.

public static class ViewDataExtensions
{
    public static ViewDataField<string, V> Title<V>(this ViewDataDictionary<V> viewData) => new ViewDataField<string, V>(viewData, "Title", "");
}

public class ViewDataField<T,V>
{
    private readonly ViewDataDictionary<V> _viewData;
    private readonly string _field;
    private readonly T _defaultValue;

    public ViewDataField(ViewDataDictionary<V> viewData, string field, T defaultValue)
    {
        _viewData = viewData;
        _field = field;
        _defaultValue = defaultValue;
    }

    public T Value {
        get => (T)(_viewData[_field] ?? _defaultValue);
        set => _viewData[_field] = value;
    }
}

Establecer datos en la página. La declaración es más fácil que la primera opción, pero la sintaxis de uso es un poco más larga.

ViewData.Title().Value = "abc";

Opción # 3

Luego, puede combinar eso con devolver un solo objeto que contenga todos los campos relacionados con el diseño con sus valores predeterminados.

public static class ViewDataExtensions
{
    private const string LayoutField = "Layout";
    public static LayoutData Layout<T>(this ViewDataDictionary<T> viewData) => 
        (LayoutData)(viewData[LayoutField] ?? (viewData[LayoutField] = new LayoutData()));
}

public class LayoutData
{
    public string Title { get; set; } = "";
}

Establecer datos en la página

var layout = ViewData.Layout();
layout.Title = "abc";

Esta tercera opción tiene varios beneficios y creo que es la mejor opción en la mayoría de los casos:

  • La declaración más simple de campos y valores predeterminados.

  • La sintaxis de uso más simple al configurar múltiples campos.

  • Permite configurar varios tipos de datos en ViewData (por ejemplo, Diseño, Encabezado, Navegación).

  • Permite código y lógica adicionales dentro de la clase LayoutData.

PD: No olvide agregar el espacio de nombres de ViewDataExtensions en _ViewImports.cshtml


0

Puede crear un archivo de afeitar en la carpeta App_Code y luego acceder a él desde sus páginas de vista.

Proyecto> Repositorio / IdentityRepository.cs

namespace Infrastructure.Repository
{
    public class IdentityRepository : IIdentityRepository
    {
        private readonly ISystemSettings _systemSettings;
        private readonly ISessionDataManager _sessionDataManager;

        public IdentityRepository(
            ISystemSettings systemSettings
            )
        {
            _systemSettings = systemSettings;
        }

        public string GetCurrentUserName()
        {
            return HttpContext.Current.User.Identity.Name;
        }
    }
}

Proyecto> Código_aplicación / IdentityRepositoryViewFunctions.cshtml:

@using System.Web.Mvc
@using Infrastructure.Repository
@functions
{
    public static IIdentityRepository IdentityRepositoryInstance
    {
        get { return DependencyResolver.Current.GetService<IIdentityRepository>(); }
    }

    public static string GetCurrentUserName
    {
        get
        {
            var identityRepo = IdentityRepositoryInstance;
            if (identityRepo != null)
            {
                return identityRepo.GetCurrentUserName();
            }
            return null;
        }
    }
}

Proyecto> Vistas / Compartido / _Layout.cshtml (o cualquier otro archivo .cshtml)

<div>
    @IdentityRepositoryViewFunctions.GetCurrentUserName
</div>

-1

en lugar de pasar por esto, siempre puedes usar otro enfoque que también sea rápido

cree una nueva vista parcial en el Directorio compartido y llame a su vista parcial en su diseño como

@Html.Partial("MyPartialView")

en su vista parcial, puede llamar a su base de datos y realizar lo que quiera hacer

@{
    IEnumerable<HOXAT.Models.CourseCategory> categories = new HOXAT.Models.HOXATEntities().CourseCategories;
}

<div>
//do what ever here
</div>

suponiendo que haya agregado su base de datos de Entity Framework


1
Hacer downcasting ya que nunca debería ser responsabilidad de la Vista obtener su propio Modelo.
Oxonhammer

-1

Es increíble que nadie haya dicho esto aquí. Pasar un modelo de vista a través de un controlador base es un desastre. Estamos utilizando reclamos de los usuarios para pasar información a la página de diseño (por ejemplo, para mostrar los datos del usuario en la barra de navegación). Hay una ventaja más. Los datos se almacenan a través de cookies, por lo que no es necesario recuperar los datos en cada solicitud a través de parciales. Simplemente haga algunas "reclamaciones de identidad de la red asp" en Google.


@CodeSmith, ¿qué? Estoy proporcionando una solución.
makore

Lo malo, pensé que era una pregunta, pero veo que es una respuesta ahora, eliminada.
CodeSmith

Si se trata de un intento de responder la pregunta, debe aclararse. Se tolera el comentario, pero no debe dominar toda la respuesta; Además, los consejos sobre qué buscar en Google no constituyen una respuesta válida.
tripleee

-6

Puedes usar así:

 @{ 
    ApplicationDbContext db = new ApplicationDbContext();
    IEnumerable<YourModel> bd_recent = db.YourModel.Where(m => m.Pin == true).OrderByDescending(m=>m.ID).Select(m => m);
}
<div class="col-md-12">
    <div class="panel panel-default">
        <div class="panel-body">
            <div class="baner1">
                <h3 class="bb-hred">Recent Posts</h3>
                @foreach(var item in bd_recent)
                {
                    <a href="/BaiDangs/BaiDangChiTiet/@item.ID">@item.Name</a>
                }
            </div>
        </div>
    </div>
</div>

77
Conectarse con la base de datos a la vista es una muy mala idea.
1_bug
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.