How the ServiceDescription Attribute Powers Dependency Injection in AntSK

The ServiceDescription attribute in AntSK enables convention-based dependency injection by allowing service implementations to self-declare their interface contracts and lifetimes, eliminating manual registration boilerplate in Program.cs.

AntSK is an open-source knowledge management platform that leverages a custom dependency injection system to keep its architecture modular and maintainable. At the heart of this system lies the ServiceDescription attribute, a metadata marker that transforms ordinary classes into self-registering services. By annotating implementations with this attribute, developers can automate service registration while maintaining explicit control over object lifetimes.

What Is the ServiceDescription Attribute?

The ServiceDescription attribute is a custom C# attribute defined in src/AntSK.Domain/Common/DependencyInjection/ServiceDescriptionAttribute.cs. It acts as a declarative marker that attaches metadata to service implementation classes, telling AntSK's DI container how to register and manage instances of those classes.

Core Metadata: ServiceType and Lifetime

The attribute captures two critical pieces of information:

  • ServiceType: The interface or base type that the implementation fulfills (the contract)
  • Lifetime: The object lifecycle—Scoped, Singleton, or Transient
// src/AntSK.Domain/Common/DependencyInjection/ServiceDescriptionAttribute.cs
public class ServiceDescriptionAttribute : Attribute
{
    public ServiceDescriptionAttribute(Type serviceType, ServiceLifetime lifetime)
    {
        ServiceType = serviceType;
        Lifetime   = lifetime;
    }

    public Type ServiceType { get; set; }
    public ServiceLifetime Lifetime { get; set; }
}

How ServiceDescription Enables Convention-Based DI

Rather than requiring explicit registration calls in Program.cs for every service, AntSK uses assembly scanning to discover and register attributed classes automatically. This convention-based approach reduces boilerplate while keeping service configuration visible at the implementation site.

The Attribute Definition

The attribute itself is lightweight, inheriting from System.Attribute and storing only the two properties needed for registration. When applied to a concrete class, it transforms that class into a self-describing service that carries its own registration instructions.

Assembly Scanning and Registration

The extension method AddServicesFromAssemblies in src/AntSK.Domain/Common/DependencyInjection/DependencyInjection.cs performs the actual registration. It loads specified assemblies, iterates through types, and registers any concrete class marked with ServiceDescriptionAttribute using the lifetime specified in the attribute.

// src/AntSK.Domain/Common/DependencyInjection/DependencyInjection.cs
public static IServiceCollection AddServicesFromAssemblies(this IServiceCollection services, params string[] assemblies)
{
    Type attributeType = typeof(ServiceDescriptionAttribute);
    foreach (var item in assemblies)
    {
        Assembly assembly = Assembly.Load(item);
        foreach (var classType in assembly.GetTypes())
        {
            if (!classType.IsAbstract && classType.IsClass && classType.IsDefined(attributeType, false))
            {
                var serviceAttribute = classType.GetCustomAttribute(attributeType) as ServiceDescriptionAttribute;
                switch (serviceAttribute!.Lifetime)
                {
                    case ServiceLifetime.Scoped:
                        services.AddScoped(serviceAttribute.ServiceType, classType); break;
                    case ServiceLifetime.Singleton:
                        services.AddSingleton(serviceAttribute.ServiceType, classType); break;
                    case ServiceLifetime.Transient:
                        services.AddTransient(serviceAttribute.ServiceType, classType); break;
                }
            }
        }
    }
    return services;
}

Practical Usage Examples

Implementing services with the ServiceDescription attribute requires minimal ceremony. Developers annotate the implementation class with the attribute, specify the interface and lifetime, and the framework handles registration automatically.

Declaring Services with ServiceDescription

To register a service, apply the attribute to the concrete implementation class, passing the contract type and desired lifetime:

// src/AntSK/Services/Template/UserService.cs
[ServiceDescription(typeof(IUserService), ServiceLifetime.Scoped)]
public class UserService : IUserService 
{ 
    // Implementation details...
}

This declaration registers UserService as the implementation for IUserService with a scoped lifetime, meaning a new instance is created for each HTTP request.

Other lifetime configurations follow the same pattern:

// Declaring a singleton service
[ServiceDescription(typeof(IApiService), ServiceLifetime.Singleton)]
public class ApiService : IApiService
{
    // Single instance shared across the application
}

// Declaring a transient service
[ServiceDescription(typeof(IEmailSender), ServiceLifetime.Transient)]
public class EmailSender : IEmailSender
{
    // New instance created every time the service is requested
}

Configuring the Host

The host application only needs to specify which assemblies to scan. In Program.cs or the application host setup, call AddServicesFromAssemblies with the assembly names containing your services:

// Startup (Program.cs) – only load assemblies, no per-service calls
var builder = DistributedApplication.CreateBuilder(args);
builder.Services.AddServicesFromAssemblies(
    "AntSK",               // main project
    "AntSK.Domain",        // domain layer
    "AntSK.Services");    // service layer
builder.Build().Run();

This approach keeps the host configuration minimal and focused on composition rather than registration details.

Summary

The ServiceDescription attribute transforms AntSK's dependency injection from an explicit registration model into a convention-driven system:

  • Self-documenting services carry their own contract and lifetime metadata via the ServiceDescription attribute defined in ServiceDescriptionAttribute.cs
  • Automatic discovery through AddServicesFromAssemblies eliminates boilerplate registration code in Program.cs
  • Explicit lifetime control allows developers to specify Scoped, Singleton, or Transient behavior at the implementation level
  • Modular architecture supports loading services from multiple assemblies without coupling the host to specific implementation details

Frequently Asked Questions

What is the ServiceDescription attribute in AntSK?

The ServiceDescription attribute is a custom metadata marker defined in src/AntSK.Domain/Common/DependencyInjection/ServiceDescriptionAttribute.cs that attaches dependency injection configuration directly to service implementation classes. It stores the service contract type (ServiceType) and the object lifetime (ServiceLifetime), allowing the framework to automatically register the class with the DI container without explicit services.Add... calls in the host configuration.

How does ServiceDescription differ from standard ASP.NET Core DI registration?

Standard ASP.NET Core DI requires explicit registration in Program.cs or Startup.cs using methods like services.AddScoped<IMyService, MyService>(). In contrast, AntSK's ServiceDescription attribute enables convention-based registration where the service class declares its own contract and lifetime via the attribute, and the AddServicesFromAssemblies extension method scans assemblies to perform registration automatically. This reduces boilerplate and keeps service configuration co-located with the implementation.

Which service lifetimes does ServiceDescription support?

The ServiceDescription attribute supports all three standard ASP.NET Core service lifetimes via the ServiceLifetime enum: Scoped (new instance per request), Singleton (single instance shared across the application), and Transient (new instance every time the service is resolved). These are specified as the second parameter to the attribute constructor, such as [ServiceDescription(typeof(IMyService), ServiceLifetime.Scoped)].

How does AntSK scan for services across multiple assemblies?

AntSK uses the AddServicesFromAssemblies extension method defined in src/AntSK.Domain/Common/DependencyInjection/DependencyInjection.cs to scan for services. This method accepts assembly names as string parameters, loads each assembly using Assembly.Load(), iterates through all types, and identifies concrete classes marked with the ServiceDescription attribute. It then registers each discovered service with the IServiceCollection using the lifetime specified in the attribute, enabling modular composition where different layers (Domain, Services) can be loaded without the host knowing specific implementation details.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →