如何在Webform中包含部分视图


80

我正在编程的某些站点同时使用ASP.NET MVC和WebForms。

我有一个局部视图,我想将此包含在网络表单中。局部视图包含一些必须在服务器中处理的代码,因此使用Response.WriteFile不起作用。它应该与禁用的JavaScript一起工作。

我怎样才能做到这一点?


我有同样的问题-Html.RenderPartial无法在WebForms上工作,但仍然应该有一种方法可以做到这一点。
基思

Answers:


98

我看了一下MVC的源代码,看是否能弄清楚该怎么做。控制器上下文,视图,视图数据,路由数据和html渲染方法之间似乎紧密耦合。

基本上,为了做到这一点,您需要创建所有这些额外的元素。它们中的一些相对简单(例如视图数据),但是却有些复杂-例如,路由数据将认为当前的WebForms页面将被忽略。

最大的问题似乎是HttpContext-MVC页面依赖于HttpContextBase(而不是像WebForms那样的HttpContext),尽管两者都实现了IServiceProvider,但它们并不相关。MVC的设计人员做出了一个明智的决定,即不更改旧版WebForm以使用新的上下文库,但是他们确实提供了包装器。

这可以工作,并且可以将部分视图添加到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()都会爆炸,并显示“ Viewstate MAC验证失败...”异常。基思,你能确认吗?
Kurt Schindler,2009年

我没有看到该viewstate错误-但是我认为它将发生的原因是您正在渲染的部分视图包括任何WebForm控件。在任何viewstate之后,都会在渲染上触发此RenderPartial方法。部分视图内的WebForm控件将被破坏并且不在正常页面生命周期之内。
基思

实际上,我现在知道了-它似乎发生在某些WebForms控件层次结构中,而不是其他情况。奇怪的是,错误是从MVC渲染方法内部抛出的,就像对Page的基础调用一样。Render希望进行页面和事件MAC验证,这在MVC中总是完全错误的。
基思

如果您想知道为什么在MVC2及更高版本中无法编译,请参阅Hilarius的答案。
珍妮·奥莱利

1
对新的更好的方法也很感兴趣。我正在使用这种方法在Webforms母版页中加载部分视图(是的,它可以工作!)当从母版页调用时,我无法获取控制器上下文,因此必须重新创建它。
Pat James

39

花了一段时间,但我找到了一个很好的解决方案。Keith的解决方案适用于很多人,但在某些情况下并不是最好的,因为有时您希望您的应用程序通过控制器的过程来渲染视图,而Keith的解决方案只是使用给定的模型来渲染视图。我在这里提出了一个可以正常运行的新解决方案。

一般步骤:

  1. 创建一个工具类
  2. 使用虚拟视图创建虚拟控制器
  3. 在您的aspx或中master page,调用Utility方法来呈现部分传递Controller,视图的视图,并在需要时调用要呈现的模型(作为对象),

在这个例子中让我们仔细检查一下

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)现在,只需将其放入您的MasterPageaspx文件中,即可部分渲染所需的视图。请注意,当您有多个要与MasterPageaspx页面混合的剃刀视图时,这是一个很好的答案。(假设我们有一个名为Controller Home的Login的PartialView)。

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

或者如果您有用于传递到动作的模型

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

这个解决方案很棒,不使用ajax call,不会导致嵌套视图的渲染延迟,它不会创建新的WebRequest,因此不会为您带来新的会话,并且它将处理用于检索的方法所需视图的ActionResult它无需传递任何模型即可工作

感谢 在Webform中使用MVC RenderAction


1
我在这篇文章中尝试了所有其他解决方案,而这个答案是迄今为止最好的。我建议其他人先尝试此解决方案。
Halcyon 2014年

嗨,丹尼尔。你能帮我么。我遵循了您的解决方案,但遇到了麻烦。我在stackoverflow.com/questions/38241661/…
Karthik Venkatraman

这绝对是我在SO上看到的最好的答案之一。非常感谢。
FrenkyB

这对我来说似乎也是一个很好的解决方案,乍一看似乎确实可行,调用了dummyController和view,并调用了contoller和partialview,但是一旦<%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
在我的回复后添加了)-:
亚历山大·塔兰

10

太好了,谢谢!

我在.NET 4上使用MVC 2,它要求将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窗体上的,并可以配置为显示来自任何URL的内容,包括MVC部分视图。这种方法类似于HTML的AJAX调用,因为参数(如果有)通过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);
        }
    }
}

然后将用户控件添加到您的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/"  />

我认为这是最好的答案,如果您打算多次使用UserControl,则可以重用UserControl,只是更改contentUrl,我只是建议当前的requestPath不能获取Port(如果您正在使用)与80不同的端口,将引发错误。
丹尼尔(Daniel)

我发现有问题,此方法为该请求生成了一个新会话。因此,就像有两个站点在同一地方工作一样。
丹尼尔(Daniel)

是的,如果您使用服务器端会话来保持应用程序状态,则此解决方案将无法正常工作。但是,我更喜欢在客户端上维护状态。
Bill Heitstuman 2014年

乍一看,使用WebRequest似乎是一种快速简便的解决方案。但是,根据我的经验,存在很多可能导致问题的隐藏问题。如其他答案所示,最好在客户端使用ViewEngine或一些ajax。无需投反对票,因为这是一种有效的解决方案,请尝试后再推荐一个。
罗伯托

这将视图代码呈现为字符串,而我想这是将呈现的视图内容呈现为@Bill
nickornotto

1

FWIW,我需要能够从现有的Webforms代码动态呈现局部视图,并将其插入给定控件的顶部。我发现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);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.