ASP.NET MVC Razor: Cómo representar el HTML de una vista parcial de Razor dentro de la acción del controlador

97

Se conoce cómo generar un HTML de una vista parcial dada en el motor de vista ASP.NET .

Pero si esta funcionalidad se usa en la vista parcial de la maquinilla de afeitar, no funciona, ya que la excepción dice que la vista parcial no se deriva de "UserControl".

¿Cómo arreglar el renderizado para admitir la vista parcial de la maquinilla de afeitar?

Necesito esto porque genero correos electrónicos a partir de estas vistas parciales ...

ACTUALIZAR:

Código que falla (@mcl):

public string RenderPartialToString(string controlName, object viewData)
    {
        ViewPage viewPage = new ViewPage() { ViewContext = new ViewContext() };
        viewPage.Url = this.GetUrlHelper();

        string fullControlName = "~/Views/Email/" + controlName + ".ascx";

        viewPage.ViewData = new ViewDataDictionary(viewData);
        viewPage.Controls.Add(viewPage.LoadControl(fullControlName));

        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        {
            using (HtmlTextWriter tw = new HtmlTextWriter(sw))
            {
                viewPage.RenderControl(tw);
            }
        }
        return sb.ToString();
    }
Peter Stegnar
fuente
1
¿Puede mostrar el código que tiene hasta ahora que genera la excepción?
mlibby

Respuestas:

154
@Html.Partial("nameOfPartial", Model)

Actualizar

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");

    ViewData.Model = model;

    using (StringWriter sw = new StringWriter()) {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
    }
}
jgauffin
fuente
Sí, así es como renderiza una vista parcial dentro de una vista. Pero, ¿cómo representarlo dentro de una acción de controlador?
Peter Stegnar
¡Genial, esto es todo ahora! Funciona con notación Razon y ASP.
Peter Stegnar
2
Una subquestiuon: ¿Cómo renderizar la vista que está en otro alcance del Controlador que el actual? Digamos que está en el alcance "EmailController" (carpeta de vista de correo electrónico).
Peter Stegnar
1
Esta fue una gran solución. Tenía la necesidad exacta de correo electrónico y opté por usar esto.
uadrive
2
@AmeyKhadatkar: no. jquery es del lado del cliente, la vista se genera en el lado del servidor antes de enviarse al navegador.
jgauffin
8

Aunque ya se han dado las respuestas adecuadas, me gustaría proponer una solución menos detallada, que se puede utilizar sin los métodos auxiliares disponibles en una clase de controlador MVC. Usando una biblioteca de terceros llamada "RazorEngine", puede usar el archivo .Net IO para obtener el contenido del archivo de afeitar y llamar

string html = Razor.Parse(razorViewContentString, modelObject);

Obtenga la biblioteca de terceros aquí .

Scott Terry
fuente
5

También puede usar el RenderView Controller extensiondesde aquí ( fuente )

y utilícelo así:

public ActionResult Do() {
var html = this.RenderView("index", theModel);
...
}

funciona para motores de visualización de formularios web y de afeitar

Omu
fuente
Verificó el enlace. @ChurkNorris es el autor de ASP.net MVC Awesome , que es un producto comercial de la versión 2.0 (actualmente la última versión es el 12 de marzo de 2012). La versión 1.9 (última versión el 9 de junio de 2011) sigue siendo de código abierto, pero probablemente no se desarrollará más. ¿Alguna bifurcación de 1.9 por ahí?
Joel Purra
@Omu: RenderView es nulo. Ver msdn.microsoft.com/en-us/library/…
roland
@Roland esta es una extensión de controlador personalizado
Omu
1

Vi que alguien se preguntaba cómo hacerlo para otro controlador.

En mi caso, tenía todas mis plantillas de correo electrónico en la carpeta Vistas / Correo electrónico, pero podría modificar esto para pasar el controlador al que tiene vistas asociadas.

public static string RenderViewToString(Controller controller, string viewName, object model)
    {
        var oldController = controller.RouteData.Values["controller"].ToString();

        if (controller.GetType() != typeof(EmailController))
            controller.RouteData.Values["controller"] = "Email";

        var oldModel = controller.ViewData.Model;
        controller.ViewData.Model = model;
        try
        {
            using (var sw = new StringWriter())
            {
                var viewResult = ViewEngines.Engines.FindView(controller.ControllerContext, viewName,
                                                                           null);

                var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
                viewResult.View.Render(viewContext, sw);

                //Cleanup
                controller.ViewData.Model = oldModel;
                controller.RouteData.Values["controller"] = oldController;

                return sw.GetStringBuilder().ToString();
            }
        }
        catch (Exception ex)
        {
            Elmah.ErrorSignal.FromCurrentContext().Raise(ex);

            throw ex;
        }
    }

Básicamente, lo que hace es tomar un controlador, como AccountController, y modificarlo para pensar que es un EmailController para que el código se vea en la Views/Emailcarpeta. Es necesario hacer esto porque el FindViewmétodo no toma una ruta directa como parámetro, quiere un ControllerContext.

Una vez que ha terminado de representar la cadena, devuelve el AccountController a su estado inicial para que lo utilice el objeto Response.

El hombre muffin
fuente
1

gran código; pequeña pista: si a veces tienes que omitir más datos y no solo el modelo de vista ..

 if (model is ViewDataDictionary)
 {
     controller.ViewData = model as ViewDataDictionary;
 } else {
     controller.ViewData.Model = model;
 }
David Riewe
fuente
2
No ha completado su respuesta
poohdedoo
0

Tomando prestada la respuesta de @jgauffin como una extensión HtmlHelper:

public static class HtmlHelperExtensions
{
    public static MvcHtmlString RenderPartialViewToString(
        this HtmlHelper html, 
        ControllerContext controllerContext, 
        ViewDataDictionary viewData,
        TempDataDictionary tempData,
        string viewName, 
        object model)
    {
        viewData.Model = model;
        string result = String.Empty;

        using (StringWriter sw = new StringWriter())
        {
            ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controllerContext, viewName);
            ViewContext viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, sw);
            viewResult.View.Render(viewContext, sw);

            result = sw.GetStringBuilder().ToString();
        }

        return MvcHtmlString.Create(result);
    }
}

Uso en una vista de navaja:

Html.RenderPartialViewToString(ViewContext, ViewData, TempData, "Search", Model)
abbaf33f
fuente
1
¿Podría explicar la diferencia con el uso de @ Html.Partial (string partialViewName, object model, ViewDataDictionary viewData)? ¿Cuáles son los beneficios ya que requiere HtmlHelper?
bkqc