Webフォーム内に部分ビューを含める方法


80

私がプログラミングしている一部のサイトでは、ASP.NETMVCとWebFormsの両方を使用しています。

部分的なビューがあり、これをWebフォームに含めたいと思います。部分ビューにはサーバーで処理する必要のあるコードが含まれているため、Response.WriteFileの使用は機能しません。javascriptを無効にして動作するはずです。

これどうやってするの?


同じ問題があります-Html.RenderPartialはWebFormsで機能しませんが、これを行う方法はまだあるはずです。
キース

回答:


99

私はMVCソースを調べて、これを行う方法を理解できるかどうかを確認しました。コントローラのコンテキスト、ビュー、ビューデータ、ルーティングデータ、およびhtmlレンダリングメソッドの間には非常に密接な関係があるようです。

基本的にこれを実現するには、これらの追加要素をすべて作成する必要があります。それらのいくつかは(ビューデータなど)比較的単純ですが、いくつかはもう少し複雑です-たとえば、ルーティングデータは現在のWebFormsページを無視すると見なします。

大きな問題はHttpContextのようです-MVCページは(WebFormsのようにHttpContextではなく)HttpContextBaseに依存しており、どちらもIServiceProviderを実装していますが、関連性はありません。MVCの設計者は、新しいコンテキストベースを使用するようにレガシーWebフォームを変更しないことを意図的に決定しましたが、ラッパーを提供しました。

これは機能し、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 );
    }

}

次に、Webフォームでこれを行うことができます。

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

1
これは基本的なページリクエストの1つで機能しますが、コンテナページでポストバックを行うと、view.Render()は「ビューステートMACの検証に失敗しました...」という例外で爆発します。キース、同じことを確認できますか?
Kurt Schindler

ビューステートエラーは発生しませんが、レンダリングしているビューの一部にWebFormコントロールが含まれている場合に発生すると思います。このRenderPartialメソッドは、任意のビューステートの後、レンダリング時に起動します。部分ビュー内のWebFormコントロールは壊れ、通常のページライフサイクルの外になります。
キース

実際、私は今持っています-それはいくつかのWebFormsコントロール階層で発生し、他では発生しないようです。奇妙なことに、ページへの基になる呼び出しのように、MVCレンダリングメソッドの内部からエラーがスローされます。Renderは、ページとイベントのMAC検証を行うことを期待していますが、これはMVCでは常に完全に間違っています。
キース

これがMVC2以降でコンパイルされない理由がわからない場合は、Hilariusの回答を参照してください。
Krisztiánバッラ

1
これを行うための新しくてより良い方法にも興味があります。私はこのアプローチを使用して、Webフォームのマスターページに部分的なビューをロードしています(そうです、動作します!)マスターページから呼び出されたとき、コントローラーコンテキストを取得できなかったため、新しいコンテキストを作成する必要がありました。
Pat James

40

しばらく時間がかかりましたが、私は素晴らしい解決策を見つけました。キースのソリューションは、多くの人々のために働くが、時にはあなたは、あなたのアプリケーションがしたいので、特定の状況では、最高ではありませんコントローラのプロセスを経るのビューをレンダリングするための、そしてキースのソリューションは、単に与えられたモデルとビューをレンダリング「I mここでは、通常のプロセスを実行する新しいソリューションを紹介します。

一般的な手順:

  1. ユーティリティクラスを作成する
  2. ダミービューでダミーコントローラを作成する
  3. あなたの中aspxmaster page、コントローラ、ビューを渡すと、あなたが必要な場合は、モデルは(オブジェクトとして)レンダリングするために、部分的にレンダリングするためのユーティリティメソッドを呼び出し、

この例で詳しく確認しましょう

1)というクラスMVCUtilityを作成し、次のメソッドを作成します。

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

パラメータを渡すためのクラスを作成します。ここでRendeActionViewModelを呼び出します(MvcUtilityクラスの同じファイルで作成できます)

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

2)ここで、という名前のコントローラーを作成します DummyController

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

次のコンテンツを使用して、PartialRender.cshtml(レイザービュー)というダミービューを作成しますDummyController。Htmlヘルパーを使用して別のレンダリングアクションを実行することに注意してください。

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

3)これをMasterPageまたはaspxファイルに入れるだけで、必要なビューを部分的にレンダリングできます。これは、MasterPageまたはaspxページと混合したい複数のかみそりのビューがある場合に最適な回答であることに注意してください。(コントローラーホームのログインと呼ばれるPartialViewがあるとします)。

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

または、アクションに渡すためのモデルがある場合

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

このソリューションは素晴らしいですが、AJAX呼び出しを使用しないことはありません、レンダリング遅れネストされたビューのために、それは新しいWebRequestクラスを作成していない、それはので、あなたに新しいセッションをもたらすことはありません、それが取得するためのメソッドを処理しますが必要なビューのActionResultモデルを渡さなくても機能します

Webフォーム内でMVCRenderAction使用していただきありがとうございます


1
私はこの投稿で他のすべての解決策を試しましたが、この答えは断然最高です。最初にこのソリューションを試すことを他の人に勧めます。
ハルシオン2014年

こんにちはダニエル。手伝ってくれませんか。私はあなたの解決策に従いましたが、ある場所で攻撃しました。私はstackoverflow.com/questions/38241661/で
Karthik Venkatraman 2016

これは間違いなく私がSOで見た中で最高の答えの1つです。本当にありがとう。
frenkyB 2017年

これは私にとっても素晴らしい解決策のように思えました。一見すると、dummyControllerとviewが呼び出され、contollerとpartialviewが呼び出されますが、<%MyApplication.MvcUtility.RenderAction( "ホーム"、 "ログイン"、新しい{}); %>行がaspxに渡されるため、ページの残りの部分はレンダリングされません。誰かがこの行動を経験し、それを解決する方法を知っていますか?
hsop 2018

20

最も明白な方法はAJAX経由です

このようなもの(jQueryを使用)

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

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

9
私の応答の後に追加されました)-:
Alexander Taran

11

これは素晴らしいです、ありがとう!

.NET4でMVC2を使用していますが、TextWriterをViewContextに渡す必要があるため、以下に示すようにhttpContextWrapper.Response.Outputを渡す必要があります。

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

5

これが私のために働いている同様のアプローチです。戦略は、部分ビューを文字列にレンダリングし、それを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();
    }
}

コードビハインドのページでは、次のことができます

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

    }
}

このページでは、レンダリングされたコンテンツにアクセスできます

<%= NavigationBarContent %>

お役に立てば幸いです。


これは実際には素晴らしいことです。特に、スクリプトブロックをどこかに配置できる場合はなおさらです。
jrizzo 2012

3

このソリューションは別のアプローチを取ります。これは、System.Web.UI.UserControl任意のWebフォームに配置でき、MVC部分ビューを含む任意のURLからコンテンツを表示するように構成できるを定義します。このアプローチは、パラメータ(存在する場合)がURLクエリ文字列を介して指定されるという点でHTMLのAJAX呼び出しに似ています。

まず、2つのファイルでユーザーコントロールを定義します。

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

次に、ユーザーコントロールを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/"  />

これが最良の答えだと思います。これを複数回使用する場合は、contentUrlを変更するだけで、UserControlを再利用できます。使用している場合は、現在のrequestPathがポートを取得しないことをお勧めします。 80とは異なるポートでは、エラーが発生します。
ダニエル

問題が見つかりました。このメソッドは、リクエストに対して新しいセッションを生成します。つまり、2つのサイトが同じ場所で機能しているようなものです。
ダニエル

はい。サーバー側のセッションを使用してアプリケーションの状態を保持している場合、このソリューションは機能しません。ただし、クライアントの状態を維持することをお勧めします。
Bill Heitstuman

一見すると、WebRequestを使用することは簡単な解決策のように思えます。しかし、私の経験から、問題を引き起こす可能性のある多くの隠れた問題があります。他の回答に示されているように、クライアント側でViewEngineまたはいくつかのajaxを使用することをお勧めします。これは有効な解決策であるため、反対票はありません。試してみた後に推奨する解決策ではありません。
ロベルト

これはビューコードを文字列としてレンダリングしますが、レンダリングされたビューコンテンツを@Billでレンダリングするのがアイデアだと思います
nickornotto

1

FWIW、既存のWebフォームコードから部分ビューを動的にレンダリングし、それを特定のコントロールの上部に挿入できるようにする必要がありました。キースの答えにより、部分ビューが外部にレンダリングされる可能性があることがわかりました。<html />タグの。

HttpContext.Current.Response.Outputに直接レンダリングするのではなく、KeithとHilariusからの回答をインスピレーションに使用して、html文字列をレンダリングし、LiteralControlとして関連するコントロールに追加しました。

静的ヘルパークラスの場合:

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

クラスの呼び出し:

    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);
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.