Showing posts with label Business Objects. Show all posts
Showing posts with label Business Objects. Show all posts

Tuesday, January 19, 2010

Agile ADO.Net Persistence Layer: Part 3 Service Class Single<DTO> Data Access Method

When I say data access methods, I’m talking about the methods my UI is going to call whenever it needs to get data.  When my Posts controller needs a list of BlogPosts to display, it’s going to call a BAL method like GetAllBlogPosts() or GetAllBlogPostsForCategory().  Last time I mentioned (over and over) that I like to keep things simple.  When I need to get or save data, I don’t want to have to search through 3 different classes just to find the one with the method I need.  Instead, I’m putting all my persistence logic for a given aggregate in just one place, a service class.  This is not a web service.  I’m using service in the Domain Driven Design sense here. That means that I have a BlogService class that is my one stop shop for all persistence that has to to with Blogs, BlogPosts, SubmittedBlogUrls, and anything else that falls within the Blog aggregate. Here is what my BlogService class looks like.  You can see that it’s mostly “Get” data access methods.

image

What’s an Aggregate?

I keep using the word aggregate.  If you’re not familiar with the term, it just means a group of entities that all share the same persistence class (whether that be a repository, a service, or something else).  This is a key concept in Domain Driven Design. If you want to know more I would recommend picking up Eric Evans’ book or Jimmy Nilsson’s book on DDD.  For now, all you need to know is that a BlogPost can never exist without a Blog, so there’s no point in BlogPost having it’s own persistence class.  In fact we find that if we do break BlogPost out into it’s own persistence class, it will lead to problems down the road due to BlogPost’s dependency on Blog.  What’s the solution?  We put data access methods for both Blog and BlogPost in the same persistence class and call it an aggregate.  That is why BlogService has methods for both Blog and BlogPost entities.

What type of data will data access methods return?

We covered this last post, but to recap all data will be returned as a Data Transfer Object (DTO).  The DTOs are all defined in our Common assembly in the DataShapes folder.  Our BAL will return data in one of the following 4 formats.

  • a single DTO
  • a List<DTO>
  • a DataPage<DTO>
  • a string value

For more see last week’s post Agile ADO.Net Persistence Layer: Part 2 Use DTOs.

A simple Single<DTO> data access method

Let’s look at the simplest possible data access method.  GetBlogPost() takes a postGuid for a parameter, defines the query to find the BlogPost entity for that postGuid, and then returns the result as a single BlogPost DTO.  Here’s the complete method.

public BlogPost GetBlogPost(Guid postGuid)

{

    string query = @"SELECT p.*, s.Score

                    FROM [dbo].[BlogPost] p

                    LEFT JOIN [dbo].[BlogPostReputationScore] s on s.PostGuid = p.PostGuid

                    WHERE PostGuid = @PostGuid";

    SqlDao dao = new SqlDao();

    SqlCommand command = dao.GetSqlCommand(query);

    command.Parameters.Add(dao.CreateParameter("@PostGuid", postGuid));

    return dao.GetSingle<BlogPost>(command);

}

The first thing you’ll notice is that this isn’t a lot of code.  All we’re really doing here is defining a parameterized TSQL query, wrapping that query up in a SqlCommand, and then passing the command and our desired return type off to a Data Access Object (DAO) that automagically executes the command and maps the results to our desired type.  It may seem counter intuitive to write code like this when we haven’t even written the DAO yet, but that’s exactly how I did it when I wrote this code for the very first time.  I decided that my data access methods should be very simple.  I would start with the query and the DTO type that I wanted it to return, then I would pass them both to some type of helper class that would handle the details of running the query and figuring out how to map the query results to the properties of my DTO.  By using this top down approach, I gave myself a very clear picture of how I needed my DAO to behave.

What’s a DAO (Data Access Object)?

By looking at the query logic above, you can see that I have this thing called a DAO or Data Access Object.  This is a class that encapsulates the helper logic for working with my database.  The DAO handles things like creating parameters, getting a connection, and most importantly it implements methods to return my four main data formats, GetSingle<DTO>, GetList<DTO>, GetDataPage<DTO>, and GetStringValue(). The DAO and it’s associated DataMappers are where you’ll find the special sauce that makes this architecture work.  We’ll get into their implementation later on.

A BAL that embraces change

It’s easy to look at the simple code above and miss something that I think is very important.  In fact that thing is the whole reason that I wrote this framework.  That simple data access method above is the blueprint for a flexible persistence layer that makes changing your entities and associated persistence code easy and almost painless.  It sets up a simple 3 step process for all data access in your application.

  1. Define a DTO in the exact data shape that you’re looking for.  That means create a DTO property for each data field that you need out of the database.
  2. Define a query that gets the data.  It can be as simple or as complex as you like.  You can develop it in Sql Server Management Studio.  You can easily optimize it.  Use whatever process or tools work for you. When you’re done just paste the query into your data access method.
  3. Pass both the query and your DTO to the DAO and it will automatically handle field mappings and pass the results back in the data shape you requested.

This is a very powerful way to work.  I can’t count the number of times that I’ve worked with and architecture where I dreaded any changes because I knew that any data fields added would require me to modify a sproc, a DAL method, a BAL method, parsing logic, an entity class, it all adds up to a lot of friction that resists any change.  This BAL design embraces change.  It’s written with the attitude that we know change is going to happen so we’re going to give you as few things as possible to modify, and make sure we don’t have any cross cutting dependencies, so that you can make changes easily.

Next time, more on the service classes.

Next Post: Agile ADO.Net Persistence Layer Part 4: Writing data access for new data shapes

Saturday, January 9, 2010

Agile Ado.Net Persistence Layer Part 1: Design Overview

Last year I did a blog post series on how to design a High Performance DAL using Ado.Net.  Judging by the response I’ve gotten from that series, there must be a lot of developers out there who believe that even with the availability of LINQ, Entity Framework, and a host of other ORM technologies, Ado.Net is still your best option when designing a persistence layer.  BTW, I’m one of them.

After that series I started digging into Entity Framework and LINQ, and I was impressed by how effortless those technologies made certain parts of application development.  Once the EF or LINQ mappings were in place, I found myself writing much less code and focusing more on the business logic of my application.  I also found myself driven right back to ADO.Net as I found myself struggling with how do something that I already knew how to do in T-SQL, or fighting errors resulting from an attached data context object that I really didn’t want to start with.

So, I found myself back with ADO.Net, but I didn’t want to give up the ease of development and coding efficiencies that I got from the ORMs.  I decided to take a fresh look at how to design an ADO.Net persistence layer.  I started at the top (the application layer) and thought about how I want my app code to consume business logic, and then I worked down from there. I incorporated many of the best practices that I’ve used over the years, but I also looked critically at each one and whenever I found that something was slowing me down or leading to duplicate code, I threw it out.  The resulting architecture is quick to develop on,testable, easily maintainable, and can be easily optimized for performance.  This series of posts will detail the entire design, from application code to database.

A peek at the final design

I always find it’s easier to follow along if I have some idea where I’m going, so this is a quick look at where we’re headed.  We’re going to cover the entire architecture for a simple blog aggregator called RelevantAssertions.com.  RelevantAssertions is an Asp.Net MVC application that uses our new Agile Ado.Net Persistence Layer.  We have 4 projects in the RA solution, WebUI, Tests, Common, and BAL Here’s a quick look.

 image

WebUI

WebUI is our Asp.Net MVC application, that’s our application layer.  This contains all UI and presentation logic, but it contains absolutely no business logic.

Common

Common contains classes that we need at all layers of our code. The DataShapes folder is where we define all of our DTO classes.

Tests

This project contains all of our automated tests for both the BAL and the UI.

BAL

I know it’s probably more correct to say BLL, but I like the term BAL.  It just sounds better. This is the big class where everything interesting happens.  The main workhorses of the BAL are the service classes.  These are not web services.  They are service classes in the DDD sense.  The service classes are going to be the one stop shop where our application code goes to do whatever it needs to do.  The service classes will also contain query logic, that’s right I said query logic.  Behind the scenes the service classes will use DAOs (Data Access Objects), Data Mappers, and Persisters to do their thing in an efficient object oriented way, but the only classes our application code will use directly are the services.  

What, no DAL??

You’ll notice that there is no DAL.  It seems a little strange to have an architecture that focuses on ADO.Net but doesn’t have a DAL, but there’s a reason.  Usually, the DAL is where I’ll put my query logic, mappings to query results, and any other database specific code.  The DAL would allow me to keep all of my TSQL and ADO.Net code separated from the rest of my application, and this separation provided me with some important benefits like:

1) Separation is it’s own virtue, that’s just the right way to do it.
2)  I wouldn’t have leakage of db or query logic into my business logic.
3)  I could easily swap out SQL Server with another database if needed.
4)  It encapsulates code that would otherwise be repeated.
5)  We need to hide TSQL from programmers, it scares them.
6)  It’s fun to make changes to 3 layers of code every time I add a new data member to an entity class.

At least that’s what I was always taught.  But after working with more ORM oriented architectures and the Domain Driven Design way of doing things, I started to look at things differently. Let’s look at some of these benefits (at least the ones that aren’t sarcastic).

I’ve never met anyone who’s ever switched out their database

YAGNI means You Ain’t Gonna Need It. The idea is that we spend a lot of time building stuff that we don’t really need. We build it because it seems like the architecturally correct way to do it, or we think we’ll need the feature one day, or maybe we’re just used to doing it that way.  Whatever the reason, the result is that we spend a lot of time coding features that are never used, and that’s not good.  After doing this for 14 years or so, I’ve never, ever, run into a single project where they’ve decided “hey, let’s trash the years of investment we’ve made in SQL Server and switch over to MySQL, or any other database.  Now I am aware that a db switch is likely if you’re writing a product that clients install onsite and it has to work with whatever their environment is, but for 99% of .Net developers this is just never going to happen.  I call YAGNI on this one.

Query logic IS business logic

One of the biggest gripes I had when I started investigating LINQ, EF, and Hibernate (yes I was looking at Java code) architectures is that they had query logic in their repository classes.  Now the query logic was written in LINQ, or EntitySQL, or some other abstracted query language, but it was still query logic.  Blasphemy!!  You can’t put query logic in a BAL class!  That stuff has to be abstracted away in the DAL or it will contaminate the rest of the application architecture! Our layered architecture is being violated!  Worlds are colliding! It’ll be chaos!! Then I started to notice something, it’s really easy to develop business logic when you include queries in the BAL.  In the past I would put my queries in a sproc, then I would write a DAL wrapper for the sproc, and a BAL wrapper for the DAL method.  Then, if the query changed, or if I needed an identical query but with a slightly different parameter list I would write a new sproc, then write a new DAL wrapper, then write yet another BAL wrapper method for the DAL wrapper method.  By the time all was said and done I would have this crazy duplication of methods across all layers of my application including my database!  And don’t even get me started on the crazy designs that I implemented to try and pass query criteria structures (basically the stuff that goes in the where clause) between my BAL an my DAL.  I came up with these crazy layers of abstraction that basically existed so that I wouldn’t have to create a simple TSQL WHERE clause in my BAL.  Then there’s the problem of handling sorting and data paging, that required even more DAL methods, and each of these DAL methods had corresponding wrapper methods in the BAL that did nothing but pass the call through to the DAL!  Why?? I was doing the right thing by separating my business logic from my query logic, why was it so painful?   The answer I finally arrived at is simply that query logic is business logic.  I’d been putting a separation where no separation belonged.  

All real programmers know TSQL

I’ve heard the argument that TSQL is too hard for programmers so we’re going to create something much easier for programmers to use like LINQ or EF.  The problem is that these tools require almost exactly the same syntax as TSQL but they put an extra layer of stuff in there that can break and a data context (or session for you nHibernate folks) that throws errors whenever you try to save a complex object graph.  How did this attitude that TSQL is a problem for programmers gain any traction?  Have you ever met a real programmer who can’t write TSQL?  And if you did meet such a person, would you let them touch your business layer code?  Why would we ever want to abstract TSQL away?  It’s the perfect DSL for accessing SQL Server data and every programmer in the world is already familiar with it.

Using good object oriented design and encapsulating data access code is a good thing

I fully believe this one, but once we decide that query logic is business logic and that we don’t need to hide TSQL from programmers, there’s no reason to put our well designed object oriented data access code in a separate project and call it a DAL.  I decided to just put it in a Persistence folder in my BAL and now I have one less DLL to worry about.

So, that’s some of what I was thinking when I made the decisions that I did.  It made sense to me.  I’m sure it won’t make sense to everyone, but I do think that it resulted in a very usable architecture.  Before I wrap up for today, I want to look at one more thing.

The target application code experience. What will it be like to use?

When I’m writing code in my application layer, consuming the logic that is provided through my BAL service classes, what does that code look like. Well I know a couple of examples of code I don’t want it to look like. 

I’ve been in a few environments where there were huge libraries of BAL classes, any of which could contain the logic I want.  I would often have to resort to a solution wide text search looking for sproc names or keywords that might exist in the method that I needed. I don’t want that.  I want everything I need to be in one easy to find place.

I’ve also seen a practice that’s common in the DDD (Domain Driven Design) crowd where you’ll need to go to a factory class to create a new entity, you need to go to a repository class to get an entity from the database,  if you have complex logic that involves more than one entity you need to go to a service class, and saving entities is a toss up between using either the repository or a separate service class. There may be a good reason to use that kind of class design inside of the BAL, but when I’m writing code in my application layer,  I don’t want to have to worry about which of 4 different classes I’m going to use.  So again, I’m a simple guy, when I need to get, save, or validate a BlogPost entity, I want a single service class that I can go to for everything. My app code should look something like this.

// instantiate service classes

BlogService blogService = new BlogService();

CategoryService categoryService = new CategoryService();

// Get data shaped as lists and pages of our DTOs

DataPage<BlogPost> page = blogService.GetPageOfBlogPosts(pageSize.Value, pageIndex.Value, sortBy);

List<Category> categoryList = categoryService.GetTopCategoryList(30);

// create and save a new BlogPost

BlogPost newPost = new BlogPost();

newPost.BlogGuid = blog.BlogGuid;

newPost.PostTitle = item.Title.Text;

newPost.PostUrl = item.Links[0].Uri.AbsoluteUri;

newPost.PostSummary = item.Summary.Text;

newPost.Score = 0;

blogService.Save(newPost);

Next time we’ll focus less on discussion and more on code.  We’ll look at DTO classes and the 4 main data shapes that will go into and come out of our BAL: DTO, List<DTO>, DataPage<DTO>, and String. 

Next Post:  Agile ADO.Net Persistence Layer Part 2: Use DTOs

Saturday, February 7, 2009

Custom Validation Step 4: Automated Validation Controls

In my previous posts we covered the creation of a ValidationError class that we can use as a validation error container (Custom Validation Step 1: The Validation Error Class), we showed how to implement a  List<ValidationError> to contain errors on a business object (Custom Validation Step 2: Business Object Validation), and then we showed an ASP.Net page implementation that demonstrated how page level validation and business object validation can be easily integrated when they both use the List<ValidationError> mechanism (Custom Validation Step 3: Simple Page Validation).  Now we’re going to take a look at how create a ValidationSummary control and a ValidationLabel control that will give us some automated error handling behavior.  This really is a continuation of the previous 3 posts.  If you haven’t read them, you may want to

Start at the End

We’ll start by looking at where we want to end up.  First, we want our pages to have a PageErrors member that contains a List<ValidationError> member that will contain all page level and business object level validation errors.  Then we want to be able to place ValidationSummary and ValidationLabel controls on our pages that will automatically generate an error summary and an indicator for which data is invalid whenever PageErrors contains errors.  The end result will look like this.

image

I also think it’s very important at this point to think about what we want our code to look like when using these controls.  The error handling code should be simple. We call page level validation, we call object level validation, we then add any errors to the PageErrors.  It should look something like this:

// Run page and entity level validation

this.PageErrors.AddList(ValidatePage());

this.PageErrors.AddList(person.Validate());

 

// If any errors were found bail out and let automated validation

// controls show the errors.

if (this.PageErrors.Errors.Count != 0) { return; }

Using the validation controls should be even simpler.  We want to be able to place tags anywhere in the markup, set some properties, write no code, and have them just work. The only thing we should have to do is give the ValidationLabel controls the name of the field that they are supposed to be validating. This name will use the pseudo fully qualified naming convention that we’ve been using throughout these posts.  This FieldName is how a label will be tied to validation errors for a specific field. Markup should look like:

<go:ValidationSummary ID="valSummary" runat="server" ValidationMode="Auto" BoxWidth="600px" />
&nbsp;
<table>
  <tr>
    <td class="formLbl">
      <go:ValidationLabel ID="vlblName" runat="server"  FieldName="Person.Name">Name:</go:ValidationLabel>
    </td>
    <td class="formTd">
      <asp:TextBox ID="txtName" runat="server" CssClass="formTextBox" />
    </td>
  </tr>
  <tr>
    <td class="formLbl">
      <go:ValidationLabel ID="vlblEmail" runat="server" FieldName="Person.Email">Email:</go:ValidationLabel>
    </td>
    <td class="formTd">
      <asp:TextBox ID="txtEmail" runat="server" CssClass="formTextBox" />
    </td>
  </tr>
  <tr>

 

The WebValidationControls Project

We’re going to put all of the automated web validation controls in a separate project that can be easily included by and referenced by any web application.

image

The project contains our ValidationLabel and ValidationSummary classes, a ValidationBox class that will serve as a container for all of our validation wire up code on a page, an IValidationContainer interface that a page must implement to indicate that it has the members required to behave as a valid validation container, and we have the PageErrorList class which we created in the previous post. The most important class is the ValidationBox.  It is the glue that holds everything else together. It contains our PageErrors (of type PageErrorList), it contains lists of all ValidationLabel and ValidationSummary controls on the page, and it encapsulates our logic for doing things like binding error lists to ValidaitonSummary controls and setting ErrorFlag and color on ValidationLabel controls.

image

The IValidationContainer Interface

The importance of interfaces to modern object oriented design just can’t be overemphasized.  They allow an object to tell us about all the different behaviors that it can support.  In our case, before we start doing things like registering our validation controls with the page, we need to make sure that the page contains a ValidationBox.  We’re encapsulating all of the things we need the page to do within this ValidationBox class.  That makes it really easy for us to tell if a page is a valid validation contiainer, it just needs to contain a ValdiationBox.  That’s the purpose of the IValidaitonContainer interface.  It requires that any page that implements it have a ValidationBox property that contains an instance of our ValidationBox class.

public interface IValidationContainer
{
    // ValidationBox
    ValidationBox ValidationBox { get; }
}

The ValidationLabel Class

ValidationLabel acts as the label for a field, and if there is a validation error for the value entered in that field, it changes color to flag the error.  To do this we just extend the existing Label class and add some functionality.  We want our control to have

  • A FieldName property that allows us to map a label to a specific fully qualified FieldName (remember FieldName is used by our ValidationError class to identify which data member produced an error),
  • An ErrorColor which is the color used for render if there is an error
  • A NormalColor which is the default render color when there is no error
  • An IsError flag which tells the control to render using the ErrorColor instead of the NormalColor
  • And we want the control to register itself with the page. Register just means it adds itself to a generic List<ValidationLabel> member kept in the page’s ValidationBox.

The implementation is listed below.  Notice that the control has properties that let it define what the ErrorColor and NormalColor are, but nowhere does it actually use these colors.  It just checks to see if its containing page is an IValidationContainer.  If it is, the control registers with the page and let’s the page’s ValidationBox decide which color to use.  Also, you’ll see that we set default values in the onInit method, but we check for existing values just in case they were already set in the markup.

public class ValidationLabel : Label
    {
        // FieldName
        public string FieldName { get; set; }
        // IsError
        private bool _isError;
        public bool IsError { get { return _isError; } }
        // Mode
        public Mode ValidationMode { get; set; }
        // ErrorColor
        public System.Drawing.Color ErrorColor { get; set; }
        // NormalColor
        public System.Drawing.Color NormalColor { get; set; }
 
        // Local Enums
        public enum Mode { Null, Auto, Manual }
 
        // OnInit
        // We want to set the initial error state of the 
        // label and register it with the page.
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            IValidationContainer page = this.Page as IValidationContainer;
            if (page != null) { page.ValidationBox.RegisterValidationLabel(this); }
            // set defaults colors
            this.ErrorColor = this.ErrorColor.IsEmpty ? System.Drawing.Color.Red : this.ErrorColor;
            this.NormalColor = this.NormalColor.IsEmpty ? System.Drawing.Color.Black : this.NormalColor;
            if (this.ValidationMode != Mode.Manual) { this.ValidationMode = Mode.Auto; }
            // always start assuming no error
            this.ClearError();
        }
 
        // SetError
        public void SetError()
        {
            _isError = true;
            this.ForeColor = this.ErrorColor;
        }
 
        // ClearError
        public void ClearError()
        {
            _isError = false;
            this.ForeColor = this.NormalColor;
        }
    }

The ValidationSummary Class

The ValidationSummary class gives us the red box that displays on our page and shows a summary of the error messages.  It contains a number of properties that allow us to control the look and feel of the box, things like BoxWidth, BoxTitle, BoxMessage, ErrorColor, and ErrorBullet.  It also contains an ErrorList member that is a List<ValidationError>.  The way the summary works is we add any errors to the ErrorList, then if there are any items in the ErrorList at render time, the summary uses the look and feel properties to render an error summary box. That’s an important point. The ValidationSummary implements it’s own custom render logic.  All we need to do is bind a list of ValidationErrors to it and it will handle the rest.  If ErrorList.Count > 0 then the control will render an error box.  If ErrorList.Count<1 then the control won’t even render.

public class ValidationSummary : WebControl

    {

        // Errors

        public PageErrorList ErrorList { get; set; }

        // BoxTitle

        public string BoxTitle { get; set; }

        // BoxMessage

        public string BoxMessage { get; set; }

        // Width

        public string BoxWidth { get; set; }

        // Mode

        public Mode ValidationMode { get; set; }

        // ErrorColor

        public System.Drawing.Color ErrorColor { get; set; }

        // ErrorBullet

        public string ErrorBullet { get; set; }

 

        // Local Enums

        public enum Mode{Null, Auto, Manual}

 

        // OnInit

        // We want to set the initial error state of the

        // label and register it with the page.

        protected override void OnInit(EventArgs e)

        {

            base.OnInit(e);

            IValidationContainer page = this.Page as IValidationContainer;

            if (page != null) { page.ValidationBox.RegisterValidationSummary(this); }

            // set defaults

            this.ErrorColor = this.ErrorColor.IsEmpty ? System.Drawing.Color.Red : this.ErrorColor;

            this.ErrorBullet = String.IsNullOrEmpty(this.ErrorBullet) ? "- " : this.ErrorBullet;

            this.BoxTitle = String.IsNullOrEmpty(this.BoxTitle) ? "Sorry, but an error was made" : this.BoxTitle;

            this.BoxMessage = String.IsNullOrEmpty(this.BoxMessage) ? "Please check the following:" : this.BoxMessage;

            if (this.ValidationMode != Mode.Manual) { this.ValidationMode = Mode.Auto; }

            if (this.BoxWidth == null) { this.BoxWidth = "auto"; }

            // always start assuming no error

            //this.Visible = false;

        }

 

        // Render

        protected override void Render(HtmlTextWriter writer)

        {

            try

            {

                // We're only going to render if there were errors.

                if (this.ErrorList.Errors.Count > 0)

                {

                    string color = System.Drawing.ColorTranslator.ToHtml(this.ErrorColor);

                    StringBuilder sb = new StringBuilder(512);

                    // Build out html for a box with a Title and a 2px border that

                    // displays the message for each ValidationError in the ErrorList.

                    sb.Append("<div style=\"width:" + this.BoxWidth + ";\" >");

                    // Show the title only if BoxTitle has a value.

                    if (!String.IsNullOrEmpty(this.BoxTitle))

                    { sb.Append("<div style=\"width:auto; background-color:" + color + "; padding-left:7px; padding-bottom:2px; padding-top:2px; color: White; font-family:Verdana; font-weight:bold; font-size:small;\">" + this.BoxTitle + "</div>"); }

                    // We always show the rest of the box.

                    sb.Append("<div style=\"width:auto; border:2px solid " + color + "; padding: 5px; color:" + color + "; font-family:Verdana; font-size:small;\">");

                    sb.Append("<strong>" + this.BoxMessage + "</strong><br />");

                    // Get a handle on the ValidationBox

                    IValidationContainer valPage = this.Page as IValidationContainer;

                    if (valPage == null){return;}

                    ValidationBox valBox = valPage.ValidationBox;

                    // Set the error sort order to match the order of the

                    // validation labels on the page.

                    foreach (ValidationError error in this.ErrorList.Errors)

                    {

                        if (valBox.ValidationLabels.Exists(n => n.FieldName == error.FieldName))

                        {

                            error.SortOrder = valBox.ValidationLabels.FindIndex(n => n.FieldName == error.FieldName);

                        }

                        else

                        {

                            error.SortOrder = int.MaxValue;

                        }

                    }

                    this.ErrorList.Errors.Sort((ValidationError n1, ValidationError n2) =>  n1.SortOrder.CompareTo(n2.SortOrder));

                    foreach (ValidationError error in this.ErrorList.Errors)

                    {

                        sb.Append(this.ErrorBullet + error.ErrorMessage + "<br />");

                    }

                    sb.Append("</div>");

                    sb.Append("</div>");

                    writer.Write(sb.ToString());

                }

            }

            catch (Exception e)

            {

                // do nothing

            }          

        }

The ValidationBox Class

This is the big one.  We encapsulate all of our logic for keeping the PageErrors list, implementing an IsValid property for the page, keeping a reference to the page’s FieldMapping method (which maps FieldNames to the UIFieldNames actually used in the UI), registering ValidationLabel controls, registering ValidationSummary controls, and handling the processing of ValidationLabel and ValidationSummary controls. 

public class ValidationBox

    {

 

        #region "PROPERTIES"

 

            // PageErrors

            private PageErrorList _pageErrors;

            public PageErrorList PageErrors

            {

                get { if (_pageErrors == null) { _pageErrors = new PageErrorList(); }; return _pageErrors; }

                set { _pageErrors = value; }

            }

            // ValidationLabels

            private List<ValidationLabel> _validationLabels;

            public List<ValidationLabel> ValidationLabels

            {

                get

                {

                    if (_validationLabels == null) { _validationLabels = new List<ValidationLabel>(); }

                    return _validationLabels;

                }

            }

            // ValidationSummaries

            private List<ValidationSummary> _validationSummaries;

            public List<ValidationSummary> ValidationSummaries

            {

                get

                {

                    if (_validationSummaries == null) { _validationSummaries = new List<ValidationSummary>(); }

                    return _validationSummaries;

                }

            }

            // SuccessMessageControls

            private List<SuccessMessage> _successMessageControls;

            public List<SuccessMessage> SuccessMessageControls

            {

                get

                {

                    if (_successMessageControls == null) { _successMessageControls = new List<SuccessMessage>(); }

                    return _successMessageControls;

                }

            }

            // SuccessMessage

            public String SuccessMessage { get; set; }

            // SuccessTitle

            public String SuccessTitle { get; set; }

            // IsValid

            public Boolean IsValid

            {

                get { return this.PageErrors.Errors.Count > 0 ? false : true; }

            }

 

 

            // MapFieldNames

            // Delegate for Page Method that maps BAL Entity field names

            // to the UI Names used in  error messages. Once fields are

            // mapped, the PageErrors object can automatically generate 

            // usable error messages for entity validation errors.

            public delegate void FieldMapper(PageErrorList ErrorList);

            public FieldMapper FieldMapperFunction{get; set; }

        #endregion

 

 

 

 

 

        #region "CONSTRUCTORS"

            public ValidationBox(FieldMapper MapperFunction)

            {

                // We get the field mapper function from the page as a

                // constructor parameter.

                this.FieldMapperFunction = MapperFunction;

                // Create the PageErrorList and run the field mapper.

                this.PageErrors = new PageErrorList();

                FieldMapperFunction.Invoke(this.PageErrors);

                // At this point we have a new ValidationBox with a

                // PageErrorList that contains no errors but has all

                // of it's field mappings set.

            }

        #endregion

 

 

 

 

 

        #region "CLASS METHODS"

            //

            // RegisterValidationLabel

            //

            public void RegisterValidationLabel(ValidationLabel label)

            {this.ValidationLabels.Add(label);}

 

 

            //

            // RegisterValidationSummary

            //

            public void RegisterValidationSummary(ValidationSummary summary)

            {this.ValidationSummaries.Add(summary);}

 

 

            //

            // RegisterSuccessMessageControl

            //

            public void RegisterSuccessMessageControl(SuccessMessage sm)

            { this.SuccessMessageControls.Add(sm); }

 

            //

            // ProcessValidationControls

            // To make this method run right before the render we manually

            // add it to the PreRender event in the constructor.

            //

            public void ProcessValidationControls(Object sender, EventArgs e)

            {

                // Set the ErrorList collection for all summaries

                foreach (ValidationSummary summary in this.ValidationSummaries)

                { summary.ErrorList = this.PageErrors; }

                // Reset all ValidationLabels

                foreach (ValidationLabel label in this.ValidationLabels)

                { label.ClearError(); }

 

                if (this.IsValid)

                {

                    // No errors, set the success message if it exists.

                    if (String.IsNullOrEmpty(this.SuccessMessage))

                    {

                        foreach (SuccessMessage sm in this.SuccessMessageControls)

                        { sm.BoxTitle = String.Empty; sm.BoxMessage = String.Empty; }

                    }

                    else

                    {

                        foreach (SuccessMessage sm in this.SuccessMessageControls)

                        { sm.BoxTitle = this.SuccessTitle; sm.BoxMessage = this.SuccessMessage; }

                    }

                }

                else

                {

                    // There were errors, set the isError state on each validation label.

                    foreach (ValidationError error in this.PageErrors.Errors)

                    {

                        foreach (ValidationLabel label in this.ValidationLabels.FindAll(n => n.FieldName == error.FieldName))

                        { label.SetError(); }

                    }

                }

            }

        #endregion

 

 

    }

FormPageBase

Now that we’ve defined our validation controls, we need to use them on our ASP.Net page.  Since there are a number of things I want to happen on a data entry/validation container page, I usually create a FormPageBase.  This can then be the base class for any page where I’m entering data.  The FormPageBase implements IValidationContainer and inherits from the PageBase for my application. Notice that FormPageBase requires a MapFieldNames sub, and a delegate to this sub is passed to ValidationBox as a constructor parameter.

    abstract public class FormPageBase : PageBase, IValidationContainer

    {

        // ValidationBox

        private ValidationBox _validationBox;

        public ValidationBox ValidationBox

        {

            get

            {

                if (_validationBox == null) { _validationBox = new ValidationBox( new ValidationBox.FieldMapper(MapFieldNames) ); }

                return _validationBox;

            }

        }

 

        // Constructor - default

        public FormPageBase() : base()

        {

            // Register method to automatically process validation controls.

            this.PreRender += new EventHandler(this.ValidationBox.ProcessValidationControls);

        }

 

        // MapFieldNames

        // Required by the ValidationBox. This method maps BAL Entity field names to

        // the UI Names that are used in error messages. Once fields are mapped, the

        // PageErrors object can automatically generate usable error messages for

        // entity validation errors. The method is passed to the ValidationBox as

        // a delegate. If there is no need to map field names then just create a

        // method with the right signature that does nothing.

        abstract protected void MapFieldNames( PageErrorList ErrorList );

    }

The Payoff – Our Concrete Page

We written a lot of code, but the good part is that it’s all plumbing.  Now that the validation classes and the FormPageBase are written, we never have to touch them again. To create pages that use all of this automated validation code is a simple 3 step process:

  1. Add ValidationLabel and ValidationSummary controls to my markup
  2. Implement a MapFieldNames() method
  3. Bind any errors to my ValidationBox.PageErrors list.

So the framework/plumbing code got a little complex and took some work, but using it is easy.  Below is a listing of the PersonForm page that shows all of the pieces that are directly required for our validation implementation.  I’ve omitted boilerplate code like GetPersonFromForm since I’m sure you’ve seen enough code by now.

public partial class PersonForm : FormPageBase
    {
            //--------------------------------------------------------
            // btnSave_Click
            //--------------------------------------------------------
            protected void btnSave_Click(object sender, EventArgs e)
            {
                BAL.Person person = GetPersonFromForm();
 
                // Run page and entity level validation
                ValidatePage();
                this.ValidationBox.PageErrors.AddList(person.Validate());
 
                // If any errors were found during validation then bail out 
                // and let the validation controls will automatically handle
                // displaying the errors.
                if (this.ValidationBox.PageErrors.Errors.Count != 0) { return; }
 
                // No errors at this point so we'll try to save. If we run into a
                // save-time error we just add it to the PageErrors and bail out.
                try
                {
                    PersonRepository.SavePerson(ref person, true);
                }
                catch (Exception ex)
                { 
                    this.ValidationBox.PageErrors.Add(new ValidationError("Unknown", ex.Message));
                    return;
                }
            }
        
            //--------------------------------------------------------
            // ValidatePage
            //--------------------------------------------------------
            protected void ValidatePage()
            { 

                // Password Confirm Password must match - *** Page Validation ***

                if (FormHelper.ParseString(txtPassword.Text) != FormHelper.ParseString(txtPasswordConfirm.Text))

                {

                    this.ValidationBox.PageErrors.Add(new ValidationError("Page.ConfirmPassword", "Confirm Password and Password don't match"));

                }

            }      
 
 
            //--------------------------------------------------------
            // MapFieldNames
            // Required for PageBase implementation. Method maps full 
            // Entity field names to the UI Names that need to be
            // used in error messages. Once fields are mapped, the 
            // PageErrors object can automatically generate usable 
            // error messages for entity validation errors. If no fields
            // need to be mapped then just create an empty method.
            //--------------------------------------------------------
            override protected void MapFieldNames(PageErrorList ErrorList)
            {
                // Password
                ErrorList.MapField("Person.Password", "Password");
                // ConfirmPassword
                ErrorList.MapField("Page.ConfirmPassword", "Confirm Password");
                // Name
                ErrorList.MapField("Person.Name", "Name");
                // Nickname
                ErrorList.MapField("Person.Nickname", "Nickname");
                // PhoneMobile
                ErrorList.MapField("Person.PhoneMobile", "Mobile Phone");
                // PhoneHome
                ErrorList.MapField("Person.PhoneHome", "Home Phone");
                // Email 
                ErrorList.MapField("Person.Email", "Email");
                // City
                ErrorList.MapField("Person.City", "City");
                // State
                ErrorList.MapField("Person.State", "State");
                // ZipCode
                ErrorList.MapField("Person.ZipCode", "Zip Code");
                // ImAddress
                ErrorList.MapField("Person.ImAddress", "IM Address");
                // ImType
                ErrorList.MapField("Person.ImType", "IM Type");
                // TimeZoneId
                ErrorList.MapField("Person.TimeZoneId", "Time Zone");
                // LanguageId
                ErrorList.MapField("Person.LanguageId", "Language");
            }
    }

 

Summary

So that’s one design for custom validation that allows us to consolidate validation logic in our business objects, but still use some nice features for automated display of error messages and error indicators.  This is probably more work than most people want to do for validation design, but hopefully you’ve gotten some good ideas for how something like this can work.  At some point in the future, I’m going to revisit this topic and show how to use the standard ASP.Net validation controls in combination with the Enterprise Library validation block to provide similar functionality.