Как включить частичное представление в веб-форму

Некоторые сайты, которые я программирую, используют как ASP.NET MVC, так и WebForms.

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

Как я могу это сделать?


person eKek0    schedule 31.03.2009    source источник
comment
У меня та же проблема - Html.RenderPartial не может работать с WebForms, но способ сделать это все равно должен быть.   -  person Keith    schedule 01.07.2009


Ответы (7)


Я взглянул на исходный код MVC, чтобы понять, смогу ли я понять, как это сделать. Кажется, существует очень тесная связь между контекстом контроллера, представлениями, данными представления, данными маршрутизации и методами рендеринга html.

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

Похоже, что большой проблемой является HttpContext - страницы MVC полагаются на HttpContextBase (а не на HttpContext, как это делают WebForms), и хотя оба реализуют IServiceProvider, они не связаны. Разработчики MVC приняли сознательное решение не изменять устаревшие WebForms для использования новой контекстной базы, однако они предоставили оболочку.

Это работает и позволяет вам добавить частичное представление в веб-форму:

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

}

Затем в своей веб-форме вы можете сделать это:

<% WebFormMVCUtil.RenderPartial( "ViewName", this.GetModel() ); %>
person Keith    schedule 02.07.2009
comment
Это работает с одним базовым запросом страницы, но view.Render () взрывается с исключением Validation of viewstate MAC failed ..., если вы выполняете какие-либо обратные сообщения на странице контейнера. Вы можете подтвердить то же самое, Кит? - person Kurt Schindler; 31.07.2009
comment
Я не получаю эту ошибку состояния просмотра, однако я думаю, что это произойдет, если частичное представление, которое вы визуализируете, включает любые элементы управления WebForm. Этот метод RenderPartial запускается при рендеринге - после любого состояния просмотра. Элементы управления WebForm внутри частичного представления будут повреждены и выходят за рамки обычного жизненного цикла страницы. - person Keith; 04.08.2009
comment
На самом деле у меня есть сейчас - похоже, это происходит для некоторых иерархий элементов управления WebForms, а не для других. Как ни странно, ошибка возникает изнутри методов рендеринга MVC, как если бы основной вызов Page. Render ожидает проверки MAC страниц и событий, что всегда было бы совершенно неверным в MVC. - person Keith; 11.08.2009
comment
См. Ответ Хилариуса, если вам интересно, почему это не компилируется под MVC2 и выше. - person Krisztián Balla; 17.06.2013
comment
@ JennyO'Reilly Сейчас есть способы сделать это лучше. WebForms теперь поддерживает работу с MVC, которой не хватало еще в 2009 году, когда я писал это. На самом деле это просто прокладка для MVC1 и более старых WebForms. - person Keith; 18.06.2013
comment
Также интересуюсь новыми и лучшими способами сделать это. Я использую этот подход для загрузки частичных представлений на главной странице веб-форм (ура, это работает!) При вызове с главной страницы я не мог получить контекст контроллера, поэтому пришлось создать новый. - person Pat James; 02.10.2013
comment
Этот образец у меня практически сразу заработал. ViewContext () требовал одного дополнительного аргумента, а именно httpCtx - person Joel Hansen; 28.07.2016

Это заняло некоторое время, но я нашел отличное решение. Решение Кейта работает для многих людей, но в определенных ситуациях оно не самое лучшее, потому что иногда вы хотите, чтобы ваше приложение прошло через процесс контроллера для рендеринга представления, а решение Кейта просто отображает представление с заданной моделью. Я представляю здесь новое решение, которое будет запускать обычный процесс.

Общие шаги:

  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 под названием Login for the Controller Home).

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

или если у вас есть модель для перехода в действие

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

Это отличное решение, не использует вызов ajax, который не вызывает отложенного рендеринга для вложенных представлений, он не создает новый WebRequest < / strong>, поэтому он не приведет к новому сеансу и обработает метод получения ActionResult для нужного вам представления, он работает без передачи любая модель

Благодаря использованию MVC RenderAction в веб-форме

person Daniel    schedule 21.07.2014
comment
Я пробовал все другие решения в этом посте, и этот ответ, безусловно, лучший. Я бы порекомендовал всем сначала попробовать это решение. - person Halcyon; 07.11.2014
comment
Привет, Дэниел. Не могли бы вы мне помочь. Я последовал твоему решению, но попал в точку. Я поднял его в разделе stackoverflow.com/questions/38241661/ - person Karthik Venkatraman; 07.07.2016
comment
Это определенно один из лучших ответов, которые я видел на SO. Большое спасибо. - person FrenkyB; 21.06.2017
comment
Мне это тоже показалось отличным решением, и на первый взгляд кажется, что оно работает, вызывается dummyController и view, вызывается мой contoller и partialview, но затем запрос завершается, как только ‹% MyApplication.MvcUtility.RenderAction ( Главная, Логин, новый {}); Строка% ›передана в моем aspx, поэтому остальная часть страницы не отображается. Кто-нибудь испытал такое поведение и знает, как его решить? - person hsop; 26.04.2018

наиболее очевидный способ - через 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>
person Alexander Taran    schedule 31.03.2009
comment
был добавлен после моего ответа) -: - person Alexander Taran; 01.04.2009

Это здорово, спасибо!

Я использую 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);
    }
person Dr. C. Hilarius    schedule 12.09.2011

Вот аналогичный подход, который работал у меня. Стратегия состоит в том, чтобы отобразить частичное представление в строке, а затем вывести ее на странице 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 %>

Надеюсь, это поможет!

person aarondcoleman    schedule 14.08.2011
comment
Это действительно здорово, особенно когда вы можете где-нибудь разместить блоки сценария! - person jrizzo; 21.04.2012

Это решение использует другой подход. Он определяет 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/"  />
person Bill Heitstuman    schedule 10.06.2014
comment
Я думаю, что это лучший ответ, вы можете повторно использовать UserControl, если собираетесь использовать это более одного раза, просто изменив contentUrl, я просто советую, чтобы текущий requestPath не получил порт, если вы используете другой порт, отличный от 80, возникнет ошибка. - person Daniel; 10.07.2014
comment
Я обнаружил проблему с этим, этот метод генерирует новый сеанс для запроса. Это как если бы два сайта работали в одном месте. - person Daniel; 17.07.2014
comment
Да, если вы используете сеансы на стороне сервера для хранения состояния вашего приложения, это решение не сработает. Однако я предпочитаю сохранять состояние на клиенте. - person Bill Heitstuman; 27.07.2014
comment
На первый взгляд, использование WebRequest кажется простым и быстрым решением. Однако, исходя из моего опыта, существует множество скрытых проблем, которые могут вызвать проблемы. Лучше использовать ViewEngine или некоторый ajax на стороне клиента, как показано в других ответах. Нет голосов против, так как это действительное решение, но я бы не рекомендовал его после того, как попробовал. - person Roberto; 17.04.2018
comment
Это отображает код представления в виде строки, в то время как я предполагаю, что идея состоит в том, чтобы отобразить визуализированное содержимое представления @Bill - person nickornotto; 03.11.2020

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);
    }
person lukep    schedule 07.03.2019