웹 양식 내부에 부분보기를 포함하는 방법


80

프로그래밍중인 일부 사이트는 ASP.NET MVC와 WebForms를 모두 사용하고 있습니다.

부분보기가 있고 이것을 웹 양식에 포함하고 싶습니다. 부분보기에는 서버에서 처리해야하는 일부 코드가 있으므로 Response.WriteFile을 사용하면 작동하지 않습니다. 자바 스크립트가 비활성화 된 상태에서 작동합니다.

어떻게 할 수 있습니까?


나는 같은 문제가 있습니다-Html.RenderPartial은 WebForms에서 작동하지 않지만 여전히이 방법이 있습니다.
Keith

답변:


99

이 작업을 수행하는 방법을 알아 내기 위해 MVC 소스를 살펴 보았습니다. 컨트롤러 컨텍스트, 뷰, 뷰 데이터, 라우팅 데이터 및 html 렌더링 메서드간에 매우 밀접한 결합이있는 것 같습니다.

기본적으로이를 실현하려면 이러한 모든 추가 요소를 만들어야합니다. 그들 중 일부는 (보기 데이터와 같은) 비교적 단순하지만 일부는 조금 더 복잡합니다. 예를 들어 라우팅 데이터는 현재 WebForms 페이지를 무시하는 것으로 간주합니다.

큰 문제는 HttpContext로 보입니다. MVC 페이지는 WebForms와 같은 HttpContext가 아닌 HttpContextBase에 의존하며 둘 다 IServiceProvider를 구현하지만 서로 관련이 없습니다. MVC의 디자이너는 새로운 컨텍스트 기반을 사용하기 위해 레거시 WebForms를 변경하지 않기로 의도적으로 결정했지만 래퍼를 제공했습니다.

이것은 작동하며 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 );
    }

}

그런 다음 WebForm에서 다음을 수행 할 수 있습니다.

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

1
이것은 기본 페이지 요청으로 작동하지만, 컨테이너 페이지에서 포스트 백을 수행하면 view.Render ()가 "Validation of viewstate MAC failed ..."예외와 함께 폭발합니다. 당신도 같은 것을 확인할 수 있습니까, Keith?
Kurt Schindler

해당 viewstate 오류가 발생하지 않지만 렌더링하는 부분보기에 WebForm 컨트롤이 포함되어 있다고 생각합니다. 이 RenderPartial 메서드는 모든 viewstate 후에 렌더링시 발생합니다. 부분보기 내부의 WebForm 컨트롤이 손상되고 정상적인 페이지 수명주기를 벗어납니다.
Keith

실제로 나는 지금 가지고 있습니다-일부 WebForms 제어 계층에서 발생하고 다른 계층에서는 발생하지 않는 것 같습니다. 이상하게도 페이지에 대한 기본 호출처럼 MVC 렌더링 메서드 내부에서 오류가 발생합니다. Render는 MVC에서 항상 완전히 잘못된 페이지 및 이벤트 MAC 유효성 검사를 수행 할 것으로 예상합니다.
Keith

이것이 MVC2 이상에서 컴파일되지 않는 이유가 궁금하다면 Hilarius의 답변을 참조하십시오.
Krisztián Balla

1
또한 새롭고 더 나은 방법에 관심이 있습니다. 이 접근 방식을 사용하여 웹 양식 마스터 페이지에서 부분보기를로드하고 있습니다 (예, 작동합니다!). 마스터 페이지에서 호출 할 때 컨트롤러 컨텍스트를 가져올 수 없으므로 새 컨텍스트를 만들어야했습니다.
Pat James

40

시간이 좀 걸렸지 만 훌륭한 해결책을 찾았습니다. 키스의 솔루션은 많은 사람들을 위해 작동하지만 때때로 당신은 당신의 응용 프로그램을 원하기 때문에 특정 상황에서 그것은 최선이 아니다 컨트롤러의 절차를 통해 보기를 렌더링, 그리고 키스의 솔루션 그냥 주어진 모델과 뷰를 렌더링 'I m 여기서는 정상적인 프로세스를 실행할 새로운 솔루션을 제시합니다.

일반 단계 :

  1. 유틸리티 클래스 만들기
  2. 더미 뷰가있는 더미 컨트롤러 만들기
  3. 당신에 aspx또는 master 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 { }); %>

또는 Action에 전달하기위한 모델이있는 경우

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

이 솔루션은 중대하다, 아약스 호출을 사용하지 않는 A가 발생하지 않습니다, 렌더링 지연 중첩 된 뷰를 들어, 그것은 새로운 WebRequest 클래스가되지 않습니다 그것 때문에 당신에게 새로운 세션을 가져 오지 않습니다 , 그리고 그것을 검색하는 방법을 처리합니다 원하는 뷰에 대한 ActionResult모델을 전달하지 않고 작동합니다.

Webform 내에서 MVC RenderAction 사용 덕분에


1
이 게시물의 다른 모든 솔루션을 시도 했으며이 답변이 가장 좋습니다. 다른 사람에게이 솔루션을 먼저 시도하는 것이 좋습니다.
Halcyon 2014

안녕 다니엘. 제발 날 좀 도와 줄 수 있니. 나는 당신의 해결책을 따랐지만 한 장소에 부딪 쳤습니다. 나는 아래를 제기 stackoverflow.com/questions/38241661/...
KARTHIK 벤 카트 라만에게

이것은 확실히 내가 본 최고의 답변 중 하나입니다. 큰 감사를 드린다.
FrenkyB

이것은 나에게도 훌륭한 해결책처럼 보였고, 언뜻보기에는 작동하는 것 같고, dummyController와 view가 호출되고 내 컨트롤러와 partialview가 호출되지만 요청은 <% MyApplication.MvcUtility.RenderAction ( "홈", "로그인", 새 {}); %> 줄이 내 aspx에 전달되므로 페이지의 나머지 부분이 렌더링되지 않습니다. 누구든지이 행동을 경험하고 그것을 해결하는 방법을 알고 있습니까?
hsop apr

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

고마워요!

TextWriter가 ViewContext에 전달되어야하는 .NET 4에서 MVC 2를 사용하고 있으므로 아래와 같이 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

3

이 솔루션은 다른 접근 방식을 취합니다. System.Web.UI.UserControl웹 양식에 배치 할 수 있고 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);
        }
    }
}

그런 다음 웹 양식 페이지에 사용자 정의 컨트롤을 추가합니다.

<%@ 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/"  />

나는 이것이 최선의 대답이라고 생각합니다.이를 두 번 이상 사용하려는 경우 UserControl을 재사용 할 수 있습니다. contentUrl을 변경하면 현재 requestPath가 포트를 얻지 못한다고 조언합니다. 80과 다른 포트를 사용하면 오류가 발생합니다.
Daniel

문제를 발견했습니다.이 메서드는 요청에 대한 새 세션을 생성합니다. 따라서 같은 장소에서 두 개의 사이트를 작업하는 것과 같습니다.
Daniel

예, 애플리케이션 상태를 유지하기 위해 서버 측 세션을 사용하는 경우이 솔루션은 작동하지 않습니다. 그러나 나는 클라이언트에서 상태를 유지하는 것을 선호합니다.
빌 Heitstuman

언뜻보기에 WebRequest를 사용하는 것은 빠르고 쉬운 해결책처럼 보입니다. 그러나 내 경험상 문제를 일으킬 수있는 숨겨진 문제가 많이 있습니다. 다른 답변에 표시된 것처럼 클라이언트 측에서 ViewEngine 또는 일부 ajax를 사용하는 것이 좋습니다. 이것은 유효한 솔루션이므로 반대표가 없으며 시도한 후 권장하지 않습니다.
Roberto

이 아이디어는 렌더링 된 뷰 콘텐츠를 렌더링하는 것이라고 생각하는 동안 뷰 코드를 문자열로 렌더링합니다 @Bill
nickornotto

1

FWIW, 기존 웹 양식 코드에서 부분보기를 동적으로 렌더링하고 지정된 컨트롤의 맨 위에 삽입 할 수 있어야했습니다. Keith의 답변으로 인해 부분보기가 외부에서 렌더링 될 수 있음을 발견했습니다.<html /> 태그 .

영감을 얻기 위해 Keith와 Hilarius의 답변을 사용하여 HttpContext.Current.Response.Output에 직접 렌더링하는 대신 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.