Thursday, 8 May 2008

Using ASP.NET MVC HtmlHelper.Form

Today, I tried making a HTML form with the ASP.NET MVC HtmlHelper, but it took me a while to get it working as documentation on the web is a little sparse at the moment. Here's some basic instructions.

View

In the view, you can use a using statement to ensure the Form tag is closed properly. I am using the generic version of the HtmlHelper.Form method, so that I can easily specify a method on my controller to be called when the form submit button is clicked. This method can take parameters if you want (for example the ID when creating an edit form), but it should not have parameters for the actual input fields on the form - they will be read out of the Request object by the controller.

<h2>New Post</h2>
<% using (Html.Form<BlogController>(c => c.CreatePost()))
  { %>
<label for="postTitle">Title</label>
<%= Html.TextBox("postTitle") %>
<br />
<%= Html.TextArea("postBody","", 10,80) %>
<%= Html.SubmitButton("submitButton","Save") %>
<% 
  } %>

Controller

The controller method, as I have already indicated does not need to take the form inputs as parameters. It reads these out of the Request.Form dictionary.

public ActionResult CreatePost()
{
   string postTitle = Request.Form["postTitle"];
   string postBody = Request.Form["postBody"];   
   Post post = new Post { Title = postTitle, Body = postBody };
   post.Status = PublishStatus.Published; 
   blogService.CreatePost(post);
   return RedirectToAction(new { action = "Index" });
}

There is a Binding.UpdateFrom helper method that you can use to speed up the code further if you name your fields correctly.

Testing

It's fairly easy to test your controller's new method. Simply add references to System.Web and System.Web.Abstractions and you can populate the Request.Form dictionary before calling your action method:

[Test]
public void BlogControllerCanCallCreatePost()
{
   BlogController blogController = new BlogController(new TestBlogRepository());
   blogController.Request.Form["postTitle"] = "Title";
   blogController.Request.Form["postBody"] = "Body";
   ActionResult result = blogController.CreatePost();
}

EDIT: Looks like I spoke too soon. The above test code will actually fail because blogController.Request is null. I'll update this post once I have worked out an easy way to populate the Request.Form dictionary. And in future I'll try to remember to actually run my unit tests before declaring them a success!

6 comments:

kamini said...

nice post...

just tell me what is ActionResult
and what is blogservice?

can u explain me....

and i am doing r&d on MVC..and if possible send me a some document about MVC.....

Mark H said...

Hi kamini, go to Scott Gu's blog, and the official ASP.NET MVC site to find out more.

blogservice is just my class that allows me to access my blog database.

ActionResult is what is returned by MVC controller methods.

Grande said...

Hi,

I'm using pretty much the same thing as you, but I'm getting a NullReferenceException. It seems Html is coming back as null. Any idea why?

<% using (Html.Form<UsersController>(action => action.Create())) { %>

Erik Porter said...

Best pattern we've found for writing tests like you have shown is by using fake classes. Stephen Walther has many great posts on these.

http://weblogs.asp.net/stephenwalther/archive/2008/08/03/asp-net-mvc-tip-29-build-a-controller-to-debug-your-custom-routes.aspx

Leo H. said...

Where do I get Html.SubmitButton("submitButton","Save")? My HtmlHelper class does not contain this method.

Mark H said...

Hi Leo, this was with an old tech preview, so I imagine that HtmlHelper has changed since then. I haven't used the latest version yet.