Chapter 19. Adding Validation

In this chapter, we tackle adding validation and business rules to our blog application. As always, we begin with a set of stories (see Figure 19.1).

Figure 19.1. User stories

image

We need to implement the following stories:

• If a user submits a blog entry without a title, the validation error message “Title Is Required” displays.

• If a user submits a blog entry with a title longer than 500 characters, the validation error message “Title Is Too Long” displays.

• If a user submits a blog entry without text, the validation error message “Text Is Required” displays.

• Blog entry names should be encoded in an easy-to-understand way automatically. For example, the blog entry name “My Summer Vacation” should be encoded "My-Summer-Vacation".

• If a user submits a blog entry with a title but not a name, the name is retrieved from the title.

Performing Validation in the Simplest Possible Way

First, we want to prevent a user from a submitting a blog entry when the user does not include a title for the blog entry. We can capture this requirement with the test in Listing 19.1.

Listing 19.1. UnleashedBlog.TestsControllersBlogControllerTests.cs (C#)

image


Listing 19.1. UnleashedBlog.TestsControllersBlogControllerTests.vb (VB)

image


The test in Listing 19.1 creates a blog entry without a title and invokes the Blog controller Create() action. If the model state does not include the specific error “Title Is Required,” the test fails.

Notice that the test makes use of a utility method named HasErrorMessage() that iterates through all the error messages in model state to perform a match. The HasErrorMessage() method is also included in Listing 19.1.

If you run this new test, it fails (see Figure 19.2). The test fails because we have not yet implemented any validation logic. Now that we have a failing test, we can modify the application code for our blog application.

Figure 19.2. CreateTitleRequired() test fails.

image

The simplest way to get the test in Listing 19.1 to pass is to add the required validation logic directly to the Create() action of the Blog controller. The modified Create() action in Listing 19.2 includes a new validation section that verifies that the Title property is not empty.

Listing 19.2. ControllersBlogController.cs (C#)

image


Listing 19.2. ControllersBlogController.vb (VB)

image


After you modify the Blog controller, our new test passes (see Figure 19.3). Time to celebrate? Unfortunately, if you look again at Figure 19.3, you notice that several tests that previously passed are now failing. We have introduced a change that broke other code in our application.

Figure 19.3. CreateTitleRequired() passes.

image

In this particular case, there is nothing wrong with our application code. The problem is with our existing test code. None of the tests that we previously wrote passed a value for the Title property. Because we just made the Title property required, all these existing tests now fail.

For example, Listing 19.3 contains the IndexReturnsBlogEntriesByYear() test. Before we made the Title property into a required property, this test passed. Now, this test fails because when the test creates new blog entries, the test does not create a value for the Title property.

Listing 19.3. UnleashedBlog.TestsControllersArchiveControllerTests.cs (C#)

image


Listing 19.3. UnleashedBlog.TestsControllersArchiveControllerTests.vb (VB)

image


An easy fix to this problem would be to simply modify all our tests so that they include the required blog entry Title property. However, this approach to solving the problem is not a good solution. The next time that we introduce a new property or modify an existing property, we would need to modify all our tests again.

The real problem is that our test code is too brittle. We need to consider how we can make our test code more resilient to change. In other words, this is a good time to consider how we can refactor our test code to improve its design.

Refactoring the Test Code

You need to tend to the design of your test code as much as you tend to the design of your application code. In this section, we improve our test code so that it is more resilient to change.

The problem with our test code right now is that we create blog entries everywhere. We need to create a single location in our code where we create our blog entries (a single point of failure). That way, if we need to change the properties of the blog entry class, we don’t need to make that change everywhere.

Listing 19.4 contains a new class named BlogEntryFactory. This class exposes a static method named Get() that returns a valid blog entry with all its properties in a valid state. In addition, the BlogEntryFactory class exposes a GetWithDatePublished() method that enables you to easily return a blog entry with a particular publication date.

Listing 19.4. UnleashedBlog.TestsFactoriesBlogEntryFactory.cs (C#)

image


Listing 19.4. UnleashedBlog.TestsFactoriesBlogEntryFactory.vb (VB)

image


Now that we have a BlogEntryFactory class, we can use this class within our test code to create our blog entries. For example, the test in Listing 19.5 has been refactored to use the BlogEntryFactory class GetWithDatePublished() method.

Listing 19.5. UnleashedBlog.TestsControllersBlogControllerTests.cs (C#)

image


Listing 19.5. UnleashedBlog.TestsControllersBlogControllerTests.vb (VB)

image


After the test code is updated, all the tests run successfully (see Figure 19.4).

Figure 19.4. All green

image

Validating the Length of a Property

According to the next user story in our list, we need to verify that a user cannot submit a blog entry Title that contains more than 500 characters. This story will be easy to implement.

First, we create the test in Listing 19.6. This test creates a blog entry with a title that is too long and passes the blog entry to the Blog controller Create() method. The test verifies that the validation error message “Title Is Too Long” is added to model state.

Listing 19.6. UnleashedBlog.TestsControllersBlogControllerTests.cs (C#)

image


Listing 19.6. UnleashedBlog.TestsControllersBlogControllerTests.vb (VB)

image


If you run the test in Listing 19.6, the test fails (see Figure 19.5). We want the test to fail so that we can allow ourselves to write new application code.

Figure 19.5. Failing CreateTitleMaximumLength500 test

image

The Blog controller Create() action in Listing 19.7 has been updated with the necessary code to pass the test. The modified Create() action adds an error to model state when the Title property is longer than 500 characters. At this point, if you run the tests again, we are back to green.

Listing 19.7. UnleashedBlogControllersBlogController.cs (C#)

image


Listing 19.7. UnleashedBlogControllersBlogController.vb (VB)

image


A Web Browser Sanity Check

Every once in a while, it is a good idea to actually run the ASP.NET MVC application that you build and view it in a web browser. The final goal, after all, is to create a working blog application.

If you run the blog application—by selecting the menu option Debug, Start Debugging or pressing F5—and you click the Create New link, you get the form for creating a new blog entry. If you attempt to submit the form without entering any values in the form fields, you get the validation error messages illustrated in Figure 19.6.

Figure 19.6. Validation errors

image

Notice that there are three validation error messages. We can account for the error associated with the Title property. We modified the Blog controller to add this error to model state when the Title property is empty.

However, there are two “A Value Is Required” errors. Where do these errors come from?

We can easily account for one of the “A Value Is Required.” errors. One of these errors corresponds to the DatePublished property. The DatePublished property is a DateTime property, and a DateTime property cannot accept an empty value. Therefore, the ASP.NET MVC framework adds an error message to model state automatically. (In particular, the default model binder adds this error message to model state.)

But, there is still one validation error message left. This last error is mysterious because it does not correspond to any of the fields in the HTML form.

The final validation error message corresponds to the blog entry Id property. The Id property is an integer property. Like the DatePublished property, you cannot assign an empty value to the Id property.

However, we want the ASP.NET MVC framework to ignore the Id property because this property gets its value from the database. The Id property corresponds to an Identity column in the database.

To make the validation error message go away, we need to modify the Create() action so that it ignores the Id property when creating an instance of a blog entry from the HTML form fields. A modified Create() action is contained in Listing 19.8.

Listing 19.8. UnleashedBlogControllersBlogController.cs (C#)

image


Listing 19.8. UnleashedBlogControllersBlogController.vb (VB)

image


Notice that the blog entry parameter in Listing 19.8 is decorated with a Bind attribute. This attribute excludes the Id property from the set of properties bound to the HTML form fields submitted to the server.

Refactoring to Use a Service Layer

Our validation code works, but it is not pretty. We are violating the Single Responsibility Principle (SRP) by mixing our validation logic into our controller logic. In general, you want each layer of your application to assume a single responsibility.

The software design principles exist for a reason. Mixing together different types of logic makes an application more difficult to maintain and modify over time. We need to clean up our code. A place for everything and everything in its place.

In this section, we refactor our code to migrate our validation logic to a separate service layer. We can fearlessly refactor our application to improve its design because our application is well covered by tests. The tests act as our safety net for change.

Listing 19.9 contains a new blog service class that acts as our service layer. This class contains all the validation logic that was previously located in the blog controller class.

Listing 19.9. UnleashedBlogModelsBlogService.cs (C#)

image

image


Listing 19.9. UnleashedBlogModelsBlogService.vb (VB)

image

image


The CreateBlogEntry() method validates the properties of the blog entry passed to the method. If there are any validation errors, the method returns the value false. Otherwise, the CreateBlogEntry() method calls the CreateBlogEntry() method on the blog repository class to add the new blog entry to the database and returns the value true.

We need to modify the Blog controller and Archive controller to use the blog service instead of the blog repository. The modified Blog controller is contained in Listing 19.10.

Listing 19.10. UnleashedBlogControllersBlogController.cs (C#)

image

image

Listing 19.10. UnleashedBlogControllersBlogController.vb (VB)

image


Notice that an instance of the blog service is created in the blog controller constructors. Previously, the blog controller interacted directly with the blog repository. The new version of the blog controller interacts with the blog service and the blog service interacts with the blog repository.

These changes provide us with a clean separation of concerns. Our controller logic is kept in the controller layer, our validation logic is kept in our service layer, and our database logic is kept in our repository layer.

Because we have the safety net of our tests, we can refactor fearlessly. After these massive changes to the architecture of our application, our original tests continue to pass (see Figure 19.7).

Figure 19.7. Refactored tests pass

image

Adding Business Rules

Before we can end this chapter, there are two final stories that we need to implement. Both of these stories relate to business rules concerning a blog entry name:

• Blog entry names should be encoded in an easy-to-understand way automatically. For example, the blog entry name “My Summer Vacation” should be encoded like "My-Summer-Vacation".

• If a user submits a blog entry with a title but not a name, the name is retrieved from the title.

What’s the difference between the title and name of a blog entry? The name of a blog entry is used when retrieving a blog entry. For example, you can retrieve a blog entry named “My Summer Vacation” by requesting the following URL:

www.MyBlog.com/2010/12/25/My-Summer-Vacation.

The title of a blog entry, on the other hand, is the title that is displayed for the blog entry in a page and in the RSS feed. For example, My Summer Vacation.

A blog entry name can only contain valid characters for a URL. According to the official standards (RFC 1738), a URL can contains only alphanumeric characters and a limited number of special characters. In particular, a name cannot contain spaces.

Let’s start with a test for the first story. We need to create a test that verifies that any special characters in a blog entry name get encoded automatically. The test in Listing 19.11 uses a regular expression to verify that a set of blog entry names get encoded correctly.

Listing 19.11. UnleashedBlog.TestsControllersBlogControllerTests.cs (C#)

image


Listing 19.11. UnleashedBlog.TestsControllersBlogControllerTests.vb (VB)

image


To pass the test in Listing 19.11, we need to modify the blog service so that it encodes the blog entry Name property. The updated blog service in Listing 19.12 correctly encodes the Name property.

Listing 19.12. UnleashedBlogModelsBlogService.cs (C#)

image


Listing 19.12. UnleashedBlogModelsBlogService.vb (VB)

image


One last story. If a user supplies a blog entry title, but not a name, we should get the name from the title automatically. We can express this requirement with the test in Listing 19.13.

Listing 19.13. UnleashedBlog.TestsControllersBlogController.cs (C#)

image


Listing 19.13. UnleashedBlog.TestsControllersBlogController.vb (VB)

image


The test in Listing 19.13 verifies that when a blog entry is created with a title, but not a name, that the name is retrieved from the title. The modified CreateBlogEntry() method in Listing 19.14 causes this test to pass.

Listing 19.14. UnleashedBlogModelsBlogService.cs (C#)

image

Listing 19.14. UnleashedBlogModelsBlogService.vb (VB)

image


Summary

In this chapter, we added validation and business rules to our blog application. We started by adding the validation rules in the simplest way possible; we added the validation rules directly to our controller class.

After we successfully passed our tests, it was time to refactor our application to have a better design. We refactored our validation code into a separate service layer.

Finally, we implemented two business rules related to blog entry names. We verified that blog entry names are valid. We also implemented a business rule that causes the Name property to be retrieved from the Title property when no name is supplied.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.107.254