Cách bao gồm chế độ xem một phần bên trong biểu mẫu web


80

Một số trang web tôi đang lập trình đang sử dụng cả ASP.NET MVC và WebForms.

Tôi có một góc nhìn và tôi muốn đưa nó vào trong một biểu mẫu web. Chế độ xem từng phần có một số mã phải được xử lý trong máy chủ, vì vậy việc sử dụng Response.WriteFile không hoạt động. Nó sẽ hoạt động với javascript bị tắt.

Tôi có thể làm cái này như thế nào?


Tôi gặp vấn đề tương tự - Html.RenderPartial không thể hoạt động trên WebForms, nhưng vẫn phải có cách để thực hiện việc này.
Keith

Câu trả lời:


99

Tôi đã xem qua nguồn MVC để xem liệu tôi có thể tìm ra cách thực hiện việc này hay không. Dường như có sự kết hợp rất chặt chẽ giữa bối cảnh bộ điều khiển, khung nhìn, dữ liệu xem, dữ liệu định tuyến và các phương thức kết xuất html.

Về cơ bản để thực hiện điều này, bạn cần tạo tất cả các yếu tố phụ này. Một số trong số chúng tương đối đơn giản (chẳng hạn như dữ liệu dạng xem) nhưng một số phức tạp hơn một chút - ví dụ: dữ liệu định tuyến sẽ coi trang WebForms hiện tại bị bỏ qua.

Vấn đề lớn dường như là các trang HttpContext - MVC dựa vào HttpContextBase (chứ không phải HttpContext như WebForms) và trong khi cả hai triển khai IServiceProvider thì chúng không liên quan. Các nhà thiết kế của MVC đã có một quyết định có chủ ý là không thay đổi các WebForms kế thừa để sử dụng cơ sở ngữ cảnh mới, tuy nhiên họ đã cung cấp một trình bao bọc.

Điều này hoạt động và cho phép bạn thêm một phần dạng xem vào 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 );
    }

}

Sau đó, trong WebForm của bạn, bạn có thể làm điều này:

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

1
Điều này hoạt động một yêu cầu trang cơ bản, nhưng view.Render () nổ tung với ngoại lệ "Xác thực MAC của viewstate không thành công ..." nếu bạn thực hiện bất kỳ bài đăng nào trở lại trang vùng chứa. Bạn có thể xác nhận như vậy không, Keith?
Kurt Schindler,

Tôi không gặp lỗi dạng xem đó - tuy nhiên, tôi nghĩ nó sẽ xảy ra là dạng xem một phần mà bạn đang hiển thị bao gồm bất kỳ điều khiển WebForm nào. Phương thức RenderPartial này kích hoạt khi kết xuất - sau bất kỳ trạng thái xem nào. Các điều khiển WebForm bên trong dạng xem từng phần sẽ bị hỏng và nằm ngoài vòng đời của trang bình thường.
Keith

Trên thực tế, bây giờ tôi đã có - nó dường như xảy ra đối với một số cấu trúc phân cấp kiểm soát WebForms và không xảy ra với những cấu trúc phân cấp khác. Điều kỳ lạ là lỗi được đưa ra từ bên trong các phương thức kết xuất MVC, như thể một lệnh gọi cơ bản tới Trang. Render đang mong đợi thực hiện xác thực MAC của trang và sự kiện, điều này sẽ luôn sai trong MVC.
Keith

Xem câu trả lời của Hilarius nếu bạn thắc mắc tại sao điều này không biên dịch theo MVC2 trở lên.
Krisztián Balla

1
Cũng quan tâm đến những cách mới và tốt hơn để thực hiện việc này. Tôi đang sử dụng cách tiếp cận này để tải các chế độ xem từng phần trong trang chính của biểu mẫu web (vâng, nó hoạt động!) Khi được gọi từ trang chính, tôi không thể tải ngữ cảnh bộ điều khiển nên phải nâng cấp mới.
Pat James

40

Phải mất một lúc, nhưng tôi đã tìm thấy một giải pháp tuyệt vời. Giải pháp của Keith phù hợp với nhiều người, nhưng trong một số trường hợp nhất định, nó không phải là tốt nhất, bởi vì đôi khi bạn muốn ứng dụng của mình trải qua quy trình của bộ điều khiển để hiển thị chế độ xem và giải pháp của Keith chỉ hiển thị chế độ xem với một mô hình nhất định I ' m trình bày ở đây một giải pháp mới sẽ chạy quá trình bình thường.

Các bước chung:

  1. Tạo một lớp Tiện ích
  2. Tạo Bộ điều khiển giả với chế độ xem giả
  3. Trong aspxhoặc master page, hãy gọi phương thức tiện ích để kết xuất một phần chuyển Bộ điều khiển, dạng xem và nếu bạn cần, mô hình sẽ kết xuất (dưới dạng một đối tượng),

Hãy kiểm tra kỹ nó trong ví dụ này

1) Tạo một Lớp được gọi MVCUtilityvà tạo các phương thức sau:

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

Tạo một lớp để truyền các tham số, tôi sẽ gọi ở đây là RendeActionViewModel (bạn có thể tạo trong cùng một tệp của Lớp MvcUtility)

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

2) Bây giờ tạo một Bộ điều khiển có tên DummyController

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

Tạo một dạng xem Dummy được gọi là PartialRender.cshtml(dạng xem dao cạo) cho DummyControllernội dung sau, lưu ý rằng nó sẽ thực hiện một Hành động kết xuất khác bằng cách sử dụng trình trợ giúp Html.

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

3) Bây giờ chỉ cần đặt nó vào MasterPagehoặc aspxtệp của bạn , để hiển thị một phần chế độ xem mà bạn muốn. Lưu ý rằng đây là một câu trả lời tuyệt vời khi bạn có nhiều chế độ xem dao cạo mà bạn muốn kết hợp với MasterPagehoặc aspxcác trang của mình. (giả sử chúng ta có Chế độ xem từng phần được gọi là Đăng nhập cho Trang chủ bộ điều khiển).

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

hoặc nếu bạn có một mô hình để chuyển vào Hành động

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

Giải pháp này rất tuyệt vời, không sử dụng lệnh gọi ajax , điều này sẽ không gây ra hiện tượng chậm trễ cho các khung nhìn lồng nhau, nó không tạo một WebRequest mới vì vậy nó sẽ không mang lại cho bạn một phiên mới và nó sẽ xử lý phương thức để truy xuất ActionResult cho chế độ xem bạn muốn, nó hoạt động mà không cần chuyển bất kỳ mô hình nào

Nhờ sử dụng MVC RenderAction trong một biểu mẫu web


1
Tôi đã thử tất cả các giải pháp khác trong bài đăng này và câu trả lời này cho đến nay là tốt nhất. Tôi muốn giới thiệu cho bất kỳ ai khác thử giải pháp này trước.
Halcyon

Chào Daniel. Bạn có thể giúp tôi không. Tôi đã làm theo giải pháp của bạn nhưng đã xảy ra ở một nơi. Tôi đã lớn lên nó dưới stackoverflow.com/questions/38241661/...
Karthik Venkatraman

Đây chắc chắn là một trong những câu trả lời hay nhất mà tôi đã thấy trên SO. Cảm ơn nhiều.
FrenkyB

Đây cũng có vẻ là một giải pháp tuyệt vời đối với tôi, và ngay từ cái nhìn đầu tiên, nó dường như hoạt động, dummyController và view được gọi và contoller và partview của tôi được gọi nhưng sau đó yêu cầu kết thúc ngay sau khi <% MyApplication.MvcUtility.RenderAction ( "Trang chủ", "Đăng nhập", mới {}); %> dòng được chuyển trong aspx của tôi, vì vậy phần còn lại của trang không hiển thị. Có ai trải qua hành vi này và biết làm thế nào để giải quyết nó?
hsop

20

cách rõ ràng nhất sẽ là thông qua AJAX

một cái gì đó như thế này (sử dụng 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
đã được thêm vào sau câu trả lời của tôi) -:
Alexander Taran

11

Điều này là tuyệt vời, cảm ơn!

Tôi đang sử dụng MVC 2 trên .NET 4, yêu cầu TextWriter được chuyển vào ViewContext, vì vậy bạn phải chuyển vào httpContextWrapper.Response.Output như hình bên dưới.

    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

Đây là một cách tiếp cận tương tự đã làm việc cho tôi. Chiến lược là hiển thị một phần dạng xem thành một chuỗi, sau đó xuất ra dạng đó trong trang 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();
    }
}

Trong phần mã của trang, bạn có thể làm

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

    }
}

và trong trang, bạn sẽ có quyền truy cập vào nội dung được hiển thị

<%= NavigationBarContent %>

Hy vọng rằng sẽ giúp!


Điều này thực sự tuyệt vời, đặc biệt là khi bạn có thể đặt các khối script ở đâu đó!
jrizzo

3

Giải pháp này có một cách tiếp cận khác. Nó định nghĩa một System.Web.UI.UserControlcái có thể được đặt trên bất kỳ Biểu mẫu Web nào và được cấu hình để hiển thị nội dung từ bất kỳ URL nào… bao gồm cả chế độ xem từng phần MVC. Cách tiếp cận này tương tự như lệnh gọi AJAX cho HTML trong đó các tham số (nếu có) được đưa ra thông qua chuỗi truy vấn URL.

Đầu tiên, xác định điều khiển người dùng trong 2 tệp:

/controls/PartialViewControl.ascx tệp

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

Sau đó, thêm điều khiển người dùng vào trang biểu mẫu web của bạn:

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

Tôi nghĩ đây là câu trả lời tốt nhất, bạn có thể sử dụng lại UserControl nếu bạn định sử dụng cái này nhiều lần, chỉ cần thay đổi contentUrl, tôi chỉ khuyên rằng requestPath hiện tại không nhận được Cổng, nếu trong trường hợp bạn đang sử dụng một cổng khác không có 80, nó sẽ phát sinh lỗi.
Daniel

Tôi đã tìm thấy sự cố với nó, phương pháp này tạo một Phiên mới cho yêu cầu. Vì vậy, nó giống như có hai trang web làm việc cùng một nơi.
Daniel

Có, nếu bạn đang sử dụng phiên phía máy chủ để giữ trạng thái ứng dụng của mình, giải pháp này sẽ không hoạt động. Tuy nhiên, tôi thích duy trì trạng thái trên máy khách hơn.
Bill Heitstuman

Thoạt nhìn, sử dụng WebRequest có vẻ như là một giải pháp dễ dàng nhanh chóng. Tuy nhiên, từ kinh nghiệm của tôi có rất nhiều vấn đề tiềm ẩn có thể gây ra vấn đề. Tốt hơn nên sử dụng ViewEngine hoặc một số ajax ở phía máy khách như được hiển thị trong các câu trả lời khác. Không bỏ phiếu vì đây là một giải pháp hợp lệ, chỉ không phải là giải pháp tôi muốn giới thiệu sau khi thử nó.
Roberto

Điều này hiển thị mã chế độ xem dưới dạng chuỗi trong khi tôi đoán ý tưởng là hiển thị nội dung chế độ xem được hiển thị
@Bill

1

FWIW, tôi cần có thể hiển thị động một phần chế độ xem từ mã biểu mẫu web hiện có và chèn nó vào đầu một điều khiển nhất định. Tôi thấy rằng câu trả lời của Keith có thể khiến chế độ xem một phần được hiển thị bên ngoài <html />thẻ.

Sử dụng câu trả lời từ Keith và Hilarius để lấy cảm hứng, thay vì hiển thị trực tiếp đến HttpContext.Current.Response.Output, tôi đã hiển thị chuỗi html và thêm nó dưới dạng LiteralControl vào điều khiển liên quan.

Trong lớp trợ giúp tĩnh:

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

Trong lớp gọi:

    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);
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.