2009-03-31 6 views
69

Certains sites que je programme utilisent à la fois ASP.NET MVC et WebForms.Comment inclure une vue partielle dans un formulaire Web

J'ai une vue partielle et je veux l'inclure dans un formulaire Web. La vue partielle a du code qui doit être traité sur le serveur, donc l'utilisation de Response.WriteFile ne fonctionne pas. Cela devrait fonctionner avec javascript désactivé.

Comment est-ce que je peux faire ceci?

+0

J'ai le même problème - Html.RenderPartial ne peuvent pas travailler WebForms, mais il devrait toujours y avoir un moyen de le faire. – Keith

Répondre

90

J'ai eu un coup d'oeil à la source MVC pour voir si je pouvais comprendre comment faire cela. Il semble y avoir un couplage très étroit entre le contexte du contrôleur, les vues, les données d'affichage, les données de routage et les méthodes de rendu html.

Fondamentalement, pour que cela se produise, vous devez créer tous ces éléments supplémentaires. Certains d'entre eux sont relativement simples (comme les données de vue) mais certains sont un peu plus complexes - par exemple, les données de routage considéreront que la page WebForms actuelle doit être ignorée. Le gros problème semble être le HttpContext - MVC pages reposent sur un HttpContextBase (plutôt que HttpContext comme WebForms faire) et tandis que tous les deux implémenter IServiceProvider ils ne sont pas liés. Les concepteurs de MVC ont délibérément décidé de ne pas modifier les anciens WebForms pour utiliser la nouvelle base de contexte, mais ils ont fourni un wrapper.

Cela fonctionne et vous permet d'ajouter une vue partielle à un WebForm:

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); 
    } 

} 

Ensuite, dans votre WebForm vous pouvez faire ceci:

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

Cela fonctionne une demande de page de base, mais view.Render() explose avec l'exception "Validation de viewstate MAC a échoué ..." si vous faites des publications sur la page du conteneur. Pouvez-vous confirmer la même chose, Keith? –

+0

Je ne reçois pas cette erreur viewstate - mais je pense qu'il se produirait que la vue partielle que vous affichez inclut des contrôles WebForm. Cette méthode RenderPartial se déclenche sur le rendu - après n'importe quel état de vue. Les contrôles WebForm à l'intérieur de la vue partielle vont être brisés et en dehors du cycle de vie normal de la page. – Keith

+0

En fait, j'ai maintenant - il semble se produire pour certaines hiérarchies de contrôle WebForms et pas pour d'autres. Bizarrement, l'erreur est renvoyée à l'intérieur des méthodes de rendu MVC, comme si l'appel sous-jacent à Page. Render s'attend à effectuer une validation de page et d'événement MAC, ce qui serait toujours complètement faux dans MVC. – Keith

20

façon la plus évidente serait via AJAX

quelque chose comme ça (en utilisant 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 question: Cela devrait fonctionner avec javascript désactivé. –

+9

a été ajouté après ma réponse) -: –

5

Voilà une approche similaire qui a travaillé pour moi . La stratégie consiste à rendre la vue partielle à une chaîne, puis à l'afficher dans la page 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(); 
    } 
} 

Dans la page behind, vous pouvez faire

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); 

    } 
} 

et dans la page que vous aurez accès au contenu rendu

<%= NavigationBarContent %> 

Hope that helps!

+0

C'est vraiment génial, surtout quand on peut mettre des blocs de script quelque part! – jrizzo

8

C'est génial, merci! J'utilise MVC 2 sur .NET 4, qui nécessite qu'un TextWriter soit passé dans ViewContext, donc vous devez passer dans httpContextWrapper.Response.Output comme indiqué ci-dessous.

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

Cela fonctionne uniquement lorsque je ne cast pas la vue en WebFormView. –

1

Cette solution adopte une approche différente. Il définit un System.Web.UI.UserControl qui peut être placé sur n'importe quel formulaire Web et être configuré pour afficher le contenu de n'importe quelle URL ... y compris une vue partielle MVC. Cette approche est similaire à un appel AJAX pour HTML en ce que les paramètres (le cas échéant) sont donnés via la chaîne de requête d'URL.

d'abord, définir un contrôle utilisateur dans 2 fichiers:

fichier /controls/PartialViewControl.ascx

<%@ 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); 
     } 
    } 
} 

Ensuite, ajoutez le contrôle de l'utilisateur à la page de votre formulaire 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

Je pense que c'est la meilleure réponse, vous pouvez réutiliser le UserControl si vous allez l'utiliser plus d'une fois, juste en changeant le contentUrl, je vous conseille juste que le requestPath actuel n'obtient pas le Port, si vous êtes en utilisant un port différent de 80, cela va générer une erreur. – Daniel

+0

J'ai trouvé un problème, cette méthode génère une nouvelle session pour la requête. C'est comme si deux sites travaillaient au même endroit. – Daniel

+0

Oui, si vous utilisez des sessions côté serveur pour conserver votre état d'application, cette solution ne fonctionnerait pas. Cependant, je préfère maintenir l'état sur le client. –

30

Cela a pris du temps, mais j'ai trouvé une solution géniale. Comme la solution Keith fonctionne pour beaucoup de gens, mais dans certaines situations ce n'est pas le meilleur, parce que parfois vous voulez que votre application passe par le processus du contrôleur pour le rendu de la vue, et La solution de Keith rend juste la vue avec un modèle donné Je présente ici une nouvelle solution qui fonctionnera normalement.

étapes générales:

  1. créer une classe utilitaire
  2. Créer un contrôleur factice en vue factice
  3. Dans votre aspx ou master page, appelez la méthode utilitaire pour rendre partielle passage du contrôleur, vue et si vous avez besoin, le modèle à rendre (comme un objet),

Vérifions-le de près dans cet exemple

1) Créer une classe appelée MVCUtility et créer les méthodes suivantes:

//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 }); 
    } 

créer une classe pour passer les paramètres, je vais appeler ici RendeActionViewModel (vous pouvez créer dans le même fichier de la classe MvcUtility)

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

2) maintenant, créez un contrôleur nommé DummyController

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

    } 

Créer une vue factice appelé PartialRender.cshtml (vue rasoir) pour la DummyController avec le contenu suivant, notez qu'il effectuera une autre Render action en utilisant l'assistant Html

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

3) Maintenant, il suffit de mettre dans votre MasterPage ou aspx fichier, pour rendre partiel une vue que vous voulez. Notez que c'est une excellente réponse lorsque vous avez plusieurs vues de rasoir que vous voulez mélanger avec vos pages MasterPage ou aspx.(Suposing nous avons un PartialView appelé Connectez-vous pour le contrôleur Accueil

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

ou si vous avez un modèle pour passer dans l'action

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

Cette solution est grande, n'utilise pas appel ajax , qui ne provoquera pas rendu différé pour les vues imbriquées, il ne fait pas une nouvelle demande Web alors ne vous apportera pas une nouvelle session, et traitera la méthode pour récupérer le ActionResult pour la vue que vous voulez, cela fonctionne sans passer aucun modèle

Merci àUsing MVC RenderAction within a Webform

+1

J'ai essayé toutes les autres solutions dans ce post et cette réponse est de loin la meilleure. Je recommanderais à quiconque d'essayer cette solution en premier. – Halcyon

+0

Cela ne fonctionnera pas si je veux faire une demande de poste. – maxspan

+0

Salut daniel. Pouvez-vous m'aider s'il vous plaît. J'ai suivi votre solution mais a frappé dans un endroit. Je l'ai soulevé sous http://stackoverflow.com/questions/38241661/showing-a-razor-view-in-an-web-form-iframe –

Questions connexes