Model-View-Controller Approach (MVC) with Wisej.NET
What is MVC?
MVC is a way of separating an application into three interconnected parts: the data storage (Model), user interface (View), and connection between the view and the model (Controller).
The Model represents the application's data. It provides an interface for storing, accessing and updating the data. The View represents the user interface, displaying data from the model to the user and accepting input. The Controller acts as an intermediary between the View and Model, handling user input and updating the Model and View as needed.
MVC enables a clear separation of concerns which improves code organization, enhances maintainability, and facilitates code reusability. MVC's testability enables focused unit testing of individual components, leading to more robust and bug-free applications. Moreover, the flexibility of having multiple views for the same data supports the development of scalable and adaptable applications across different platforms or devices.
Creating a Wisej.NET application with MVC Architecture
You can find the full code of the sample project here: https://github.com/iceteagroup/wisej-architecture-examples
The Model
Simple Example Model
A simple example of a model would look like this:
In this example, the model represents a StudentModel
object with its properties Id
, Email
, Name
and Age
.
Model with validation logic
The model is responsible for all validation logic that enforces business rules on the data. For example, if you wanted to check that the Email
property had the correct format of local-part@domain, the proper place to put that logic would be in the model. Likewise, if you wanted to check that the Id
property was between 1 and 999, that logic would belong in the model.
Here is the StudentModel
, updated with data annotations to check that the Id
and Email
are formatted correctly. It also contains a method, ValidateData()
, that determines if a given model contains valid data.
Let's break down what this code does.
System.Collections.Generic
is needed for the List<ValidationResult>
that is created later in the code. The System.ComponentModel.DataAnnotations
namespace provides code for validating data.
Notice that there is a new line of code in brackets above the declaration of the Id
property. This is a validation attribute that comes from the System.ComponentModel.DataAnnotations
namespace. In this case, it indicates that the Id should be between 1 and 999, inclusive, and it provides an error message that can be shown if the Id is not valid.
Likewise, the line of code [EmailAddress(ErrorMessage = "Invalid Email Address")]
indicates that the Email
property must be a valid e-mail, and gives the error message to show if the email is not set to a valid e-mail.
Note that these attributes do not prevent you from making an "invalid" instance of StudentModel
. You can write this line of code, and it will compile and run:
So how can we avoid creating an invalid StudentModel
? Simple: we define a function called
ValidateData()
which can determine if a given instance of StudentModel
contains valid data. Let's look at the function in more detail:
string message = "";
Defines a string variable to hold the error message, or lack therof. This variable will be returned at the end of the function.
ValidationContext context = new ValidationContext(model, null, null);
The ValidationContext
class contains information about the object being validated, the validation attributes that are applied to the object's properties, and the current validation state. It provides a way to get the values of the object's properties. In this line of code, we create an instance of the ValidationContext
class, passing in the object being validated (model
) and null values for the service provider and items.
List<ValidationResult> validationResults = new List<ValidationResult>();
The ValidationResult
class represents the result of a validation operation. It contains an error message and a reference to the property that failed validation. If the validation was successful, the ValidationResult
object will not contain any error messages. In this line of code, we create a list called validationResults
that can contain ValidationResult
objects. We will use this list to store a ValidationResult
object for each property that fails validation.
bool valid = Validator.TryValidateObject(model, context, validationResults, true);
We send 4 parameters to the Validator.TryValidateObject()
function: model
(the object to be validated), context
(a ValidationContext
object), validationResults
(a list of ValidationResult
objects- it will be used to store any failed validations) and true
(indicates that all properties will be validated)
The function returns true if model
is valid, false if model
is not valid, and we store this in the variable valid
.
You can read more about this function in the documentation:
If valid
is false, meaning that at least one property of model
was not valid, the code loops through the validationResult
list and appends each error message to message
.
If valid
is true, meaning that all properties of model were valid, message
is set to "The data is valid!".
return message;
returns the contents of the variable message
, so that they can be used outside of this function.
Model interaction with database
The model is also responsible for retrieving data from and storing data in the database.
The GetStudents()
function accesses the database, and returns a list of the items in the database.
The AddStudent()
function adds the StudentModel to the database. Note the use of the keyword this
. Each instane of the StudentModel class can call AddStudent()
to add itself to the database.
In this sample, the "database" is simply a JSON file. If the database was a SQL server database, you would still have the GetStudents()
and AddStudent()
functions. You would connect to the database via a connection string instead of via the filename of the JSON file.
The View
The View is a Wisej page that displays the list of students in a DataGridView and has an "Add Student" button. Generally, the view will be edited by using drag-and-drop to add controls to the designer. An example of a View would look like this, when seen in the designer:
Designer.cs
When the user adds or edits controls in the designer, code is produced in the Designer.cs file (In this case- Page1.Designer.cs) Let's look at the code for the View:
Look between the tags #region Wisej Designer generated code
and #endregion
to find the code that was generated by the designer. You will notice a pattern- there are 3 commented lines, followed by a series of lines of code that set the properties of a Wisej control.
Button code
For example, here is how the button was set up:
private Wisej.Web.Button button1;
The variable button1
is declared
this.button1 = new Wisej.Web.Button();
A new button object is created
The button's properties- Location, Name, Size, TabIndex and Text are set. this.button1.Click += new System.EventHandler(this.button1_Click);
assigns an event handler function (this.button1_Click
) to the "Click" event of the button1
control. Thus, whenever the Click event fires, the function button1_Click()
will be called. button1_Click()
can be found in Page1.cs - it is part of the controller.
The Controller
Page1.cs serves as the controller in this sample.
The "Controller" is merely a bridge between the Model and the View.
If you have to format data to later display it in your View, that should happen in the Controller. For example, if your model stored a string as "Example_Text_Here", and you wanted the View to display it as "Example Text Here", it would be the controller's job to replace the underscores with spaces. In this sample, our data is pretty simple, so we don't format it.
Adding a new student to the database
The button1_Click()
event handler contains code for adding a new student to the database. The user enters data in the textboxes, and then clicks this button. A new student is added to the database, based on the values that were entered in the textboxes in the view. This is an excellent example of how the controller connects the View to the Model+database. Let's break it down:
We first check to make sure that all the textboxes have text. This ensures that a value will be sent in for each field of the model.
We then read the data from each textbox, and store it in a variable. Note that the view contains some very generic data validation in order to make this code run without errors. For example, the user is only able to enter numbers in the "id" textbox- they cannot enter words. Validation that is specific to the model- ie checking that the id is between 1 and 999- is done by the model.
Next, a StudentModel
is created based on the data from the text fields. The AddStudent() method is called, which checks that the data meets the validation requirements in the student model. (In this case, the requirements are that the id is between 1 and 999, the id is unique, and that the email address is formatted correctly. A message is shown- it either indicates that the data was sucessfully added to the database, or it gives an explanation of why the data was invalid.
Finally, the textboxes are cleared.
Synchronizing the View and the database
We see this code snippet twice- it runs when the page (the View) is initially loaded, and after a new student is added to the database. This code ensures that the View and the database are in sync- the same information that the user sees in the view is what is stored in the database.
Switching Views
In the context of a Wisej.NET application, the View is generally a Page or a Form. The controller is responsible for determining which view will be shown. There can be multiple views for each controller. An application can also have multiple controllers- in this case, each controller would most likely have its own view. Note that this sample only has one view and one controller.
The Code-Behind (Not featured in this sample)
The code-behind is the code that connects the View to the Controller. With a Wisej.NET project, the code-behind would be located in Page1.cs, and you would have a separate Controller class (ie Controller.cs). Ideally, the code-behind is as small as possible- it's job is to call Controller class functions at the appropriate times (ie in event handlers, when switching views). This sample does not have a code-behind because Page1.cs is the controller.
Summary
Model -Has all validation logic that enforces business rules on the data -Responsible for retrieving data from and storing data in the database. View -Can be created using drag-and-drop controls in the Wisej designer -The User Interface of the application -In Wisej.NET applications, generally a Page or a Form Controller -Connects the Model and the View -Contains event handlers -Formats data from the model before it is displayed in the View -Responsible for syncing the View and the database
Last updated