December 12 2016

C# / ASP.NET - Domain Validation Logic in Domain Driven Design (DDD)

This is an example showing how and where to implement Domain Model Validation and Business Logic in C# and ASP.NET projects, including MVC, Web API or ASP.NET Core, the code is pure C# so it doesn't matter what type of project or solution it's in.

I split most of my ASP.NET projects into a typical 3 tiered architecture, so a project each for the Data, Domain and Web or API layers. The domain layer contains services that contain all the core business logic of the application, this is also where I put domain validation code.

It's a fairly simple approach, I have a private Validate() method in each of my service classes that takes 2 parameters - the entity instance and the operation being performed (i.e. Create, Update or Delete). If validation fails a custom exception is thrown with the details of the broken validation rules.

Below is an example PageService class and associated classes from an ASP.NET CMS project I'm working on at the moment.


C# PageService with Domain Validation

The page service enables standard CRUD operations on Page entities and is where domain model validation is implemented.

public class PageService : IPageService
{
    private IRepository<Page> _pageRepository;

    public PageService(IRepository<Page> pageRepository)
    {
        _pageRepository = pageRepository;
    }

    public void Create(Page page)
    {
        // execute domain validation for the create page operation
        Validate(page, Operation.Create);

        page.CreatedDate = DateTime.Now;

        _pageRepository.Create(page);
    }

    public void Update(Page page)
    {
        // execute domain validation for the update page operation
        Validate(page, Operation.Update);

        _pageRepository.Update(page);
    }

    public void Delete(int id)
    {
        // execute domain validation for the delete page operation
        Validate(_pageRepository.GetById(id), Operation.Delete);

        _pageRepository.Delete(id);
    }

    public IList<Page> GetAll()
    {
        return _pageRepository.GetAll().ToList();
    }

    public Page GetById(int id)
    {
        return _pageRepository.GetById(id);
    }

    public Page GetByPath(string path)
    {
        return _pageRepository.GetAll().FirstOrDefault(x => x.Path == path); ;
    }

    // domain validation 

    private void Validate(Page page, Operation operation)
    {
        var brokenRules = new List<string>();

        // create domain validation logic
        if (operation == Operation.Create)
        {
            if (GetByPath(page.Path) != null)
                brokenRules.Add("Page already exists with the path '" + page.Path + "'");
        }

        // update domain validation logic
        if (operation == Operation.Update)
        {
            var pageEntity = _pageRepository.GetById(page.Id);

            if (page.Path != pageEntity.Path && GetByPath(page.Path) != null)
                brokenRules.Add("Page already exists with the path '" + page.Path + "'");
        }

        // delete domain validation logic
        if (operation == Operation.Delete)
        {
        }

        if (brokenRules.Any())
            throw new EntityException(string.Join("\r\n", brokenRules));
    }
}


C# IPageService Interface

The ipage service interface defines the methods that a page service implements and is used to support dependency injection.

public interface IPageService
{
    void Create(Page page);
    void Update(Page page);
    void Delete(int id);
    IList<Page> GetAll();
    Page GetById(int id);
    Page GetByPath(string name);
}


C# Operation Enum

The operation enum defines the CRUD operations that are used by the page service to determine which validation logic to execute.

public enum Operation
{
    Create,
    Update,
    Delete
}


C# Page Entity Class

The page entity class defines all properties for pages.

public class Page : IEntity
{
    public int Id { get; set; }
    public string Path { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public DateTime CreatedDate { get; set; }
}


C# Custom Entity Exception

The custom entity exception class is a thin wrapper around the standard System.Exception class. A custom exception is thrown when validation rules are broken so the web layer of the application can tell the difference between domain validation exceptions and uncaught system exceptions.

public class EntityException : Exception
{
    public EntityException(string message)
        : base(message)
    {
    }

    public EntityException(
        string message,
        params object[] args)
        : base(String.Format(CultureInfo.CurrentCulture, message, args))
    {
    }
}

Sponsored by