Skip to content

Commit

Permalink
Improved the customisation of API URIs per domain and subdomain
Browse files Browse the repository at this point in the history
  • Loading branch information
panosru committed Jan 6, 2024
1 parent e72d444 commit cf2b4a8
Show file tree
Hide file tree
Showing 15 changed files with 112 additions and 38 deletions.
7 changes: 7 additions & 0 deletions CleanDDDArchitecture.sln
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Domains\Shar
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Domains\Shared\Infrastructure\Infrastructure.csproj", "{AAB306BA-578A-4901-B3EA-937A5B30F04B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Domains\Todo\Hosts\RestApi\Core\Core.csproj", "{8222BDF6-58A8-4A51-8E84-0E822209D624}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -515,6 +517,10 @@ Global
{AAB306BA-578A-4901-B3EA-937A5B30F04B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAB306BA-578A-4901-B3EA-937A5B30F04B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAB306BA-578A-4901-B3EA-937A5B30F04B}.Release|Any CPU.Build.0 = Release|Any CPU
{8222BDF6-58A8-4A51-8E84-0E822209D624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8222BDF6-58A8-4A51-8E84-0E822209D624}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8222BDF6-58A8-4A51-8E84-0E822209D624}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8222BDF6-58A8-4A51-8E84-0E822209D624}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -632,6 +638,7 @@ Global
{CA1238B6-798A-436F-936E-C0CCFC768D0A} = {4A502855-3537-4B0D-9B91-D490E67A17BE}
{AAB306BA-578A-4901-B3EA-937A5B30F04B} = {4A502855-3537-4B0D-9B91-D490E67A17BE}
{76B2D7ED-67AF-48CA-A107-263329651CD0} = {4E4A33E0-8A4D-4E9E-99CA-C9B8CD8378CF}
{8222BDF6-58A8-4A51-8E84-0E822209D624} = {981EBF63-EE0D-41CB-A3F0-EB77E40D01FD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3CB609D9-5D54-4C11-A371-DAAC8B74E430}
Expand Down
8 changes: 8 additions & 0 deletions Domains/Todo/Core/ITodoDomainConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Aviant.Core.DDD.Domain;

namespace CleanDDDArchitecture.Domains.Todo.Core;

public interface ITodoDomainConfiguration : IDomainConfigurationContainer
{
const string RouteSegment = "todo";
}
39 changes: 39 additions & 0 deletions Domains/Todo/Hosts/RestApi/Core/ApiController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Aviant.Application.Persistence.Orchestration;
using Aviant.Application.UseCases;
using CleanDDDArchitecture.Domains.Todo.Application.Persistence;
using CleanDDDArchitecture.Domains.Todo.Core;
using CleanDDDArchitecture.Hosts.RestApi.Core.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace CleanDDDArchitecture.Domains.Todo.Hosts.RestApi.Core;

/// <inheritdoc />
/// <summary>
/// Todo endpoints
/// </summary>
[RouteSegment(ITodoDomainConfiguration.RouteSegment)]
public abstract class ApiController : CleanDDDArchitecture.Hosts.RestApi.Core.Controllers.ApiController
{
/// <summary>
/// </summary>
protected new IOrchestrator<ITodoDbContextWrite> Orchestrator =>
HttpContext.RequestServices.GetRequiredService<IOrchestrator<ITodoDbContextWrite>>();
}

/// <inheritdoc />
/// <summary>
/// Todo endpoint
/// </summary>
/// <typeparam name="TUseCase"></typeparam>
/// <typeparam name="TUseCaseOutput"></typeparam>
[RouteSegment(ITodoDomainConfiguration.RouteSegment)]
public abstract class ApiController<TUseCase, TUseCaseOutput>
: CleanDDDArchitecture.Hosts.RestApi.Core.Controllers.ApiController<TUseCase, TUseCaseOutput>
where TUseCase : class, IUseCase<TUseCaseOutput>
where TUseCaseOutput : class, IUseCaseOutput
{
/// <inheritdoc />
protected ApiController(TUseCase useCase)
: base(useCase)
{ }
}
15 changes: 15 additions & 0 deletions Domains/Todo/Hosts/RestApi/Core/Core.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>CleanDDDArchitecture.Domains.Todo.Hosts.RestApi.Core</RootNamespace>
<AssemblyName>CleanDDDArchitecture.Domains.Todo.Hosts.RestApi.Core</AssemblyName>
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\Hosts\RestApi\Core\Core.csproj" />
<ProjectReference Include="..\..\..\CrossCutting\CrossCutting.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\CrossCutting\CrossCutting.csproj" />
<ProjectReference Include="..\..\..\SubDomains\TodoItem\Hosts\RestApi\Presentation\Presentation.csproj" />
<ProjectReference Include="..\..\..\SubDomains\TodoList\Hosts\RestApi\Presentation\Presentation.csproj" />
</ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion Domains/Todo/Infrastructure/TodoDomainConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Aviant.Infrastructure.DDD.CrossCutting;
using CleanDDDArchitecture.Domains.Todo.Core;
using Microsoft.Extensions.Configuration;

namespace CleanDDDArchitecture.Domains.Todo.Infrastructure;

public sealed class TodoDomainConfiguration : DomainConfigurationContainer
public sealed class TodoDomainConfiguration : DomainConfigurationContainer, ITodoDomainConfiguration
{
/// <inheritdoc />
public TodoDomainConfiguration(IConfiguration configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Aviant.Core.DDD.Domain;

namespace CleanDDDArchitecture.Domains.Todo.SubDomains.TodoItem.Core;

public interface ITodoItemDomainConfiguration : IDomainConfigurationContainer
{
const string RouteSegment = "item";
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
using Aviant.Application.Persistence.Orchestration;
using Aviant.Application.UseCases;
using Microsoft.Extensions.DependencyInjection;
using CleanDDDArchitecture.Domains.Todo.Application.Persistence;
using CleanDDDArchitecture.Domains.Todo.SubDomains.TodoItem.Core;
using CleanDDDArchitecture.Hosts.RestApi.Core.Routing;

namespace CleanDDDArchitecture.Domains.Todo.SubDomains.TodoItem.Hosts.RestApi.Presentation;

/// <inheritdoc />
/// <summary>
/// Todo Item endpoints
/// </summary>
public abstract class ApiController : CleanDDDArchitecture.Hosts.RestApi.Core.Controllers.ApiController
{
/// <summary>
/// </summary>
protected new IOrchestrator<ITodoDbContextWrite> Orchestrator =>
HttpContext.RequestServices.GetRequiredService<IOrchestrator<ITodoDbContextWrite>>();
}
[RouteSegment(ITodoItemDomainConfiguration.RouteSegment)]
public abstract class ApiController : Todo.Hosts.RestApi.Core.ApiController;

/// <inheritdoc />
/// <summary>
/// Todo Item endpoint
/// </summary>
/// <typeparam name="TUseCase"></typeparam>
/// <typeparam name="TUseCaseOutput"></typeparam>
[RouteSegment(ITodoItemDomainConfiguration.RouteSegment)]
public abstract class ApiController<TUseCase, TUseCaseOutput>
: CleanDDDArchitecture.Hosts.RestApi.Core.Controllers.ApiController<TUseCase, TUseCaseOutput>
: Todo.Hosts.RestApi.Core.ApiController<TUseCase, TUseCaseOutput>
where TUseCase : class, IUseCase<TUseCaseOutput>
where TUseCaseOutput : class, IUseCaseOutput
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\..\Hosts\RestApi\Core\Core.csproj" />
<ProjectReference Include="..\..\..\..\..\Hosts\RestApi\Core\Core.csproj" />
<ProjectReference Include="..\..\..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Aviant.Infrastructure.DDD.CrossCutting;
using CleanDDDArchitecture.Domains.Todo.SubDomains.TodoItem.Core;
using Microsoft.Extensions.Configuration;

namespace CleanDDDArchitecture.Domains.Todo.SubDomains.TodoItem.Infrastructure;

public sealed class TodoItemDomainConfiguration : DomainConfigurationContainer
public sealed class TodoItemDomainConfiguration : DomainConfigurationContainer, ITodoItemDomainConfiguration
{
/// <inheritdoc />
public TodoItemDomainConfiguration(IConfiguration configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Aviant.Core.DDD.Domain;

namespace CleanDDDArchitecture.Domains.Todo.SubDomains.TodoList.Core;

public interface ITodoListDomainConfiguration : IDomainConfigurationContainer
{
const string RouteSegment = "list";
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
using Aviant.Application.Persistence.Orchestration;
using Aviant.Application.UseCases;
using Microsoft.Extensions.DependencyInjection;
using CleanDDDArchitecture.Domains.Todo.Application.Persistence;
using CleanDDDArchitecture.Domains.Todo.SubDomains.TodoList.Core;
using CleanDDDArchitecture.Hosts.RestApi.Core.Routing;

namespace CleanDDDArchitecture.Domains.Todo.SubDomains.TodoList.Hosts.RestApi.Presentation;

/// <inheritdoc />
/// <summary>
/// Todo Lists endpoints
/// </summary>
public abstract class ApiController : CleanDDDArchitecture.Hosts.RestApi.Core.Controllers.ApiController
{
/// <summary>
/// </summary>
protected new IOrchestrator<ITodoDbContextWrite> Orchestrator =>
HttpContext.RequestServices.GetRequiredService<IOrchestrator<ITodoDbContextWrite>>();
}
[RouteSegment(ITodoListDomainConfiguration.RouteSegment)]
public abstract class ApiController : Todo.Hosts.RestApi.Core.ApiController;

/// <inheritdoc />
/// <summary>
/// Todo Lists endpoints
/// </summary>
/// <typeparam name="TUseCase"></typeparam>
/// <typeparam name="TUseCaseOutput"></typeparam>
[RouteSegment(ITodoListDomainConfiguration.RouteSegment)]
public abstract class ApiController<TUseCase, TUseCaseOutput>
: CleanDDDArchitecture.Hosts.RestApi.Core.Controllers.ApiController<TUseCase, TUseCaseOutput>
: Todo.Hosts.RestApi.Core.ApiController<TUseCase, TUseCaseOutput>
where TUseCase : class, IUseCase<TUseCaseOutput>
where TUseCaseOutput : class, IUseCaseOutput
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\..\Hosts\RestApi\Core\Core.csproj" />
<ProjectReference Include="..\..\..\..\..\Hosts\RestApi\Core\Core.csproj" />
<ProjectReference Include="..\..\..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Aviant.Infrastructure.DDD.CrossCutting;
using CleanDDDArchitecture.Domains.Todo.SubDomains.TodoList.Core;
using Microsoft.Extensions.Configuration;

namespace CleanDDDArchitecture.Domains.Todo.SubDomains.TodoList.Infrastructure;

public sealed class TodoListDomainConfiguration : DomainConfigurationContainer
public sealed class TodoListDomainConfiguration : DomainConfigurationContainer, ITodoListDomainConfiguration
{
/// <inheritdoc />
public TodoListDomainConfiguration(IConfiguration configuration)
Expand Down
17 changes: 7 additions & 10 deletions Hosts/RestApi/Presentation/Routing/CustomRouteConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,23 @@ private string GetRouteTemplate(Type controllerType)
// Traverse the controller's inheritance hierarchy to build the route template.
while (controllerType != null && controllerType != typeof(ControllerBase))
{
// Skip the custom base controller types to avoid adding their names to the route.
if (IsBaseControllerType(controllerType))
{
// Skip custom base controller types in route template generation.
controllerType = controllerType.BaseType;
continue;
}

// Check for and use the RouteSegmentAttribute if present.
// Check for the presence of the RouteSegmentAttribute.
var segmentAttribute = controllerType.GetCustomAttribute<RouteSegmentAttribute>();
if (segmentAttribute != null)
{
// Clear existing segments and use the custom segment from the attribute.
// This ensures the route only consists of the segment defined in the attribute.
segments.Clear();
// Add the custom segment from the attribute to the segments list.
segments.Insert(0, segmentAttribute.Segment);
break; // No need to check further up the hierarchy.
}

// If no RouteSegmentAttribute is present, and the parent type is not a base controller,
// use the default controller name as a segment in the route.
if (!IsBaseControllerType(controllerType.BaseType))
else if (!IsBaseControllerType(controllerType.BaseType))
{
// Use the default controller name as a segment if no RouteSegmentAttribute is present.
var segmentName = GetSegmentNameFromType(controllerType);
segments.Insert(0, segmentName);
}
Expand All @@ -85,6 +80,7 @@ private string GetRouteTemplate(Type controllerType)
return string.Join("/", segments);
}


/// <summary>
/// Checks if a given type is one of the base controller types.
/// </summary>
Expand All @@ -95,6 +91,7 @@ private bool IsBaseControllerType(Type controllerType)
// A controller type is considered a base controller if it is either ApiController or
// a generic variant of ApiController<TUseCase, TUseCaseOutput>.
return controllerType == typeof(ApiController) ||
controllerType == typeof(ApiSharedController) ||
(controllerType.IsGenericType &&
controllerType.GetGenericTypeDefinition() == typeof(ApiController<,>));
}
Expand Down

0 comments on commit cf2b4a8

Please sign in to comment.