web-dev-qa-db-fra.com

Comment inclure une vue partielle dans un formulaire Web

Certains sites que je programme utilisent à la fois ASP.NET MVC et WebForms.

J'ai une vue partielle et je souhaite 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 puis-je faire ceci?

78
eKek0

J'ai jeté un œil à 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 de vue, 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 la page WebForms actuelle comme ignorée.

Le gros problème semble être le HttpContext - les pages MVC s'appuient sur un HttpContextBase (plutôt que sur HttpContext comme le font les WebForms) et bien que les deux implémentent IServiceProvider, elles ne sont pas liées. Les concepteurs de MVC ont délibérément décidé de ne pas modifier les WebForms hérités 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() ); %>
96
Keith

Cela a pris du temps, mais j'ai trouvé une excellente solution. La solution de Keith fonctionne pour beaucoup de gens, mais dans certaines situations, ce n'est pas la meilleure, car 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 simplement la vue avec un modèle donné Je présente ici une nouvelle solution qui exécutera le processus normal.

Étapes générales:

  1. Créer une classe utilitaire
  2. Créer un contrôleur factice avec une vue factice
  3. Dans votre aspx ou master page, appelez la méthode utilitaire pour effectuer un rendu partiel en passant le contrôleur, affichez et si vous en avez besoin, le modèle à rendre (en tant qu'objet),

Vérifions-le attentivement dans cet exemple

1) Créez une classe appelée MVCUtility et créez 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éez 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) Créez maintenant un contrôleur nommé DummyController

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

Créez une vue factice appelée PartialRender.cshtml (vue rasoir) pour le DummyController avec le contenu suivant, notez qu'il exécutera une autre action de rendu à l'aide de l'assistant Html.

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

3) Il suffit maintenant de mettre ceci dans votre fichier MasterPage ou aspx, pour rendre partiellement une vue que vous voulez. Notez que c'est une excellente réponse lorsque vous avez plusieurs vues de rasoir que vous souhaitez mélanger avec vos pages MasterPage ou aspx. (en supposant que nous ayons une vue partielle appelée Login pour le contrôleur Home).

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

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

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

Cette solution est excellente, n'utilise pas d'appel ajax , ce qui ne provoquera pas de rendu retardé pour les vues imbriquées, il ne fait pas de nouvelle WebRequest donc il n'apportera pas vous une nouvelle session , et elle traitera la méthode pour récupérer le ActionResult pour la vue que vous voulez, cela fonctionne sans passer par aucun modèle

Merci à tilisation de MVC RenderAction dans un formulaire Web

34
Daniel

le moyen le plus évident 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>
20
Alexander Taran

C'est super, merci!

J'utilise MVC 2 sur .NET 4, qui nécessite qu'un TextWriter soit passé dans ViewContext, vous devez donc passer 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);
    }
10
Dr. C. Hilarius

Voici une approche similaire qui a fonctionné pour moi. La stratégie consiste à restituer la vue partielle dans une chaîne, puis à la produire 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 codebehind, 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 vous aurez accès au contenu rendu

<%= NavigationBarContent %>

J'espère que ça t'as aidé!

5
aarondcoleman

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 AJAX appel HTML pour que les paramètres (le cas échéant) soient donnés via la chaîne de requête URL.

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

/controls/PartialViewControl.ascx, fichier

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

Ajoutez ensuite le contrôle utilisateur à votre page de 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/"  />
3
Bill Heitstuman

FWIW, je devais être en mesure de rendre une vue partielle dynamiquement à partir du code de formulaires Web existant et de l'insérer en haut d'un contrôle donné. J'ai trouvé que la réponse de Keith peut provoquer le rendu partiel de la vue en dehors de <html /> tag.

En utilisant les réponses de Keith et Hilarius pour l'inspiration, plutôt que de rendre directement à HttpContext.Current.Response.Output, j'ai rendu la chaîne html et l'ai ajoutée en tant que LiteralControl au contrôle approprié.

Dans la classe d'assistance statique:

    public static string RenderPartial(string partialName, object model)
    {
        //get a wrapper for the legacy WebForm context
        var httpCtx = new HttpContextWrapper(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(), new StringWriter());

        // This will render the partial view direct to the output, but be careful as it may end up outside of the <html /> tag
        //view.Render(vctx, HttpContext.Current.Response.Output);

        // Better to render like this and create a literal control to add to the parent
        var html = new StringWriter();
        view.Render(vctx, html);
        return html.GetStringBuilder().ToString();
    }

En appelant la classe:

    internal void AddPartialViewToControl(HtmlGenericControl ctrl, int? insertAt = null, object model)
    {
        var lit = new LiteralControl { Text = MvcHelper.RenderPartial("~/Views/Shared/_MySharedView.cshtml", model};
        if (insertAt == null)
        {
            ctrl.Controls.Add(lit);
            return;
        }
        ctrl.Controls.AddAt(insertAt.Value, lit);
    }
0
lukep