Hugoware

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

Using WebControls In ASP.NET MVC Views – Part 3

with 18 comments

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

WebControls In MVC Series

I mean’t to have this post out this morning, but I was busy working on getting my personal website online — Please check it out and tell me what you think!

Up until now all of the post in this series have been tests to see if we could make the process work. Now, finally, now 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 lets go over briefly how to setup the code.

To start, the code you need to download is found at the end of the post. When you download it, put both the .cs and the .aspx file into your project. By default the code expects the .aspx to be in your Views directory, but you can move it — but 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 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>
    <!-- snip... -->
    <system.web>
        <pages>
            <controls>
                <!-- snip... -->
                <add tagPrefix="mvc" 
					namespace="MvcWebControls" 
					assembly="YourProjectAssemblyName" />
            </controls>
            <namespaces>
                <!-- snip... -->
                <add namespace="MvcWebControls"/>
            </namespaces>
        </pages>
    </system.web>
    <!-- snip... -->
</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.

Example 1 — Simple Postbacks

<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 excepts a delegate to render the control onto the page. We do it like this because it allows us to provide markup to our page along side with the WebControls. 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.

Example 2 — Using Events

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();
                   
                   //post back this information
                   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 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.

Example 3 — MvcControlGroup

So far all of our examples have only used a couple 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 WebControls with Mvc — not just use WebControls 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 they can post the finished form back to the correct Controller Action!

**whew** — that’s a lot to take in!

Limitations

There are a couple 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.

Source Code

Download MvcWebForms.zip

.

Advertisements

Written by hugoware

August 12, 2009 at 8:58 pm

18 Responses

Subscribe to comments with RSS.

  1. […] to VoteUsing WebControls In ASP.NET MVC Views – Part 3 (8/12/2009)Wednesday, August 12, 2009 from webdev_hbWebControls In MVC Series Part 1 Part 2 Part 3 I mean’t […]

  2. really nice and useful piece of work.

    I tried to use your code to place a MVC view, but I failed miserably.

    The first hurdle was when “XDocument.Parse” failed because of a noscript block in the html generated by the ReportViewer.

    I removed the noscript block using a regex before parsing it, but the parser now complains about the way the ReportViewer is setting up the parameters:
    “Reserved.ReportViewerWebControl.axd?Mode=true&ReportID=7c…”

    Do you have any solution for this issue?

    ogrig

    August 21, 2009 at 1:42 am

  3. Sorry about the previous message, I should have checked what I wrote before submitting.

    What I wanted to say was: “I tried to use your code to place a reportViewer control in a MVC view”

    ogrig

    August 21, 2009 at 1:45 am

    • Interesting, I’d love to help you sort this out – Can you send me your e-mail address using my contact form.

      If I had to take a guess off the top of my head then it might be from the ampersand (&) signs in your output. You might need to escape those to “&amp;”

      In any case, this is something that the MvcWebForm class should do for you on it’s own. Thanks for letting me know about it!

      webdev_hb

      August 21, 2009 at 7:16 am

      • In the meantime I managed to get a bit further.

        First, I put the offending script in a CDATA block.
        I can now create a view with an empty report (just “form.RenderControl(new ReportViewer());” and nothing else).

        Next, when I tried to use the control with a local rdlc report, The ReportViewer generates a script with 2 onclick events, one of them for the DocumentMapButton.
        The easiest way out was to just disable the document map button.

        After a bit of struggle I now have a page with a partially functional ReportViewer control.
        Mind you, I only tried it with very simple reports. But at least I can display a report page and navigate between pages.
        What does not work right now:
        – the document map.
        – printing the report. When I tried to print the control tried to install something and I stopped. It would have probably worked, but I wasn’t very interested at the time.
        – exporting. This is a bit unpleasant, but it should not be very difficult to solve. And I can always set ShowExportControls to false, provide my own controls on the page and then stream the PDF or Excel content on the server.

        I will send you my email address shortly

        ogrig

        August 23, 2009 at 7:07 pm

      • Unfortunately, you’re having to jump through a lot more hoops than I had intended. If you would include some of what the output should look like (if you were to render it on a normal page).

        Right now I’m working changing out using the XDocument.Parse to simply overriding the render event of the container control and outputting the content based on the rendered content – which should get around the error.

        In any case, thanks for the feedback – this will help a lot fixing this code.

        webdev_hb

        August 23, 2009 at 7:43 pm

  4. […] Using WebControls In ASP.NET MVC Views – Part 3 « Yet Another WebDev Blog somewebguy.wordpress.com/2009/08/12/using-webcontrols-in-mvc-part-3 – view page – cached #Yet Another WebDev Blog RSS Feed Yet Another WebDev Blog » Using WebControls In ASP.NET MVC Views – Part 3 Comments Feed Yet Another WebDev Blog jLinq – Going Online! The Super Secure HttpRequest New Hugoware.net — From the page […]

  5. […] with 5 comments Check out a new post about using WebControls inline with MVC that actually works with postbacks! […]

  6. You reallly rocks man.. that was Quite interesting..

    Nithin Mohan T K

    October 5, 2009 at 6:54 am

    • Great! Had fun messing around with it (but haven’t found a use for it yet though :))

      webdev_hb

      October 5, 2009 at 9:48 am

  7. […] Using WebControls With Inline MVC […]

  8. […] I say ‘pretty much’ because I’ve done a lot of work to let people use WebControls inline with MVC code. […]

  9. I was able to use the http://demos.devexpress.com/ASPxperienceDemos/SiteMap/Features.aspx control working via your MvcControlGroup and it works well. However I ran into one gotcha.

    I get a “Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.” error if I try to use data binding and something like the following:

    <asp:Label Text='’ />

    inside of the MvcControlGroup blob.

    NOTE: the code uses the namespace MvcWebForms but your
    article says to use MvcWebControls.

    Todd Smith

    November 11, 2009 at 2:17 pm

    • Pretty interesting — the basic idea behind this code is that it moves all of the controls into a separate page instance, performs the page lifecycle and then returns the content to the correct containers.

      It would make sense that the contexts wouldn’t exactly match up if they are using inline rendered code (like this.Databind()) because this is referring to the first page instance. There is actually a property on the this.Html.WebForm argument that represents the instance of the second rendered page context.

      I’m not sure if that code is open source or editable but that seems like the most probable situation…

      Do you have any source code to share?

      webdev_hb

      November 11, 2009 at 8:58 pm

  10. I’ve tried to implement your control with asp.net mvc on vs2010 but I’m getting “Filtering is not allowed” error on this line during runtime:

    HttpContext.Current.Response.Filter = new _WebFormStreamFilter();

    Has anyone come accross this?

    (i’m running on the ASPET development server on Server2008)

    David

    March 26, 2010 at 5:04 am

    • David, I’ve heard other reports about the Filter not working anymore in MVC2 – I’m not sure if this is a .NET 4.0 issue or if it is MVC2 at this point.

      Right now I’m trying to figure out a different way to handle this.

      In the meantime, you might check out this blog post where you use binding with a Model to WebControls. Not exactly the same thing but might get you started in a different direction.

      hugoware

      March 27, 2010 at 9:15 am


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: