Hugoware

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

Posts Tagged ‘WebForms

Cobalt Beta – jQuery Style MVC View Templating!

leave a comment »

In a recent blog post I shared a preview of the Cobalt project I’ve been working on. Today, the source code has been uploaded to git-hub and the first beta binaries are being released!

Not sure what Cobalt is? Watch a screencast showing how you can use Cobalt in your MVC projects.
Watch the video now!

If you’ve seen any of my previous blog posts then you realize I’m not much of a fan of the current way to populate templates in ASP.NET MVC. I’ve tried a handful of different ways to improve the experience but so far nothing really felt like an improvement.

Cobalt uses CSS selectors to find and update content, much like the way that jQuery works. Cobalt works *with* ASP.NET MVC (and WebForms) so you can include it in an already existing project and use it right away!

So what does Cobalt look like? Here is an example of an MVC View…

<%@ Page Language="C#" 
    Inherits="System.Web.Mvc.ViewPage<IEnumerable<ProductDetail>>" %>
<%  
    this.Ready(() => {

        //select by tag names
        this.Find("form")
            .Attribute(new {
                action = this.Url.Action("CheckOut")
            });
        
        //find and set values for elements
        string current = this.Find("h3").Text();
        this.Find("#title").Text(current.ToUpper());
        
        //select classes and certain pseudo selectors
        this.Find(".list :even").AddClass("alt-item");
        
        //create new elements on the fly
        var link = this.Create("<a/>")
            .Text("Read About Us")
            .Attribute("href", this.Url.Action("AboutUs"));
        this.Find("p").Append(link);
       
        //create custom elements (like CustomControls)
        Stylesheet sheet = new Stylesheet("~/site.css");
        sheet.AddToPage();
        
    });
    
%>

<html>
    <head>
        <title>Sample</title>
    </head>
    <body>
        <form method="post" >
            <h3>Temp Title</h3>
            <p>The description goes here</p>
            <ul class="list" >
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
                <li>Item 4</li>
                <li>Item 5</li>
            </ul>
        </form>
    </body>
</html>

You can see that much like jQuery we have to queue up the work we need to execute once the document is ready.

I have a lot of documentation to write and a lot of screencasts to record, but don’t forget about the preview video which should get you started. For now, here are a list of the more important details.

The Important Stuff!

  • You must call CobaltManagement.Initialize() in your Global.asax file in the Init method (just override it and call it).
    public override void Init() {
        base.Init();
        CobaltManagement.Initialize();
    }
    
  • Cobalt uses HtmlAgilityPack to handle parsing of html content. I’m not happy with the framework so it will be one of the first things to be phased out, but for now you need to make sure to download it if you get the source code instead of the binaries.
  • You must use this.Ready from your pages and controls to wrap your commands. Otherwise your commands will run before the document is ready and fail.
  • Cobalt is aware of context, so if you use this.Find within a UserControl then you’re only going to select elements within that control. If you need to access the Page level from a UserControl then you can call this.Page.Find to change the scope of the command.
  • Cobalt supports a decent amount of selectors. Below is a list of currently available options.
    • Combinators: ‘ ‘, >, +
    • Shortcut Attributes: #elementId, .cssClass, $elementName
    • Attribute Matching (ex. [@name=’value’]): equals (=), starts with (|=), ends with ($=), contains (~=)
    • Pseudo: first, last, even, odd, nth(#), lt(#), lte(#), gt(#), gte(#), text, password, button, checkbox, radio, submit, file

Keep in mind this is very much beta code – I was literally working on changes today :). If you’re feeling brave then download the source and dig right in!

Download Beta

Cobalt (Source Code)

Advertisements

Written by hugoware

May 31, 2010 at 11:38 pm

WebControls In MVC… again…

with 11 comments

I’m a big fan of MVC so far but there are certainly parts that I don’t like. For example, who is responsible for logical changes to a HTML markup in a view? The Controller? The Model? Personally, I don’t think either of them should which leaves the View to pick up the slack.

But the problem is that I end up with a bunch of crud in my View — which to me looks a lot like a Classic ASP website.

It doesn’t take a lot — Just look at a fairly reasonable example…

<div class="<% if (user.IsAdmin) { %>admin-icon<% } else if (user.IsEditor) { %>editor-icon<% } else { %>user-icon<% } %>" >
<!--snip...-->

I realize that you always have access to RenderPartial and you can also start using a templating engine but I still feel this is something that a WebControl could handle perfectly.

Using A WebControl For Markup

Now if your first thought was “You can’t do anything to WebControls in the Render stage of the page lifecycle” then you’re absolutely right. However, it is possible to gain access to earlier than that and use it with models.

This next bit of code is pretty daunting… I’d recommend you send any children out of the room and shield your eyes…

<!-- Old Sample...
<mvc:MyControl SomeProperty='<% #DataBinder.GetPropertyValue(this.Model, "TheProperty") %>' runat="server" />-->

<!-- As it turns out, you can simply call it like... -->
<mvc:MyControl SomeProperty="<% #this.Model.TheProperty %>" runat="server" />

And that is about it! Really!

The second step requires that you call the DataBind function for the page… but you can’t call it as part of the render (otherwise it is too late). You could use a server side script tag at the top of your page, but I recommend you just create your own ViewPage class that takes care of it for you. Below is the class that use in later examples. Again, there is an insane amount of code so be warned…

/// <summary>
/// ViewPage with Model access for WebControls
/// </summary>
public class MyControl : UserControl {

    //some point the the control lifetime call DataBind
    public override void OnLoad(object sender, EventArgs e) {
        this.DataBind();
    }

}

How About A Real Example Then?

So this sounds interesting, but how could it really be used? I’ve include a Visual Studio solution file with some examples that go over possible ways to use WebControls in your MVC applications.

  • Twitter Feed: Writes all of the logic and rendering of a simple Twitter feed. Uses the BoundedViewPage to attach a user name to the Property of the control. Also allows normal access (assignment from a string) so you could reuse the code anywhere.
  • Calendar: Example that uses a Calendar control and a Grid View to create a a simple page of content but uses binding to the Model to get information for all of the controls.
  • Task List: Simple task list that shows how you can use a UserControl with button click events to update the Model and then show the results on the page.
  • Crash Example: Using RenderPartial on a page that has a server form does not work correctly. However, if your WebControl doesn’t use a server form (which is better anyways) then you can use all MVC features without a problem (as far as I know, that is)

Of course, I’m sure a lot of people will be against this idea but I think it is worth exploring. If you think about all of the new features that ASP.NET 4 is going to introduce (especially better control over the client side markup) then this might end up being a perfect combination!

Source Code

WebFormAccess.zip (Solution Files)

Previous Posts On WebControls In MVC

Here are some earlier posts messing around with this same concept — Admittedly, these are geared to using WebControls and the entire WebForms model.

Written by hugoware

January 31, 2010 at 10:54 pm

WebForms : FrontPage For Programmers

with 9 comments

Yeah – I said it. I called WebForms FrontPage for programmers – whadaya goin’ to do?

Now before people start throwing hate mail at me its worth pointing out that up until MVC I used WebForms for all of my web development. In fact, I really do like WebForms. I think that they are especially great for whipping out quick internal projects that normally draw away from the time you could use developing something useful.

But, as much as I like WebForms, I think that too often they spew out more crap than value. If FrontPage was the way for a non-technical person to make a website then WebForms is the way for a non-web developer to make a website.

A Simple Example

(It’s worth noting my measurements are based on uncompressed HTML – I’m mostly pointing out the differences between output created by the developers – not the content that is compressed down the wire.)

Well here is a page that really rubs me wrong…

windows-example-1

[Microsoft.com/Windows]

Why? Nothing bad about it – the page looks pretty good… until you look under the hood at the source code. Once you start digging into it you find that the page is a whopping 123K and of that 28K is in the ViewState — clearly something that could have been done away with. You can also tell that ASP.NET gave a bunch of the elements on the page a unique ID even though they aren’t going to be referenced by any sort of Javascript. How about the id ctl00_BaseBody_FadingHeroEmbeddedSliverlight1_FadingHeroEmbeddedSL_ HeroBottomTitleShortDescriptionDivID (I had to put a break in the middle).

ASP.NET really does an incredible job abstracting the web so that a desktop programmer can jump right into web development, sometimes even using drag-and-drop controls *shudder*. It seems to me that web development ought to be done by people that understands the model.

Here is an example where you can tell that web developers had constructed it. It pains me to show it but I think it is relevant here since they are direct competition.

apple-example-1

[Apple.com/Mac]

Yeah, it’s the website of the enemy. But if you go over the page there is roughly the same amount of information (if not more) but their page is only 25K (smaller than the ViewState alone on the previous example). Even if you compare all of the resources on the two pages, the Apple site loads about 30K less in stylesheets and scripts than the Microsoft site. It is worth mentioning that the Apple site does load more images but their entire pages is full of large, high quality images — which is something that grabs attention much better than text. Not only that, but the Apple site actually does stuff!

Why? If you look at the source code you can an immediate difference between the two. The source code on the Apple page is cleaner, better designed and smaller. There is no ViewState or unnecessary ids for elements on the page. Everything (appears) to have been placed onto the page through careful consideration and not generated by some Page Lifecycle that injects code and markup into your page without you even knowing it.

So What Is My Point

Both these examples are two high profile sites. They are from two very competent technology companies — however, one looks a little less skilled to the other – at least from a web development perspective. It seems to me that extra time and effort should have been placed by the Microsoft developers to trim out all the fat and make it as web friendly as possible.

I might be nit-picking at the HTML output but I do think its relevant. That code – even if it is compressed – is the final rendered output from a WebForms page. As soon as the page opens there is immediately markup errors, unmatched tags, extra body elements. It’s clearly not the best it can be. I’m not trying to make Microsoft look bad either – personally, I want to work for them some day, but the point is that in order to improve then you must first be honest with yourself and those around you.

WebForms are a powerful tool – don’t get me wrong – but it seems to me that they have abstracted web development too far. I always thought it was really cool about .NET the way you could go from web, desktop, console, mobile or whatever development you wanted to and it was still like second nature! It allowed you to focus on solving problems and less on how each area was supposed to work.

I’m not saying people can’t develop great applications with WebForms. In fact I still use them from time to time when I need to hack out a quick application that only the internal eyes of my company are going to see. However, when I’m browsing the web and come across a website that is packing a 50K ViewState or is littered with wasteful, pointless 40 character long ids, I just can’t help but feel that WebForms is starting to hurt the web a little.

That’s why I recommend that if you haven’t already, go try out ASP.NET MVC. It doesn’t take long to break away from WebForms and you’ll probably feel more comfortable within a few days.

Written by hugoware

August 28, 2009 at 2:12 am

Using WebControls In ASP.NET MVC Views – Part 4

with 2 comments

Check out my newest blog post about using WebControls inside of MVC (source code included)πŸ™‚

WebControls In MVC Series

Disclaimer : This post is a rough overview how the MvcWebForms class works. If you don’t really care then you probably won’t want to finish reading this πŸ™‚

In part 3 of this series we finally got to use some code that actually allowed us to use the entire post back process from an inline call with MVC. The next few parts of the series dives into how the code actually works — and hopefully discovers a few bugs and optimizations along the way!

For part 4 we’re going to focus on this small segment of code.

<% this.Html.WebForm((build) => { %>
<label for="name" >Name</label>
<% build.RenderControl(new TextBox() { id = "name" }); %>
<% }); %>

The WebForm Extension Method

Line 1: <% this.Html.WebForm((build) => { %>

The first step to render a WebForm is to call the HtmlHelper method WebForm. This method might look a little odd since it expects a delegate but it is required so that we can continue to use markup along side our WebControls, which we explain a bit more in a moment.

The delegate accepts a single parameter which is the MvcWebForm. You can think of this as the Page.Form you’re used to having access to. You can make changes to the postback target, attributes, content type, etc. The MvcWebForm is also where you provide the WebControls you want to render using .RenderControl(). There are several overloads to this method so you might want to poke around to see what kinds of options you have.

The delegate you provide isn’t executed immediately, in fact the Form element is created first and then a few content markers are added to the code. After all of the setup has taken place the code is the delegate you provided is rendered onto the page.

You might think that the rest of the code simply renders the output inline and then returns to executing the ViewPage code, but interestingly enough, the controls you created aren’t rendered to the page yet but instead placed into a queue.

Queuing Controls For Rendering

Line 3: <% build.RenderControl(new TextBox() { id = "name" }); %>

In order to render the WebControls correctly we need a context in which they can all be aware of one another. With WebForms, each stage in the Page Life Cycle gives you time to make changes to the document before the content is rendered, but in MVC you’re already in the Render stage — you can’t make changes to the content you just finished rendering! Clearly, we can’t render these controls inline.

Each time we call .RenderControl() we are actually creating a content marker for that control and then moving it to a queue. After the control is rendered the content is inserted back into the correct marker along with any scripts or stylesheets.

You might be wondering how we could do something like that, since we’re in the middle of the Render() call and that part of the page has already passed by. Recently, I had written some code to help me add stylesheets to the header of my MVC page from inline code — To say the least, it ended coming up very handy in this project.

Completing The WebForm Request

Line 4: <% }); %>

After the delegate has finished creating the content the remaining code finishes the rendering process and closes up the WebForm we are creating. After that point it is time to actually render the WebControls to display.

After the delegate has finished the code combines all of the controls into a dynamically loaded .ASPX page and then processes it. Once finished we end up with a string of the rendered HTML output which can be parsed into a XDocument! Each control is then matched up and the rendered HTML is inserted into the matching content marker! If any scripts or stylesheets are created then they are rendered into the top of the WebForm.

Really, There Is A Lot More

This is a rough overview of how the process works but I’ll get into detail more on each section through the rest of the series.

Written by hugoware

August 17, 2009 at 2:42 am

The Super Secure HttpRequest

with 3 comments

I’ve been working on a little project lately to see if it is possible to use WebControl inline with ASP.NET MVC. It’s been going well so far, but I encountered a little, tiny, insignificant problem… actually, it’s quite a big problem.

The general idea behind the project was to display the normal view, use the inline code to create a second instance of a page, render the content with the correct ViewState information, and then output the content back onto the ViewPage so it can be used.

Now passing a ViewState into the wrong type of page causes some problems. To get around it we simply rename our ViewState to something else and then change the name back when the page is being loaded in our code. It worked well when I was creating a new HttpRequest object, but the problem is that if you create one manually, it changes to a GET request — not a POST!

At first I figured that I would just get in there and reuse the same HttpRequest and make a few changes, but it wasn’t going to be that easy. The problem is that you just can’t do anything to the HttpRequest — Anything!! Try to change the parameters? Read-Only. Try to inherit from the StateBag? Sealed. Inherit from the HttpRequest itself? Sealed! Holy cow!!

I’ve been battling this quite a bit and at this point I’m feeling defeated (and a little grumpy). I’m trying to find a point that I can make a change to the incoming data, but before the ViewState is loaded by the WebForm page.

After going round and round for awhile I’m starting to think my best (maybe only) option is to get work with the HttpApplication class. Not exactly what I was hoping for, but it may be my only option at this point.

… let’s just hope not everything is read only in this class as well.

Written by hugoware

August 10, 2009 at 1:47 am

Using WebControls In ASP.NET MVC Views – Part 2

with 5 comments

Check out my newest blog post about using WebControls inside of MVC (source code included)πŸ™‚

WebControls In MVC Series

In this 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 expected.

You might be thinking “you shouldn’t use WebControls in Views!!” and you are absolutely right! I have two reasons for doing this project.

  1. A lot of investment has been put into creating WebControls. Some developers may not want to switch to MVC because they don’t want to lose everything they have already put together. Having this option available might help move them in that direction.
  2. I wanted to see if it was even possible to do!

I don’t really recommend mixing the two worlds together that much, but this might be handy for someone at some point somewhere (somehow…)

This is a series of posts to investigate if it is possible to use WebControls inline with MVC. The code in this post is not a final version and shouldn’t be used. Additionally, at the time of writing this I already have the code done for part 3 so I know the code gets better, but I still need do part 2 before I move to part 3. Hang in there! πŸ™‚

It’s Just HTML

One thing I’ve kept repeating to myself while working on this is ‘it’s just HTML’ — If you think about it, we only need to get rendered content and post it back to the server in the same way that a normal WebForms page would do it. However, the ASP.net page lifecycle is not just HTML, in fact, there is a lot that goes into it.

If you remember, in the last attempt we we’re able to hook up events to our WebControls and then render them inline with the rest of our MVC content. We then used the BuildManager class to load an instance of a page and then process the request — but only to be met with a strange error message about our ViewState.

While I never did entirely figure out what the specific problem was I did suspect it had something to do that the ViewPage was some how having a problem with the ViewState that we were posting back that was actually rendered by the dynamically loaded page. If it doesn’t make sense, don’t worry — I’m still guessing here. πŸ™‚

In any case, it appears that we were still on the right track. After a little more hacking I came up with the following code.

//snip...
/// <summary>
/// Class to allow WebForm Controls to be used inline with MVC
/// </summary>
public static class WebFormExtensionMethods {

	/// <summary>
	/// Executes a delegate to generate the control to render onto the page
	/// </summary>
	public static void RenderWebFormControl(this ViewPage page, Func<Control> gen) {
		WebFormExtensionMethods.RenderWebFormControl(page, gen());
	}

	/// <summary>
	/// Renders a WebControl onto the page that supports postbacks
	/// </summary>
	public static void RenderWebFormControl(this ViewPage page, Control control) {

		//the name of the loader container and the viewstate hack
		string path = "~/ViewPageWithWebForm.aspx";
		string mvcPrefix = "MVCSTATE";

		//load the container and add the control
		WebControlOutputPage template = (WebControlOutputPage)
			BuildManager.CreateInstanceFromVirtualPath(path, typeof(WebControlOutputPage));

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

		//capture the output to format
		template.Rendered += (output) => {

			//change the names of the viewstate items to prevent
			//postback errors
			XDocument doc = XDocument.Parse(output);
			foreach (var item in doc.Descendants()
				.Where(o => o.Attribute("name") is XAttribute &&
					o.Attribute("name").Value.StartsWith("__"))) {

				//change the state name
				item.Attribute("name").Value = string.Concat(
					mvcPrefix,
					item.Attribute("name").Value
					);
			}

			//display each of the elements inline
			foreach (XElement show in doc.XPathSelectElement("html/body/form").Elements()) {
				page.Response.Write(show.ToString());
			}

		};

		//generate a query string of all the requested items
		//there is probably a much better way to do this
		StringBuilder query = new StringBuilder();
		foreach (var item in page.Request.Params.AllKeys) {
			string name = item.Replace(mvcPrefix, string.Empty);
			query.Append(name + "=" + page.Server.UrlEncode(page.Request[item]));
			if (!page.Request.Params.AllKeys.Last().Equals(item)) { query.Append("&"); }
		}

		//create a new HttpRequest to process the request with
		HttpRequest req = new HttpRequest(
			path,
			page.Request.Url.AbsoluteUri,
			query.ToString()
			);

		//process the container page
		((IHttpHandler)template).ProcessRequest(
			new HttpContext(req, new HttpResponse(new StringWriter()))
			);
	}
}

/// <summary>
/// Page rendering class for WebControl output
/// </summary>
public class WebControlOutputPage : Page {

    //notify the calling parent the HTML is ready
    public event Action<string> Rendered;

    //render the output to work with
    protected override void Render(HtmlTextWriter writer) {
        using (StringWriter output = new StringWriter()) {
            using (HtmlTextWriter html = new HtmlTextWriter(output)) {
                base.Render(html);
                this.Rendered(output.ToString());
            }
        }
    }
}

You will also need an .ASPX page to load your controls with. Make sure it inherits from the WebControlOutputPage class above. Below is an example that will work.

<%@ Page Language="C#" Inherits="WebControlOutputPage" %>
<html>
    <head runat="server" />
    <body>
        <form id="pf" runat="server" />
    </body>
</html>

Since this code isn’t the final version I won’t explain too much of it and instead focus on the relevant parts.

A ViewState By Any Other Name Is… Uh…

I personally found HTML to be a little strange when it came to the name and id attributes on certain elements, it seemed to me that those were redundant, but hardly a real issue. In this instance, having the two attributes works to our advantage.

You’ll notice that in our code we append and remove a prefix to the names of the state containers, for example the __VIEWSTATE and __EVENTSTATE, on our page. By doing this, the id remains the same so Javascript can continue to access it but the name changes to avoid being caught as an error in the ViewPage.

You’ll also notice that we create a new HttpRequest and rename the state values to their original name. This allows any state information to slip past the ViewPage, but then be used by the WebControl Form. There is more than likely a better way to do that part, but for now it works — at least for this purpose.

So How About An Example?

The helper methods have changed up a bit to allow the control to be created by a delegate inline. This way it is a little easier to assign event handlers to your controls before they are rendered. Let’s put a simple button on the page.

<html>
    <body>
        <h2>WebControl Inline With MVC</h2>
        <form method="post" >

        <% this.RenderWebFormControl(() => {

               //create the button
               Button test = new Button() {
                   ID = "myButton"
               };

               //assign an event
               test.Click += (s, e) => {
                   test.Text += test.Text.Length.ToString();
               };               

               //return the control so it can be used
               return test;

           }); %>

        </form>
    </body>
</html>

By using a delegate we’re given a chance to assign a click handler to append some text onto our WebControl. After it is returned in the delegate it is rendered to the page. After we wrap the control with a form and then run it check out what we get.

postback-success

Success!! Our postback went through and our control is being updated as we wanted — and all inline with our MVC code!!

Not Quite There…

This code works pretty well, but there is a couple problems already. First, we can only have one control per page because each control outputs its own ViewState — so having a Button talk to a TextBox isn’t going work right now. Additionally, code that uses the __doPostback method is going to fail because that method isn’t added to the page yet.

Good news is that we solve those problems in the next post in this series! Hang in there and check back soon!

Written by hugoware

August 7, 2009 at 1:10 am

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!

Written by hugoware

July 27, 2009 at 12:05 am