A web framework would be nothing without easy and flexible form handling. Vici MVC includes very flexible and easy to use form handling, without using the infamous ViewState used in ASP.NET WebForms.
In HTML, a form is a collection of controls defined inside a <form> section. A form can be posted to the server by clicking on a "submit" button.
To retrieve the contents of a posted form, you can can read the PostData collection, which maps directly to the ASP.NET Request.Form collection. While this is an easy way to retrieve form data, it becomes complicated if you want to set the initial values of controls in a form, and maintain the state (value) of controls across postbacks.
In Vici MVC, this is handled by the WebForm class. You simply create a WebForm-derived class and add members representing the data you want to show on (and retrieve from) the HTML form. You tell Vici MVC what type of control should be rendered for the member, and optionally what kind of validation should be done.
As always, an example tells more than a thousand words, so here is a simple one:
public class FormSampleController : PageController { public class SampleForm : WebForm { [ValidateLength(3)] [FormTextBox(MaxLength=30)] public string EmployeeName; [FormTextBox(Min=0)] public decimal Salary; } public void Run(int id) { SampleForm form = new SampleForm(); form.Bind(); if (form.Validated) { // do something with the posted data ViewData["Result"] = "Name entered: " + form.EmployeeName + ", Salary=" + form.Salary; } } }
The associated view template looks something like this:
<html>
<body>
<form method="post" action="{{CurrentUrl}}">
<label>Employee name</label>
[[EmployeeName]]
<label>Salary</label>
[[Salary]]
<div><input type="submit" name="btnSave" value="Save" /></div>
</form>
<!--{{ if Result }}--><div>Result: {{Result}}</div><!--{{ endif }}-->
</body>
</html>
What happens in the controller's action method is:
To set the initial values of the form, you should override the OnFill() method of the WebForm class. This method will be called the first time the form is shown, and when no postback is performed:
public class SampleForm : WebForm { [ValidateLength(3)] [FormTextBox(MaxLength=30)] public string EmployeeName; [FormTextBox(Min=0)] public decimal? Salary; protected override void OnFill() { EmployeeName = "Some default value"; // or a value retrieved from a database Salary = null; // setting it null will make the field blank } }
You should aways let the HTML form post to the original URL. This way the WebForm-derived object will correctly handle the data entered by the user and state will be preserved.
To capture posted data, you don't have to do anything. You only have to check the Validated property of the WebForm class. If it is true, the form was posted and the input was validated according to the validation rules (attributes) that were defined for each field.
Another way of capturing posted fields is by overriding the OnPost() method. This method has one parameter validated, which is set to true if all validations were successful. You would typically use this scenario when you bind a form object to a data object that was read from the database:
public class SampleForm : WebForm { private Employee _employee; // not mapped to a control because there's no attribute [ValidateLength(3)] [FormTextBox(MaxLength=30)] public string EmployeeName; [FormTextBox(Min=0)] public decimal? Salary; public SampleForm(Employee employee) { _employee = employee; } protected override void OnFill() { EmployeeName = _employee.Name; Salary = _employee.Salary; } protected override OnPost(bool validated) { _employee.Name = EmployeeName; _employee.Salary = Salary; } }
Then, in your controller, you simply attach an employee record to your form and save the record when the Validated proprty of the form is true:
public class FormSampleController : PageController { public void Run(int id) { Employee employee = DataLayer.ReadEmployee(id); SampleForm form = new SampleForm(employee); form.Bind(); if (form.Validated) { employee.Save(); Response.Redirect("...some other place..."); } } }
Note that in version 2.0 of Vici MVC there is no automatic data binding. You should set the form's fields to values from a data object in code. This is a feature that will likely be added in the next release of the framework.
As mentioned above, Vici MVC performs validation on form objects. There are 3 ways to validate a form, and they can be combined at will:
Validation is discussed in the section about Form validation.
These are just the built-in controls. You can create your own controls which can be bound to form fields.
For an explanation of the built-in controls, check the section on Vici MVC controls
So far we have only used simple text boxes as controls, but what if you have a dropdown control or another control that needs additional data?
For that purpose, there's another method you can override: OnBind(). This method is called just before the page is rendered. This also means that the fields in the form class already have the correct value (either initialized by OnFill or submitted by the user) and the actual controls have been created also.
Every control has a DataSource property, which should be set to the data required by the control. For a listbox this is the list of items that should be displayed in the listbox.
For example:
public class SampleForm : WebForm { [FormTextBox(MaxLength=30)] public string EmployeeName; [FormDropDown(KeyMember="CountryID", ValueMember="CountryName", ShowBlank=true, BlankKey=0)] public int CountryCode; protected override OnBind() { Fields["CountryCode"].DataSource = DataLayer.GetCountries(); } }
If you want to access the control directly, you can read the Control property of the field:
public class SampleForm : WebForm { [FormTextBox(MaxLength=30)] public string EmployeeName; [FormDropDown(KeyMember="CountryID", ValueMember="CountryName", ShowBlank=true, BlankKey=0)] public int CountryCode; protected override OnBind() { DropdownControl countryControl = (DropdownControl) Fields["CountryCode"]; countryControl.DataSource = DataLayer.GetCountries(); } }
As explained earlier, control placeholders are represented in the followin form
[[ControlName]]
This is the basic notation, but there's more. The full syntax of the control placeholder is:
[[ControlName:CssClass:CssClassError]]
What happens is that the control will be rendered using the specified CSS class "CssClass" when the initial form is rendered, and when the control is valid (validation succeeded).
When validation fails, the control will be rendered with the CSS class "CssClassError". This CSS class will be used instead of the normal CSS class. If you want to add a class to the "normal" CSS class, you should prepend the class name with "+".
Examples:
| Usage | Normal CSS class | CSS class for invalid fields | |
|---|---|---|---|
| [[Control:TextBox]] | TextBox | TextBox | |
| [[Control:TextBox:TextBoxError]] | TextBox | TextBoxError | |
| [[Control::Error]] | Error | ||
| [[Control:TextBox:+Error]] | TextBox | TextBox Error | |