Hugoware

The product of a web developer with a little too much caffeine

Using WebControls In ASP.NET MVC Views – Part 1

with 4 comments

Check out my newest blog post about using WebControls inside of MVC (source code included)🙂

WebControls In MVC Series

I always thought it would be interesting to be able to use WebControls within an MVC application. I’m sure that there is a lot of people that have invested a lot of time in developing custom WebControls that would hate to see them be thrown out just to use the newest framework from Microsoft.

If you think about it, using WebControls inside of MVC doesn’t seem to be that impossible of a goal. All we need to do it post back the ViewState and we’re good to go, right?

Well, as it turns out, it’s a little harder than I thought it might be. Over the next few weeks I’ll be discussing what I’ve tried and where I get. I’m really not even sure if it will end up working out, but here goes anyways.

Disclaimer: This post is the first in a series of experiments to see if bringing WebControls inline with MVC is even possible — the code in these posts do not work and are only for getting ideas and promoting discussion — basically, this code is crap. 🙂

Rendering The Control

I blogged awhile back about using WebControls inside an MVC application, but it covered only controls that didn’t need to postback to the server. Not really that much help but interesting to say the least. The basic idea was to simply create an extension method that performed the DataBind and Render event on the control and then spit out the HTML output to the page.

Unfortunately, there is a lot more to a WebControl than just the rendered output. You’ve got the entire page lifecycle that isn’t being processed anymore, no page level forms with unique IDs, missing your ViewState, on and on… Not looking so great at the moment.

The Plan

Have you ever used the BuildManager class? Its probably not the most commonly used class in .NET, but it has some interesting potential for what we’re wanting to do.

Let’s say for a moment that in the render event of the page we create a second page to host our control, process the page life-cycle and then dump the rendered content back into our page. Granted that won’t work for all of our controls, it might give us a point to start from. By using the BuildManager class we can load an instance of our host page and then process the request all from a helper method in MVC.

The Setup

Let’s hack out a few bits of code to get started. First, lets make a page in our Views directory and make some changes to the code behind.

[ControlLoader.aspx.cs]

//snip...
namespace MvcTest.Views {
    
    public partial class ControlLoader : System.Web.UI.Page {

        //event to catch the formatted string
        public event Action<XDocument> ProcessOutput;

        //override the render event to send the string for processing
        protected override void Render(HtmlTextWriter writer) {
            XDocument document = null;
            using (StringWriter output = new StringWriter()) {
                using (HtmlTextWriter html = new HtmlTextWriter(output)) {
                    base.Render(html);
                    document = XDocument.Parse(output.ToString());
                }
            }
            this.ProcessOutput(document);
        }    

    }

}

[ControlLoader.aspx]

<%@ Page Language="C#" 
    AutoEventWireup="true" 
    CodeBehind="ControlLoader.aspx.cs" 
    Inherits="MvcTest.Views.ControlLoader" %>
<html>
    <head runat="server" />
    <body>
        <form id="pf" runat="server" />
    </body>
</html>

So basically, we’re creating a page class that is going to raise an event that contains the rendered code as a parsed XDocument. With our output we’re now able to insert the relevant code into our MVC page.

Now let’s look at the code for our helper method.

//snip...
namespace MvcTest {

    public static class RenderControlHelper {

        //renders a control and then attempts to perform the page
        //lifecycle using it ((WARNING: Doesn't work -- just experiment code))
        public static void RenderControl(this HtmlHelper helper, Control control) {

            //load our control loader page
            ControlLoader page = (ControlLoader)BuildManager.CreateInstanceFromVirtualPath(
                "~/Views/ControlLoader.aspx", 
                typeof(ControlLoader)
                );

            //add the control to the page
            page.Init += (sender, e) => {
                page.Form.Controls.Add(control);
            };

            //add our event to process the string after the render
            page.ProcessOutput += (output) => {

                //output any header stuff (trying to think of a better way)
                foreach (XElement element in 
                    output.XPathSelectElement("html/head").Elements()) {
                    HttpContext.Current.Response.Write(element.ToString());
                }

                //output any form stuff
                foreach (XElement element in 
                    output.XPathSelectElement("html/body/form").Elements()) {
                    HttpContext.Current.Response.Write(element.ToString());
                }

            };

            //setup a separate context (maybe)
            HttpContext context = new HttpContext(
                HttpContext.Current.Request,
                HttpContext.Current.Response
                );

            //process this request separately
            ((IHttpHandler)page).ProcessRequest(context);
        
        }    
    
    }

}

Our helper method is clearly a little more complicated, but the basic idea is to load a instance of a second page, insert the control into the page, render it and then output our content inline. There is probably several mistakes in the code above that needs to be sorted out (for example, do I need to make a new HttpContext or just use the current one), but those can be worked on later. For now, this method does exactly what we’re wanting.

Let’s actually try this out with a simple TextBox.

[Index.aspx]

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="MvcTest.ViewStatePage" 
    AutoEventWireup="true"
    %>    
<%@ Import Namespace="MvcTest" %>

<script runat="server" >    
    TextBox box;

    //use this code to prepare our control
    void Page_Load(object sender, EventArgs args) {
        box = new TextBox();
        box.Load += (s, e) => {
            box.Text = "Some text";
        };
    }    
</script>

<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <form method="post" >
        <% this.Html.RenderControl(box); %>
    </form>
</asp:Content>

Okay, looks good – We want to see if our events are registering like we hoped so we add a Load event to add a value to the page. When we load the page we see the following…

webforms-1-1

Whoa! Am I seeing this correctly? Did it work? We look at the HTML that is generated and we see what we were hoping to see.

<!--snip-->
<form method="post" > 
        <title></title><div> 
  <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTcwNjgyNTAxMGRk6BLytKdG60ETSGMbSrNnJrnccfg=" /> 
</div><input name="ctl01" type="text" value="Some text" /><div> 
  <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAgKeid+0CQKiwImNC/5nNKY1w+4g2ZcTRkCNUf9YR9ax" /> 
</div> 
    </form>
<!--snip-->

It’s ugly, but it’s what we we’re wanting to see — we can always come back later and do some cleanup. So what happens when we hit enter and post it back?

webforms-1-2

Ouch…

Back To The Drawing Board

I messed around with that error for a couple hours but couldn’t find anything that would make it go away. I tried turning off the ViewState encryption, hardcoding the encryption the keys, etc — no fix. I’m certainly not ready to give up on this idea, but for now it looks like I have to admit defeat. 🙂

If I understand correctly you can run WebForms pages side by side with MVC pages, so I may be trying to solve a non-existent problem.

What do you think? Is this a waste of time? Does it solve anything? Is there an easier way?

Stay tuned for Part 2 — Coming soon!

Advertisements

Written by hugoware

July 27, 2009 at 12:05 am

4 Responses

Subscribe to comments with RSS.

  1. […] post we continue the investigation if we can put WebControls inline with a MVC ViewPage. If you read my previous post on this topic, you’ll remember we got close, but couldn’t quite get it to work as […]

  2. […] terrible way to approach ASP.NET MVC for the first time is to be the guy that is “Using WebControls In ASP.NET MVC Views.” Having this very thoughtful article in my personal notes show my careful consideration for MVC […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: