How to include a partial view inside a webform

60,130

Solution 1

I had a look at the MVC source to see if I could figure out how to do this. There seems to be very close coupling between controller context, views, view data, routing data and the html render methods.

Basically in order to make this happen you need to create all of these extra elements. Some of them are relatively simple (such as the view data) but some are a bit more complex - for instance the routing data will consider the current WebForms page to be ignored.

The big problem appears to be the HttpContext - MVC pages rely on a HttpContextBase (rather than HttpContext like WebForms do) and while both implement IServiceProvider they're not related. The designers of MVC made a deliberate decision not to change the legacy WebForms to use the new context base, however they did provide a wrapper.

This works and lets you add a partial view to a 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 );
    }

}

Then in your WebForm you can do this:

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

Solution 2

It took a while, but I've found a great solution. Keith's solution works for a lot of people, but in certain situations it's not the best, because sometimes you want your application to go through the process of the controller for rendering the view, and Keith's solution just renders the view with a given model I'm presenting here a new solution that will run the normal process.

General Steps:

  1. Create a Utility class
  2. Create a Dummy Controller with a dummy view
  3. In your aspx or master page, call the utility method to render partial passing the Controller, view and if you need, the model to render (as an object),

Let's check it closely in this example

1) Create a Class called MVCUtility and create the following methods:

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

Create a class for passing the parameters, I will call here RendeActionViewModel (you can create in the same file of the MvcUtility Class)

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

2) Now create a Controller named DummyController

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

Create a Dummy view called PartialRender.cshtml (razor view) for the DummyController with the following content, note that it will perform another Render Action using the Html helper.

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

3) Now just put this in your MasterPage or aspx file, to partial render a view that you want. Note that this is a great answer when you have multiple razor's views that you want to mix with your MasterPage or aspx pages. (supposing we have a PartialView called Login for the Controller Home).

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

or if you have a model for passing into the Action

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

This solution is great, doesn't use ajax call, which will not cause a delayed render for the nested views, it doesn't make a new WebRequest so it will not bring you a new session, and it will process the method for retrieving the ActionResult for the view you want, it works without passing any model

Thanks to Using MVC RenderAction within a Webform

Solution 3

most obvious way would be via AJAX

something like this (using jQuery)

<div id="mvcpartial"></div>

<script type="text/javascript">
$(document).load(function () {
    $.ajax(
    {    
        type: "GET",
        url : "urltoyourmvcaction",
        success : function (msg) { $("#mvcpartial").html(msg); }
    });
});
</script>

Solution 4

This is great, thanks!

I'm using MVC 2 on .NET 4, which requires a TextWriter gets passed into the ViewContext, so you have to pass in httpContextWrapper.Response.Output as shown below.

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

Solution 5

Here's a similar approach that has been working for me. The strategy is to render the partial view to a string, then output that in the WebForm page.

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

In the page codebehind, you can do

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

    }
}

and in the page you'll have access to the rendered content

<%= NavigationBarContent %>

Hope that helps!

Share:
60,130

Related videos on Youtube

eKek0
Author by

eKek0

I'm a software developer living in Santiago del Estero, one of the smallest towns in Argentina. I'm also a system analyst, and program in Delphi and ASP.NET with C#. Currently, I'm working in a bank developing in-house software.

Updated on July 05, 2022

Comments

  • eKek0
    eKek0 almost 2 years

    Some site I'm programming is using both ASP.NET MVC and WebForms.

    I have a partial view and I want to include this inside a webform. The partial view has some code that has to be processed in the server, so using Response.WriteFile don't work. It should work with javascript disabled.

    How can I do this?

    • Keith
      Keith almost 15 years
      I have the same problem - Html.RenderPartial can't work on WebForms, but there should still be a way to do this.
  • Kurt Schindler
    Kurt Schindler almost 15 years
    This works one a basic page request, but view.Render() blows up with the "Validation of viewstate MAC failed..." exception if you do any post backs on the container page. Can you confirm the same, Keith?
  • Keith
    Keith almost 15 years
    I don't get that viewstate error - however I think it would occur is the partial view that you're rendering includes any WebForm controls. This RenderPartial method fires on render - after any viewstate. WebForm controls inside the partial view are going to be broken and outside of the normal page lifecycle.
  • Keith
    Keith almost 15 years
    Actually I have now - it seems to occur for some WebForms control hierarchies and not for others. Weirdly the error is thrown from inside the MVC render methods, as if the underlying call to Page. Render is expecting to do page and event MAC validation, which would always be entirely wrong in MVC.
  • jrizzo
    jrizzo about 12 years
    This is actually great, especially when you can put script blocks somewhere!
  • Krisztián Balla
    Krisztián Balla almost 11 years
    See the answer of Hilarius if you wonder why this does not compile under MVC2 and above.
  • Keith
    Keith almost 11 years
    @JennyO'Reilly There are better ways of doing this now. WebForms now has support for working with MVC that was lacking back in 2009 when I wrote this. It's really just a shim for MVC1 and older WebForms.
  • Pat James
    Pat James over 10 years
    Also interested in the new and better ways of doing this. I am using this approach to load partial views in a webforms master page (yay, it works!) When called from the master page I couldn't get a controller context so had to new one up.
  • Daniel
    Daniel almost 10 years
    I Think this is the best answer, you can reuse the UserControl if you are going to use this more than one time, just changing the contentUrl, I just advice that the current requestPath doesn't get the Port, if in case you are using a different port thant 80, it´s going to rise an error.
  • Daniel
    Daniel almost 10 years
    I Found a problem with it, this method generates a new Session for the request. So it´s like having two sites working at the same place.
  • Bill Heitstuman
    Bill Heitstuman almost 10 years
    Yes, if you are using server-side sessions to hold your application state, this solution would not work. However, I prefer maintaining state on the client.
  • Halcyon
    Halcyon over 9 years
    I tried all the other solutions in this post and this answer is by far the best. I would recommend to anyone else to try this solution first.
  • Karthik Venkatraman
    Karthik Venkatraman almost 8 years
    Hi daniel. Can you please help me. I followed your solution but struck in a place. I have raised it under stackoverflow.com/questions/38241661/…
  • Joel Hansen
    Joel Hansen almost 8 years
    This sample almost worked out of the box for me. ViewContext() required one extra argument, namely the httpCtx
  • FrenkyB
    FrenkyB almost 7 years
    This is definitely one of the best answers I've seen on SO. Big thanks.
  • Yogi
    Yogi about 6 years
    At first glance, using WebRequest seems like a quick easy solution. However, from my experience there are many hidden issues that can cause problems. Better to use ViewEngine or some ajax on the client side as shown in other answers. No down vote as this is a valid solution, just not one I would recommend after trying it.
  • hsop
    hsop about 6 years
    This seemed like a great solution to me also, and at first sight it does seem to work, the dummyController and view is called and my contoller and partialview is called but then the request ends as soon as the <% MyApplication.MvcUtility.RenderAction("Home", "Login", new { }); %> line is passed in my aspx, so the rest of the page doesn't render. Has anyone experience this behavior and know how to solve it?
  • nickornotto
    nickornotto over 3 years
    This renders the view code as string while I guess the idea is to render the rendered view content @Bill