Umbraco v5 Surface Controller Forms

This post will show you how to create a form in Umbraco v5 using Surface Controllers. The information in this post assumes that you are familiar with Surface Controllers (see this previous post if not) and with creating forms in ASP.Net MVC.

Disclaimer

This post is about developing for Umbraco v5 (Jupiter) which at the time of this post is still under development. The technical details described below may change by the time Umbraco Jupiter is released. If you have feedback on the technical implementation details, please comment below.

Create a model

The easiest way to create a form in MVC is to create a model that represents your form. This isn’t mandatory but then you’ll have to use some old school techniques like getting posted values directly from the HttpRequest object.

An example could be:

namespace MySite.Models
{
    public class MyTestModel
    {
        [Required]
        public string Name { get; set; }

        [Required]
        [Range(18,30)]
        public int Age { get; set; }        
    }
}

Create a Surface Controller

For this example we’ll assume that we’re creating a locally declared Surface Controller (not a plugin, see the previous post for full details).

Some code first and explanation after:

namespace MySite.Controllers
{   
    public class MySurfaceController : SurfaceController
    {
        [HttpPost] 
        public ActionResult HandleFormSubmit(
            [Bind(Prefix = "MyTestForm")]
            MyTestModel model)
        {
            if (!ModelState.IsValid)
            {
                return CurrentUmbracoPage();
            }            
            
            //do stuff here with the data in the model... send
            // an email, or insert into db, etc...
            
            return RedirectToUmbracoPage(
                new HiveId(
                    new Guid("00000000000000000000000000001049")));
        }
    }
}

Lets break down some parts of the above Controller code:

Namespace: Generally you’ll put your locally declared controllers in the ~/Controllers folder, the above namespace reflects this.

Class: The Surface Controller class is suffixed with the term ‘SurfaceController’. This is a required convention (as per the previous post), without that suffix, the controller will not get routed.

[Bind] attribute: In the code below you’ll see that we are creating the editor with a ‘prefix’ called ‘MyTestForm’. This ensures that each input element on the form will get its name prefixed with this value. For example:

<input type="text" name="MyTestForm.Name" />

This is a recommended practice however it is optional. If you don’t prefix your form controls then you don’t need to use the [Bind] attribute. Here’s a few reasons why this is a recommended practice:

  1. Without the prefix, If there is more than 1 form rendered on your webpage and both are using the MVC validation summary, then both validation summaries will display the errors for both forms. When there is a prefix you can tell the validation summary to only display errors for the form with the specified prefix.
  2. The MVC Html helpers will generate the html id for each element and without specifying a prefix and if you have more than 1 form rendered on your page you may end up with duplicate html element Ids.
  3. If you create a scaffolded form with a custom model object such as doing:

    @{ var someModel = new MyModel(); }
    @Html.EditorFor(x => someModel)

    then MVC will automatically prefix your input fields with ‘someModel’ . For some reason MVC does this when the model name isn’t exactly ‘Model’.

return CurrentUmbracoPage: This method is built in to the base SurfaceController class and simply returns the currently executing Umbraco page without a redirect to it which is generally what you want to do in order to display any validation errors.

return RedirectToUmbracoPage: This method is built in to the base SurfaceController class and performs a redirect to a Umbraco page given an Id which is generally what you want to do when a form submission is successful. (chances are that you wont have a page with a Guid id of 00000000000000000000000000001234…. this is just an example :)

NOTE: There is also a RedirectToCurrentUmbracoPage() method!

Rendering a form

There are a few ways to render a form:

  • Directly in your Umbraco template/view
  • Using a Partial View macro
  • Using a Child Action macro which renders a partial view

Regardless of which you use to render a form, the markup will be very similar. Similar to MVC’s @Html.BeginForm, we have an @Html.BeginUmbracoForm helper:

@{
    var formModel = new MySite.Models.MyTestModel();
}

@using(Html.BeginUmbracoForm("HandleFormSubmit", "MySurface"))
{
    @Html.ValidationSummary(prefix: "MyTestForm")
    @Html.EditorFor(x => formModel, "", "MyTestForm")
    <input type="submit"/>
}

Here’s the breakdown of the above mark-up:

Html.BeginUmbracoForm: A normal MVC form would simply use Html.BeginForm which will create an action attribute for the html form tag with a URL of your controller’s action. In Umbraco however, we want the URL to be posted to the same as the URL being rendered (post back), so the BeginUmbracoForm call handles this all for us. It will create a form tag with the URL of the currently rendered Umbraco node and add some custom hidden fields to your form containing the values of where the data will actually post to (your Surface Controller’s action). The Umbraco front-end route handler will take care of all of this for you.

The parameters passed in to BeginUmbracoForm will differ depending on if your Surface Controller is a plugin controller or a locally declared controller. In this example, its a locally declared controller so we just need to give it the action name and controller name. If its a plugin Surface Controller, you’ll need to give it the action and and the controller ID. There’s also a few overloads so that you can add additional html attributes to the rendered html form tag.

@Html.ValidationSummary: The native MVC ValidationSummary doesn’t let you target specific input tags via prefixes so we’ve created a ‘better’ one that does let you do that. In this example we’re telling the validation summary to only display errors for input values that are prefixed with “MyTestForm” which is real handy if you’re rendering a few forms on the same page and using a validation summary for each.

@Html.EditorFor: This is the native EditorFor method in MVC which lets you supply a prefix name which will be used for all of your input fields. The MVC methods for rendering individual input tags also have an overload to supply a prefix if you choose not to have it scaffold your form for you.

formModel: The above mark-up will scaffold the form for us based on the model that we created previously. This example creates an inline model object (formModel) to scaffold the form but if you had a strongly typed partial view with your model type, you could just as well use the view’s Model property.

And that’s pretty much it! Happy form making :)

Author

Administrator (1)

comments powered by Disqus