First play with ASP.net MVC

(UPDATE: Cheers LiveWriter – Post to weblog as draft causes it to publish. Sorry for any mistakes in first attempt, this I have fixed them all)
In this post, I’m going to discuss my experience while having a play with the ASP.net MVC framework. Over the next series of posts, I’ll go back over some of the items discussed and deep dive into them to provide more information. Tonight, I just wanted to get my initial thoughts out while it was still hot.

The installation and download was quite simply and didn’t have any problems with the install.

image

However, this brings me onto problem number one. The assemblies are required to be installed into the GAC! This is impossible with Shared Hosters (unless some hosters out there are willing to install CTPs on production servers). This means I can’t deploy samples / test sites until its RTM! Guess I’ll just have to publish the code. If anyone knows any hosters offering ASP.net MVC hosting, please leave a comment.

Once installed, you will have a number of new templates within your Visual Studio new project dialog.

image

There are two main projects, one is ASP.NET MVC Web Application and Test with the other being ASP.NET MVC Web Application. The first project includes the second project type (just the web app) which will setup the correct folder directory and give you the base template to fill in. It will also create a MSTest project to start writing your unit tests.

After creating the projects. the layout is below.

image

A few things I wanted to point out. The content directory has the stylesheet for the site, this is where images etc will go. The controllers folder will contain the controller classes which will have all the methods for the application. The models will contain any objects related to storing your data. Views will contain the ASPX pages (and master pages).

The other thing you notice is that there is a default.aspx in the root directory. If you open the file, it has the following message.

This makes sense, bit of a hack but never mind. If we load the website, then it will look like below.

image

Great, now lets start having a play. First thing I want to do, add a text box. Hit F5 and I got an exception.

System.Web.HttpException was unhandled by user code
Message=”Control ‘ctl00_MainContentPlaceHolder_TextBox1’ of type ‘TextBox’ must be placed inside a form tag with runat=server.”
Source=”System.Web”
ErrorCode=-2147467259

As I added the control in code view, it didn’t automatically add a form tag for me. Easily fixed.

Hit F5 again and I got another exception.

System.NullReferenceException was unhandled by user code
Message=”Object reference not set to an instance of an object.”
Source=”App_Web_t1migfpo”

This was because Visual Studio had pointed me to the full path for the ASP.net page (WebForms style – http://localhost:64701/Views/Home/Index.aspx). However, with MVC this doesn’t work as you navigate the site via defined routes, if I go directly to the root of the website and it works fine.

Now, the main thing I want to look at first is how to test the controller.

Testing the controller

The first task I wanted to do was delete the test project and re-created it with a C# class library referencing the MbUnit.Framework assembly.

After I had it correctly building, I wanted to test the Index action on the test controller. This is the first method called in our code and deals with what should happen when the homepage / index is visited. At the moment, all it does it tell the Index page to render.

Just to get an idea, I just wrote the simple test below to see what happened (and changed) when Index() was called.

[Test]
public void IndexTest()
{
HomeController controller = new HomeController();
controller.Index();
}

However, this throw an exception.

[failure] HomeControllerTests.IndexTest
TestCase ‘HomeControllerTests.IndexTest’ failed: Object reference not set to an instance of an object.
System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: System.Web.Extensions

Instant mistake. All the methods on the Controller have the [ControllerAction] attribute and a number of dependencies exist behind the scenes.

At this point, I referred to Phil Haack’s post on Writing Unit Tests for Controller Actions (sounds like what i’m doing). What Phil describes is to use a ‘Test specific subclass’. Using his approach, I change my test to interact with HomeControllerTester

[Test]
public void IndexTest()
{
HomeControllerTester controller = new HomeControllerTester();
controller.Index();
}

I then create the HomeControllerTester which overrides the RenderView method and stores the name in a property.

internal class HomeControllerTester : HomeController
{
public string RenderViewAction { get; private set; }
protected override void RenderView(string viewName, string masterName, object viewData)
{
RenderViewAction = viewName;
}
}

I can then include a Assert.AreEqual(“Index”, controller.RenderViewAction); to ensure the correct view is called. This is easy, but it is additional work just to verify that the correct view is being called.

Another approach he mentioned was using Rhino Mocks, which I have used once or twice before in previous posts. However, it appears as if that support for mocking the RenderView method in this CTP is broken. The only approach is to use the subclass above.

Outputting data

Next thing I wanted to try was creating data within the controller and passing it to the view for outputting.

Within my index method, I just create a dummy generic list of strings which I want to output.

[ControllerAction]
public void Index()
{
List topics = new List();
topics.Add(“My String 1”);
topics.Add(“My String 2”);
topics.Add(“My String 3”);
topics.Add(“My String 4”);
RenderView(“Index”, topics);
}

When calling RenderView, I pass in the generic list as an argument. Now, I just need to output the data. Within index.aspx, I added the following code snippet. This

Outputting data

    <% foreach(string s in ViewData as System.Collections.Generic.List)
    {
    %>

  • <%= s %>
  • <% }

    %>

However, with this I got a compiler error message.

Compiler Error Message: CS0039: Cannot convert type ‘System.Web.Mvc.ViewData’ to ‘System.Collections.Generic.List‘ via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion

Looking at the ViewData within the debug view, it has added a _data block and each element within my list is a separate item. However, I couldn’t find a way to extract that data as _data is a private member. The only way to extract the data is based on a name.

image

Within my controller, I changed the code to setup the ViewData

ViewData[“Topics”] = topics;
RenderView(“Index”);

I then changed my output code to be the following:

foreach(string s in ViewData[“Topics”] as System.Collections.Generic.List)

The data is then correctly outputted to the screen.

image

The debug window then looks as I expect with _data containing the generic list.

image

I’m guessing the ViewData constructor is used for something else or its a bug in the rendering. In the documentation, it does say we this is valid syntax:

[ControllerAction]
public void Categories()
{
List categories = northwind.GetCategories();
RenderView(“Categories”, categories);
}

Strange! Moving on, referring object is so .Net 1.1. I want to work with strongly typed objects. The first thing I want to do is create the object

public class TopicsViewData
{
private List topics = new List();
public List Topics
{
get { return topics;}
}
}

This will store all of my view data. I can then change my Index code to use the ViewData object.

[ControllerAction]
public void Index()
{
TopicsViewData viewData = new TopicsViewData();
viewData.Topics.Add(“My String 1”);
viewData.Topics.Add(“My String 2”);
viewData.Topics.Add(“My String 3”);
viewData.Topics.Add(“My String 4”);
RenderView(“Index”, viewData);
}

I then pass in the viewData object as the parameter, which works fine. Within Index.aspx.cs (yes, MVC still has code behind, its just used less often) I change what the page is inheriting from. In the previous version, it simply inherited from ViewPage.

public partial class Index : ViewPage

However, to use a custom View Data object we need to use the generic ViewPage and pass in the ViewData object.

public partial class Index : ViewPage

We can now access all the properties of TopicsViewData from within Index.aspx

<% foreach(string s in ViewData.Topics) { %>

  • <%= s %>
  • <% } %>

    Bit of additional work, but does make it a lot easier to work with.

    MVCToolkit

    The MVCToolkit has the following (according to the readme):

    “- Rendering helpers which make it easier to output various HTML tags in MVC Views

    Dynamic Data support: this gives ASP.NET MVC a powerful and extensible scaffolding architecture. The toolkit also adds metadata pluggability, which allows the metadata used by Dynamic Data to use alternate stores (instead of the default attribute based mechanism).”

    One thing not mentioned is that it includes the Blog sample application which uses the Dynamic Data support controls. Subject for another post…

    Documentation

    There is a bit of documentation over at http://quickstarts.asp.net/3-5-extensions/

    Summary

    In summary, this was just a very quick look at wjats om the CTP, the framework is still very early in the development. Some of the concepts are there and its at the level I expected for thisw CTP. Still worth looking at and having a play. I’ll post more as I find things out.

    One thing I have quickly realised is that I have a lot of reading and work to do to get up to speed with MVC, IoC and how to other MVC frameworks (Monorail) interact. Don’t worry – I’ll be blogging my learning and experience.

    Download MVC: http://www.microsoft.com/downloads/details.aspx?FamilyId=A9C6BC06-B894-4B11-8300-35BD2F8FC908&displaylang=en

    Download MVC Toolkit: http://asp.net/downloads/3.5-extensions/MVCToolkit.zip

    Technorati Tags: ,

    5 thoughts on “First play with ASP.net MVC”

    Leave a Reply

    Your email address will not be published. Required fields are marked *