The making of Blogo.NET

Published: 18 Apr 2008
By: Ferdy Christant

This article explains in detail how I developed Blogo.NET, a N-tiered blog application based upon the .NET 3.5 framework.

Introduction

Part of this article is a discussion about the requirements and architecture, and details about the design and development of the application, from start to finish.

Much of the architectural principles are borrowed from Imar Spaanjaars' most-excellent series of articles: Building Layered Web Applications with Microsoft ASP.NET 2.0. Whilst Spaanjaars' article is extensive enough to build a N-tiered application by itself, my article will focus on applying these principles to a real-life application, instead of just a simple example. Furthermore, a big part of the architecture in my article is different, in that it uses LINQ to access data, and the new .NET 3.5 controls to display data.

This article does not claim to explain the best way to develop a N-tier ASP.NET application. It does try to explain a scalable, somewhat "standard" way to do it using modern tooling and components. I encourage everyone to provide feedback about even better ways.

Getting started

There are different ways to go through this article, and each has different instructions.

  • Just read it. No extra instructions apply.
  • Follow along. The article does not explain every line of source code in detail; therefore it is highly recommend that you open up the Blogo.NET solution in Visual Studio. You can download the Blogo.NET source here. Just unzip the download package and open the solution file inside. Note that this requires Visual Studio 2008 and the .NET 3.5 framework.
  • Experiment/Run. If you want to actually run Blogo.NET and experiment a bit with the source code and settings, you can download the Blogo.NET source here. Inside the download comes a manual that explains how to run Blogo.NET. Note that besides Visual Studio 2008 and the .NET framework, this also requires SQL Server 2005 and it is recommended to also install SQL Server 2005 Management Studio.

With the introduction and instructions behind us, it is time to have a closer look at what Blogo.NET is.

About Blogo.NET

As mentioned, Blogo.NET is a blog application. Figure 1 shows the homepage of a blog that is using Blogo.NET:

Figure 1: Blogo.NET in action

Blogo.NET in action

Below is a short list of functionality that is implemented in Blogo.NET. Before I started this project, I considered them the requirements:

  • Blogo.NET has two types of users: visitors and administrators. Visitors are anonymous users visiting the blog. Administrators are not anonymous and there can be multiple administrators. Administrators maintain the blog.
  • Visitors can view blog entries and articles from the homepage, by tag and by month. They can also open a single blog entry from these overviews. The overviews shows x blog entries at a time, and offers a way to paginate entries. Visitors can only see content that is in "published" mode, so not the draft ones.
  • From a specific blog entry or article,visitors can anonymously leave a comment by supplying a nickname and comment text (mandatory fields). HTML is not allowed in either of these fields. The comment panel must also be protected against spam bots. Visitors can see comments made by other visitors.
  • Visitors can subscribe to the three available content feeds using RSS: blog entries, articles, or comments.
  • Administrators can manage (created, read, update, delete) blog entries, users (other administrators), tags, comments and files from the password-protected administration panel, which can be accessed from any page.
  • Administrators can inspect and manage the error log of the blog. Users will only see a friendly error page in case of an error.
  • Administrors can manage a selection of configuration parameters that affect the Blogo.NET behaviour from a setup screen.

Of course this set of requirements needs more detail, but it should be enough for you to understand what is being developed.

Next to the functional requirements, there are also a number of non-functional requirements:

  • Since this concerns an internet application, basic security measures must be in place. This means that SQL injection, XSS and comment spam attacks must be prevented with reasonable effort.
  • The user-facing pages (visitors) must be rendered with proper markup, in order to establish reasonable cross-browser compatibility and search-engine friendliness.
  • For the sake of development speed and future maintenance, the application must be built using common standards. This applies to the architecture (n-tier as an enterprise standard), development (use as much from the .NET framework as possible), tooling (Visual Studio and SQL Server Management Studio), and other aspects.
  • Although parts of the source code of this application could have been generated, all source code is crafted manually, in order to maximize the learning effect and flexibility.

With the background of the application and its' requirments out of the way, it is now time to start discussing the architecture.

Designing Blogo.NET - Architecture

Although you could argue that Blogo.NET is a simple application (simple web front-end on top of a database), there are still various architecture choices to make. An important one is to define the layers in the architecture.

The simplest architecture would be to build pages that databind directly to the database. Although this certainly would be a speedy way to develop the application, it has major downfalls:

  • View logic will be intertwined with both business and data access logic
  • It is likely that you will repeat this logic in a lot of places, making the application hard to maintain. It will also become quite unclear what the impact of a change in the code is.
  • It will be hard to centralize application facilities such as security, validation and logging
  • Without separate data and business layers, it is cumbersome to decouple your application back-end from its front-end. Such a decoupling is essential if you want to provide a service layer or a different front-end in the future, i.e. a mobile or Adobe Flex client.

The page-centric architecture is not in all cases a bad choice, and perhaps for a simple blog application even suitable. Nevertheless, we will go for a different model. One that is more scalable, maintainable and applied (in some way or another) in the enterprise world: the N-tier model.

There are many applications of the N-tier model, hence the "N". However, the most common approach seems to be to have separate layers for data, data access, business logic, and view. Applied to the design of Blogo.NET, the high level architecture looks like figure 2.

Figure 2: Blogo.NET high level architecture

Blogo.NET high level architecture

In any layered architecture, it is important to understand two things:

  • The scope of the layer. It must be very clear what the responsibility of each layer is.
  • Layer interaction. How do layers interact with each other.

Let us hereby define the scope of the layers:

  • Database: Persists data used in the blog application.
  • Data access: Implements logic to read from the database and write to the database.
  • Business: Implements logic to manage business objects and related functionality
  • View: Implements the user interface

The block "Utilities" is not considered a layer; it provides application facilities that are possible used by multiple layers, i.e. security.

Concerning the interaction of layers, we will follow the classic abstraction pattern. This means that layers demand from the layer below, and supply to the layer above. For example, the View layer should not directly call the Data Access layer to retrieve data. Instead, it should use the Business layer to do this. When layers operate beyond their interaction scope, we have what is called "leaky abstraction". This leads to application mainteance nightmares and beats the whole purpose of having layers in the first place.

Now that we have the layers of the architecture defined, it is time to start thinking about how to implement them. For Blogo.NET, the following applies:

  • Database: We will use SQL Server 2005, since we are required to store relational data.
  • Data Access: This layer will be implemented using LINQ. To be more specific the mapping from data to objects will be done using LINQ, whilst we will use simple C# classes to provide Data Objects to the Business Layer.
  • Business: This layer will be implemented using C# classes. We will be using two kinds of classes in this layer, one to implement a Business Object, and one to manage instances of Business Objects, these are called Business Object Managers.
  • View: We will mostly be using ASPX pages and standard controls to implement the View.

With these choices made, we can now add a little detail to the architecture. The result can be seen in figure 3.

Figure 3: Blogo.NET detailed architecture

Blogo.NET detailed architecture

This covers enough of the architecture to start with the implementation. During the development process, we will see into detail how each layer is implemented.

Developing Blogo.NET - The Database

There a two well-known approaches when implementing a n-tiered architecture: domain-driven, or database-driven. A domain-driven approach is to design the class diagram first, and then the database. The database-driven approach works the other way around; database first and then the class diagram. In both scenarios, there are many opportunities for source code generation.

Blogo.NET uses the traditional database-driven approach. To maximize the learning effect and flexibility in implementation, no code is generated. As mentioned before, the database will be a SQL Server 2005 database. We will design it using the SQL Server Management Studio 2005 tool. Alternatively, the Visual Studio's built-in database functionality can be used. We will design the database using simple tables only (no views, triggers or stored procedures).

To implement the database, we first need to extract the data entities from the requirements, here they are:

  • blog enry
  • articles
  • tags
  • comments
  • authors
  • files
  • log

When we have a closer look at this list, we can optimize and enrich it instantly:

  • Blog entries and articles will in essence have the same fields, therefore we can combine them in a single table and add a "type" field.
  • Blog entries can be assigned to multiple tags, and a tag can contain multiple blog entries. This means that we have a many-to-many relationship that will require a mapping table
  • Comments will be related to a single blog entry
  • Blog entries will be written by a single author, and an author can write multiple blog entries
  • Files and log entries stand on their own, they have no relationship with any particular data entity
  • Given this limited set of data entities and relationships, it is easy to come up with the database design, as illustrated in figure 4.

Figure 4: Blogo.NET database model

Blogo.NET database model

It can be easily spotted from figure 4 that the "blogentries" table takes a central place in the design. While it goes too far for this article to explain how to implement such a model in detail, here are a few helpful clues:

  • Choose a naming convention for your tables and fields and stick to it.
  • Every table has a ID field which is the primary key, an auto-incremented number. This makes it easy to change other fields in the table without worrying about the relationships with the table.
  • Some tables have an extra key (unique constraint) on one or more fields. For example, the "tags" table "tagname" field is marked as unique.
  • Foreign key fields are named to the table to which they link to, i.e. the field "author_id" of the "blogentries" table links to the "authors" table by ID.
  • For each foreign key relationship, you have to decide what needs to happen when the entity get's updated or deleted. For example, when a comment is deleted, you do not want the parent blog entry to be deleted. When a blog entry is deleted however, you will want to delete all related comments.
  • When you know in advance that you will be firing specific queries at a table often, it is recommended to created indexes for those tables. For example, we know blog entries will be queried a lot sorted descending by publication date, therefore a index is created just for that.
  • Fields that are required should be set to not accept null values.

It is tempting to start coding now, but before we do, we need to test the database model. The database is the last resort in our architecture to make sure that your data is always in a consistent state. It is of specific importance to test all the constraints (not null, unique, foreign key actions). In such a small database model it really is a question of entering test data and trying out every possible operation. Next you requery the database to see that the operations did what it was supposed to do. With a solid database model, the implementation of the other layers becomes a lot easier.

Developing Blogo.NET - The Data Access Layer

As can be seen from our architecture, there are two parts that make up the Data Access Layer: the ORM (Object Relational Mapper) and the Data Access Objects. Keep in mind that the purpose of this layer is to read/write from the database and transport results back to the Business Layer, which is the only layer to use the Data Access Layer.

We have chosen to use LINQ to talk to the database. The main advantage of LINQ is that it allows us to communicate with the database as if it is a set of databases. Using a simple query language, we can dynamicaly make calls to the database.

Before we can make use of LINQ queries however, we have to establish a mapping. This can easily be done in Visual Studio by adding a new "LINQ to SQL Classes" item. These items are stored as .dbml files. In Blogo, the file is called "BlogoMap.dbml", which is located in the Code/Data/Mapping folder of the solution.

To implement the mapping, all we really need to do is drag our tables from the Server Explorer into the mapping file. The result will look something like this:

Figure 5: Blogo.NET data mapping file

Blogo.NET data mapping file

Once the mapping file is saved, we have established a powerful API to our database. We can treat the entities as objects and write LINQ queries to retrieve and manipulate them. Here's an example:

This simple statement will retrieve a tag object that matches the specified ID value. With the tag object retrieved, you can now manipulate that object and write it back to the database:

This is only a very small example of the power of LINQ. If you're new to LINQ, I recommend you have a look at this large list of example queries.

We will now perform a step that may not seem intuitive. We will create classes for the Business Objects in the Business Layer. The reason we must do this first is that the Data Access layer will provide the Business Layer with Business Objects as return values. Without the Business Objects defined, it is cumbersome to define the Data Access layer.

So what are these Business Objects? They are so-called "dumb" objects. Objects with a sole purpose of holding data. They do not contain any methods. Our Business Objects will simply match the properties of the data entities. As mentioned before, we need these objects to pass results back to the business layer. You can hand-code these Business classes, or design them in the Visual Studio class diagram. Below is the result of this exercise:

Figure 6: Blogo.NET Business Objects

Blogo.NET Business Objects

At this point you may be thinking: why do I have to essentially duplicate classes that LINQ has readily available because of the mapping? While this step can indeed be seen as laboursome, and sensitive to data definition updates, it is still sensible to do this:

  • By context, a Data Object (which is what LINQ provides) is entirely different from a Business Object. A Business Object could define a more abstract interface than the Data Object. For example, our LINQ model contains a blog_tags mapping object that maps blog entries to tags. We do not have a blog_tag Business Object however, the Business Layer is not interested in such a database implementation detail, it simply has a "BlogEntry" Business Object with an associated "Tags" collection.
  • A Business Object is entirely decoupled from the data store. It has a data-provider agnostic view of the domain model; they are essentially neutral ground within the application.
  • LINQ returns anonymous collections of objects that make it hard to serialize them and use them in web services, for example. Whilst this example does not apply to Blogo.NET currently, it makes sense to build our application in a future-proof way.
Note: To overcome the manual process of keeping the Business Objects in sync with the data entities, you could generate the code of the Business Objects.

In the Blogo.NET solution, the Business Objects are located in the Code/Business folder. Per entity, there is a file. Note that these files contain both the Business Object Manager (discussed later) and the Business Object itself. Let's have a closer look at one of the Business Object classes: Tag, which is located in the bottom of the Tag.cs file:

The following remark applies to this Business Object, and all the other ones:

  • The Business Object is dumb, it does not contain any methods or logic, it simply holds data properties.
  • Each Business Object has a unique identifier (ID). The “magic number” -1 is used to indicate that a Business Object is new, as in not saved to the database yet.
  • All data elements are exposed as public properties that can be “get” and “set”. Notice how the new C# short syntax ({GET;SET;} is used to define these properties.

With the Business Objects implemented, it is time to return to the Data Access Layer. As explained before, it is the responsibility of the Data Access layer to provide database operations to the Business Layer. At the very minimum, this means that for each entity within the domain, the following operations must be implemented:

  • GetItem: Returns a single Business Object from the database, based upon a unique identifer.
  • GetList: Returns a list of Business Objects from the database, based upon filter criteria.
  • Save: Saves a Business Object to the database.
  • Delete: Deletes a Business Object from the database, based up on a unique identifier.

In addition to these basic operations, most Data Objects in the Data Access Layer implement the following:

  • Count functions. Counts functions return the number of Business Objects based upon filter criteria. Count functions are needed to implement server-side pagination.
  • GetList overloads. The default GetList operation returns all Business Objects of a certain type from the database. However, often specific queries are required, i.e. different filter criteria, sorting and pagination. Therefore, most Data Objects implement various GetList overloads. In all cases, they return a list of Business Objects.
  • FillRecord. Each DataObject has an internal method “FillRecord” that converts a LINQ object to a Business Object, so that it can be returned to the Business Layer.

Let’s have a look at the implementation of these operations by examining the source code of “Comment.db.cs”, which is located in the Code/Data/Access folder.

The private db property is available in all Data Object classes. It is a reference to the mapping file we created earlier. By defining it at class level, all operations within the class can refer to the database.

The Count method is used for server-side pagination. It simply returns the number of comments in the database. Note that the StartRow and PageSize parameters are not used. These parameters are here to match the signature of a related GetList operation. The combination of a Count and GetList method with identical parameters is required to provide server-side pagination.

The GetItem method finds the LINQ object from the database, converts it to a Business Object, and returns it. Notice the suffix call FirstOrDefault to force LINQ to return a single object. The private method FillRecord is used to convert the LINQ object to the Business Object.

The GetList method returns a list of all comments in the database, sorted descending by creation date. The LINQ result set is converted to a generic list of Business Objects using again the FillRecord method.

This GetList method is an overload. Where the previous GetList method returns all comments at once, this overload returns it paginated, PageSize items at a time, starting from position StartRow. The result is again returned as a generic list of Comment Business Objects.

This is yet another overload of the GetList operation. This time, all comments related to a specific blog entry are returned. Note that often you will not know upfront which overloads your Data Access layer must support. Therefore, it is common that you will refine and extend this layer as you build your view.

The Save method seems a bit lengthy, but is simple in essence. All it does is copy properties from a Business Object to a LINQ data object, and save it to the database. There is some additional logic to deal with both existing and new objects, since the Save method is used for both new and existing objects (we do not have a separate “update” method).

The Delete method takes the unique ID of the object to delete, finds it in the database and then deletes it.

The FillRecord method is an internal conversion operation that takes LINQ comment object and converts it to a Comment Business Object, so that it can be returned to the Business Layer.

Now that we have walked through the implementation of a single Data Object in the Data Access layer, you should understand the other ones as well. A few remarks apply:

  • Some Save operations have additional logic to apply data constraint checking. For example, the Save method of the Tag Data Object first checks in the database if a tag with the specified tagname does not already exist. Whilst these constraints are enforced by the database, we still need to duplicate this logic in the Data Access layer.
  • Some operations are more complex in that they need to deal with compound objects; objects that have relations with other objects. For example, the tags assigned to a blog entry upon saving that blog entry, need to be inserted to the mapping table.
  • The File Data Access class has a FillRecord method that takes a Boolean getFile parameter, whilst all other Data Access classes do not have this parameter. The parameter is part of a lazy loading mechanism: the binary content of a file is only retrieved from the database when explicitly requested, not when we simply want to retrieve a list of file names.

Finally, notice how the source code files within the Data Access layer are enriched by:

  • Regions. Source code regions enable you to drill down into a set of operations quickly. For example, I have defined regions to separate public and private methods.
  • Comments. Each operation and property is preceded with standardized comments in the C# notation. Not only do these comments explain what the operations do, they also enable Intellisense on your own objects.

Summary

In the first part of this series, we introduced the Blogo.NET web application. We overviewed its architecture and then discussed the Database and the Data Access Layer. In the next part, we will introduce the Business Layer.

Other Parts

About Ferdy Christant

"I am a software engineer and architect from the Netherlands. I've started out with Lotus Notes development in 1999 and have since then developed and certified myself in PHP, J2EE and .NET. In my free time, I blog and have a small open source web site for my projects. Furthermore, I enjoy m...

View complete profile

Top Articles in this category

JavaScript with ASP.NET 2.0 Pages - Part 1
ASP.NET 2.0 has made quite a few enhancements over ASP.NET 1.x in terms of handling common client-side tasks. It has also created new classes, properties and method of working with JavaScript code. This article explores the enhancements and the various ways of injecting JavaScript programmatically into ASP.NET 2.0 pages.

ASP.NET ComboBox
The ASP.NET ComboBox is an attempt to try and enhance some of the features of the Normal ASP.NET DropDownList.

JavaScript with ASP.NET 2.0 Pages - Part 2
ASP.NET provides a number of ways of working with client-side script. This article explores the usage and drawbacks of ASP.NET script callbacks, and briefly presents a bird's view of ASP.NET AJAX.

Upload multiple files using the HtmlInputFile control
In this article, Haissam Abdul Malak will explain how to upload multiple files using several file upload controls. This article will demonstrates how to create a webform with three HtmlInputFile controls which will allow the user to upload three files at a time.

Using WebParts in ASP.Net 2.0
This article describes various aspects of using webparts in asp.net 2.0.

Top
 
 
 

Discussion


Subject Author Date
placeholder Some suggestions Kelvin Li 4/28/2008 10:27 PM
Great work Diogo Almeida 4/30/2008 5:21 AM
placeholder Re: Ferdy Christant 4/30/2008 5:48 AM
RE: Re: Shailendra Singh Chauhan 5/3/2008 4:16 PM
placeholder RE: Re: Shailendra Singh Chauhan 5/3/2008 4:27 PM
Great Jon Kaushal Parik 5/2/2008 5:48 AM
placeholder Re: Ferdy Christant 5/2/2008 11:06 AM
RE: Re: Kaushal Parik 5/3/2008 12:54 AM
placeholder Questions Lucas Stark 5 days ago

Please login to rate or to leave a comment.

Product Spotlight