WebControls In MVC Series
Up until now, all of the posts in this series have been tests to see if we could make the process work. Now, finally, we get into some real code that we can actually use. I'll take just a little bit of space to explain how it works and how to set it up, but then the rest of the post to explain how to use it.
Getting The Code Setup
Before we discuss how to use the code, let's briefly go over how to setup the code.
To start, the code you need to download is found at the top of the post. When you download it, put both the .cs and the .aspx files into your project. By default, the code expects the .aspx to be in your Views directory, but you can move it — if you do, then you need to update MVCWebFormExtensionMethods._WebFormRenderControl.TEMPLATE_PAGE
with the new location (yeah, it’s a long name ).
Optionally, you can make a couple of changes to your Web.config to make sure that the extension method and MvcControlGroup
are available to the rest of your application without needing to add anything special onto each of your pages.
<configuration>
<system.web>
<pages>
<controls>
<add tagPrefix="mvc"
namespace="MvcWebControls"
assembly="YourProjectAssemblyName" />
</controls>
<namespaces>
<add namespace="MvcWebControls"/>
</namespaces>
</pages>
</system.web>
</configuration>
I’ve hidden most of the Web.config from this example, so make sure to add the areas you see above, not simply replace your entire config.
The rest of this post goes over some demos found out on my website, you may want to follow along to better understand what is going on.
<html>
<head runat="server">
<title>Simple Postback Example</title>
</head>
<body>
<h2>Just A Simple Form</h2>
<% this.Html.WebForm((form) => { %>
<% form.RenderControl(new TextBox()); %>
<% form.RenderControl(new Button() { Text = "Save" }); %>
<hr />
<% form.RenderControl(new Calendar()); %>
<% }); %>
</body>
</html>
For the first example, we want to see if we can use a few simple WebControls and see if our values are posted back the way we would expect. In this example, we add a TextBox
, Button
and Calendar
. Pay attention to how this HtmlHelper
method works.
If you notice, the method accepts a delegate to render the control onto the page. We do it like this because it allows us to provide markup to our page alongside the WebControl
s. The method accepts a single parameter the MvcWebForm
. Think of this as a very, very simple version of a WebForm
.
The MvcWebForm
gives you access to the page that is being used to render the controls. This is important to remember and I’ll go over it in the next section.
This is handy so far but unless we are using some events for our controls, nothing much has changed. Let’s look at another example.
<html>
<head runat="server">
<title>Using Events</title>
</head>
<body>
<h2>Simple Button Click Event</h2>
<% this.Html.WebForm((form) => { %>
<strong>Clicked : </strong>
<% Label text = form.RenderControl(new Label() { Text = "0" }); %>
<br /><br />
<% Button submit = form.RenderControl(new Button() { Text = "Add" });
submit.Click += (s, e) => {
int value = 0;
int.TryParse(text.Text, out value);
text.Text = (++value).ToString();
form.Page.ClientScript.RegisterStartupScript(
typeof(Page),
"confirm",
string.Format("alert('Updated to {0}');", text.Text),
true
);
}; %>
<% }); %>
</body>
</html>
In this example, we are assigning a Click event to the button we added to the page. You’ll notice that when we run this example the value is incremented up by one on each press.
Notice that we use form.Page
instead of this.Page
. When you use this code, you must remember that you are working with two separate Page instances. One for the View and the other for rendering the control. In this example, we use RegisterStartupScript
to display an alert box. If you were to have used this.Page.ClientScript
, nothing would happen.
It’s a subtle difference that you are going to want to keep in mind while you use this class.
So far all of our examples have only used a couple of controls that were all rendered inline with the rest of the page. While this is going to work in most situations, some places won’t work as well.
The example below shows how you can use the MvcControlGroup
to group controls together, for example, the Wizard control.
<html>
<head runat="server">
<title>Using Events</title>
</head>
<body>
<h2>Using Wizard Control with MvcControlGroup</h2>
<div>
<% this.Html.WebForm((form) => { %>
<mvc:MvcControlGroup runat="server" >
<asp:Wizard runat="server" >
<WizardSteps>
<asp:WizardStep runat="server" Title="Personal Information" >
<p>Leaving fields blank will catch the validators.</p>
<strong>Name</strong>
<asp:TextBox ID="name" runat="server" />
<asp:RequiredFieldValidator runat="server"
ControlToValidate="name" ErrorMessage="Must provide a name" />
<br /><br />
<strong>City</strong>
<asp:TextBox ID="city" runat="server" />
<asp:RequiredFieldValidator runat="server"
ControlToValidate="city" ErrorMessage="Must provide a city" />
</asp:WizardStep>
<asp:WizardStep runat="server" Title="Reservation Date" >
<strong>Date Requested</strong>
<asp:Calendar ID="date" runat="server" />
</asp:WizardStep>
<asp:WizardStep runat="server" Title="Confirm" >
<h2>Confirm This Order?</h2>
<p>This step maps to a Controller Action before submitting</p>
<% MvcWebForm.Current.Action = "/Projects/MvcWebForms/Submit"; %>
<% MvcWebForm.Map(() => new {
name = name.Text,
city = city.Text,
date = date.SelectedDate.ToShortDateString()
}); %>
</asp:WizardStep>
</WizardSteps>
</asp:Wizard>
</mvc:MvcControlGroup>
<% }); %>
</div>
</body>
</html>
Just like with other controls, the MvcControlGroup
must be rendered in a separate page instance. This control moves it to the correct context before it is processed.
If you remember, the idea behind this project was to allow people to use WebControl
s with MVC — not just use WebControl
s in MVC. On the last step, we point our form to a Controller Action and use a method called Map
to format our information for the postback.
When the user finished the Wizard, she/he can post the finished form back to the correct Controller Action!
**whew** — that’s a lot to take in!
Limitations
There are a couple of limitations that still need to be worked through…
- HTML output must be well formed – If your control outputs bad HTML, this thing will croak. The reason is that instead of using Regular Expressions to try and match content, the output is parsed using the
XDocument
. - PageMethods, AJAX calls will probably not work – Controls like the
UpdatePanel
or using PageMethods
will most likely not work anymore. Some additional work is going to be required to make those work.
I’m sure there is more that needs to be looked at but at this point it seems as if it can be used — at least for testing.
Please let me know if you have any ideas, feedback or suggestions! I’m going to continue to work on this and see what kinds of improvements can be made.