Introduction

This tutorial will guide you throw all the steps needed to create a responsive HTML form with client and server side validation.

1. Create a model

We need to create a model (for example: SubscriberModel) and define it’s properties as you can see in the code bellow.

 public class SubscriberModel
{
    [Required]
    [Display(Name = "Email", Prompt = "Email address")]
    [BsControl(BsControlType.Email)]
    public string Email { get; set; }

    [Required]
    [Display(Name = "Name", Prompt = "Name and surname")]
    [BsControl(BsControlType.TextBox)]
    public string Name { get; set; }

    [Required]
    [Display(Name = "Subscription type", Prompt="Choose")]
    [BsControl(BsControlType.DropDownList)]
    public BsSelectList<int?> SubscriptionType { get; set; }
}
            

We use BsControl and BsControlType to describe a property type. Follow the link Input controls to read more about BsControlType.Email, BsControlType.TextBox, BsControlType.DropDownList and so on.

Data binding

In order for SubscriptionType to be a valid dropdown we need to fill it with data. Let’s bind it to an enum :

public enum SubscriberEnum : int
{
    [Display(Name = "Daily subscription")]
    Daily = 1,

    [Display(Name = "Weekly subscription")]
    Weekly = 2,

    [Display(Name = "Monthly subscription")]
    Monthly = 3
}
            

Then you can use our helper:

SubscriptionType = BsSelectList<int?>.FromEnum(typeof(SubscriberEnum))
            

Note that, if you have set display attribute on your enum’s elements, the name or the resource specified in the attribute will represent the dropdown option’s text. Otherwise option’s text will be the enum elements name.

Here is an example where we use resources in the display attribute, as we mentioned in the previous paragraph.

public enum SubscriberEnum : int
{
    [Display(Name = "Daily", ResourceType =typeof(Resource))]
    Daily = 1,

    [Display(Name = "Weekly", ResourceType = typeof(Resource))]
    Weekly = 2,

    [Display(Name = "Monthly", ResourceType = typeof(Resource))]
    Monthly = 3
}
            

2. Create the View

As you could guess, in this view we have rendered all model properties. We used BsInputFor, which is a generic helper. You can read more about this helper and about other BForms input controls here.

@using (Html.BeginForm("Index", "Subscriber", null, 
	FormMethod.Post, new { @class = "js-subscriberForm" }))
{
    <div class="col-lg-12">
        @Html.BsValidationSummary()
    </div>

    <div class="col-lg-12 form-group @Html.BsValidationCssFor(m => m.Email)">
        @Html.BsLabelFor(m => m.Email)
        <div class="input-group">
            @Html.BsGlyphiconAddon(Glyphicon.Envelope)
            @Html.BsInputFor(m => m.Email)
            @Html.BsValidationFor(m => m.Email)
        </div>
    </div>

    <div class="col-lg-12 form-group @Html.BsValidationCssFor(m => m.Name)">
        @Html.BsLabelFor(m => m.Name)
        <div class="input-group">
            @Html.BsGlyphiconAddon(Glyphicon.User)
            @Html.BsInputFor(m => m.Name)
            @Html.BsValidationFor(m => m.Name)
        </div>
    </div>

    <div class="col-lg-12 form-group @Html.BsValidationCssFor(m => m.SubscriptionType)">
        @Html.BsLabelFor(m => m.SubscriptionType)
        <div class="input-group">
            @Html.BsGlyphiconAddon(Glyphicon.Inbox)
            @Html.BsSelectFor(m => m.SubscriptionType)
            @Html.BsValidationFor(m => m.SubscriptionType)
        </div>
    </div>

    <div class="col-lg-12">
        <button class="btn btn-default js-subscriberBtn" type="submit">Subscribe</button>
    </div>
}

            

There are two ways of displaying forms validation errors: the @Html.BsValidationSummary() helper will render an alert at the top of the form and @Html.BsvalidationFor that will render specific messages for each filed inside a tooltip.

3. Create a Controller

We showed you earlier how to bind data and now you will see where to use it. We have our code in Controller because it’s small and it’s just for demo purpose, but we recommend you to use repositories for data binding, CRUD operations, mapping operations and so on.

We created a controller named SubscriberController where we’ll put our actions for HttpGet and HttpPost.

[HttpGet]
public ActionResult Index()
{
    var model = new SubscriberModel()
    {
        SubscriptionType = BsSelectList<int?>.FromEnum(typeof(SubscriberEnum))
    };

    return View(model);
}

[HttpPost]
public ActionResult Index(SubscriberModel model)
{
    model.SubscriptionType = BsSelectList<int?>.FromEnum(typeof(SubscriberEnum));

    //add field validation error
    if (model.Email == "test@gmail.com")
        ModelState.AddFieldError("Email", null, "This address has been already used");

    //add global validation error
    ModelState.AddFormError("",
        "You can't subscribe at the moment");

    return View(model);
}
            

You can see we have two actions named Index: one is for GET and the other one for POST.

GET

The one for GET returns a strongly-typed view which receives a new model of type SubscriberModel (that has SubscriberType property of type dropdown filled with data from our enum).

POST

The one for POST executes actions for subscribing in your application, but in this case we added some ModelState errors, because it’s just for demo purpose. As you can see, when we try to subscribe with the email test@gmail.com we added a validation error for email model’s property which throws you this message : "This address has been already used".

Also, we added a form validation error (remember this). The first parameter of this method it’s empty(“”) because you don’t have a prefix for your form.

For example, if you have a main model which has a property of type SubscriberModel and your actions receive that model as a parameter than the prefix will be the name of the property. This explanation is illustrated bellow:

[HttpPost]
public ActionResult Index(MainModel model)
{
	 ModelState.AddFormError("Subscriber", "You can't subscribe at the moment");

	 ...
}
            

public class MainModel
{
    public SubscriberModel Subscriber { get; set; }
}

            

Ajax Submit

If you don’t want to use server side post, you can also submit your form by Ajax.

It's easy and we'll show you how.

We use RequireJS so we can send action’s path to our ajax from controller. You can do this from the GET action as in the following code:

RequireJsOptions.Add("subscriberUrl", Url.Action("SubscribeByAjax"));
            

In our case, we added the option above on server POST too, because we have two submit methods in the same form. As you could guess, if you press the server side submit button, when the page is reloaded, you’ll lose the url option because you don’t send it again. If you don’t have the same situation in your project, you don’t have to do this( it’s just for demo purpose).

In our case we use RequireJsOptions.Add with two parameters. The first one represents the option’s name sent to javascript file (you'll see further in this guide how to use it) and the second parameter is the actual path to our action. Here we sent the url to use it in ajax, but with RequireJsOptions.Add you can send practically any .NET object that supports JSON serialization. For example, if you have a list of int elements like var myList= new List<int>(){1,2,3}, the code for sending it to js it's like this:

RequireJsOptions.Add("list_of_int",myList)

Our ajax action:

public BsJsonResult SubscribeByAjax(SubscriberModel model)
{

    //add field validation error
    if (model.Email == "test@gmail.com")
        ModelState.AddFieldError("Email", null, "This address has been already used");

    //add global validation error
    ModelState.AddFormError("", "You can't subscribe at the moment");

    //re-bind data to dropdown
    model.SubscriptionType = BsSelectList<int?>.FromEnum(typeof(SubscriberEnum));

    if (!ModelState.IsValid)
    {
        //Serialize ModelState errors
        return new BsJsonResult(new Dictionary<string, object>
        {
            { "Errors", ModelState.GetErrors() }
        }, BsResponseStatus.ValidationError);
    }

    return new BsJsonResult(new
    {
        Status = BsResponseStatus.Success
    });

}
            

As you can see, there are not so many changes in the SubscribeByAjax action compared to server POST action, Index, which was presented few moments ago.

First of all, this action returns a BsJsonResult because it’s called by ajax. If the model is valid then the status of my response will be Success. Otherwise we serialize ModelState errors and we send them to ajax with a validation error status. In our case, we return the ModelState errors as we talked earlier.

As you can see, BsResponseStatus is an enum defined like this:

 public enum BsResponseStatus
  {
    Success = 1,
    ValidationError = 2,
    AccessDenied = 3,
    ServerError = 4,
  }
            

4. Create the JavaScript

First of all you need to create a folder named Controllers under Scripts. After that, if you don’t work in a MVC area, create a folder called Root under Controllers (that you have created few seconds ago). Otherwise, replace Root with the area name. Under last added folder create a new one which has the same name as your controller.

Finally we create our JavaScript file subscriber-index.js. In order for RequireJS.NET to work, all your js files must be named like controller_name-action_name.js.

In the end you should have something like this:

Scripts/Controllers/Root/
└── Subscriber/
	└──subscriber-index.js

            

Subscriber-Index.js content:

require([
'bforms-initUI'
], function () {

    var SubscriberIndex = function (options) {
        this.options = $.extend(true, {}, options);
    };

    SubscriberIndex.prototype.init = function () {
        this.$subscriberForm = $('.js-subscriberForm');

        //apply BForms plugins
        this.$subscriberForm.bsInitUI(this.options.styleInputs);

	};

   $(document).ready(function () {
        var ctrl = new SubscriberIndex(requireConfig.pageOptions);
        ctrl.init();
    });
});

            

To apply styles to our form we need to select it and use our method bsInitUI. That's all you have to do here.

Ajax Submit

We talked earlier how your controller looks like when you want to submit a form by Ajax. Let's see how javascript file should look.

First of all, you need to include references to some scripts:

require([
'bforms-validate-unobtrusive',
'bforms-extensions',
'bforms-initUI'
], function () {

...

});

            

Than we need to select our button from the view. In our case:

<button class="btn btn-default pull-right js-subscriberAjaxBtn" type="button">Ajax Subscribe</button>
            

To do so, we took a variable named this.$ajaxSubscribeBtn = $('.js-subscriberAjaxBtn'); . We also have to add a handler, which means an action that will be executed for an event (in our case a click event).

SubscriberIndex.prototype.init = function () {
    this.$subscriberForm = $('.js-subscriberForm');
    this.$ajaxSubscribeBtn = $('.js-subscriberAjaxBtn');

    //apply BForms plugins
    this.$subscriberForm.bsInitUI(this.options.styleInputs);

    this.addHandlers();
};

SubscriberIndex.prototype.addHandlers = function () {
    this.$subscriberForm.on('click', '.js-subscriberAjaxBtn', $.proxy(this.onSubscribe, this));
};
            

We bind a click event for ".js-subscriberAjaxBtn" at our form. When this event is happening we call a function named onSubscribe.

 SubscriberIndex.prototype.onSubscribe = function (e) {
    e.stopPropagation();
    e.preventDefault();
    var $target = $(e.currentTarget);

    $.validator.unobtrusive.parse(this.$subscriberForm);
    var validatedForm = this.$subscriberForm.validate();

    if (this.$subscriberForm.valid()) {

        var subscriberData = this.$subscriberForm.parseForm();

        $target.prop('disabled', "disabled");

        $.ajax({
            url: this.options.subscriberUrl,
            data: JSON.stringify(subscriberData),
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8'
        }).then($.proxy(function (response, status, jqXHR) {
            if (response.Status == 2) { //validation error
                validatedForm.showErrors(response.Data.Errors, true);
            } else {
                //add your success logic here
            }

            $target.removeProp('disabled');

        }, this), function () { //fail
            $target.removeProp('disabled');
        });
    }
};
            

As you can see, we call $.validator.unobtrusive.parse and we validate the form. In case the form has no client side errors (if (this.$subscriberForm.valid()) {...}) we call our ajax.

Things you need to notice at ajax call is that url is referenced by this.options.subscriberUrl (we talked earlier about this).

When we receive a response for our ajax (on .then function), we first have to check if response.Status (which in our case is an element from an enum - BsResponseStatus, which we presented you earlier at the controller step) was sent as a validation error. If so, we show errors for the form using .showErrors function from bforms.validate.js.

If you don't know what is .then you can read more about it here: jQuery .then.


5. Final Result

Here is a picture of what we achieved.


Validation errors: