So fügen Sie eine Teilansicht in ein Webformular ein

80

Einige Websites, die ich programmiere, verwenden sowohl ASP.NET MVC als auch WebForms.

Ich habe eine Teilansicht und möchte diese in ein Webformular aufnehmen. Die Teilansicht enthält Code, der auf dem Server verarbeitet werden muss, sodass die Verwendung von Response.WriteFile nicht funktioniert. Es sollte mit deaktiviertem Javascript funktionieren.

Wie kann ich das machen?

eKek0
quelle
Ich habe das gleiche Problem - Html.RenderPartial kann nicht mit WebForms arbeiten, aber es sollte immer noch eine Möglichkeit geben, dies zu tun.
Keith

Antworten:

98

Ich habe mir die MVC-Quelle angesehen, um herauszufinden, wie ich das machen kann. Es scheint eine sehr enge Kopplung zwischen Controller-Kontext, Ansichten, Ansichtsdaten, Routing-Daten und den HTML-Render-Methoden zu geben.

Um dies zu erreichen, müssen Sie grundsätzlich alle diese zusätzlichen Elemente erstellen. Einige von ihnen sind relativ einfach (z. B. die Ansichtsdaten), andere sind etwas komplexer. Beispielsweise wird bei den Routing-Daten die aktuelle WebForms-Seite als ignoriert betrachtet.

Das große Problem scheint der HttpContext zu sein - MVC-Seiten basieren auf einer HttpContextBase (anstelle von HttpContext wie bei WebForms), und während beide IServiceProvider implementieren, sind sie nicht miteinander verbunden. Die Designer von MVC haben absichtlich beschlossen, die alten WebForms nicht zu ändern, um die neue Kontextbasis zu verwenden. Sie haben jedoch einen Wrapper bereitgestellt.

Dies funktioniert und Sie können einer WebForm eine Teilansicht hinzufügen:

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

}

Dann können Sie in Ihrem WebForm Folgendes tun:

<% WebFormMVCUtil.RenderPartial( "ViewName", this.GetModel() ); %>
Keith
quelle
1
Dies funktioniert bei einer grundlegenden Seitenanforderung, aber view.Render () wird mit der Ausnahme "Validierung des Viewstate MAC fehlgeschlagen ..." angezeigt, wenn Sie auf der Containerseite Post-Backs durchführen. Können Sie das auch bestätigen, Keith?
Kurt Schindler
Ich erhalte diesen Viewstate-Fehler nicht. Ich denke jedoch, dass die Teilansicht, die Sie rendern, WebForm-Steuerelemente enthält. Diese RenderPartial-Methode wird beim Rendern ausgelöst - nach jedem Ansichtsstatus. WebForm-Steuerelemente in der Teilansicht werden beschädigt und liegen außerhalb des normalen Seitenlebenszyklus.
Keith
Eigentlich habe ich jetzt - es scheint für einige WebForms-Steuerungshierarchien und nicht für andere aufzutreten. Seltsamerweise wird der Fehler innerhalb der MVC-Rendermethoden ausgelöst, als ob der zugrunde liegende Aufruf von Page. Render erwartet eine Seiten- und Ereignis-MAC-Validierung, was in MVC immer völlig falsch wäre.
Keith
Sehen Sie sich die Antwort von Hilarius an, wenn Sie sich fragen, warum dies unter MVC2 und höher nicht kompiliert wird.
Jenny O'Reilly
1
Interessiert auch an den neuen und besseren Möglichkeiten, dies zu tun. Ich verwende diesen Ansatz, um Teilansichten in eine Webforms-Masterseite zu laden (yay, es funktioniert!). Als ich von der Masterseite aufgerufen wurde, konnte ich keinen Controller-Kontext abrufen und musste einen neuen einrichten.
Pat James
39

Es hat eine Weile gedauert, aber ich habe eine großartige Lösung gefunden. Keiths Lösung funktioniert für viele Menschen, aber in bestimmten Situationen ist es nicht die beste, weil manchmal mögen Sie Ihre Anwendung durch den Prozess des Controllers geht für das Rendern der Ansicht, und Keith-Lösung macht nur die Ansicht mit einem bestimmten Modell I‘ Ich präsentiere hier eine neue Lösung, die den normalen Prozess ausführt.

Allgemeine Schritte:

  1. Erstellen Sie eine Utility-Klasse
  2. Erstellen Sie einen Dummy-Controller mit einer Dummy-Ansicht
  3. Rufen Sie in Ihrem aspxoder master pagedie Dienstprogrammmethode auf, um das teilweise Übergeben des Controllers zu rendern, die Ansicht anzuzeigen und bei Bedarf das zu rendernde Modell (als Objekt).

Lassen Sie es uns in diesem Beispiel genau überprüfen

1) Erstellen Sie eine aufgerufene Klasse MVCUtilityund erstellen Sie die folgenden Methoden:

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

Erstellen Sie eine Klasse zum Übergeben der Parameter. Ich werde hier RendeActionViewModel aufrufen (Sie können in derselben Datei der MvcUtility-Klasse erstellen).

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

2) Erstellen Sie nun einen Controller mit dem Namen DummyController

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

Erstellen Sie eine Dummy-Ansicht mit dem Namen PartialRender.cshtml(Rasiereransicht) für die DummyControllermit dem folgenden Inhalt. Beachten Sie, dass mit dem HTML-Helfer eine weitere Renderaktion ausgeführt wird.

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

3) Fügen Sie dies nun einfach in Ihre MasterPageoder aspx-Datei ein, um eine gewünschte Ansicht teilweise zu rendern. Beachten Sie, dass dies eine gute Antwort ist, wenn Sie mehrere Rasiereransichten haben, die Sie mit Ihren MasterPageoder Ihren aspxSeiten mischen möchten . (Angenommen, wir haben eine PartialView namens Login für die Controller-Startseite).

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

oder wenn Sie ein Modell für die Übergabe an die Aktion haben

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

Diese Lösung ist großartig, verwendet keinen Ajax-Aufruf , der kein verzögertes Rendern für die verschachtelten Ansichten verursacht, keine neue WebRequest erstellt, sodass keine neue Sitzung angezeigt wird , und die Methode zum Abrufen verarbeitet Das ActionResult für die gewünschte Ansicht funktioniert ohne Übergabe eines Modells

Dank der Verwendung von MVC RenderAction in einem Webformular

Daniel
quelle
1
Ich habe alle anderen Lösungen in diesem Beitrag ausprobiert und diese Antwort ist bei weitem die beste. Ich würde jedem empfehlen, diese Lösung zuerst auszuprobieren.
Halcyon
Hallo, Daniel. Kannst du mir bitte helfen. Ich folgte Ihrer Lösung, schlug aber an einer Stelle zu. Ich habe es unter stackoverflow.com/questions/38241661/…
Karthik Venkatraman
Dies ist definitiv eine der besten Antworten, die ich auf SO gesehen habe. Vielen Dank.
FrenkyB
Dies schien mir auch eine großartige Lösung zu sein, und auf den ersten Blick scheint es zu funktionieren, der DummyController und die Ansicht werden aufgerufen und mein Controller und meine Teilansicht werden aufgerufen, aber dann endet die Anforderung, sobald die <% MyApplication.MvcUtility.RenderAction ( "Home", "Login", neu {}); %> Zeile wird in meinem Aspx übergeben, sodass der Rest der Seite nicht gerendert wird. Hat jemand dieses Verhalten erlebt und weiß, wie man es löst?
Hsop
20

Der naheliegendste Weg wäre über AJAX

so etwas (mit jQuery)

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

<script type="text/javascript">
$(document).load(function () {
    $.ajax(
    {    
        type: "GET",
        url : "urltoyourmvcaction",
        success : function (msg) { $("#mvcpartial").html(msg); }
    });
});
</script>
Alexander Taran
quelle
9
wurde nach meiner Antwort hinzugefügt) -:
Alexander Taran
10

Das ist großartig, danke!

Ich verwende MVC 2 unter .NET 4, für das ein TextWriter an ViewContext übergeben werden muss. Daher müssen Sie httpContextWrapper.Response.Output wie unten gezeigt übergeben.

    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);
    }
Dr. C. Hilarius
quelle
5

Hier ist ein ähnlicher Ansatz, der für mich funktioniert hat. Die Strategie besteht darin, die Teilansicht in eine Zeichenfolge zu rendern und diese dann auf der WebForm-Seite auszugeben.

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

Im Seitencodebehind können Sie dies tun

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

    }
}

und auf der Seite haben Sie Zugriff auf den gerenderten Inhalt

<%= NavigationBarContent %>

Hoffentlich hilft das!

aarondcoleman
quelle
Das ist wirklich großartig, besonders wenn Sie Skriptblöcke irgendwo platzieren können!
jrizzo
3

Diese Lösung verfolgt einen anderen Ansatz. Es definiert ein Element, das in einem System.Web.UI.UserControlbeliebigen Webformular platziert und so konfiguriert werden kann, dass der Inhalt von einer beliebigen URL angezeigt wird… einschließlich einer MVC-Teilansicht. Dieser Ansatz ähnelt einem AJAX-Aufruf für HTML, bei dem Parameter (falls vorhanden) über die URL-Abfragezeichenfolge angegeben werden.

Definieren Sie zunächst ein Benutzersteuerelement in 2 Dateien:

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

Fügen Sie dann das Benutzersteuerelement zu Ihrer Webformularseite hinzu:

<%@ 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/"  />
Bill Heitstuman
quelle
Ich denke, dies ist die beste Antwort. Sie können das UserControl wiederverwenden, wenn Sie es mehrmals verwenden möchten. Ändern Sie einfach die contentUrl. Ich rate nur, dass der aktuelle requestPath den Port nicht erhält, falls Sie ihn verwenden Bei einem anderen Port als 80 wird ein Fehler auftreten.
Daniel
Ich habe ein Problem damit gefunden. Diese Methode generiert eine neue Sitzung für die Anforderung. Es ist also so, als würden zwei Standorte am selben Ort arbeiten.
Daniel
Ja, wenn Sie serverseitige Sitzungen verwenden, um Ihren Anwendungsstatus zu speichern, funktioniert diese Lösung nicht. Ich bevorzuge es jedoch, den Status auf dem Client beizubehalten.
Bill Heitstuman
Auf den ersten Blick scheint die Verwendung von WebRequest eine schnelle und einfache Lösung zu sein. Aus meiner Erfahrung gibt es jedoch viele versteckte Probleme, die Probleme verursachen können. Verwenden Sie besser ViewEngine oder einen Ajax auf der Clientseite, wie in anderen Antworten gezeigt. Keine Abstimmung, da dies eine gültige Lösung ist, nur keine, die ich nach dem Versuch empfehlen würde.
Roberto
Dies rendert den Ansichtscode als Zeichenfolge, während ich denke, dass die Idee darin besteht, den gerenderten Ansichtsinhalt
vor
1

FWIW, ich musste in der Lage sein, eine Teilansicht dynamisch aus vorhandenem Webforms-Code zu rendern und sie oben in ein bestimmtes Steuerelement einzufügen. Ich fand heraus, dass die Antwort von Keith dazu führen kann, dass die Teilansicht außerhalb von gerendert wird<html /> Tags .

Ich habe die Antworten von Keith und Hilarius als Inspiration verwendet, anstatt sie direkt in HttpContext.Current.Response.Output zu rendern. Ich habe die HTML-Zeichenfolge gerendert und sie als LiteralControl zum entsprechenden Steuerelement hinzugefügt.

In der statischen Hilfsklasse:

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

In der aufrufenden Klasse:

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