Hugoware

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

Posts Tagged ‘WebControls

Using A Page Control For MVC Templating

with 2 comments

In previous posts I’ve talked about using WebControls along with ControlAdapters to have cleaner markup by using the built in ASP.NET page life cycle to assign values to the correct elements on the page. This approach can make views easier to read by removing server side code blocks and allowing logic to be handled within the code behind of the view and not within the page itself.

This post continues the discussion by looking at how you can use a Controller to create instances of views before rendering them to the page.

In previous examples, I used the View method as you normally would in MVC. This worked well enough – We had access to the Model property and the ViewData which gave us enough information to render our page.

However, this example approaches the same problem but a little differently. Instead, we are going to create the view and assign to the properties immediately and then render the content for the view. Below is some code that illustrates the idea.

//Keep in mind, this is sample code and needs more work
//before you plug it into a project but it should give a 
//general idea of how this could work.

using System;
using System.Web;
using System.Web.Compilation;
using System.Web.Mvc;

namespace MvcTemplating.Controllers {

    public class SampleController : Controller {

        /// <summary>
        /// Creates a new instance of a view 
        /// </summary>
        public T CreateView<T>(string path) where T : class {
            return BuildManager.CreateInstanceFromVirtualPath(path, typeof(T)) as T;
        }

        /// <summary>
        /// Renders a handler to the page
        /// </summary>
        public ActionResult Handler(IHttpHandler handler) {

            //update and process the handler
            this.HttpContext.Handler = handler;
            this.HttpContext.Handler.ProcessRequest(System.Web.HttpContext.Current);

            //quit handling this request
            this.Response.End();
            return null;
        }

    }

}

The Handler function takes care of performing the work for the view and then returns a null since the action is still going to be wanting a return value. If you know a better way to handle this please let me know. πŸ™‚

Instead of reading values from the ViewData or the Model we can now just assign them directly to the view.

namespace MvcTemplating.Controllers {

    [HandleError]
    public class HomeController : Controller {

        //displays content using an instance of the view
        public ActionResult Index() {

            //create the view
            var view = this.CreateView<MvcTemplating.Views.Home.Index>("~/Views/Home/Index.aspx");

            //assign to properties
            view.LatestBlogPosts = BlogPosts.Recent(5);
            view.LatestTweet = TweetRepository.GetLatest();
            view.Title = "Welcome Visitor!";

            //and then show the view
            return this.Handler(view);
            
        }
 
    }

}

This sample requires we know the path to the view but that can be fixed by using standard path names or possibly scanning the site in advance and saving the paths in memory (which is something I’ve done before with UserControls)

This approach, combined with ControlAdapters, can result in clean markup and provide additional logical functionality for some of the more complex views in your existing MVC projects.

Of course, this is just “proof of concept” stuff… I’ll hopefully have more code to share before long.

Advertisements

Written by hugoware

April 25, 2010 at 11:00 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

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

Include Stylesheets and Scripts From A WebControl In MVC

with 8 comments

As of MVC2 this code may not work anymore – Check here for updated code.

Have you ever created a WebControl in MVC and though “Gee, it sure would be nice if I could add a stylesheet/script to the header of my page.” It’s not as easy as it used to be. Lets just pretend you ran this code right here.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<html>
    <head runat="server">
        <title>Just A Test</title>
    </head>
    <body>
        <% Page.Header.Controls.Add(
            new LiteralControl(
                @"<script src=""/script.js"" type=""text/javascript"" ></script>"
                )); %>
    </body>
</html>

It would compile and run just fine but you wouldn’t see your script! That’s because the page life-cycle has come and gone and you’re in the middle of the page render! We’re too late!

Inline code doesn’t make it easy to communicate with other parts of the page. As far as I can tell, inline code is rendered in the same order that it appears on the page (or at least, parsed), so once you finally get to the end of the page, the code at the start has already been executed.

A Workable Solution

I’ve included some code at the end of this post that simplifies the whole process and for the remaining of this post I explain how it works. In an effort to solve this problem I ended up coming up with two extension methods that are attached to the HtmlHelper.

InsertMarker(id) : Creates a point on the page that will render any content that is added to it. Uses the id provided (either an enum or a string)

AppendToMarker(id, content) : Appends the string content to the matching marker id (either an enum or a string)

These methods allow marker points to be added to the page inline and then content appended to each marker, regardless of where they are used. Let’s look at an example of the code. If this code doesn’t make sense just yet, don’t worry — I’ll explain πŸ™‚

A Simple Example

Lets use this code to add a script to the header of our page — but from within a WebControl.

[Index.aspx (the View)]

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<html>
    <head runat="server">
        <title>Just A Test</title>
        <% this.Html.InsertMarker(Document.Head); %>
    </head>
    <body>
        <% this.Html.RenderPartial("SomeControl"); %>
    </body>
</html>

[SomeControl.ascx (the WebControl)]

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% this.Html.AppendToMarker(
    Document.Head,
    @"<script src=""/script.js"" type=""text/javascript"" ></script>"
    ); %>

As you can see, we can pick the name of the marker that we want to append content and it is added to the page — all from within our WebControl! You can define your own custom markers and append content to really anywhere you want on the page. If the marker isn’t found, then the content is never rendered.

How It Works (The Short Version)

Once we’re in the middle of our render event for our page it makes it quite difficult for us to make many changes to the rest of the document. This is where the HttpContext.Response.Filter stream comes in handy. With this stream we’re able to intercept all of the content before it is really sent out.

Each time we set a marker onto the page we place a bit of text to mark it as a point we need to replace before we push the content out. I had preferred the idea of remembering the position of the output stream, but apparently the output stream is written to all at once which took that out of the picture.

Once we’re done its as simple as using a Regular Expression and replacing all of the markers with the correct content.

MVC-ish – Kinda Sorta…

This may not have been what the guys who designed MVC were thinking of when they created it, but the ability to work with multiple areas of the page was definitely a feature that I felt was missing. How could something like this help you with your projects?

Source Code

ContentMarkerHtmlHelper.cs (Source)

.

Written by hugoware

July 28, 2009 at 1:57 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

WebForms And MVC In Harmony — Almost…

with 6 comments

Check out a new post about using WebControls inline with MVC that actually works with postbacks!

Pop Quiz – What happens with the following snippet of ASP.NET code?

<% int number = 5; %>
<asp:PlaceHolder runat="server" >
<% =number %>    
</asp:PlaceHolder>

Prints the number 5? Nope. Maybe it equals zero? Sorta. How about this…

Compiler Error Message: CS0103: The name ‘number’ does not exist in the current context

Yikes…

I’ve complained before about the disconnect between WebControls and actual inline code. WebControls are still a very convenient way to write templates but because they exist in a different context than inline code they are effectively off limits. As cool as MVC is you’re pretty much stuck throwing all your existing WebControls out the window. Or are you?

Using Extension Methods Instead of Controls

Extension Methods came in really at the best time possible. I can’t see MVC working without them.

If you’ve never used one before, an Extension Method lets you create a static method else where in your project, do a couple fancy assignments and then it attaches that method onto the class you’re targeting. LINQ heavily relies on Extension Methods to provide such a seamless programming experience.

One way that ASP.NET MVC uses Extension Methods is to make working with certain control types easier. For example there is a method to create the input tag, one to render a form tag, etc…

Below is an example of how you could create an Extension Method that is attached to the HtmlHelper.

public static class MyExtensionMethods {

    //example method - don't write things this ugly
    public static string BulletList(this HtmlHelper helper, params string[] items) {
        return string.Concat(
            "<ul><li>",
            string.Join("</li><li>", items),
            "</li></ul>"
            );
    }
}

In our example we create a static class to house our Extension Methods. We also create static methods with a strange argument at the start. This argument is actually the class were attaching the method to. Now we can use our code like so…

<% =this.Html.BulletList("Apple", "Orange", "Pear") %>

Cool. If you’re not familiar on the things you can do with Extension Methods then I recommend you read about them some more before you start trying to add them to your project. You could also use delegates to simulate templating within an Extension Method.

public static class MyExtensionMethods {

    //example method - renders the content of each action
    public static void TwoColumns(this HtmlHelper helper, Action left, Action right) {
        HttpContext.Current.Response.Write("<div class='left'>");
        left();
        HttpContext.Current.Response.Write("</div>");
        
        HttpContext.Current.Response.Write("<div class='right'>");
        right();
        HttpContext.Current.Response.Write("</div>");
    }
}

Then you can use your “template” like so…

<% this.Html.TwoColumns(() => { /* Left Column */ %>
    I'm on the left!
<% }, () => { /* Right Column */ %>
    I'm on the right!
<% }); /* End Two Column */ %>

Code like this can get ugly in a hurry – so be conservative in your use.

Using IDisposable To Close Tags

Another way you can create a “WebControl” with ASP.NET MVC is to create a class that implements IDisposable. By placing markup in the constructor and the Dispose method you can essentially write your RenderBeginTag() and RenderEndTag() methods you normally find on CompositeControls!

public class StyledHeader : IDisposable {

    public StyledHeader(string color) {
        HttpContext.Current.Response.Write("<h1 style='color:" + color + "' >");
    }

    public void Dispose() {
        HttpContext.Current.Response.Write("</h1>");
    }
}

Naturally, StyledHeader should have been added to the core of the ASP.NET MVC library, but somehow it got missed :). In any case, our class can be used with the using keyword to render our fancy new header.

<% using (new StyledHeader("#f00")) { %>
    Howdy - This is my header control!
<% } /* End StyledHeader */ %>

The Super Secret Final Method

As you noticed at the beginning of my post I mentioned about throwing away WebControls since they aren’t any use to us anymore. Well, that isn’t true — We can still use WebControl with our inline code for the page!

If you’ve read any of my previous blog posts, you can see that I’m a big fan of overriding the Render() method for WebControls. In similar fashion, we’re going to use the RenderControl() method to render our WebControls right when we need them.

using System.Reflection;
using System.IO;
using System.Web.UI;
using System.Web;
using System.Web.Mvc;

public static class MyExtensionMethods {

    //example method - renders a webcontrol to the page
    public static void RenderControl(this HtmlHelper helper, Control control) {

        //perform databinding if needed
        //MethodInfo bind = control.GetType().GetMethod("DataBind");
        //if (bind is System.Reflection.MethodInfo) {
        //    bind.Invoke(control, null);
        //}

        //Call a courtesy databind
        //Thanks for pointing it out Richard
        control.DataBind();
        
        //render the HTML for this control
        StringWriter writer = new StringWriter();
        HtmlTextWriter html = new HtmlTextWriter(writer);       
        control.RenderControl(html);

        //write the output 
        HttpContext.Current.Response.Write(writer.ToString());
        
        //and cleanup the writers
        html.Dispose();
        writer.Dispose();         
    }

}

You may notice the courtesy DataBind() call we’re doing there — Just in case something has a DataSource I was calling the method as well. Depending on how you use this you may want to change this some. But enough of that, how is it used?

<% int&#91;&#93; numbers = { 1, 2, 3, 4, 5 }; %>
<% this.Html.RenderControl(new DataGrid() { DataSource = numbers }); %>

You can also define your class before you pass it into the RenderControl method in case you need to do a little more to it than just assign some values to the properties.

Finally, WebForms and MVC In Harmony… Or Maybe Not…

Now I won’t pretend that you can plug all of your WebControls into this and expect it to work like WebForms used to. A lot of things are missing that a lot of WebControls rely on (like the ViewState). But, if your mainly interested in the rendered output of a WebControl then you’re in luck.

Written by hugoware

June 18, 2009 at 2:11 am