Throughout the course of web development, you often find yourself in a situation where you need data input from your user. Scenarios range from a simple contact form, to something as complex as a multi-part job application. In either case, Sitefinity offers multiple solutions. The easiest solution, for the simpler forms such as the aforementioned contact form, is to use Sitefinity’s built-in Forms capabilities. Much like with their pages, Sitefinity Forms provide a very simple, powerful way to create and customize even more complex forms for your site. The resulting data is then stored in Sitefinity, and can be set to notify anyone of new submissions and have its data exported in an Excel format.
Despite these built-in features, you’ll still find yourself needing to create your own forms from scratch from time to time. Sitefinity MVC widgets are another powerful means to extend your Sitefinity application, and within them you can create forms as well. Unlike the forms created in the backend, however, these are completely customizable, as you have 100% control over your widget’s Razor views. These are hybrid forms, forms which leverage ASP.NET’s already-existing <form> tag and submit data to your MVC controller. If you are working on anything requiring more than what the out-of-the-box Sitefinity Forms can easily provide, this is for you!
Initial Setup
To start, we’ll define a basic Sitefinity MVC widget controller, with two actions: The Default action which is invoked when first hitting the page, and the SubmitForm action for when the form is submitted by the user. In addition, we’ll have a blank viewmodel class called “MyFormViewModel” which we’ll populate with the properties we want to capture as we progress.
[ControllerToolboxItem(Name = "CustomFormWidget", Title = "Custom Form Widget", SectionName = "MvcWidgets")]
public class CustomFormWidgetController : Controller
{
public ActionResult Index()
{
return View("Default", new MyFormViewModel());
}
[HttpPost]
public ActionResult SubmitForm(MyFormViewModel viewModel)
{
throw new NotImplementedException();
}
}
public class MyFormViewModel { } // To be populated!
Out of the box, Sitefinity sites have an MVC folder, with subfolders for your Controllers, Models (viewmodels), and Views. Our Controller is CustomFormWidgetController, so for its view, we’ll create a subfolder called “CustomFormWidget” inside the Views folder, and a blank “Default.cshtml” within the subfolder.
![01-mvc-folder-structure]()
Inside Default.cshtml, here is the bare minimum needed to instantiate a form that our controller can tap into and utilize.
@using Telerik.Sitefinity.UI.MVC
@model SitefinityWebApp.Mvc.Controllers.MyFormViewModel
@using (Html.BeginFormSitefinity("SubmitForm", "MyFormName"))
{
@* The form's elements will go here! *@
}
This snippet of code instantiates a form with a name (“MyFormName”), and specifies the MVC Action to call when it’s submitted (“SubmitForm” — the other action we defined earlier).
BeginFormSitefinity and the ID of the Page Form Tag
“BeginFormSitefinity” is an extension method on Html that is built into Sitefinity (hence the inclusion of a using statement at the top). Note that it differs in implementation from the MVC BeginForm method, in that it doesn’t insert a <form> tag on the page. Instead, it inserts JavaScript that submits the form, including any elements inside the using curly braces and their inputted values. It is implemented this way so that the form can be created even in hybrid Sitefinity pages (the default kind of Pages you create in the Sitefinity backend). These pages already have a <form> tag wrapping the majority of the page (standard ASP.NET fare), and only one <form> tag can exist in this context.
While this works well enough, it does lend itself to an issue that I and many others have run into multiple times. If you have a master page or custom Sitefinity template, and you give the form an ID other than the default “aspnetForm”, the form won’t submit! You’ll get a JavaScript error instead (which you’ll only see if you have your browser’s dev console open).
This is because the JavaScript Sitefinity writes to the page depends on the ID of the form being exactly “aspnetForm” and nothing else. If you have control of the <form> tag on your template or page, and are not depending on the ID being different, go ahead and change it to “aspnetForm.” If you cannot change the form ID, however, then you can insert the following simple snippet of jQuery to update the <form> tag’s ID client-side, so that BeginFormSitefinity will work:
<script type="text/javascript">
$("form").attr("id", "aspnetForm");
</script>
This little hack should get your Sitefinity MVC widget-driven form running if you’re finding it not submitting.
The Submit Button
The simplest form you can make here is simply one that allows you to click a button and execute the given action. This might sound pointless, but in actuality this is a very simple way to create a client-facing button that activates some action, program, task, etc. in your Sitefinity application. Since Sitefinity MVC widgets can be placed on pages in the backend as well as the frontend, it’s a nice quick way to get a simple “you can perform this action whenever you feel like” functionality up and running.
The submit button itself is nothing special. It is literally an HTML submit input (I wrapped it in a div for neatness when displayed in a browser):
@using (Html.BeginFormSitefinity("SubmitForm", "MyFormName"))
{
<div>
<input type="submit" value="Submit Form" />
</div>
}
Currently, our controller will do nothing but throw a NotImplementedExcpetion(). This does tell us something, though: That our form is successfully wired up to our controller, and is calling the right action. We’ve overcome the potential form ID issue, and are ready to begin adding data to our viewmodel and our form itself.
Basic Data: Strings and Numbers
I won’t get into too much detail in this section, as creating textboxes, radio buttons, checkboxes, etc. is pretty much identical to how you would create form fields in non-Sitefinity MVC forms. You can use HTML helper methods defined in MVC to generate the labels and inputs. We’ll also update our viewmodel with a couple fields. Here’s what our form and viewmodel class look like now (hybrid forms, at the end of the day, look quite similar to MVC forms):
@using (Html.BeginFormSitefinity("SubmitForm", "MyFormName"))
{
<div>
@Html.LabelFor(m => m.TextField)
@Html.TextBoxFor(m => m.TextField)
@Html.ValidationMessageFor(m => m.TextField, "Please enter some text", new {@class = "error"})
</div>
<div>
@Html.LabelFor(m => m.NumberField)
@Html.TextBoxFor(m => m.NumberField, new { min = "0", type = "number" })
@Html.ValidationMessageFor(m => m.NumberField, "Please enter a number", new {@class = "error"})
</div>
<div>
<input type="submit" value="Submit Form" />
</div>
}
public class MyFormViewModel
{
public int NumberField { get; set; }
public string TextField { get; set; }
}
Form Validation: Client and Server-Side
In addition to defining a textbox and label for both fields, we also define a validation message section. This text will only appear when the Model State is invalid. This validation can occur automatically on the client side, but it is also vital to validate the inputs server-side as well. A user can always bypass any and all client-side validations, feeding your method any data they want. It is up to you to validate server-side as well. That being said, client-side validation is convenient to the user as form submission can be blocked until the correct data is inputted.
In the TextBoxFor the number field, you see we define its type as number, with a minimum value. This provides convenience to the user (only lets them type numbers, doesn’t submit the form if the number is too low, etc) and makes sure basic validation takes place before form submission. On submit, you can check on the numeric value (the model binder can only bind numbers to your viewmodel, since “NumberField” is an integer), and do the usual MVC “ModelState.AddModelError” and “ModelState.IsValid” testing, before proceeding with handling the form submission. You can enforce requirements not defined on the client, as well. Taking these into account, here’s what our SubmitForm action could look like:
[HttpPost]
public ActionResult SubmitForm(MyFormViewModel viewModel)
{
if (string.IsNullOrWhiteSpace(viewModel.TextField))
{
ModelState.AddModelError("TextField", "Please enter some text.");
}
if (viewModel.NumberField < 0)
{
ModelState.AddModelError("NumberField", "Please enter a valid number.");
}
if (!ModelState.IsValid)
{
// Return the submitted viewModel to the view, so that form fields are pre-populated on reload.
return View("Default", viewModel);
}
// Handle the form here, since the fields are validated.
throw new NotImplementedException();
}
Submitting Files
With checkboxes, radio buttons, drop down lists, textboxes, and textareas, the data submitted can be easily captured as strings and numbers. Files, however, are another matter altogether. It takes a little bit of tweaking to the page content, but other than that file submission in hybrid forms is a cinch.
Setting Enctype on the Form
In order for files to be successfully submitted, you have to set “enctype” to “multipart/form-data” on your page’s <form> tag. You don’t want this on every page, of course, so if your <form> is on every page (part of a template/ASP.NET master page, etc.), you can add it just to your form by adding the following snippet to your view:
<script type="text/javascript">
(function ($) {
"use strict";
$("form").attr("enctype", "multipart/form-data");
})(jQuery);
</script>
If you don’t, then when you submit the form, the variable the file gets saved to will be null.
The Field and the ViewModel Property
The field itself is a normal HTML field, whose type is set to file. In your viewmodel, create a property of type “HttpPostedFileBase.” This property will house the selected file, and have metadata on it as well (MIME type, size, etc). Adding those things makes our current view and viewmodel look like the following:
@using (Html.BeginFormSitefinity("SubmitForm", "MyFormName"))
{
<div>
@Html.LabelFor(m => m.TextField)
@Html.TextBoxFor(m => m.TextField)
@Html.ValidationMessageFor(m => m.TextField, "Please enter some text", new {@class = "error"})
</div>
<div>
@Html.LabelFor(m => m.NumberField)
@Html.TextBoxFor(m => m.NumberField, new { min = "0", type = "number" })
@Html.ValidationMessageFor(m => m.NumberField, "Please enter a number", new {@class = "error"})
</div>
<div>
@Html.LabelFor(m => m.UploadedFile, "My File")
@Html.TextBoxFor(m => m.UploadedFile, new { type = "file" })
@Html.ValidationMessageFor(m => m.UploadedFile, "Please upload a file", new { @class = "error"})
</div>
<div>
<input type="submit" value="Submit Form" />
</div>
}
<script type="text/javascript">
(function ($) {
"use strict";
$("form").attr("enctype", "multipart/form-data");
})(jQuery);
</script>
public class MyFormViewModel
{
public int NumberField { get; set; }
public string TextField { get; set; }
public HttpPostedFileBase UploadedFile { get; set; }
}
Now, in our SubmitForm action, we’ll receive the file as well as the other form data, to do with what we please.
Wrapping Up
As demonstrated above, MVC and Sitefinity provide you with a lot of power when it comes to creating customized hybrid forms that Sitefinity’s out-of-the-box form creation tools can’t handle on their own. You can create something as simple as a single button “push to activate feature” form, or very complex forms that accept multiple types of data and files. You can supply custom validation and create detailed error messages, all while leveraging out-of-the-box MVC functionality to leave the amount of code you have to actually write at a minimum.
The post Working with Hybrid Forms in Sitefinity Widgets appeared first on Falafel Software Blog.