Як включити частковий вигляд всередину веб-форми


80

Деякий сайт, який я програмую, використовує як ASP.NET MVC, так і WebForms.

У мене є частковий вигляд, і я хочу включити це всередину веб-форми. У частковому поданні є деякий код, який потрібно обробити на сервері, тому використання Response.WriteFile не працює. Він повинен працювати з вимкненим javascript.

Як я можу це зробити?


У мене така сама проблема - Html.RenderPartial не може працювати в WebForms, але все одно повинен бути спосіб зробити це.
Кіт

Відповіді:


99

Я подивився джерело MVC, щоб зрозуміти, чи можу я зрозуміти, як це зробити. Здається, існує дуже тісний зв'язок між контекстом контролера, поданнями, даними перегляду, даними маршрутизації та методами відтворення html.

В основному для того, щоб це відбулося, вам потрібно створити всі ці додаткові елементи. Деякі з них відносно прості (наприклад, дані перегляду), але деякі дещо складніші - наприклад, дані маршрутизації вважатимуть поточну сторінку WebForms ігноруванням.

Здається, великою проблемою є HttpContext - сторінки MVC покладаються на HttpContextBase (а не на HttpContext, як це роблять WebForms), і хоча обидві реалізують 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 () задуває з винятком "Перевірка перевірки MAC-даних viewstate не вдалася ...", якщо ви робите будь-які зворотні повідомлення на сторінці контейнера. Ти можеш підтвердити те саме, Кіт?
Курт Шиндлер

Я не отримую цієї помилки viewstate - однак я думаю, що це могло б статися, це часткове представлення, яке ви надаєте, включає будь-які елементи керування WebForm. Цей метод RenderPartial запускається при отрисуванні - після будь-якого стану перегляду. Елементи керування WebForm всередині часткового подання будуть порушені і поза звичайним життєвим циклом сторінки.
Кіт,

Насправді я маю зараз - схоже, це трапляється для одних ієрархій управління WebForms, а не для інших. Дивно, що помилка викидається зсередини методів візуалізації MVC, ніби базовий виклик Page. Render очікує перевірки MAC сторінки та події, що завжди буде абсолютно неправильно в MVC.
Кіт,

Дивіться відповідь Гіларія, якщо вам цікаво, чому це не компілюється під MVC2 і вище.
Krisztián Balla

1
Також цікавлять нові та кращі способи зробити це. Я використовую цей підхід для завантаження часткових переглядів на головній сторінці веб-форм (так, це працює!) При виклику із головної сторінки я не зміг отримати контекст контролера, тому довелося створити новий.
Pat James

40

Це зайняло деякий час, але я знайшов чудове рішення. Рішення Кіта працює для багатьох людей, але в певних ситуаціях це не найкраще, тому що іноді ви хочете, щоб ваша програма проходила процес контролера для рендерінгу подання, а рішення Кіта просто рендерить вигляд із заданою моделлю I ' м, представляючи тут нове рішення, яке буде запускати звичайний процес.

Загальні кроки:

  1. Створіть клас Utility
  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 під назвою Login for the Controller Home).

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

або якщо у вас є модель для переходу до дії

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

Це рішення чудове, не використовує виклик ajax , що не спричинить затримки візуалізації для вкладених подань, не створює нового WebRequest, тому не приносить вам нового сеансу , а також обробляє метод для отримання ActionResult для подання, яке ви хочете, воно працює, не передаючи жодної моделі

Завдяки використанню MVC RenderAction у веб-формі


1
Я спробував усі інші рішення в цій публікації, і ця відповідь є безумовно найкращою. Я рекомендую будь-кому іншому спробувати це рішення спочатку.
Halcyon,

Привіт, Даніелю. Чи можете ви допомогти мені? Я дотримався вашого рішення, але вдарив у місці. Я підняв це під stackoverflow.com/questions/38241661/…
Картік Венкатраман

Це, безумовно, одна з найкращих відповідей, яку я бачив на SO. Велике спасибі.
FrenkyB

Це здалося мені також чудовим рішенням, і на перший погляд це, здається, працює, викликається dummyController і подання, а також викликається мій контролер і частковий перегляд, але потім запит закінчується, як тільки <% MyApplication.MvcUtility.RenderAction ( "Домашня сторінка", "Вхід", нова {}); Рядок%> передано в моєму aspx, тому решта сторінки не відображається. Хтось відчував таку поведінку і знав, як її вирішити?
hsop

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
було додано після моєї відповіді) -:
Олександр Таран

11

Це чудово, дякую!

Я використовую MVC 2 у .NET 4, для чого потрібно, щоб 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

3

Це рішення застосовує інший підхід. Він визначає, System.Web.UI.UserControlяке можна розмістити в будь-якій веб-формі та налаштувати для відображення вмісту за будь-якою URL-адресою…, включаючи частковий вигляд MVC. Цей підхід подібний до виклику AJAX для HTML, оскільки параметри (якщо такі є) задаються через рядок запиту URL.

Спочатку визначте елемент керування користувача у 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, це спричинить помилку.
Даніель

Я знайшов проблему з цим, цей метод генерує новий сеанс для запиту. Отже, це як мати два сайти, що працюють в одному місці.
Даніель

Так, якщо ви використовуєте сеанси на стороні сервера для утримання стану програми, це рішення не буде працювати. Однак я вважаю за краще підтримувати стан клієнта.
Білл Хайтстуман

На перший погляд, використання WebRequest здається швидким простим рішенням. Однак з мого досвіду є багато прихованих проблем, які можуть спричинити проблеми. Краще використовувати ViewEngine або якийсь ajax на стороні клієнта, як показано в інших відповідях. Не голосувати проти, оскільки це дійсне рішення, просто не те, яке я рекомендував би після його випробування.
Роберто,

Це робить код перегляду у вигляді рядка, тоді як, мабуть, ідея полягає у відтворенні відтвореного вмісту перегляду @Bill
nickornotto

1

FWIW, мені потрібно було мати можливість динамічно відображати часткове представлення з коду веб-форм та вставляти його у верхню частину даного елемента керування. Я виявив, що відповідь Кіта може призвести до часткового подання за межами <html />тегу.

Використовуючи відповіді Кіта та Іларіуса для натхнення, а не для прямого відображення на 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);
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.