By:Darius Kucinskas Posted On: Topic:Engineering

Devbridge Standard Web Project Template (Part 1)

Our Standard Web Project Template serves two purposes: to share expertise between developers and to make it easier for new team members to setup new web applications correctly. Here at Devbridge, we believe in open communication and like to share our knowledge with others who may find it helpful.  You can always get the latest version of StandardWebProjectTemplate from github.

**Note** Please, keep in mind that not all projects are equal. This template may not be suited for your project (don’t copy blindly rather adopt parts that you find useful).

In this blog post, I will write about the following components of our template:

  • third party libraries we use in our projects
  • assembly versioning
  • dependency injection
  • data layer

Third party libraries we use in our projects

  • NHibernate - open source object-relational mapper for the .NET framework.
  • FluentNHibernate – open source, fluent, XML-less, compile safe, automated, convention-based mappings for NHibernate.
  • NUnit – unit test framework.
  • Moq – mocking library for .NET.
  • Common.Logging - provides the base logging interfaces to wrap all popular logging frameworks.
  • NLog – library for logging.
  • Unity – dependency injection container.
  • Unity.Mvc3 – Unity dependency injection container extensions for ASP.NET MVC3.
  • CommonServiceLocator – library contains a shared interface for service location which application and framework developers can reference

We use NuGet package manager to manage libraries in projects with an enabled package restore option.

Assembly versioning

We use the following simple, unified versioning scheme for assemblies.


In solution Version folder, we have AssemblyVersion.cs file. This file contains assembly version, assembly file version and other general information for all assemblies. Concrete project adds AssemblyVersion.cs file as link removing duplications from AssemblyInfo.cs file. This solution provides easy way to share single assembly information across all assemblies.

Dependency injection

We use Unity Dependency Injection Container and Unity.Mvc3 extensions. It includes a DependencyResolver that creates a child container per HTTP request and disposes of all registered IDisposable instances at the end of the request. It allows us to configure the UnitOfWork life span in such way that at each request the new UnitOfWork is created and disposed at the end of the request.

Data layer


Structure

Data layer consist of the following projects:

  • Data
  • DataContracts
  • DataEntities

Data Contracts

DataContracts project defines contracts of the data layer. The contract is defined in terms of the following interfaces and exceptions:

IEntity is an interface that all entities must implement. It consists of the following common properties:

  • Id (int) – unique identifier of entity (Primary key in terms of database).
  • CreatedOn (DateTime) – this property is used for audit trail.
  • DeletedOn (DateTime?) - this property is used for soft deleting entity (for those entities that can’t be deleted from the database physically. Instead, you want them to be marked and not appear in UI).

IRepository is an interface of common generic repository and consists of the following methods:

  • TEntity First(int id) - gets non deleted entity by primary key (Id). Throws EntityNotFoundException exception if entity is not found.
  • TEntity First(Expression<Func<TEntity, bool>> filter) - gets first non deleted entity filtered by lambda expression defined filter. Throws EntityNotFoundException exception if entity is not found.
  • TEntity FirstOrDefault(int id) - gets non deleted entity by primary key (Id). Returns null if entity is not found or deleted.
  • TEntity FirstOrDefault(Expression<Func<TEntity, bool>> filter) - gets first non deleted entity filtered by lambda expression defined filter. Returns null if entity is not found or deleted.
  • TEntity CreateProxy(int id) – returns an entity proxy. This method will not hit the database when you call it, but will return proxy of entity. This is very useful if you are sure that the value exists in the database and you do not want to make the extra trip around the database for fetching it. However, you can add a proxy object as reference to an object without fetching it. For example:
    orderRepository.Save(new Order { Amount = amount, customer = customerRepository.CreateProxy(customerId)}); The code above will not hit the database. Then we commit the transaction, value of the CustomerID column will be set to customerId.
  • IQueryable<TEntity> AsQueryable() - returns not deleted entities query.
  • IQueryable<TEntity> AsQueryable(Expression<Func<TEntity, bool>> filter) - returns not deleted entities query filtered by lambda expression filter.
  • bool Any(Expression<Func<TEntity, bool>> filter) - checks if non deleted entity exists.
  • void Save(TEntity entity) - saves entity in unit of work transaction.
  • void Delete(TEntity entity) - deletes entity in unit of work transaction.
  • void Delete(int id) – deletes entity by primary key in unit of work transaction.

IUnitOfWork interface consists of the following properties and methods:

  • ISession Session { get; } – gets current unit of work session.
  • void Commit() – commits unit of work changes to database (commits current transaction if it exists or flushes session otherwise).
  • bool IsActiveTransaction { get; } – checks if unit of work has active transaction.
  • void BeginTransaction() – starts new transaction. Throws DataException if active transaction already exists in unit of work.
  • void BeginTransaction(IsolationLevel isolationLevel) – starts new transaction with specified isolation level. Throws DataException if active transaction already exists in unit of work.
  • void Rollback() – rollback unit of work transaction. Throws DataException if active transaction was not found for unit of work.

IUnitOfWorkFactory

  • IUnitOfWork New() - factory to create unit of work objects. It is a way to create extra unit of work objects for special cases as each web request default unit of work object is created automatically.

IUnitOfWorkRepository

  • void Use(IUnitOfWork unitOfWork) - interface with method to reattach unit of work object for specific repository.

ISessionFactoryProvider

  • ISessionFactory SessionFactory { get; } - singleton object with initialized nHibernate session factory object.

Exceptions

There are following predefined exception types: KnownException, DataException, EntityNotFoundException.
KnownException - base exception class that all other exceptions are derived from.
DataException - data layer exception.
EntityNotFoundException - is specialized exception used in cases when entity is not found in database.

Data Entities

EntityBase implements IEntity interface. All data entities implement EntityBase abstract class. The main purpose of EntityBase class is to provide implementation of common properties and operators used in all entities and, most importantly, proper GetHashCode() method implementation. Good GetHashCode() implementation should obey the following rules and guidelines: equal items should have equal hashes; the integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable; GetHashCode must never throw an exception, and must return; the implementation of GetHashCode must be fast. Our implementation of GetHashCode is based on an Id property which is assigned from the database by ORM. While the new entity is constructed and not saved to database – Object class GetHashCode method calculates its hash code. It remains unchanged throughout the lifetime of the object. For entities, loaded from database, hash code is calculated from Id property.

Data

UnitOfWork implementation

Our implementation of UnitOfWork operates in the following two modes: with and without explicit transaction. If you begin a transaction you are responsible for committing it. If you do not do this, the transaction will be rollbacked in the end of the session. Active, not committed transactions will be rollbacked if an exception occurs, too. If you do not use transactions, UnitOfWork flushes and closes the session. Due to how NHibernate works, it is recommended to wrap all creation of objects with children into a transaction. You can also use an external transaction with UnitOfWork. By using an external transaction, you can combine several service operations into one transaction if you need this.

Mapping tests

For entity mapping testing we use FluentNHibernate PersistenceSpecification tests. These tests perform writing and reading of entities to check if mappings are correct. A general principle of all of our tests is using transaction rollback on error or in the end of a test in order to keep our test database clean. You can find these tests in Devbridge.Templates.WebProject.Tests project Mappings folder.

To be continued in part 2:

  • Complex NHibernate situations and queries examples.
  • Business logic and services implementation with testing.
  • Web project implementation with ASP.NET MVC 3.
  • Configuration sections management examples (forget oversized appSettings section).
  • Client side implementation best practices.
Darius Kucinskas

Want more industry news?

comments powered by Disqus
Let's Talk