Table of Contents

Application services

What is an application service?

In this template, an application service is a facade for a feature area (for example “DemoThings”). It is what clients—REST controllers, Blazor pages, or other hosts—depend on: a small, stable interface with methods that accept DTOs and return Result<T> or Result<CommandResult>.

Application services do not contain the full business workflow themselves. They translate caller-friendly inputs into commands and queries, send them through the DevKit IRequestDispatcher (MediatR), and return the Result produced by the pipeline (including validation errors, domain failures, and success payloads). That keeps orchestration inside use cases while the service stays a thin entry point.

How this connects to the DevKit

The DevKit base type ApplicationService holds a single dependency used by feature services: IRequestDispatcher.

The DevKit type ApplicationService is defined in ApplicationService.cs.

// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

namespace CodeBlock.DevKit.Application.Services;

/// <summary>
/// Base abstract class for application services.
/// Provides common functionality and dependency injection for application service implementations.
/// </summary>
public abstract class ApplicationService
{
    protected readonly IRequestDispatcher _requestDispatcher;

    /// <summary>
    /// Initializes a new instance of ApplicationService with required dependencies.
    /// </summary>
    /// <param name="requestDispatcher">Service for dispatching requests and publishing events.</param>
    protected ApplicationService(IRequestDispatcher requestDispatcher)
    {
        _requestDispatcher = requestDispatcher;
    }
}

IRequestDispatcher is the contract your application service uses to run use cases: SendCommand for anything that inherits BaseCommand, SendQuery for BaseQuery<TResponse>, and PublishEvent when you need to publish a domain event outside a normal handler flow.

The DevKit interface IRequestDispatcher is defined in IRequestDispatcher.cs.

// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Application.Commands;
using CodeBlock.DevKit.Application.Queries;
using CodeBlock.DevKit.Core.Helpers;
using CodeBlock.DevKit.Domain.Events;

namespace CodeBlock.DevKit.Application.Services;

/// <summary>
/// Service interface for dispatching commands, queries, and domain events within the application.
/// Provides centralized request handling and event publishing capabilities.
/// </summary>
public interface IRequestDispatcher
{
    /// <summary>
    /// Sends a command for execution and returns the result.
    /// </summary>
    /// <typeparam name="TCommand">The type of command to execute.</typeparam>
    /// <param name="cmd">The command instance to execute.</param>
    /// <param name="cancellationToken">Optional cancellation token.</param>
    /// <returns>A task containing the command execution result.</returns>

    Task<Result<CommandResult>> SendCommand<TCommand>(TCommand cmd, CancellationToken cancellationToken = default)
        where TCommand : BaseCommand;

    /// <summary>
    /// Sends a query for execution and returns the result.
    /// </summary>
    /// <typeparam name="TQueryResult">The type of result returned by the query.</typeparam>
    /// <param name="query">The query instance to execute.</param>
    /// <param name="cancellationToken">Optional cancellation token.</param>
    /// <returns>A task containing the query result.</returns>

    Task<Result<TQueryResult>> SendQuery<TQueryResult>(BaseQuery<TQueryResult> query, CancellationToken cancellationToken = default);

    /// <summary>
    /// Publishes a domain event for handling by registered event handlers.
    /// </summary>
    /// <param name="event">The domain event to publish.</param>
    /// <param name="cancellationToken">Optional cancellation token.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task PublishEvent(IDomainEvent @event, CancellationToken cancellationToken = default);
}

How to implement an application service

Follow the DemoThings example under Application/Services/DemoThings:

  1. Define a public interface (IYourFeatureService) with async methods returning Task<Result<...>>. Use DTOs for inputs and for query results so callers stay decoupled from domain types.
  2. Implement the interface with an internal class that inherits ApplicationService and takes IRequestDispatcher in the constructor (pass it to base(...)).
  3. In each method, build the appropriate BaseCommand or BaseQuery<T> instance from route/body parameters or DTOs, then return await _requestDispatcher.SendCommand(...) or SendQuery(...).
  4. Register the interface and implementation in Application/Startup.cs with AddScoped<...>(). Use RegisterHandlers(typeof(Startup)) so MediatR picks up your use case handlers in the same assembly.

Use case handlers are covered in Use cases. For how failures surface on Result, see Exception handling.

Example: IDemoThingService and DemoThingService

The public contract is IDemoThingService.cs.

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

namespace CanBeYours.Application.Services.DemoThings;

/// <summary>
/// Service interface for managing DemoThing entities.
/// This interface demonstrates how to define service contracts with proper return types
/// and async operations following CQRS patterns.
/// 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 interface IDemoThingService
{
    /// <summary>
    /// Retrieves a demo thing by its unique identifier.
    /// </summary>
    /// <param name="id">The unique identifier of the demo thing</param>
    /// <returns>A result containing the demo thing data or an error</returns>
    Task<Result<GetDemoThingDto>> GetDemoThing(string id);

    /// <summary>
    /// Creates a new demo thing with the specified data.
    /// </summary>
    /// <param name="input">The data for creating the new demo thing</param>
    /// <returns>A result containing the command execution result or an error</returns>
    Task<Result<CommandResult>> CreateDemoThing(CreateDemoThingDto input);

    /// <summary>
    /// Updates an existing demo thing with new data.
    /// </summary>
    /// <param name="id">The unique identifier of the demo thing to update</param>
    /// <param name="input">The new data for the demo thing</param>
    /// <returns>A result containing the command execution result or an error</returns>
    Task<Result<CommandResult>> UpdateDemoThing(string id, UpdateDemoThingDto input);

    /// <summary>
    /// Searches for demo things based on specified criteria.
    /// </summary>
    /// <param name="input">The search criteria and pagination parameters</param>
    /// <returns>A result containing the search results with pagination information</returns>
    Task<Result<SearchOutputDto<GetDemoThingDto>>> SearchDemoThings(SearchDemoThingsInputDto input);
}

The implementation is DemoThingService.cs.

using CanBeYours.Application.Dtos.DemoThings;
using CanBeYours.Application.UseCases.DemoThings.CreateDemoThing;
using CanBeYours.Application.UseCases.DemoThings.GetDemoThing;
using CanBeYours.Application.UseCases.DemoThings.SearchDemoThings;
using CanBeYours.Application.UseCases.DemoThings.UpdateDemoThing;
using CodeBlock.DevKit.Application.Srvices;
using CodeBlock.DevKit.Contracts.Dtos;
using CodeBlock.DevKit.Core.Helpers;

namespace CanBeYours.Application.Services.DemoThings;

/// <summary>
/// Application service for managing DemoThing entities.
/// This class demonstrates how to implement application services that coordinate between
/// different use cases and provide a clean API for the presentation layer.
/// 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 DemoThingService : ApplicationService, IDemoThingService
{
    /// <summary>
    /// Initializes a new instance of the DemoThingService with the required dependencies.
    /// </summary>
    /// <param name="requestDispatcher">The request dispatcher for sending commands and queries</param>
    public DemoThingService(IRequestDispatcher requestDispatcher)
        : base(requestDispatcher) { }

    /// <summary>
    /// Retrieves a demo thing by its unique identifier.
    /// This method demonstrates how to delegate to use cases using the request dispatcher.
    /// </summary>
    /// <param name="id">The unique identifier of the demo thing</param>
    /// <returns>A result containing the demo thing data or an error</returns>
    public async Task<Result<GetDemoThingDto>> GetDemoThing(string id)
    {
        return await _requestDispatcher.SendQuery(new GetDemoThingRequest(id));
    }

    /// <summary>
    /// Creates a new demo thing with the specified data.
    /// This method demonstrates how to delegate to use cases using the request dispatcher.
    /// </summary>
    /// <param name="input">The data for creating the new demo thing</param>
    /// <returns>A result containing the command execution result or an error</returns>
    public async Task<Result<CommandResult>> CreateDemoThing(CreateDemoThingDto input)
    {
        return await _requestDispatcher.SendCommand(new CreateDemoThingRequest(input.Name, input.Description, input.Type));
    }

    /// <summary>
    /// Updates an existing demo thing with new data.
    /// This method demonstrates how to delegate to use cases using the request dispatcher.
    /// </summary>
    /// <param name="id">The unique identifier of the demo thing to update</param>
    /// <param name="input">The new data for the demo thing</param>
    /// <returns>A result containing the command execution result or an error</returns>
    public async Task<Result<CommandResult>> UpdateDemoThing(string id, UpdateDemoThingDto input)
    {
        return await _requestDispatcher.SendCommand(new UpdateDemoThingRequest(id, input.Name, input.Description, input.Type));
    }

    /// <summary>
    /// Searches for demo things based on specified criteria.
    /// This method demonstrates how to delegate to use cases using the request dispatcher
    /// and map complex input parameters.
    /// </summary>
    /// <param name="input">The search criteria and pagination parameters</param>
    /// <returns>A result containing the search results with pagination information</returns>
    public async Task<Result<SearchOutputDto<GetDemoThingDto>>> SearchDemoThings(SearchDemoThingsInputDto input)
    {
        return await _requestDispatcher.SendQuery(
            new SearchDemoThingsRequest(
                input.Term,
                input.Type,
                input.PageNumber,
                input.RecordsPerPage,
                input.SortOrder,
                input.FromDateTime,
                input.ToDateTime
            )
        );
    }
}

Registering the service

Application module registration is in Startup.cs.

using CanBeYours.Application.Services.DemoThings;
using CodeBlock.DevKit.Application.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace CanBeYours.Application;

/// <summary>
/// Startup configuration class for the Application module.
/// This class demonstrates how to configure dependency injection for application services.
/// 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 static class Startup
{
    /// <summary>
    /// Registers all application services and handlers with the dependency injection container.
    /// This method is called during application startup to configure the Application module.
    /// </summary>
    /// <param name="services">The service collection to register services with</param>
    public static void AddApplicationModule(this IServiceCollection services)
    {
        services.RegisterHandlers(typeof(Startup));

        services.AddScoped<IDemoThingService, DemoThingService>();
    }
}

Add your own line next to IDemoThingService: services.AddScoped<IYourFeatureService, YourFeatureService>();

Using an application service from a web client (REST API)

API controllers inject the application service and return its Result directly so the host can apply a consistent response shape (success payload vs. errors).

The template example is DemoThingsController.cs.

using CanBeYours.Application.Dtos.DemoThings;
using CanBeYours.Application.Services.DemoThings;
using CanBeYours.Core.Domain.DemoThings;
using CodeBlock.DevKit.Contracts.Dtos;
using CodeBlock.DevKit.Core.Helpers;
using CodeBlock.DevKit.Web.Api.Filters;
using CodeBlock.DevKit.Web.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace CanBeYours.Api.Controllers;

/// <summary>
/// API controller for managing DemoThings - serves as an example of how to implement
/// CRUD operations in your own controllers. This controller demonstrates:
/// - Standard REST API patterns (GET, POST, PUT)
/// - Authorization using policies
/// - Input validation and DTOs
/// - Service layer integration
///
/// The current functionality is just for you to learn and understand how to implement
/// your own unique features into the current codebase.
/// </summary>
[Tags("DemoThings")]
[Route("demo-things")]
[Authorize(Policies.ADMIN_ROLE)]
public class DemoThingsController : BaseApiController
{
    private readonly IDemoThingService _demoThingService;

    /// <summary>
    /// Initializes a new instance of the DemoThingsController.
    /// </summary>
    /// <param name="demoThingService">Service for managing demo things</param>
    public DemoThingsController(IDemoThingService demoThingService)
    {
        _demoThingService = demoThingService;
    }

    /// <summary>
    /// Retrieves a demo thing by its unique identifier.
    /// Example: GET /demo-things/123
    /// </summary>
    /// <param name="id">Unique identifier of the demo thing</param>
    /// <returns>Demo thing data wrapped in a Result object</returns>
    [HttpGet]
    [Route("{id}")]
    public async Task<Result<GetDemoThingDto>> Get(string id)
    {
        return await _demoThingService.GetDemoThing(id);
    }

    /// <summary>
    /// Creates a new demo thing.
    /// Example: POST /demo-things with CreateDemoThingDto in body
    /// </summary>
    /// <param name="input">Data for creating the demo thing</param>
    /// <returns>Command result indicating success/failure</returns>
    [HttpPost]
    public async Task<Result<CommandResult>> Post([FromBody] CreateDemoThingDto input)
    {
        return await _demoThingService.CreateDemoThing(input);
    }

    /// <summary>
    /// Updates an existing demo thing by its identifier.
    /// Example: PUT /demo-things/123 with UpdateDemoThingDto in body
    /// </summary>
    /// <param name="id">Unique identifier of the demo thing to update</param>
    /// <param name="input">Updated data for the demo thing</param>
    /// <returns>Command result indicating success/failure</returns>
    [Route("{id}")]
    [HttpPut]
    public async Task<Result<CommandResult>> Put(string id, [FromBody] UpdateDemoThingDto input)
    {
        return await _demoThingService.UpdateDemoThing(id, input);
    }

    /// <summary>
    /// Searches for demo things with pagination, sorting, and filtering options.
    /// Example: GET /demo-things/1/10/asc?term=search&type=Type1&fromDateTime=2024-01-01
    /// </summary>
    /// <param name="pageNumber">Page number for pagination (1-based)</param>
    /// <param name="recordsPerPage">Number of records per page</param>
    /// <param name="sortOrder">Sort order (asc/desc)</param>
    /// <param name="term">Search term for filtering by name/description</param>
    /// <param name="type">Optional filter by demo thing type</param>
    /// <param name="fromDateTime">Optional start date filter</param>
    /// <param name="toDateTime">Optional end date filter</param>
    /// <returns>Paginated search results with demo things</returns>
    [HttpGet]
    [Route("{pageNumber}/{recordsPerPage}/{sortOrder}")]
    public async Task<Result<SearchOutputDto<GetDemoThingDto>>> Get(
        int pageNumber,
        int recordsPerPage,
        SortOrder sortOrder,
        [FromQuery] string term = null,
        [FromQuery] DemoThingType? type = null,
        [FromQuery] DateTime? fromDateTime = null,
        [FromQuery] DateTime? toDateTime = null
    )
    {
        var dto = new SearchDemoThingsInputDto
        {
            Term = term,
            PageNumber = pageNumber,
            RecordsPerPage = recordsPerPage,
            FromDateTime = fromDateTime,
            ToDateTime = toDateTime,
            SortOrder = sortOrder,
            Type = type,
        };
        return await _demoThingService.SearchDemoThings(dto);
    }
}

The same IDemoThingService (or your equivalent) can be injected into Blazor components or other clients that reference the Application project or a shared contracts surface—always check Result.IsSuccess and Result.Errors (or your UI binding helpers) after each call.

  • Use cases — commands, queries, and handlers behind the dispatcher.
  • Exception handling — how managed exceptions become Result.Errors.
  • Pre-built module services — injecting DevKit module services into use cases (and indirectly benefiting callers of your application services).