(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.
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.
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.
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.
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.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
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
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.
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.
The debug window then looks as I expect with _data containing the generic list.
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
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
public List
{
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) { %>
<% } %>
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
I was looking into hosting myself and this should be able to run it.
http://discountasp.net/features.aspx
The same goes for any hosting that has the .net 3.5 framework intalled.
Try out the MbUnit templates for ASP.Net MVC in the latest MbUnit v2 and v3 previews: http://code.google.com/p/mb-unit/
Ok… I’ll admit this is part of what kept me up for many many hours last night. So I have a vested interest in people noticing them. 😉
In your test that failed, substitute
foreach(string s in ViewData….
for
foreach(string s in ViewData.model….
This will work.
😉
Hi John,
Thanks, MVC team changed their API which is why it failed.
Ben
where this MVC software we will get plz send to me this is mail id [email protected]