2009-03-31 19 views
69

Algunos sitios que estoy programando usan ASP.NET MVC y WebForms.Cómo incluir una vista parcial dentro de un formulario web

Tengo una vista parcial y quiero incluir esto dentro de un formulario web. La vista parcial tiene algún código que debe procesarse en el servidor, por lo que el uso de Response.WriteFile no funciona. Debería funcionar con JavaScript desactivado.

¿Cómo puedo hacer esto?

+0

tengo el mismo problema - Html.RenderPartial no pueden trabajar en WebForms, pero todavía debería haber una manera de hacer esto. – Keith

Respuesta

90

tuve un vistazo a la fuente MVC para ver si podía encontrar la manera de hacer esto. Parece haber un acoplamiento muy estrecho entre el contexto del controlador, las vistas, los datos de visualización, los datos de enrutamiento y los métodos de renderizado html.

Básicamente para que esto suceda, debe crear todos estos elementos adicionales. Algunos de ellos son relativamente simples (como los datos de visualización) pero algunos son un poco más complejos, por ejemplo, los datos de enrutamiento considerarán que la página actual de WebForms se ignorará.

El gran problema parece ser que las páginas HttpContext - MVC se basan en una HttpContextBase (en lugar de HttpContext como WebForms) y mientras ambas implementan IServiceProvider no están relacionadas. Los diseñadores de MVC tomaron la decisión deliberada de no cambiar los WebForms heredados para usar la nueva base de contexto, sin embargo, sí proporcionaron un contenedor.

Esto funciona y le permite añadir una vista parcial a un formulario Web:

public class WebFormController : Controller { } 

public static class WebFormMVCUtil 
{ 

    public static void RenderPartial(string partialName, object model) 
    { 
     //get a wrapper for the legacy WebForm context 
     var httpCtx = new HttpContextWrapper(System.Web.HttpContext.Current); 

     //create a mock route that points to the empty controller 
     var rt = new RouteData(); 
     rt.Values.Add("controller", "WebFormController"); 

     //create a controller context for the route and http context 
     var ctx = new ControllerContext( 
      new RequestContext(httpCtx, rt), new WebFormController()); 

     //find the partial view using the viewengine 
     var view = ViewEngines.Engines.FindPartialView(ctx, partialName).View; 

     //create a view context and assign the model 
     var vctx = new ViewContext(ctx, view, 
      new ViewDataDictionary { Model = model }, 
      new TempDataDictionary()); 

     //render the partial view 
     view.Render(vctx, System.Web.HttpContext.Current.Response.Output); 
    } 

} 

Luego, en el formulario Web se puede hacer esto:

<% WebFormMVCUtil.RenderPartial("ViewName", this.GetModel()); %> 
+1

Esto funciona como una solicitud de página básica, pero view.Render() explota con la excepción "Validación de viewview MAC falló ..." si realiza alguna publicación posterior en la página del contenedor. ¿Puedes confirmar lo mismo, Keith? –

+0

No recibo ese error de viewstate; sin embargo, creo que ocurriría es que la vista parcial que estás renderizando incluye cualquier control de WebForm. Este método RenderPartial se activa en el procesamiento, después de cualquier viewstate. Los controles de WebForm dentro de la vista parcial van a estar rotos y fuera del ciclo de vida normal de la página. – Keith

+0

En realidad lo tengo ahora: parece ocurrir para algunas jerarquías de control de WebForms y no para otras. Extrañamente, el error se produce desde el interior de los métodos de renderizado MVC, como si la llamada subyacente a Page. Render espera realizar la validación MAC de página y evento, lo que siempre sería completamente incorrecto en MVC. – Keith

20

manera más obvia sería a través de AJAX

algo como esto (usando jQuery)

<div id="mvcpartial"></div> 

<script type="text/javascript"> 
$(document).load(function() { 
    $.ajax(
    {  
     type: "GET", 
     url : "urltoyourmvcaction", 
     success : function (msg) { $("#mvcpartial").html(msg); } 
    }); 
}); 
</script> 
+1

De la pregunta: Debería funcionar con JavaScript desactivado. –

+9

se agregó después de mi respuesta) -: –

5

Aquí es un enfoque similar que ha estado trabajando para mí . La estrategia consiste en convertir la vista parcial en una cadena y luego generarla en la página WebForm.

public class TemplateHelper 
{ 
    /// <summary> 
    /// Render a Partial View (MVC User Control, .ascx) to a string using the given ViewData. 
    /// http://www.joeyb.org/blog/2010/01/23/aspnet-mvc-2-render-template-to-string 
    /// </summary> 
    /// <param name="controlName"></param> 
    /// <param name="viewData"></param> 
    /// <returns></returns> 
    public static string RenderPartialToString(string controlName, object viewData) 
    { 
     ViewDataDictionary vd = new ViewDataDictionary(viewData); 
     ViewPage vp = new ViewPage { ViewData = vd}; 
     Control control = vp.LoadControl(controlName); 

     vp.Controls.Add(control); 

     StringBuilder sb = new StringBuilder(); 
     using (StringWriter sw = new StringWriter(sb)) 
     { 
      using (HtmlTextWriter tw = new HtmlTextWriter(sw)) 
      { 
       vp.RenderControl(tw); 
      } 
     } 

     return sb.ToString(); 
    } 
} 

En la página de código subyacente, que puede hacer

public partial class TestPartial : System.Web.UI.Page 
{ 
    public string NavigationBarContent 
    { 
     get; 
     set; 
    } 

    protected void Page_Load(object sender, EventArgs e) 
    { 
     NavigationVM oVM = new NavigationVM(); 

     NavigationBarContent = TemplateHelper.RenderPartialToString("~/Views/Shared/NavigationBar.ascx", oVM); 

    } 
} 

y en la página que va a tener acceso al contenido representado

<%= NavigationBarContent %> 

Espero que ayude!

+0

Esto es realmente genial, ¡especialmente cuando se pueden colocar bloques de scripts en alguna parte! – jrizzo

8

Esto es genial, gracias!

Estoy usando MVC 2 en .NET 4, que requiere que TextWriter pase a ViewContext, por lo que debe pasar httpContextWrapper.Response.Output como se muestra a continuación.

public static void RenderPartial(String partialName, Object model) 
    { 
     // get a wrapper for the legacy WebForm context 
     var httpContextWrapper = new HttpContextWrapper(HttpContext.Current); 

     // create a mock route that points to the empty controller 
     var routeData = new RouteData(); 
     routeData.Values.Add(_controller, _webFormController); 

     // create a controller context for the route and http context 
     var controllerContext = new ControllerContext(new RequestContext(httpContextWrapper, routeData), new WebFormController()); 

     // find the partial view using the viewengine 
     var view = ViewEngines.Engines.FindPartialView(controllerContext, partialName).View as WebFormView; 

     // create a view context and assign the model 
     var viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), httpContextWrapper.Response.Output); 

     // render the partial view 
     view.Render(viewContext, httpContextWrapper.Response.Output); 
    } 
+1

Esto funciona solo cuando NO lanzo la Vista a WebFormView. –

1

Esta solución tiene un enfoque diferente. Define un System.Web.UI.UserControl que puede colocarse en cualquier formulario web y configurarse para mostrar el contenido desde cualquier URL ... incluida una vista parcial de MVC. Este enfoque es similar a una llamada AJAX para HTML en que los parámetros (si los hay) se dan a través de la cadena de consulta URL.

En primer lugar, definir un control de usuario en 2 archivos:

/controls/PartialViewControl.ascx presentar

<%@ Control Language="C#" 
AutoEventWireup="true" 
CodeFile="PartialViewControl.ascx.cs" 
Inherits="PartialViewControl" %> 

/controls/PartialViewControl.ascx.cs:

public partial class PartialViewControl : System.Web.UI.UserControl { 
    [Browsable(true), 
    Category("Configutation"), 
    Description("Specifies an absolute or relative path to the content to display.")] 
    public string contentUrl { get; set; } 

    protected override void Render(HtmlTextWriter writer) { 
     string requestPath = (contentUrl.StartsWith("http") ? contentUrl : "http://" + Request.Url.DnsSafeHost + Page.ResolveUrl(contentUrl)); 
     WebRequest request = WebRequest.Create(requestPath); 
     WebResponse response = request.GetResponse(); 
     Stream responseStream = response.GetResponseStream(); 
     var responseStreamReader = new StreamReader(responseStream); 
     var buffer = new char[32768]; 
     int read; 
     while ((read = responseStreamReader.Read(buffer, 0, buffer.Length)) > 0) { 
      writer.Write(buffer, 0, read); 
     } 
    } 
} 

A continuación, añadir el control del usuario a su página de formulario web:

<%@ Page Language="C#" %> 
<%@ Register Src="~/controls/PartialViewControl.ascx" TagPrefix="mcs" TagName="PartialViewControl" %> 
<h1>My MVC Partial View</h1> 
<p>Below is the content from by MVC partial view (or any other URL).</p> 
<mcs:PartialViewControl runat="server" contentUrl="/MyMVCView/" /> 
+0

Creo que esta es la mejor respuesta, puedes reutilizar el UserControl si vas a usar esto más de una vez, simplemente cambiando el contentUrl, solo te advierto que el requestPath actual no obtiene el puerto, si es que estás usando un puerto diferente que sea 80, se levantará un error. – Daniel

+0

Encontré un problema con él, este método genera una nueva sesión para la solicitud. Entonces es como tener dos sitios trabajando en el mismo lugar. – Daniel

+0

Sí, si está utilizando sesiones del lado del servidor para mantener su estado de aplicación, esta solución no funcionaría. Sin embargo, prefiero mantener el estado en el cliente. –

30

Me tomó un tiempo, pero encontré una gran solución. Como la solución Keith funciona para muchas personas, pero en ciertas situaciones no es la mejor, porque a veces quieres que tu aplicación pase por el proceso del controlador para visualizarla, y la solución de Keith simplemente muestra la vista con un modelo dado Les presento aquí una nueva solución que ejecutará el proceso normal.

Pasos generales:

  1. crear una clase de utilidad
  2. Crear un controlador Maniquí con una visión ficticia
  3. En su aspx o master page, llame al método de utilidad para rendir parcial pasar el controlador, ver y si lo necesita, el modelo a renderizar (como un objeto),

Veámoslo detenidamente en este ejemplo

1) Crear una clase llamada MVCUtility y crear los métodos siguientes:

//Render a partial view, like Keith's solution 
    private static void RenderPartial(string partialViewName, object model) 
    { 
     HttpContextBase httpContextBase = new HttpContextWrapper(HttpContext.Current); 
     RouteData routeData = new RouteData(); 
     routeData.Values.Add("controller", "Dummy"); 
     ControllerContext controllerContext = new ControllerContext(new RequestContext(httpContextBase, routeData), new DummyController()); 
     IView view = FindPartialView(controllerContext, partialViewName); 
     ViewContext viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), httpContextBase.Response.Output); 
     view.Render(viewContext, httpContextBase.Response.Output); 
    } 

    //Find the view, if not throw an exception 
    private static IView FindPartialView(ControllerContext controllerContext, string partialViewName) 
    { 
     ViewEngineResult result = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName); 
     if (result.View != null) 
     { 
      return result.View; 
     } 
     StringBuilder locationsText = new StringBuilder(); 
     foreach (string location in result.SearchedLocations) 
     { 
      locationsText.AppendLine(); 
      locationsText.Append(location); 
     } 
     throw new InvalidOperationException(String.Format("Partial view {0} not found. Locations Searched: {1}", partialViewName, locationsText)); 
    }  

    //Here the method that will be called from MasterPage or Aspx 
    public static void RenderAction(string controllerName, string actionName, object routeValues) 
    { 
     RenderPartial("PartialRender", new RenderActionViewModel() { ControllerName = controllerName, ActionName = actionName, RouteValues = routeValues }); 
    } 

crear una clase para pasar los parámetros, que llamaré aquí RendeActionViewModel (que puede crear en el mismo archivo de la Clase MvcUtility)

public class RenderActionViewModel 
    { 
     public string ControllerName { get; set; } 
     public string ActionName { get; set; } 
     public object RouteValues { get; set; } 
    } 

2) Ahora crear un controlador llamado DummyController

//Here the Dummy controller with Dummy view 
    public class DummyController : Controller 
    { 
     public ActionResult PartialRender() 
     { 
      return PartialView(); 
     } 

    } 

Crear una vista simulada llamada PartialRender.cshtml (vista de afeitar) para la DummyController con el siguiente contenido, tenga en cuenta que se llevará a cabo otra inutilidad la actuación mediante el asistente de HTML

@model Portal.MVC.MvcUtility.RenderActionViewModel 
@{Html.RenderAction(Model.ActionName, Model.ControllerName, Model.RouteValues);} 

3) Ahora sólo hay que poner esto en su MasterPage o aspx archivo, para renderizar parcialmente una vista que desee. Tenga en cuenta que esta es una gran respuesta cuando tiene varias vistas de maquinilla de afeitar que desea mezclar con sus páginas MasterPage o aspx.(Suposing tenemos un PartialView llamada Acceder al controlador principal

<% MyApplication.MvcUtility.RenderAction("Home", "Login", new { }); %> 

o si tiene un modelo para pasar a la acción

<% MyApplication.MvcUtility.RenderAction("Home", "Login", new { Name="Daniel", Age = 30 }); %> 

Esta solución es grande, no utiliza ajax llamada , que no causará un retraso en el procesamiento para las vistas anidadas, no hace una nueva WebRequest por lo que no le traerá una nueva sesión, y procesará el método para recuperar el ActionResult para la vista que desee, funciona sin pasar cualquier modelo

Gracias aUsing MVC RenderAction within a Webform

+1

Probé todas las otras soluciones en esta publicación y esta respuesta es de lejos la mejor. Recomendaría a cualquier otra persona que pruebe esta solución primero. – Halcyon

+0

No funcionará si quiero hacer una solicitud posterior. – maxspan

+0

Hola, Daniel. Podrías ayudarme. Seguí tu solución pero golpeé en un lugar. Lo he planteado en http://stackoverflow.com/questions/38241661/showing-a-razor-view-in-an-web-form-iframe –

Cuestiones relacionadas