Welcome to MSDN Blogs Sign in | Join | Help

Organizing Legacy Pages Into Controllers, Actions and Views

I've been asked to refactor a number of run-of-the-mill ASP.NET pages to use ASP.NET MVC. Here is part of the application's file structure:

~/UserAccount/
             /Login.aspx
             /Register.aspx
             /UpdateProfle.aspx
             /UserProfile.aspx

The Login.aspx page has already been done, so I decide to look at it and use it as a template for refactoring the rest of the pages. I find a UserAccountController that has a Login action method with a corresponding Login.aspx view. This doesn't smell right.

First, the controller isn't a noun that describes the resource being acted upon. UserAccountController is a noun, but the resource the login form acts on is a user's session. I would have expected a SessionController or UserSessionController.

Second, there should be two action methods: one to show the login form to the user and a second to create a new session after the user submits the form. I'm curious how this controller works, with just one action method. The first line of the existing Login action method is my answer:

if (Request.HttpMethod == "POST" && Request.Form["loginPostAction"] == "true")

The action is doing double duty; it both shows the login form (for a GET request) and creates a new session (for a POST request). This isn't really any different than the "old" days of using Page.IsPostBack, and is something we shouldn't need to do anymore with routing and MVC. In fact, it's basically putting routing inside your controller, which is definitely a bad smell.

This brief experience got me thinking about how I should organize controllers and actions in general. I ended up with the following self-guidance:

  1. Controllers should be plural nouns that describe the type of resource being acted upon (users, sessions, projects, etc.)
  2. Action methods should be verbs that describe the action that the user is taking (view, edit, update, etc.)
  3. The names of views should match their corresponding action methods (view.aspx, edit.aspx, update.aspx, etc)
  4. The conventional action methods to use (and to be deviated from when appropriate) are:
    1. list; show a list of the resources
    2. view; show an individual resource
    3. add; show the form to add a new resource
    4. create; create the resource on submission of the add form
    5. edit; show the form to edit a resource
    6. update; update the resource on submission of the edit form
    7. remove; show a form to remove a resource (often unnecessary)
    8. delete; delete the resource on submission of the remove form
  5. The conventional routes to use (again, to be deviated from when appropriate) are:
    1. For everything other than the list action: {controller}/{action}/{id} or {controller}/{action} (for example, /users/view/1 or /users/add)
    2. For the list action: {controller} (for example, /users)

[ Yes, my self-guidance borrows heavily from my experiences with Ruby on Rails. But I don't like a couple of their choices because they either aren't verbs (in the case of new) or they describe the controller's action and not the user's (in the case of show). I have to admit that having both add and create, both edit and update, is another smell that isn't very good. I have some thoughts on this as well, coming soon. ]

Self-guidance in hand, here's how I would take the four pages mentioned at the top of this 'blog post and organize them into controllers, actions, and views:

Controllers
    UsersController
        Add action
        Create action
        Edit action
        Update action
        View action
    SessionsController
        Add action
        Create action
Views
    Users
        Add.aspx view
        Edit.aspx view
        View.aspx view
    Sessions
        Add.aspx view

If you happen to read this 'blog post and you have another, different preference for organizing controllers, actions, and views, please leave a comment and share it. I'm very interested in other approaches.

Posted by Drew Miller | 0 Comments
Filed under:

When will I learn?

Update: Well, I've decided to give this 'blog another go. Attending the Seattle ALT.NET conference, or more correctly the other folks who attended, both energized and inspired me. I don't know how often or what I will write here, but there are at least a few things in my head right now. Also, I've run into some odd problems lately whose solutions might be good to record (for myself) for posterity. Finally, I hope that I've learned that I what I write in this 'blog is for myself, as an outlet, and not for an audience. We'll see.

When will I finally learn that I'm just not built for 'blogging? I'm not actually going to delete this 'blog, like I've done in the past, but I have no plans to continue to write here. It's odd, but while I love other types of writing, I just don't have a taste for 'blogging. So, instead, I'm going to find a handful of Agile- and .NET-focused email lists to join. I much prefer conversation and a community to writing for myself or an audience that doesn't exist. 

Posted by Drew Miller | 0 Comments

How To Create an HTML Helper for ASP.NET MVC

If you've spent some time with ASP.NET MVC (part of the recently released ASP.NET 3.5 Extensions Preview), you've probably done something like this in a view:

<%= Html.ActionLink(
    "TheLinkText", 
    new { 
        controller = "TheController", 
        action = "TheAction",
        id = "TheID"}) %>

That's the ActionLink method in, well, action. It's used to create a hyperlink that is routed to a given controller and action, along with an optional set of arguments (an identifier being most common).

Okay, that last sentence might not have made sense to you if you're new to web-based MVC. Let me try again. You use the ActionLink method to generate a link "the right way" [1] when using ASP.NET MVC. It ensures that the link's URL is routed to the right place.

Introducing HTML Helpers

The ActionLink method is what's commonly called an HTML helper method. Don't believe me? Look at it's parent type. It's named HtmlHelper. An instance of this type is made available to MVC views via the Html property of the ViewPage type (and those similar to it).

So, you might wonder what other HTML helpers ship with ASP.NET MVC? Well, none. Sorry, but ActionLink is it. So what do you do if, for instance, you want to put a text box in your view but you don't want to dirty your newly cleansed MVC hands with server and user controls, and you don't want to maintain duplicate HTML across your views. Enter the MVCToolkit.

The MVCToolkit

The MVCToolkit is a supporting library that has (among other things) a bunch of HTML helpers for rendering common HTML. You can get it here (look for it in the ASP.NET MVC section) and can read more about it at Rob Conery's 'blog. Simply reference MVCToolkit and now you can do this in your view [2]:

<%= Html.TextBox(
    "theName",  // name
    "theValue", // value
    100,        // size
    32,         // maxLength
    new {
        _class="theCssClass", 
        onclick="alert('foo')" }) %>

And this HTML will be sent to the browser for your efforts:

<input 
    id="theName" 
    name="theName" 
    size="100" 
    onclick="alert('foo')" 
    class="theCssClass" 
    maxlength="32" 
    value="theValue" />

Note: You might have noticed that the TextBox HTML helper magically appeared on the Html property (Intellisense and all). Well, it wasn't magic; it's an extension method that extends the HtmlHelper type. All (or at least most) of the HTML elements you'd expect are covered by the MVCToolkit. Freedom! (From server and user controls, that is.)

But What About My Needs?

The MVCToolkit offers some goodness, but what happens when you need something it doesn't offer? For instance, let's say you have a Wiki feature on your site, and you need to display Wiki words. That's the sort of thing you'd probably have used a control for in old-fashioned ASP.NET. But what do we do now?

We do the same thing the MVCToolkit does. We extend HtmlHelper to add our WikiWord HTML helper method. But before we write the code, let's look at the requirements:

  • The rendered HTML will be an A HTML element
  • The link should route to the Wiki controller
  • If the Wiki word exists, the link should route to the View action
  • If the Wiki word doesn't exist, the link should route to the Create action

The steps to create an HTML helper are [3]:

  1. Create a static class to put the HTML helper methods into
  2. Create an extension method for HtmlHelper (that means the first parameter must be "this HtmlHelper")
  3. Return the HTML to render from the method

Here's the code for our WikiWord HTML helper:

public static class OurHtmlHelpers
{
    public static string WikiWord(
        this HtmlHelper htmlHelper, 
        string wikiWord, 
        object htmlAttributes)
    {
        const string wikiWordFormat = "<a href=\"{0}\" {1}>{2}</a>";

        UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext);

        string action = WikiModel.WikiWordExists(wikiWord) ? "View" : "Create";
        
        return string.Format(
            wikiWordFormat,
            urlHelper.Action(new { controller = "Wiki", action = action, id = wikiWord }),
            htmlAttributes.ToAttributeList(),
            wikiWord);
    }
}
Here's an example of use in a view:
<%= Html.WikiWord(
    "FooBar", 
    new { 
        _class="WikiWord", 
        onclick="alert('foo_bar');" })  %>

And here is the HTML output:

<a href="/Wiki/View/FooBar" onclick="alert('foo_bar');" class="WikiWord" >FooBar</a>

Follow this simple pattern and add your own HTML helper methods.

Looking Forward

HTML helper methods works very well for simple HTML output or when the logic driving the render is simple (or owned by the by controller). But imagine trying to write something like a data grid with this approach. It'd be pretty awful. Some folks will be tempted to resort to server and user controls. Not me! What I want is something like Rails' render :partial=> support. So that's what I'm playing with next.

Footnotes

1: Yes, I know that URL routing is not actually part of ASP.NET MVC, and that you can do MVC with other routing approaches. But just about every web-based MVC framework does it this way, so please forgive my generous use of "the right way".)

2: If you're curious about the _class anonymous member, the MVCToolkit methods ignore the underscore so you can use attribute names that are reserved keywords in C# (like class).

3: There is actually one more step to using your own HTML helper methods, but you'll only have to do it once if you keep all of your extension method classes in the same namespace. You need to add an <add /> element to the <namespaces> element in the web.config. If your classes were in the MvcApplication1 namespace, your configuration might look like this:

<namespaces>
  <add namespace="System.Web.Mvc"/>
  <add namespace="System.Linq"/>
  <add namespace="MvcApplication1"/>
</namespaces>
Posted by Drew Miller | 6 Comments
Filed under:

xUnit.net RC1

Brad and Jim released xUnit.net RC1 today. Brad 'blogged about the changes. This is particularly useful to me because I can now drive WatiN tests with xUnit.net (they changed the console runner to use STA).

Posted by Drew Miller | 1 Comments

Adventures in Automation: Firefox, Part I

WatiN is my tool of choice for automating Internet Explorer, but I need to automate Firefox as well. I'm not happy with any of my options, and I've been wanting to explore automation with MUIA for the last several weeks, so I decided to play around with it a bit this evening.

Attaching to Firefox

The first thing I need to do is attach to a running instance of Firefox. Being a good TDD'er, I write my test first. Here it is:

        [Fact]

        public void firefox_attach_will_find_the_correct_automation_element()

        {

            StartFirefox();

 

            IBrowser browser = Firefox.Attach("Mozilla Firefox");

 

            Assert.NotNull(((Firefox)browser).Instance);

            Assert.Equal("MozillaUIWindowClass", ((Firefox)browser).Instance.Current.ClassName);

        }

I know the Firefox window's class name from a previous automation effort, so I use it for my assertion (I imagine it will be hard to find good assertions going forward, as so much of the validation will be visual). Now I can create the Attach method of my Firefox class. But where should I begin? I need to know what Firefox's automation element tree looks like. Using UISpy.exe would make that easy, but I'm having trouble getting it installed. I decide to just hack at the MUIA namespace (System.Windows.Automation) and see where it gets me.

I know I'll need to reference the MUIA assembly. I expect it to be System.Windows.Automation, but I'm wrong. A quick web search shows me what I need: UIAutomationClient and UIAutomationProvider. While I'm in the references dialog, I also notice UIAutomationTypes. I add all three for good measure.

I know from previous reading that AutomationElement.RootElement provides access to the top of the automation element tree. It's the Windows Desktop. But trying to loop through its children would be slow (and stupid). My test provides the window's title, so I decide to search on that. I give this code a run: 

        public static IBrowser Attach(string windowTitle)

        {

            AutomationElement firefox = AutomationElement.RootElement.FindFirst(

                TreeScope.Children,

                new PropertyCondition(

                    AutomationElement.NameProperty,

                    windowTitle));

 

            if (firefox == null)

                    throw new InvalidOperationException("Unable to find the specified Firefox instance.");

 

            return new Firefox(firefox);

        }

Success! My test passes, and on the first try (that's not common for me). Before I move on, I'll explain the meat of this method, the FindFirst invocation.

The TreeScope.Children argument tells the FindFirst method to search only the immediate children of the Windows Desktop (as opposed to TreeScope.Descendants, which searches everything down the tree). The property condition specifies the search criteria. In this case, I searched by the name property. There are many other options as well (AutomationIdProperty and IsInvokePatternAvailableProperty are both good ones).

Maximizing the Browser

I'm pretty happy. I've made good progress for 10 or so minutes. I want to try one other thing, also very simple. I want to automate maximizing the browser window. Here is my unit test:

        [Fact]

        public void show_window_will_maximize_firefox()

        {

            StartFirefox();

            IBrowser browser = Firefox.Attach("Mozilla Firefox");

 

            browser.ShowWindow();

 

            Assert.NotNull(((Firefox)browser).Instance);

            Assert.Equal(

                AutomationElement.RootElement.Current.BoundingRectangle,

                ((Firefox)browser).Instance.Current.BoundingRectangle);

        }

My ShowWindow method should simulate the user clicking the maximize button (if you are wondering why I named a maximize method ShowWindow, it's because it will do more later; I plan to keep this tool as source compatible with WatiN as I can). In MUIA, clicking buttons is done with an InvokePattern. And I happen to remember reading that the maximize button has an AutomationID property of "Maximize". So, I create this ShowWindow method:

        public void ShowWindow()

        {

            AutomationElement maximizeButton = instance.FindFirst(

                TreeScope.Descendants,

                new PropertyCondition(AutomationElement.AutomationIdProperty, "Maximize"));

 

            if (maximizeButton != null)

                ((InvokePattern)maximizeButton.GetCurrentPattern(InvokePattern.Pattern)).Invoke();

        }

The code is pretty simple. Try to get the maximize button, and if I get it invoke it. It's important to note that the maximize button is only present when the window isn't already maximized. If the window is maximized, it's a restore button instead.

I run my tests. Uh oh, the new test fails:

TestCase 'BrowserAutomator.UnitTests.FirefoxTests.show_window_will_maximize_firefox2'

failed: Assert.Equal() Failure

Expected: 0,0,1920,1106

Actual:   -4,-4,1928,1054

Because I set the initial size for my Firefox window to 1024x768, I know that the browser was actually maximized. But comparing the bounding rectangles of the Windows Desktop and Firefox won't work. So, I go to "Plan B," and changed my test to this:

        [Fact]

        public void show_window_will_maximize_firefox()

        {

            StartFirefox();

 

            IBrowser browser = Firefox.Attach("Mozilla Firefox");

            browser.ShowWindow();

 

            Assert.NotNull(((Firefox)browser).Instance);

            Native.Types.WindowPlacement windowPlacement = new Native.Types.WindowPlacement();

            Native.Methods.GetWindowPlacement(

                new IntPtr(((Firefox)browser).Instance.Current.NativeWindowHandle),

                ref windowPlacement);

            Assert.Equal(Native.Constants.ShowCommandMaximize, windowPlacement.ShowCommand);

 

        }

I run this test and it passes. I'm done for the night.

Afterthoughts

There are some real problems with my code as it currently is. For instance, if Firefox is already running, my unit tests fail. If Firefox was previously closed unexpectedly, my unit tests hang on the "Restore Previous Session" dialog.

I foresee other difficulties as well. An example is waiting for the page to finish loading. In a tool like WatiN, the browser's object model can be queried to determine whether the page is ready. I'm hoping I can query the text of the status bar, which always reads "Done" when the page is ready. We'll see. I'm sure there will be many problems such as this ahead.

Next Time

In part two of my Firefox automation adventures I will automate a Google search. It seems that most of the browser automation tools use this as their canonical example, so I might as well get it out of the way. More importantly, typing text and clicking buttons are simple but very useful steps forward.

Regards, DM

Posted by Drew Miller | 6 Comments
Filed under: ,

SDE + T = Happy

I've been developing software for just over ten years now. I've loved most of the work I've done, and I've put alot of time and energy into improving the state of my craft. I'm one of the lucky few who, more often than not, has fun at work.

But I've always had a closet interest in QA activities and practices. In fact, at one of my longer-term gigs before joining Micrososft, I spent a lot of time mentoring testers and building test automation tools and infrastructure. Building a driver for web browser automation was probably the single-most enjoyable development project I've ever worked on. And the three months I spent building a Fit-based automation solution for a massive client/server application probably rates second.

With these experiences in mind, and with the hubris to think that I have something to offer, I've had the idea of jumping the Development ship and joining a Test team. But I love CodePlex; there isn't another gig at Microsoft where I'd rather be. So I had sort of given up on the idea.

And then an opportunity sort of fell in my lap. And I jumped. I've added a T to my title (from SDE to SDET). I'm now a Software Development Enginner in Test, and I'm still on the CodePlex team!. And that makes me very, very happy.

Posted by Drew Miller | 1 Comments

A new unit test framework for .NET: xUnit.net

James Newkirk and Brad Wilson (my superior CodePlex colleagues) have released a new unit test framework for .NET: xUnit.net. If you're wondering why we need another unit test framework, Jim addressed that question in his announcement. Thus far, the community response is positive. I've been using xUnit.net for the last few days and I'm very pleased.

Posted by Drew Miller | 1 Comments
Filed under:

Another CodePlex 'blogger

I joined the CodePlex development team several months ago and since then I've been thinking about starting a 'blog back up. As you're reading this, you know that I've done just that. I plan to write about CodePlex, open source, and agile development at this 'blog. My goal is to write something substantive once a week; we'll see if I can manage that.

Posted by Drew Miller | 1 Comments
Filed under:
 
Page view tracker