NerdDinner. Step 3: Build a Model
- Transfer
This is the third step of the free NerdDinner tutorial , which shows how to build a small but complete web application using ASP. NET MVC.
In a model-view-controller type framework, the term “model” refers to objects that represent application data, as well as the corresponding domain logic that integrates validation and business rules. In many cases, a model is the “heart” of an MVC application and, as we will see later, controls its basic behavior.
ASP.NET MVC framework supports the use of any data access technology, therefore, developers can choose different options for implementing their model including: LINQ to Entities, LINQ to SQL, NHibernate, LLBLGen Pro, SubSonic, WilsonORM or direct access through ADO.NET DataReader and DataSet .
For our NerdDinner application, we will use LINQ to SQL to create a simple model that is almost similar to the database structure, and also add some validation logic and business rules. Later we will implement the storage class, which will help abstract the permanent implementation of data storage from the rest of the application and make it easy to carry out unit tests with it.
LINQ to SQL is an ORM (Object Relational Projection) that ships as part of .NET 3.5.
LINQ to SQL provides an easy way to associate database tables with .NET classes. For our NerdDinner application, we will use to link Dinners and RSVP tables and Dinner and RSVP classes. The columns of the Dinners and RSVP tables will correspond to the properties of the Dinner and RSVP classes. Each Dinner and RSVP object will represent a separate line in the Dinner or RSVP tables in the database.
LINQ to SQL avoids the manual construction of SQL queries to receive or update Dinner and RSVP objects with data in the database. Instead, we declare the Dinner RSVP classes how they communicate with the database and define the relationships between them. LINQ to SQL will take care of creating the appropriate SQL logic for use while working with objects.
We can use the LINQ language supported in VB and C # to write queries that return Dinner and RSVP objects from the database. This minimizes the size of the code that we need to write and allows us to build really clean applications.
We start by right-clicking on the “Models” folder in the project and select Add> New Item :
In the “Add New Item” window, filter by the “Data” category and select the “LINQ to SQL Classes” template:
Name the element “NerdDinner” and add it. Visual Studio will add the NerdDinner.dbml file to the \ Models directory and open the LINQ to SQL object link constructor:
LINQ to SQL allows us to quickly create data model classes from an existing database schema. To do this, open the NerdDinner database in Server Explorer and select the tables for which you want to create a model:
Next, drag the tables into the LINQ to SQL constructor. Once we do this, LINQ to SQL will automatically create the Dinner and RSVP classes using the table schema, including the class properties that are associated with the columns of the database table:
By default, the LINQ to SQL constructor automatically processes the plural in table names and column names during class creation. For example: the table “Dinners” becomes the class “Dinner”. This class naming helps the model maintain compatibility with .NET naming rules, and when you add a large number of tables, this is convenient. If you do not like the name of the class or property that the constructor generated, you can always change it to a more suitable one through editing in the constructor or in the properties.
Also, by default, the LINQ to SQL constructor finds the primary and foreign keys of tables and, based on them, automatically creates relationships between different classes of models. For example, when we dragged the Dinners and RSVP tables to the LINQ to SQL constructor, a one-to-many relationship was created, because the RSVP table contains a foreign key to the Dinners table (this is indicated by the arrow in the constructor):
This relationship will allow LINQ to SQL Add a strongly typed “Dinner” property to the RSVP class, which developers can use to access the RSVP-bound Dinner. It will also allow the Dinner class to contain a collection of RSVP properties, giving developers the ability to retrieve or modify RSVP objects attached to Dinner.
The following is an intellisense example in VisualStudio when we create a new RSVP object and add it to the RSVP collection of an object of the Dinner class. Notice how LINQ to SQL automatically added a collection of RSVP objects for the Dinner class object:
By adding an RSVP object to the RSVP collection of a Dinner object, we tell LINQ to SQL to connect the Dinner string and RSVP in the database through a foreign key:
If, again, If you don’t like what the designer called the relationship between the tables, you can change that too. Just click on the communication arrow in the designer and rename / delete / change it. For our application, the default communication rules are great for the data model class.
Visual Studio automatically creates .NET classes that represent models and relationships using the LINQ to SQL constructor. Also, for each LINQ to SQL file created by the constructor, a DataContext class is created. Since we named our LINQ to SQL class “NerdDinner”, the DataContext class will be named “NerdDinnerDataContext”, which interacts directly with the database.
The NerdDinnerDataContext class contains two properties - “Dinners” and “RVSPs”, which represent the two tables that we created in the database. We can use C # to write LINQ queries with these properties, to get Dinner and RSVP objects from the database.
The following code demonstrates the operation of the NerdDinnerDataContext object with a LINQ request to get a queue of upcoming dinners. Visual Studio provides full intellisense support while writing LINQ queries. The objects that returned the query are strongly typed:
In addition, NerdDinnerDataContext automatically monitors any changes in Dinner and RSVP objects. You can use this functionality to easily save changes to the database, without writing SQL UPDATE code.
For example, let's look at how to use a LINQ query to retrieve one object of the Dinner class from a database, change two Dinner properties, and then save the changes back to the database:
The NerdDinnerDataContext object automatically tracks the changes that we made to the properties of the resulting Dinner object. When we call the “SubmitChanges ()” method, it will execute the corresponding SQL “UPDATE” query to save the changes to the database.
For small applications, it is sometimes convenient when the controllers work directly with the LINQ to SQL DataContext class and LINQ queries are in the controllers. But with the growth of the application, this campaign becomes cumbersome and inconvenient for testing. We will also encounter duplication of LINQ code in different places.
But there is an approach that will facilitate the support and testing of the project - this is the Repository design pattern. The repository class helps to encapsulate data requests and logic, abstracts the details of data implementation from the application. In addition, for cleaner code, using the repository pattern allows you to change the implementation of the data warehouse in the future, and also facilitates unit testing of the application, since it does not require a real database.
For our NerdDinner project, we will declare the DinnerRepositary class with the following signature:
Later we will extract the IDinnerRepository interface from this class to allow dependency injection with it in our controllers. But first, we’ll work directly with the DinnerRepository class .
To implement this class, right-click on the “Models” folder and select Add> New Item . In the window that opens, select the “Class” template and name the file “DinnerRepository.cs”:
Next, fill our DinnerRepository class with the following code:
Now that we have created the DinnerRepository class, let's take a look at a few examples that demonstrate the implementation of common tasks:
The following code returns one Dinner using the DinnerID value:
The code below returns all upcoming dinners and goes through each cycle:
The following code demonstrates the addition of two new dinners. Any additions or changes to the repository are not applied until we call the “Save ()” method. LINQ to SQL automatically wraps all changes in a database transaction, therefore, either all changes will be executed, or not a single one:
The code below returns an existing Dinner object and changes its two properties. Properties are saved to the database after calling the repository method “Save ()”:
The following code returns dinner and adds RSVP to it. It does this through the RSVP collection of the Dinner object, which was created by LINQ to SQL (based on foreign keys). These changes will be sent to the database as a new line in the RSVP table when we call the “Save ()” method:
The code below returns an existing Dinner object and then marks it as deleted. When we call the “Save ()” method, it will be deleted from the database:
A key part of any application that works with data is the integration of checks and business logic rules.
When model classes are declared using the LINQ to SQL constructor, the data types of the properties of the model class correspond to the data types of the database table. For example: if the “EventData” column in the Dinners table is of the “datetime” type, then the data model class created by LINQ to SQL will be of the “DateTime” type, which is the native data type in .NET. As a result, we will get errors at the compilation stage if we try to assign an integer or bool to this property, or an error occurs if you try to implicitly convert an incorrect format string during program execution.
LINQ to SQL also automatically processes escape sequences to help protect your application against SQL injection.
Validating a circuit is useful as a first step, but this is not enough. Many real-life situations require the ability to define a more detailed algorithm for validation, which may include several properties, execution of certain code, obtaining information from the model about the state (for example: it was created / modified / deleted or, according to the domain specification, “archived”) . There are many different patterns and frameworks that can be used to define and apply validation rules for a model class, all of this is implemented with third-party components based on .NET. You can use any of them in ASP.NET MVC applications.
The goal of our NerdDinner application is to use a fairly simple and understandable pattern where we extend the IsValid property and the GetRuleViolations () method of our Dinner model object. The IsValid property will return true or false, depending on the success of validation and business rules. The GetRuleViolations () method will return a list of errors that occurred.
We implement IsValid and GetRuleViolations () for the Dinner model by adding a partial class to our project. Partial classes are used to add methods / properties / events for classes that are controlled by a VS constructor (like the Dinner class generated by LINQ to SQL constructors) and help to avoid chaos in our code. We can add a new partial class to our project by right-clicking on the \ Models folder, selecting "Add New Item". Next, select the “Class” template and name it Dinner.cs.
By clicking on the "Add" button, the Dinner.cs file will be added to our project and will open in the IDE. Next, we define the basic rules and validation framework using the following code:
A few notes on the code:
This approach provides a simple framework that integrates validation rules and business rules. Now, let's add the rules to our GetRuleViolations () method:
We use C # “yield return” to return any sequence of RuleViolations. The first six rules verify and enforce that Dinner string properties cannot be null or empty. The last rule is more interesting, it calls the PhoneValidator.IsValidNumber () helper method, which we can add to our project to match the ContactPhone number format to the country of the dinner location.
To check the phone number, we can also use regular expressions. Below is a simple implementation of PhoneValidator, which allows you to check your phone through Regex:
By adding validation and business logic rules to the code, each time creating or modifying a Dinner object, our validation rules will be forcibly involved in the verification.
Developers can write code, as shown below, pre-determining whether the Dinner object is valid and getting a list of all violations without any exceptions:
If we try to save the Dinner with an invalid state, then after calling the Save () method, an exception will be thrown in the DinnerRepository. This will happen because LINQ to SQL will call our partial method Dinner.OnValidate () before the changes are saved, and earlier we added an exception throw to this method if there are violations of the rules in the Dinner object. We can catch this exception and instantly return a list of violations for correction:
Our business rules and validation rules are implemented at the model level, and not at the GUI level, they will be applied and used in any circumstances in our application. Later, we can change or add any business rules and be sure that they will be unconditionally applied in every corner of the application.
Having gained flexibility in managing business rules, eliminating the need to change their logic throughout the application where they are used, we have achieved a correctly written application. All this thanks to the help of the MVC framework.
We have a model that can be used to obtain and modify data in the database.
Let's now add some controllers and views to the project.
In a model-view-controller type framework, the term “model” refers to objects that represent application data, as well as the corresponding domain logic that integrates validation and business rules. In many cases, a model is the “heart” of an MVC application and, as we will see later, controls its basic behavior.
ASP.NET MVC framework supports the use of any data access technology, therefore, developers can choose different options for implementing their model including: LINQ to Entities, LINQ to SQL, NHibernate, LLBLGen Pro, SubSonic, WilsonORM or direct access through ADO.NET DataReader and DataSet .
For our NerdDinner application, we will use LINQ to SQL to create a simple model that is almost similar to the database structure, and also add some validation logic and business rules. Later we will implement the storage class, which will help abstract the permanent implementation of data storage from the rest of the application and make it easy to carry out unit tests with it.
LINQ to SQL
LINQ to SQL is an ORM (Object Relational Projection) that ships as part of .NET 3.5.
LINQ to SQL provides an easy way to associate database tables with .NET classes. For our NerdDinner application, we will use to link Dinners and RSVP tables and Dinner and RSVP classes. The columns of the Dinners and RSVP tables will correspond to the properties of the Dinner and RSVP classes. Each Dinner and RSVP object will represent a separate line in the Dinner or RSVP tables in the database.
LINQ to SQL avoids the manual construction of SQL queries to receive or update Dinner and RSVP objects with data in the database. Instead, we declare the Dinner RSVP classes how they communicate with the database and define the relationships between them. LINQ to SQL will take care of creating the appropriate SQL logic for use while working with objects.
We can use the LINQ language supported in VB and C # to write queries that return Dinner and RSVP objects from the database. This minimizes the size of the code that we need to write and allows us to build really clean applications.
Adding LINQ to SQL classes to a project
We start by right-clicking on the “Models” folder in the project and select Add> New Item :
In the “Add New Item” window, filter by the “Data” category and select the “LINQ to SQL Classes” template:
Name the element “NerdDinner” and add it. Visual Studio will add the NerdDinner.dbml file to the \ Models directory and open the LINQ to SQL object link constructor:
Creating Data Model Classes with LINQ to SQL
LINQ to SQL allows us to quickly create data model classes from an existing database schema. To do this, open the NerdDinner database in Server Explorer and select the tables for which you want to create a model:
Next, drag the tables into the LINQ to SQL constructor. Once we do this, LINQ to SQL will automatically create the Dinner and RSVP classes using the table schema, including the class properties that are associated with the columns of the database table:
By default, the LINQ to SQL constructor automatically processes the plural in table names and column names during class creation. For example: the table “Dinners” becomes the class “Dinner”. This class naming helps the model maintain compatibility with .NET naming rules, and when you add a large number of tables, this is convenient. If you do not like the name of the class or property that the constructor generated, you can always change it to a more suitable one through editing in the constructor or in the properties.
Also, by default, the LINQ to SQL constructor finds the primary and foreign keys of tables and, based on them, automatically creates relationships between different classes of models. For example, when we dragged the Dinners and RSVP tables to the LINQ to SQL constructor, a one-to-many relationship was created, because the RSVP table contains a foreign key to the Dinners table (this is indicated by the arrow in the constructor):
This relationship will allow LINQ to SQL Add a strongly typed “Dinner” property to the RSVP class, which developers can use to access the RSVP-bound Dinner. It will also allow the Dinner class to contain a collection of RSVP properties, giving developers the ability to retrieve or modify RSVP objects attached to Dinner.
The following is an intellisense example in VisualStudio when we create a new RSVP object and add it to the RSVP collection of an object of the Dinner class. Notice how LINQ to SQL automatically added a collection of RSVP objects for the Dinner class object:
By adding an RSVP object to the RSVP collection of a Dinner object, we tell LINQ to SQL to connect the Dinner string and RSVP in the database through a foreign key:
If, again, If you don’t like what the designer called the relationship between the tables, you can change that too. Just click on the communication arrow in the designer and rename / delete / change it. For our application, the default communication rules are great for the data model class.
Class NerdDinnerDataContext
Visual Studio automatically creates .NET classes that represent models and relationships using the LINQ to SQL constructor. Also, for each LINQ to SQL file created by the constructor, a DataContext class is created. Since we named our LINQ to SQL class “NerdDinner”, the DataContext class will be named “NerdDinnerDataContext”, which interacts directly with the database.
The NerdDinnerDataContext class contains two properties - “Dinners” and “RVSPs”, which represent the two tables that we created in the database. We can use C # to write LINQ queries with these properties, to get Dinner and RSVP objects from the database.
The following code demonstrates the operation of the NerdDinnerDataContext object with a LINQ request to get a queue of upcoming dinners. Visual Studio provides full intellisense support while writing LINQ queries. The objects that returned the query are strongly typed:
In addition, NerdDinnerDataContext automatically monitors any changes in Dinner and RSVP objects. You can use this functionality to easily save changes to the database, without writing SQL UPDATE code.
For example, let's look at how to use a LINQ query to retrieve one object of the Dinner class from a database, change two Dinner properties, and then save the changes back to the database:
NerdDinnerDataContext db = new NerdDinnerDataContext();
// Возвращает объект Dinner, который представляет строку с DinnerID равным 1
Dinner dinner = db.Dinners.Single(d => d.DinnerID == 1);
// Изменяем два свойства Dinner объекта
dinner.Title = "Changed Title";
dinner.Description = "This dinner will be fun";
// Сохраняем данные в базу
db.SubmitChanges();
The NerdDinnerDataContext object automatically tracks the changes that we made to the properties of the resulting Dinner object. When we call the “SubmitChanges ()” method, it will execute the corresponding SQL “UPDATE” query to save the changes to the database.
Create the DinnerRepository Class
For small applications, it is sometimes convenient when the controllers work directly with the LINQ to SQL DataContext class and LINQ queries are in the controllers. But with the growth of the application, this campaign becomes cumbersome and inconvenient for testing. We will also encounter duplication of LINQ code in different places.
But there is an approach that will facilitate the support and testing of the project - this is the Repository design pattern. The repository class helps to encapsulate data requests and logic, abstracts the details of data implementation from the application. In addition, for cleaner code, using the repository pattern allows you to change the implementation of the data warehouse in the future, and also facilitates unit testing of the application, since it does not require a real database.
For our NerdDinner project, we will declare the DinnerRepositary class with the following signature:
public class DinnerRepository {
// Методы запросов
public IQueryable FindAllDinners();
public IQueryable FindUpcomingDinners();
public Dinner GetDinner(int id);
// Вставка/удаление
public void Add(Dinner dinner);
public void Delete(Dinner dinner);
// Сохранение
public void Save();
}
Later we will extract the IDinnerRepository interface from this class to allow dependency injection with it in our controllers. But first, we’ll work directly with the DinnerRepository class .
To implement this class, right-click on the “Models” folder and select Add> New Item . In the window that opens, select the “Class” template and name the file “DinnerRepository.cs”:
Next, fill our DinnerRepository class with the following code:
public class DinnerRepository {
private NerdDinnerDataContext db = new NerdDinnerDataContext();
//
// Методы запросов
public IQueryable FindAllDinners() {
return db.Dinners;
}
public IQueryable FindUpcomingDinners() {
return from dinner in db.Dinners
where dinner.EventDate > DateTime.Now
orderby dinner.EventDate
select dinner;
}
public Dinner GetDinner(int id) {
return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
}
//
// Методы Insert/Delete
public void Add(Dinner dinner) {
db.Dinners.InsertOnSubmit(dinner);
}
public void Delete(Dinner dinner) {
db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs);
db.Dinners.DeleteOnSubmit(dinner);
}
//
// Сохранение
public void Save() {
db.SubmitChanges();
}
}
Retrieving, modifying, adding, and deleting data using the DinnerRepository class
Now that we have created the DinnerRepository class, let's take a look at a few examples that demonstrate the implementation of common tasks:
Request Examples
The following code returns one Dinner using the DinnerID value:
DinnerRepository dinnerRepository = new DinnerRepository();
// Возвращение определенного ужина по DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);
The code below returns all upcoming dinners and goes through each cycle:
DinnerRepository dinnerRepository = new DinnerRepository();
// Возвращает все предстоящие ужины
var upcomingDinners = dinnerRepository.FindUpcomingDinners();
// Проходит циклом по каждому предстоящему ужину и выводит на экран его загаловок
foreach (Dinner dinner in upcomingDinners) {
Response.Write("Title" + dinner.Title);
}
* This source code was highlighted with Source Code Highlighter.
Example Add and Remove
The following code demonstrates the addition of two new dinners. Any additions or changes to the repository are not applied until we call the “Save ()” method. LINQ to SQL automatically wraps all changes in a database transaction, therefore, either all changes will be executed, or not a single one:
DinnerRepository dinnerRepository = new DinnerRepository();
// Создание первого ужина
Dinner newDinner1 = new Dinner();
newDinner1.Title = "Dinner with Scott";
newDinner1.HostedBy = "ScotGu";
newDinner1.ContactPhone = "425-703-8072";
// Создание второго ужина
Dinner newDinner2 = new Dinner();
newDinner2.Title = "Dinner with Bill";
newDinner2.HostedBy = "BillG";
newDinner2.ContactPhone = "425-555-5151";
// Добавления ужинов в репозиторий
dinnerRepository.Add(newDinner1);
dinnerRepository.Add(newDinner2);
// Сохранение изменений
dinnerRepository.Save();
The code below returns an existing Dinner object and changes its two properties. Properties are saved to the database after calling the repository method “Save ()”:
DinnerRepository dinnerRepository = new DinnerRepository();
// Получаем определенный ужин по DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);
// Обновляем свойства Dinner
dinner.Title = "Update Title";
dinner.HostedBy = "New Owner";
// Применяем изменения
dinnerRepository.Save();
The following code returns dinner and adds RSVP to it. It does this through the RSVP collection of the Dinner object, which was created by LINQ to SQL (based on foreign keys). These changes will be sent to the database as a new line in the RSVP table when we call the “Save ()” method:
DinnerRepository dinnerRepository = new DinnerRepository();
// Получаем определенный ужин по DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);
// Создаем новый объект RSVP
RSVP myRSVP = new RSVP();
myRSVP.AttendeeName = "ScottGu";
// Добавляем RSVP в коллекцию RSVP объекта Dinner
dinner.RSVPs.Add(myRSVP);
// Применяем изменения
dinnerRepository.Save();
Delete example
The code below returns an existing Dinner object and then marks it as deleted. When we call the “Save ()” method, it will be deleted from the database:
DinnerRepository dinnerRepository = new DinnerRepository();
// Получаем определенный ужин по DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);
// Помечаем объект на удаление
dinnerRepository.Delete(dinner);
// Применяем изменения
dinnerRepository.Save();
Integrating checks and business logic rules into model classes
A key part of any application that works with data is the integration of checks and business logic rules.
Circuit check
When model classes are declared using the LINQ to SQL constructor, the data types of the properties of the model class correspond to the data types of the database table. For example: if the “EventData” column in the Dinners table is of the “datetime” type, then the data model class created by LINQ to SQL will be of the “DateTime” type, which is the native data type in .NET. As a result, we will get errors at the compilation stage if we try to assign an integer or bool to this property, or an error occurs if you try to implicitly convert an incorrect format string during program execution.
LINQ to SQL also automatically processes escape sequences to help protect your application against SQL injection.
Validation and business logic rules
Validating a circuit is useful as a first step, but this is not enough. Many real-life situations require the ability to define a more detailed algorithm for validation, which may include several properties, execution of certain code, obtaining information from the model about the state (for example: it was created / modified / deleted or, according to the domain specification, “archived”) . There are many different patterns and frameworks that can be used to define and apply validation rules for a model class, all of this is implemented with third-party components based on .NET. You can use any of them in ASP.NET MVC applications.
The goal of our NerdDinner application is to use a fairly simple and understandable pattern where we extend the IsValid property and the GetRuleViolations () method of our Dinner model object. The IsValid property will return true or false, depending on the success of validation and business rules. The GetRuleViolations () method will return a list of errors that occurred.
We implement IsValid and GetRuleViolations () for the Dinner model by adding a partial class to our project. Partial classes are used to add methods / properties / events for classes that are controlled by a VS constructor (like the Dinner class generated by LINQ to SQL constructors) and help to avoid chaos in our code. We can add a new partial class to our project by right-clicking on the \ Models folder, selecting "Add New Item". Next, select the “Class” template and name it Dinner.cs.
By clicking on the "Add" button, the Dinner.cs file will be added to our project and will open in the IDE. Next, we define the basic rules and validation framework using the following code:
public partial class Dinner {
public bool IsValid {
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable GetRuleViolations() {
yield break;
}
partial void OnValidate(ChangeAction action) {
if (!IsValid)
throw new ApplicationException("Rule violations prevent saving");
}
}
public class RuleViolation {
public string ErrorMessage { get; private set; }
public string PropertyName { get; private set; }
public RuleViolation(string errorMessage, string propertyName) {
ErrorMessage = errorMessage;
PropertyName = propertyName;
}
}
A few notes on the code:
- Класс Dinner начинается с ключевого слова “partial” – это означает, что размещенный в нем код, будет объединён с классом, сгенерированным LINQ to SQL конструктором и скомпилирован в единый класс.
- Класс RuleViolation является вспомогательным классом, который позволяет нам предоставлять больше деталей о нарушениях правил.
- Метод Dinner.GetRuleViolations() позволяет оценить, были ли соблюдены все правила и бизнес-логика (мы реализуем его буквально через пару минут). Он возвращает последовательность RuleViolation объектов, которые снабжают нас более детальной информацией о любой нарушении правил.
- Dinner.IsValid – удобное вспомогательное свойство, которое указывает, есть ли активные RuleViolation или нет. Разработчик может проверить его, через объект Dinner в любое время (оно не вызовет никаких исключений).
This approach provides a simple framework that integrates validation rules and business rules. Now, let's add the rules to our GetRuleViolations () method:
public IEnumerable GetRuleViolations() {
if (String.IsNullOrEmpty(Title))
yield return new RuleViolation("Title required","Title");
if (String.IsNullOrEmpty(Description))
yield return new RuleViolation("Description required","Description");
if (String.IsNullOrEmpty(HostedBy))
yield return new RuleViolation("HostedBy required", "HostedBy");
if (String.IsNullOrEmpty(Address))
yield return new RuleViolation("Address required", "Address");
if (String.IsNullOrEmpty(Country))
yield return new RuleViolation("Country required", "Country");
if (String.IsNullOrEmpty(ContactPhone))
yield return new RuleViolation("Phone# required", "ContactPhone");
if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
yield return new RuleViolation("Phone# does not match country", "ContactPhone");
yield break;
}
We use C # “yield return” to return any sequence of RuleViolations. The first six rules verify and enforce that Dinner string properties cannot be null or empty. The last rule is more interesting, it calls the PhoneValidator.IsValidNumber () helper method, which we can add to our project to match the ContactPhone number format to the country of the dinner location.
To check the phone number, we can also use regular expressions. Below is a simple implementation of PhoneValidator, which allows you to check your phone through Regex:
public class PhoneValidator {
static IDictionary countryRegex = new Dictionary() {
{ "USA", new Regex("^[2-9]\\d{2}-\\d{3}-\\d{4}$")},
{ "UK", new Regex("(^1300\\d{6}$)|(^1800|1900|1902\\d{6}$)|(^0[2|3|7|8]{1}[0-9]{8}$)|(^13\\d{4}$)|(^04\\d{2,3}\\d{6}$)")},
{ "Netherlands", new Regex("(^\\+[0-9]{2}|^\\+[0-9]{2}\\(0\\)|^\\(\\+[0-9]{2}\\)\\(0\\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\\-\\s]{10}$)")},
};
public static bool IsValidNumber(string phoneNumber, string country) {
if (country != null && countryRegex.ContainsKey(country))
return countryRegex[country].IsMatch(phoneNumber);
else
return false;
}
public static IEnumerable Countries {
get {
return countryRegex.Keys;
}
}
}
Handling Validation Violations and Business Logic
By adding validation and business logic rules to the code, each time creating or modifying a Dinner object, our validation rules will be forcibly involved in the verification.
Developers can write code, as shown below, pre-determining whether the Dinner object is valid and getting a list of all violations without any exceptions:
Dinner dinner = dinnerRepository.GetDinner(5);
dinner.Country = "USA";
dinner.ContactPhone = "425-555-BOGUS";
if (!dinner.IsValid) {
var errors = dinner.GetRuleViolations();
// действия по устранению ошибки
}
If we try to save the Dinner with an invalid state, then after calling the Save () method, an exception will be thrown in the DinnerRepository. This will happen because LINQ to SQL will call our partial method Dinner.OnValidate () before the changes are saved, and earlier we added an exception throw to this method if there are violations of the rules in the Dinner object. We can catch this exception and instantly return a list of violations for correction:
Dinner dinner = dinnerRepository.GetDinner(5);
try {
dinner.Country = "USA";
dinner.ContactPhone = "425-555-BOGUS";
dinnerRepository.Save();
}
catch {
var errors = dinner.GetRuleViolations();
// действия по устранению ошибок
}
Our business rules and validation rules are implemented at the model level, and not at the GUI level, they will be applied and used in any circumstances in our application. Later, we can change or add any business rules and be sure that they will be unconditionally applied in every corner of the application.
Having gained flexibility in managing business rules, eliminating the need to change their logic throughout the application where they are used, we have achieved a correctly written application. All this thanks to the help of the MVC framework.
The next step
We have a model that can be used to obtain and modify data in the database.
Let's now add some controllers and views to the project.