Table of Contents

Use cases

Use case-driven development

In Domain-Driven Design, an application use case is a workflow that fulfills something meaningful for the app: it coordinates the domain model with repositories, optional domain services, and DevKit module services.

A single use case may orchestrate several domain steps (load multiple aggregates, call several methods, publish events). What matters is a clear responsibility and a coherent operation from the caller’s perspective—not a strict one-to-one link to a single domain method.

In this template we keep that style explicit:

  • Domain owns invariants and business rules.
  • Use cases orchestrate persistence, authorization checks, DTO shaping, and dispatching domain events.
  • Clients (Blazor, REST) stay thin and talk to the application layer through application services (see below), not directly to infrastructure.

Use cases are usually one class per command or query (for example under Application/UseCases/DemoThings) so each handler stays easy to test and evolve.

Command and query use cases

We follow a CQRS-style split at the application boundary:

Command Query
Purpose Change application state Read state, no intended side effects
Typical return CommandResult (often includes created/updated entity id) A DTO or paged DTO wrapper
Base types Request inherits BaseCommand; handler inherits BaseCommandHandler Request inherits BaseQuery<TResponse>; handler inherits BaseQueryHandler

Commands are validated (data annotations and optional FluentValidation) before the handler runs. Queries load data, map to DTOs, and return them.

Implementing a new use case

When you add a feature, mirror the DemoThing layout:

  1. Domain — aggregate, repository interface, domain events (see Domain model).
  2. DTOs — under Application/Dtos/<YourFeature>/: input DTOs for APIs/forms, output DTOs for reads (see the examples below).
  3. Use case folder — under Application/UseCases/<YourFeature>/<OperationName>/: a request type and a handler implementing IRequestHandler<TRequest, TResponse>.
  4. Mapping — AutoMapper profiles in Infrastructure for entity ↔ DTO rules your queries need (see Mapping DTOs).
  5. Registration — Handlers are discovered via RegisterHandlers from Application/Startup.cs (wired through the infrastructure module). Add validators in the same assembly if you use FluentValidation.

Reference request types for other DemoThing operations: CreateDemoThingRequest.cs, UpdateDemoThingRequest.cs, GetDemoThingRequest.cs, SearchDemoThingsRequest.cs.

Application services

Use case handlers are invoked through the DevKit request dispatcher (MediatR). UI and API layers should not depend on handler types directly; they call application services that build requests, dispatch them, and return Result / Result<T> to the client.

That pattern is documented next in Application services.

Exception handling

For expected failures:

  • In the domain, throw DomainException (see Domain model).
  • In use cases, throw ApplicationException (DevKit type—not System.ApplicationException), often via small static factories such as DemoThingApplicationExceptions.cs (for example not found after a null load).

You normally do not catch these inside each handler. The pipeline treats DomainException, ApplicationException, and ValidationException as managed failures, turns them into user-facing messages, and exposes them on Result.Errors.

For the full flow (including unmanaged exceptions and API fallback), see Exception handling.

Mapping DTOs

DTOs are the contracts for inputs and outputs of the application layer; they stay separate from domain entities.

AutoMapper maps domain entities to output DTOs inside query handlers (BaseQueryHandler exposes IMapper). Mapping rules live in Infrastructure, in a Profile class registered with the app (for example DemoThingMappingProfile.cs), so the Application project does not reference mapping configuration.

For a new feature, add CreateMap<YourEntity, YourGetDto>(); (and any custom member maps) in a profile registered from Infrastructure startup alongside existing profiles.

Complete command use case (CreateDemoThing)

End-to-end create flow: input DTO (optional for API), command request, handler.

The input DTO is CreateDemoThingDto.cs.

using System.ComponentModel.DataAnnotations;
using CanBeYours.Core.Domain.DemoThings;
using CanBeYours.Core.Resources;
using CodeBlock.DevKit.Core.Resources;

namespace CanBeYours.Application.Dtos.DemoThings;

/// <summary>
/// Data Transfer Object for creating a new DemoThing entity.
/// This class demonstrates how to create DTOs with validation attributes and resource-based localization.
/// The DemoThing functionality shown here is just an example to help you learn how to implement
/// your own unique features into the current codebase.
/// </summary>
public class CreateDemoThingDto
{
    /// <summary>
    /// The name of the demo thing. This field is required and will be displayed using localized resources.
    /// </summary>
    [Display(Name = nameof(SharedResource.DemoThing_Name), ResourceType = typeof(SharedResource))]
    [Required(ErrorMessageResourceType = typeof(SharedResource), ErrorMessageResourceName = nameof(SharedResource.DemoThing_Name))]
    public string Name { get; set; }

    /// <summary>
    /// The description of the demo thing. This field is required and will be displayed using localized resources.
    /// </summary>
    [Display(Name = nameof(SharedResource.DemoThing_Description), ResourceType = typeof(SharedResource))]
    [Required(ErrorMessageResourceType = typeof(SharedResource), ErrorMessageResourceName = nameof(SharedResource.DemoThing_Description))]
    public string Description { get; set; }

    /// <summary>
    /// The type of the demo thing. This field is required and determines the category of the demo thing.
    /// </summary>
    [Display(Name = nameof(SharedResource.DemoThing_Type), ResourceType = typeof(SharedResource))]
    [Required(ErrorMessageResourceName = nameof(CoreResource.Required), ErrorMessageResourceType = typeof(CoreResource))]
    public DemoThingType Type { get; set; }
}

The command request is CreateDemoThingRequest.cs.

using System.ComponentModel.DataAnnotations;
using CanBeYours.Core.Domain.DemoThings;
using CanBeYours.Core.Resources;
using CodeBlock.DevKit.Application.Commands;
using CodeBlock.DevKit.Core.Resources;

namespace CanBeYours.Application.UseCases.DemoThings.CreateDemoThing;

/// <summary>
/// Command request for creating a new DemoThing entity.
/// This class demonstrates how to implement command requests with validation attributes,
/// immutable properties, and proper resource-based localization.
/// The DemoThing functionality shown here is just an example to help you learn how to implement
/// your own unique features into the current codebase.
/// </summary>
internal class CreateDemoThingRequest : BaseCommand
{
    /// <summary>
    /// Initializes a new instance of the CreateDemoThingRequest with the required data.
    /// </summary>
    /// <param name="name">The name of the demo thing</param>
    /// <param name="description">The description of the demo thing</param>
    /// <param name="type">The type/category of the demo thing</param>
    public CreateDemoThingRequest(string name, string description, DemoThingType type)
    {
        Name = name;
        Description = description;
        Type = type;
    }

    /// <summary>
    /// The name of the demo thing. This field is required and will be displayed using localized resources.
    /// </summary>
    [Display(Name = nameof(SharedResource.DemoThing_Name), ResourceType = typeof(SharedResource))]
    [Required(ErrorMessageResourceName = nameof(CoreResource.Required), ErrorMessageResourceType = typeof(CoreResource))]
    public string Name { get; }

    /// <summary>
    /// The description of the demo thing. This field is required and will be displayed using localized resources.
    /// </summary>
    [Display(Name = nameof(SharedResource.DemoThing_Description), ResourceType = typeof(SharedResource))]
    [Required(ErrorMessageResourceName = nameof(CoreResource.Required), ErrorMessageResourceType = typeof(CoreResource))]
    public string Description { get; }

    /// <summary>
    /// The type of the demo thing. This field is required and determines the category of the demo thing.
    /// </summary>
    [Display(Name = nameof(SharedResource.DemoThing_Type), ResourceType = typeof(SharedResource))]
    [Required(ErrorMessageResourceName = nameof(CoreResource.Required), ErrorMessageResourceType = typeof(CoreResource))]
    public DemoThingType Type { get; }
}

The handler is CreateDemoThingUseCase.cs.

using CanBeYours.Core.Domain.DemoThings;
using CodeBlock.DevKit.Application.Commands;
using CodeBlock.DevKit.Application.Srvices;
using CodeBlock.DevKit.Core.Helpers;
using MediatR;
using Microsoft.Extensions.Logging;

namespace CanBeYours.Application.UseCases.DemoThings.CreateDemoThing;

/// <summary>
/// Use case for creating a new DemoThing entity.
/// This class demonstrates how to implement command handlers that follow CQRS patterns,
/// handle domain events, and integrate with repositories and user context.
/// The DemoThing functionality shown here is just an example to help you learn how to implement
/// your own unique features into the current codebase.
/// </summary>
internal class CreateDemoThingUseCase : BaseCommandHandler, IRequestHandler<CreateDemoThingRequest, CommandResult>
{
    private readonly IDemoThingRepository _demoThingRepository;
    private readonly ICurrentUser _currentUser;

    /// <summary>
    /// Initializes a new instance of the CreateDemoThingUseCase with the required dependencies.
    /// </summary>
    /// <param name="demoThingRepository">The repository for demo thing operations</param>
    /// <param name="requestDispatcher">The request dispatcher for handling domain events</param>
    /// <param name="logger">The logger for this use case</param>
    /// <param name="currentUser">The current user context</param>
    public CreateDemoThingUseCase(
        IDemoThingRepository demoThingRepository,
        IRequestDispatcher requestDispatcher,
        ILogger<CreateDemoThingUseCase> logger,
        ICurrentUser currentUser
    )
        : base(requestDispatcher, logger)
    {
        _demoThingRepository = demoThingRepository;
        _currentUser = currentUser;
    }

    /// <summary>
    /// Handles the creation of a new demo thing.
    /// This method demonstrates the complete flow of creating an entity:
    /// 1. Creating the domain entity with proper user context
    /// 2. Persisting to the repository
    /// 3. Publishing domain events for side effects
    /// </summary>
    /// <param name="request">The command request containing the demo thing data</param>
    /// <param name="cancellationToken">Cancellation token for the operation</param>
    /// <returns>A command result with the ID of the created entity</returns>
    public async Task<CommandResult> Handle(CreateDemoThingRequest request, CancellationToken cancellationToken)
    {
        var demoThing = DemoThing.Create(request.Name, request.Description, request.Type, _currentUser.GetUserId());

        await _demoThingRepository.AddAsync(demoThing);

        await PublishDomainEventsAsync(demoThing.GetDomainEvents());

        return CommandResult.Create(entityId: demoThing.Id);
    }
}

Complete query use case (GetDemoThing)

End-to-end read flow: query request, output DTO, mapping profile, handler (includes not found handling and optional enrichment).

The query request is GetDemoThingRequest.cs.

using CanBeYours.Application.Dtos.DemoThings;
using CodeBlock.DevKit.Application.Queries;
using CodeBlock.DevKit.Core.Helpers;

namespace CanBeYours.Application.UseCases.DemoThings.GetDemoThing;

/// <summary>
/// Query request for retrieving a DemoThing entity by its identifier.
/// This class demonstrates how to implement query requests that extend base query classes
/// and include optional query options for flexible data retrieval.
/// The DemoThing functionality shown here is just an example to help you learn how to implement
/// your own unique features into the current codebase.
/// </summary>
internal class GetDemoThingRequest : BaseQuery<GetDemoThingDto>
{
    /// <summary>
    /// Initializes a new instance of the GetDemoThingRequest with the required identifier.
    /// </summary>
    /// <param name="id">The unique identifier of the demo thing to retrieve</param>
    /// <param name="options">Optional query options for customizing the retrieval behavior</param>
    public GetDemoThingRequest(string id, QueryOptions options = null)
        : base(options)
    {
        Id = id;
    }

    /// <summary>
    /// The unique identifier of the demo thing to retrieve.
    /// </summary>
    public string Id { get; set; }
}

The output DTO is GetDemoThingDto.cs.

using CanBeYours.Core.Domain.DemoThings;
using CodeBlock.DevKit.Contracts.Dtos;

namespace CanBeYours.Application.Dtos.DemoThings;

/// <summary>
/// Data Transfer Object for retrieving DemoThing entity data.
/// This class demonstrates how to create response DTOs that extend base DTOs and include
/// additional properties for display purposes.
/// The DemoThing functionality shown here is just an example to help you learn how to implement
/// your own unique features into the current codebase.
/// </summary>
public class GetDemoThingDto : GetEntityDto
{
    /// <summary>
    /// The name of the demo thing for display purposes.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// The description of the demo thing for display purposes.
    /// </summary>
    public string Description { get; set; }

    /// <summary>
    /// The type/category of the demo thing.
    /// </summary>
    public DemoThingType Type { get; set; }

    /// <summary>
    /// The email address of the user who owns this demo thing.
    /// This is populated by the service layer for display purposes.
    /// </summary>
    public string UserEmail { get; set; }

    /// <summary>
    /// The unique identifier of the user who owns this demo thing.
    /// </summary>
    public string UserId { get; set; }
}

The entity-to-DTO map is DemoThingMappingProfile.cs.

using AutoMapper;
using CanBeYours.Application.Dtos.DemoThings;
using CanBeYours.Core.Domain.DemoThings;

namespace CanBeYours.Infrastructure.Mapping;

/// <summary>
/// AutoMapper profile for DemoThing entity mapping configurations.
/// This class demonstrates how to set up object-to-object mapping between
/// domain entities and DTOs using AutoMapper.
///
/// IMPORTANT: This is an example implementation for learning purposes. Replace
/// DemoThing mappings with your actual business domain entity mappings.
///
/// Key features demonstrated:
/// - Domain entity to DTO mapping
/// - AutoMapper profile configuration
/// - Clean separation between domain and presentation layers
/// </summary>
internal class DemoThingMappingProfile : Profile
{
    /// <summary>
    /// Initializes the mapping profile with DemoThing entity mappings.
    /// This constructor sets up the mapping rules between DemoThing domain entities
    /// and their corresponding DTOs.
    ///
    /// Example: Maps DemoThing entity to GetDemoThingDto for API responses.
    /// </summary>
    public DemoThingMappingProfile()
    {
        CreateMap<DemoThing, GetDemoThingDto>();
    }
}

The handler is GetDemoThingUseCase.cs.

using AutoMapper;
using CanBeYours.Application.Dtos.DemoThings;
using CanBeYours.Application.Exceptions;
using CanBeYours.Application.Helpers;
using CanBeYours.Core.Domain.DemoThings;
using CodeBlock.DevKit.Application.Queries;
using CodeBlock.DevKit.Application.Srvices;
using CodeBlock.DevKit.Contracts.Services;
using MediatR;
using Microsoft.Extensions.Logging;

namespace CanBeYours.Application.UseCases.DemoThings.GetDemoThing;

/// <summary>
/// Use case for retrieving a DemoThing entity by its identifier.
/// This class demonstrates how to implement query handlers that include:
/// - Permission-based access control
/// - User context validation
/// - AutoMapper integration for DTO mapping
/// - Proper error handling with custom exceptions
/// The DemoThing functionality shown here is just an example to help you learn how to implement
/// your own unique features into the current codebase.
/// </summary>
internal class GetDemoThingUseCase : BaseQueryHandler, IRequestHandler<GetDemoThingRequest, GetDemoThingDto>
{
    private readonly IDemoThingRepository _demoThingRepository;
    private readonly ICurrentUser _currentUser;
    private readonly IUserAccessorService _userAccessorService;

    /// <summary>
    /// Initializes a new instance of the GetDemoThingUseCase with the required dependencies.
    /// </summary>
    /// <param name="demoThingRepository">The repository for demo thing operations</param>
    /// <param name="mapper">The AutoMapper instance for object mapping</param>
    /// <param name="logger">The logger for this use case</param>
    /// <param name="currentUser">The current user context</param>
    /// <param name="userAccessorService">The service for accessing user information</param>
    public GetDemoThingUseCase(
        IDemoThingRepository demoThingRepository,
        IMapper mapper,
        ILogger<GetDemoThingUseCase> logger,
        ICurrentUser currentUser,
        IUserAccessorService userAccessorService
    )
        : base(mapper, logger)
    {
        _demoThingRepository = demoThingRepository;
        _currentUser = currentUser;
        _userAccessorService = userAccessorService;
    }

    /// <summary>
    /// Handles the retrieval of a demo thing by its identifier.
    /// This method demonstrates a complete query flow including:
    /// 1. Entity retrieval from repository
    /// 2. Permission-based access control
    /// 3. DTO mapping with AutoMapper
    /// 4. Enriching DTO with additional user information
    /// </summary>
    /// <param name="request">The query request containing the demo thing identifier</param>
    /// <param name="cancellationToken">Cancellation token for the operation</param>
    /// <returns>The demo thing DTO with enriched user information</returns>
    /// <exception cref="DemoThingApplicationExceptions">Thrown when the demo thing is not found</exception>
    public async Task<GetDemoThingDto> Handle(GetDemoThingRequest request, CancellationToken cancellationToken)
    {
        var demoThing = await _demoThingRepository.GetByIdAsync(request.Id);
        if (demoThing == null)
            throw DemoThingApplicationExceptions.DemoThingNotFound(request.Id);

        // Ensures that the current user has permission to access the specified data
        EnsureUserHasAccess(demoThing.UserId, _currentUser, Permissions.Demo.DEMO_THINGS);

        var demoThingDto = _mapper.Map<GetDemoThingDto>(demoThing);

        // Fetch the email associated with the user Id
        demoThingDto.UserEmail = await _userAccessorService.GetEmailByUserIdIfExists(demoThing.UserId);

        return demoThingDto;
    }
}

Using pre-built DevKit services

Use cases constructor-inject interfaces registered by DevKit modules—same as in GetDemoThingUseCase: ICurrentUser for the signed-in user and permission checks, IUserAccessorService to resolve related user data (for example email by user id).

To find which services exist for a module and how to inject them, start with Pre-built module services, then open the Modules section in the documentation navigation and pick the module you need (each module includes an introduction and Features pages that describe injectable services).