Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
changelog:
exclude:
labels:
- notes:ignore
authors:
- dependabot
categories:
- title: 💥 Breaking Changes
labels:
- notes:breaking-change
- title: 🎉 New Features
labels:
- notes:new-feature
- title: 🐞 Bug Fixes
labels:
- notes:bug-fix
- title: 💪 Other Changes
labels:
- "*"
2 changes: 2 additions & 0 deletions .github/workflows/ci_build.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json

name: build

on:
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json

name: publish

on:
workflow_dispatch: # Allow running the workflow manually from the GitHub UI
push:
branches:
- main # Run the workflow when pushing to the main branch
- 'releases/**' # Run the workflow when pushing to a release branch
pull_request:
branches:
- '*' # Run the workflow for all pull requests
release:
types:
- published # Run the workflow when a new GitHub release is published
# Publish to nuget.org will only occur when a new release is published

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_NOLOGO: true
NuGetDirectory: ${{ github.workspace}}/nuget

jobs:
create_nuget:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Get all history to allow automatic versioning using MinVer

- name: Setup .NET
uses: actions/setup-dotnet@v4

- run: dotnet pack --configuration Release --output ${{ env.NuGetDirectory }}

# Publish the NuGet packages as an artifact, so they can be used in the following jobs
- uses: actions/upload-artifact@v4
with:
name: nuget
if-no-files-found: error
retention-days: 7
path: ${{ env.NuGetDirectory }}/*.nupkg

run_test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v4
- name: Run tests
run: dotnet test --configuration Release

deploy:
# Publish only when creating a GitHub Release
# https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository
# You can update this logic if you want to manage releases differently
if: github.event_name == 'release'
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
needs: [ create_nuget, run_test ]
steps:
# Download the NuGet package created in the previous job
- uses: actions/download-artifact@v4
with:
name: nuget
path: ${{ env.NuGetDirectory }}

- name: Setup .NET Core
uses: actions/setup-dotnet@v4

# Publish all NuGet packages to NuGet.org
# Use --skip-duplicate to prevent errors if a package with the same version already exists.
# If you retry a failed workflow, already published packages will be skipped without error.
- name: Publish NuGet package
run: |
foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) {
dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
}
84 changes: 83 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,85 @@
# kernel
# feature[23] Shared Kernel

[![build](https://github.com/feature23/kernel/actions/workflows/ci_build.yml/badge.svg)](https://github.com/feature23/kernel/actions/workflows/ci_build.yml)

A library of reusable types for implementing Clean Architecture in .NET and ASP.NET Core applications, based heavily on the work of [Steve Smith](https://github.com/ardalis) in the following open-sourcee projects:
- [ASP.NET Core Template](https://github.com/ardalis/CleanArchitecture)
- [Shared Kernel](https://github.com/ardalis/Ardalis.SharedKernel)

For the core functionality, only the `F23.Kernel` library is needed. This library provides types for events, results, query and command handlers, validation, and messaging. For smoother integration with ASP.NET Core, the `F23.Kernel.AspNetCore` library can be used for easily mapping between core result types and ASP.NET Core `IActionResult` and model state.

> **WARNING:** This library is currently in a pre-release state, and breaking changes may occur before reaching version 1.0.

## NuGet Installation
### Core Package
```powershell
dotnet add package F23.Kernel
```

### ASP.NET Core Helper Package
```powershell
dotnet add package F23.Kernel.AspNetCore
```

## Examples

### Query Handler
```csharp
class GetWeatherForecastQueryResult
{
public required IReadOnlyList<WeatherForecast> Forecast { get; init; }
}

class GetWeatherForecastQuery : IQuery<GetWeatherForecastQueryResult>
{
public int DaysIntoTheFuture { get; init; } = 5;
}

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

class GetWeatherForecastQueryHandler(IValidator<GetWeatherForecastQuery> validator, IWeatherForecastRepository repository)
: IQueryHandler<GetWeatherForecastQuery, GetWeatherForecastQueryResult>
{
public async Task<Result<GetWeatherForecastQueryResult>> Handle(GetWeatherForecastQuery query, CancellationToken cancellationToken = default)
{
if (await validator.Validate(query, cancellationToken) is ValidationFailedResult failed)
{
return Result<GetWeatherForecastQueryResult>.ValidationFailed(failed.Errors);
}

var forecast = await repository.GetForecast(query.DaysIntoTheFuture, cancellationToken);

var result = new GetWeatherForecastQueryResult
{
Forecast = forecast
};

return Result<GetWeatherForecastQueryResult>.Success(result);
}
}
```

#### Program.cs
```csharp
builder.Services.RegisterQueryHandler<GetWeatherForecastQuery, GetWeatherForecastQueryResult, GetWeatherForecastQueryHandler>();

// Other code omitted for brevity

app.MapGet("/weatherforecast", async (IQueryHandler<GetWeatherForecastQuery, GetWeatherForecastQueryResult> queryHandler) =>
{
var result = await queryHandler.Handle(new GetWeatherForecastQuery());

return result.ToMinimalApiResult();
})
.WithName("GetWeatherForecast")
.WithOpenApi();
```

### Command Handler
> TODO

### Event Sourcing
> TODO
1 change: 0 additions & 1 deletion src/.idea/.idea.F23.Kernel/.idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,37 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.1.0</Version>
<Authors>feature[23]</Authors>
<Copyright>feature[23]</Copyright>
<PackageProjectUrl>https://github.com/feature23/kernel</PackageProjectUrl>
<RepositoryUrl>https://github.com/feature23/kernel</RepositoryUrl>
<PackageLicenseUrl>https://github.com/feature23/kernel/blob/main/LICENSE</PackageLicenseUrl>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DocumentationFile>bin\Debug\net8.0\F23.Kernel.AspNetCore.xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\net8.0\F23.Kernel.AspNetCore.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="F23.Kernel.Tests" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\F23.Kernel\F23.Kernel.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="F23.Hateoas" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
<!-- <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />-->
</ItemGroup>

</Project>
23 changes: 22 additions & 1 deletion src/F23.Kernel.AspNetCore/ModelStateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,32 @@

namespace F23.Kernel.AspNetCore;

/// <summary>
/// Provides extension methods for working with the <see cref="Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"/> class
/// to map <see cref="ValidationError"/> to the appropriate structure for HTTP response.
/// </summary>
public static class ModelStateExtensions
{
public static void AddModelErrors(this ModelStateDictionary modelState, string key, IEnumerable<ValidationError> errors)
/// <summary>
/// Adds multiple validation errors to the <see cref="ModelStateDictionary"/> for a specified key.
/// </summary>
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance where errors will be added.</param>
/// <param name="key">The key to associate with each of the validation errors.</param>
/// <param name="errors">The collection of <see cref="ValidationError"/> objects to add to the model state.</param>
public static void AddModelErrors(this ModelStateDictionary modelState, string key,
IEnumerable<ValidationError> errors)
{
foreach (var error in errors)
{
modelState.AddModelError(key, error.Message);
}
}

/// <summary>
/// Adds multiple validation errors to the <see cref="ModelStateDictionary"/> using the keys specified by each error.
/// </summary>
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance where errors will be added.</param>
/// <param name="errors">The collection of <see cref="ValidationError"/> objects to add to the model state.</param>
public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable<ValidationError> errors)
{
foreach (var error in errors)
Expand All @@ -20,6 +36,11 @@ public static void AddModelErrors(this ModelStateDictionary modelState, IEnumera
}
}

/// <summary>
/// Converts a collection of <see cref="ValidationError"/> objects to a populated <see cref="ModelStateDictionary"/>.
/// </summary>
/// <param name="errors">The collection of <see cref="ValidationError"/> objects to add to the <see cref="ModelStateDictionary"/>.</param>
/// <returns>A <see cref="ModelStateDictionary"/> instance containing the specified validation errors.</returns>
public static ModelStateDictionary ToModelState(this IEnumerable<ValidationError> errors)
{
var modelState = new ModelStateDictionary();
Expand Down
Loading
Loading