Skip to content

Commit 0b449a4

Browse files
moved to GitHub actions, removed deprecations (#14)
* moved to GitHub actions, removed deprecations * added better Nuget badge and build history, linted README.md
1 parent 4bc20bc commit 0b449a4

File tree

16 files changed

+624
-863
lines changed

16 files changed

+624
-863
lines changed

.github/workflows/build.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Build
2+
on:
3+
push:
4+
branches:
5+
- master
6+
tags:
7+
- "*"
8+
pull_request:
9+
jobs:
10+
calculate-version:
11+
runs-on: ubuntu-20.04
12+
outputs:
13+
version: ${{ steps.version.outputs.version }}
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@master
17+
with:
18+
fetch-depth: 0
19+
- name: Install GitVersion
20+
uses: gittools/actions/gitversion/[email protected]
21+
with:
22+
versionSpec: "5.8.1"
23+
- name: Run GitVersion
24+
id: gitversion
25+
uses: gittools/actions/gitversion/[email protected]
26+
- name: Version
27+
id: version
28+
run: |
29+
version=${{ steps.gitversion.outputs.nuGetVersionV2 }}
30+
if [ "${{ github.event_name }}" == "pull_request" ]
31+
then
32+
version=${version}-${{ steps.gitversion.outputs.shortSha }}
33+
fi
34+
echo "::set-output name=version::${version}"
35+
build:
36+
runs-on: ${{ matrix.os }}
37+
needs: [calculate-version]
38+
strategy:
39+
matrix:
40+
include:
41+
- os: ubuntu-20.04
42+
nugetPush: false
43+
- os: windows-2019
44+
nugetPush: true
45+
- os: macos-10.15
46+
nugetPush: false
47+
steps:
48+
- name: Checkout code
49+
uses: actions/checkout@master
50+
with:
51+
fetch-depth: 0
52+
submodules: recursive
53+
- name: Setup dotnet SDK
54+
uses: actions/setup-dotnet@v1
55+
with:
56+
dotnet-version: "6.0.101"
57+
- name: Build
58+
run: |
59+
dotnet build -c Release -p:Version=${{ needs.calculate-version.outputs.version }}
60+
shell: bash
61+
- name: Test
62+
run: dotnet test -c Release --no-build
63+
shell: bash
64+
- name: Archive NuGet Packages
65+
uses: actions/upload-artifact@v2
66+
if: ${{ matrix.nugetPush }}
67+
with:
68+
name: packages
69+
path: |
70+
**/*.nupkg
71+
**/*.snupkg
72+
retention-days: 1
73+
nuget-push:
74+
runs-on: ubuntu-20.04
75+
needs: [build]
76+
if: github.event_name != 'pull_request'
77+
steps:
78+
- name: Download NuGet Packages
79+
uses: actions/download-artifact@v2
80+
with:
81+
name: packages
82+
- name: NuGet Push
83+
run: dotnet nuget push **/*.nupkg -s https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}

.travis.yml

Lines changed: 0 additions & 21 deletions
This file was deleted.

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
<PropertyGroup>
33
<Authors>Winton</Authors>
44
<Company>Winton</Company>
5-
<Copyright>Copyright 2020 Winton</Copyright>
5+
<Copyright>Copyright 2022 Winton</Copyright>
66
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Rules.ruleset</CodeAnalysisRuleSet>
7-
<LangVersion>8.0</LangVersion>
7+
<LangVersion>10.0</LangVersion>
88
<Nullable>enable</Nullable>
99
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
1010
</PropertyGroup>

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2018 Winton
1+
Copyright 2022 Winton
22

33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.

README.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
# Winton.DomainModelling.AspNetCore
22

3-
[![Appveyor](https://ci.appveyor.com/api/projects/status/k94y5or6toq2un7d?svg=true)](https://ci.appveyor.com/project/wintoncode/winton-domainmodelling-aspnetcore/branch/master)
4-
[![Travis CI](https://travis-ci.com/wintoncode/Winton.DomainModelling.AspNetCore.svg?branch=master)](https://travis-ci.com/wintoncode/Winton.DomainModelling.AspNetCore)
5-
[![NuGet version](https://img.shields.io/nuget/v/Winton.DomainModelling.AspNetCore.svg)](https://www.nuget.org/packages/Winton.DomainModelling.AspNetCore)
6-
[![NuGet version](https://img.shields.io/nuget/vpre/Winton.DomainModelling.AspNetCore.svg)](https://www.nuget.org/packages/Winton.DomainModelling.AspNetCore)
7-
83
Conventions useful for creating an ASP.NET Core based REST API on top of a domain model. Specifically, it provides extension methods which convert from domain model types, as defined in [`Winton.DomainModelling.Abstractions`](https://github.com/wintoncode/Winton.DomainModelling.Abstractions) to ASP.NET Core types.
94

5+
[![NuGet Badge](https://buildstats.info/nuget/Winton.DomainModelling.AspNetCore)](https://www.nuget.org/packages/Winton.DomainModelling.AspNetCore/)
6+
[![Build history](https://buildstats.info/github/chart/wintoncode/Winton.DomainModelling.AspNetCore?branch=master)](https://github.com/wintoncode/Winton.DomainModelling.AspNetCore/actions)
7+
108
## `Result` Extensions
119

12-
`Result<TData>` is a type defined in the `Winton.DomainModelling.Abstractions` package.
10+
`Result<TData>` is a type defined in the `Winton.DomainModelling.Abstractions` package.
1311
It is a type that is intended to be returned from domain operations.
1412
It allows operations to indicate both successes and failures to the client.
1513
In this case the client is an ASP.NET Core Controller.
1614
In a Controller, however, we need to return an `IActionResult` rather than a `Result<TData>`. We have two cases to consider:
15+
1716
* If the `Result<TData>` was a success then we want to return a 2xx response from the API containing the data in the body.
1817
* If the `Result<TData>` was a failure then we want to return a 4xx response from the API containing [problem details](https://tools.ietf.org/html/rfc7807) in the body.
1918

2019
This library provides a `ToActionResult` extension method for `Result<TData>` which matches on the result and converts it to an appropriate `IActionResult`.
21-
There are various overloads to provide flexibility.
20+
There are various overloads to provide flexibility.
21+
2222
It is expected that this will be used within an [`ApiController`](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2#annotation-with-apicontroller-attribute) so that ASP.NET Core will apply its REST API conventions to the `IActionResult`.
2323

2424
### Successful Result Mappings
@@ -30,7 +30,7 @@ The following default mappings happen when the `Result` is a `Success`.
3030
| `Success<TData>` | `ActionResult<TData>` | 200 Ok |
3131
| `Success<Unit>` | `NoContentResult` | 204 NoContent |
3232

33-
The defaults can be overriden by calling the extension method that takes a success mapping function.
33+
The defaults can be overriden by calling the extension method that takes a success mapping function.
3434
A common example of when this is used is in a `POST` action when an entity has been created and we would like to return a 201 Created response to the client.
3535

3636
```csharp
@@ -48,8 +48,9 @@ public async Task<IActionResult> CreateFoo(NewFoo newFoo)
4848

4949
The `CreateFoo` method performs the domain logic to create a new `Foo` and returns `Result<Foo>`.
5050

51-
*In a real application it would be defined in the domain model project.
52-
To give the domain model an API which is defined in terms of commands and queries and to decouple it from the outer application layers the mediator pattern is often adopted.
51+
*In a real application it would be defined in the domain model project.
52+
To give the domain model an API which is defined in terms of commands and queries and to decouple it from the outer application layers the mediator pattern is often adopted.
53+
5354
Jimmy Bogard's [MediatR](https://github.com/jbogard/MediatR) is a useful library for implementing that pattern.*
5455

5556
### Failure Result Mappings
@@ -66,13 +67,13 @@ The following table shows the default mappings.
6667

6768
_*This includes any other types that inherit from `Error` and are not explicitly listed._
6869

69-
The defaults can be overriden by calling the extension method that takes an error mapping function.
70+
The defaults can be overriden by calling the extension method that takes an error mapping function.
7071
This is useful when the domain model has defined additional error types and these need to be converted to the relevant problem details.
7172
The status code that is set on the `ProblemDetails` will also be set on the `IActionResult` by the extension method so that the HTTP status code on the response is correct.
7273

73-
For example consider a domain model that deals with payments.
74-
It could be a news service which requires a subscription to access content.
75-
It might contain several operations that require payment to be made before they can proceed.
74+
For example consider a domain model that deals with payments.
75+
It could be a news service which requires a subscription to access content.
76+
It might contain several operations that require payment to be made before they can proceed.
7677
This domain may therefore define a new error type as follows:
7778

7879
```csharp
@@ -85,7 +86,7 @@ public class PaymentRequired : Error
8586
}
8687
```
8788

88-
It would therefore make sense to map this to a `402 Payment Required` HTTP response with relevant `ProblemDetails`.
89+
It would therefore make sense to map this to a `402 Payment Required` HTTP response with relevant `ProblemDetails`.
8990
This can be achieved like so:
9091

9192
```csharp
@@ -105,10 +106,10 @@ public async Task<IActionResult> GetNewsItem(string id)
105106
}
106107
```
107108

108-
The type field should return a URI that resolves to human-readable documentation about the type of error that has occurred.
109-
This can either be existing documentation, such as https://httpstatuses.com for common errors, or your own documentation for domain-specific errors.
109+
The type field should return a URI that resolves to human-readable documentation about the type of error that has occurred.
110+
This can either be existing documentation, such as [https://httpstatuses.com](https://httpstatuses.com) for common errors, or your own documentation for domain-specific errors.
110111

111-
Problem details is formally documented in [RFC 7807](https://tools.ietf.org/html/rfc7807).
112+
Problem details is formally documented in [RFC 7807](https://tools.ietf.org/html/rfc7807).
112113
More information about how the fields should be used can be found there.
113114

114115
In order to maintain a loose coupling between the API layer and the domain model each action method should know how to map any kind of domain error.
@@ -135,8 +136,8 @@ internal static ProblemDetails MapDomainErrors(Error error)
135136
}
136137
```
137138

138-
By using C# pattern matching we can easily match on the type of error and map it to a `ProblemDetails`.
139+
By using C# pattern matching we can easily match on the type of error and map it to a `ProblemDetails`.
139140
Returning `null` in the default case means the existing error mappings for the common error types, as defined above, are used.
140141

141-
If you have a custom error type and you are happy for your REST API to return `400 Bad Request` when it occurs, then the default error mappings for the base `Error` type should already work for you.
142+
If you have a custom error type and you are happy for your REST API to return `400 Bad Request` when it occurs, then the default error mappings for the base `Error` type should already work for you.
142143
It maps the error's details and title to the corresponding fields on the problem details.

appveyor.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/Winton.DomainModelling.AspNetCore/DomainExceptionFilter.cs

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/Winton.DomainModelling.AspNetCore/ErrorExtensions.cs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,33 @@
55
using Microsoft.AspNetCore.Http;
66
using Microsoft.AspNetCore.Mvc;
77

8-
namespace Winton.DomainModelling.AspNetCore
8+
namespace Winton.DomainModelling.AspNetCore;
9+
internal static class ErrorExtensions
910
{
10-
internal static class ErrorExtensions
11+
internal static ActionResult ToActionResult(this Error error, Func<Error, ProblemDetails?>? selectProblemDetails)
1112
{
12-
internal static ActionResult ToActionResult(this Error error, Func<Error, ProblemDetails?>? selectProblemDetails)
13+
ProblemDetails problemDetails = selectProblemDetails?.Invoke(error) ?? CreateDefaultProblemDetails(error);
14+
return new ObjectResult(problemDetails)
1315
{
14-
ProblemDetails problemDetails = selectProblemDetails?.Invoke(error) ?? CreateDefaultProblemDetails(error);
15-
return new ObjectResult(problemDetails)
16-
{
17-
StatusCode = problemDetails.Status
18-
};
19-
}
16+
StatusCode = problemDetails.Status
17+
};
18+
}
2019

21-
private static ProblemDetails CreateDefaultProblemDetails(Error error)
20+
private static ProblemDetails CreateDefaultProblemDetails(Error error)
21+
{
22+
int statusCode = error switch
23+
{
24+
UnauthorizedError _ => StatusCodes.Status403Forbidden,
25+
NotFoundError _ => StatusCodes.Status404NotFound,
26+
ConflictError _ => StatusCodes.Status409Conflict,
27+
_ => StatusCodes.Status400BadRequest
28+
};
29+
return new ProblemDetails
2230
{
23-
int statusCode = error switch
24-
{
25-
UnauthorizedError _ => StatusCodes.Status403Forbidden,
26-
NotFoundError _ => StatusCodes.Status404NotFound,
27-
ConflictError _ => StatusCodes.Status409Conflict,
28-
_ => StatusCodes.Status400BadRequest
29-
};
30-
return new ProblemDetails
31-
{
32-
Detail = error.Detail,
33-
Status = statusCode,
34-
Title = error.Title,
35-
Type = $"https://httpstatuses.com/{statusCode}"
36-
};
37-
}
31+
Detail = error.Detail,
32+
Status = statusCode,
33+
Title = error.Title,
34+
Type = $"https://httpstatuses.com/{statusCode}"
35+
};
3836
}
39-
}
37+
}

0 commit comments

Comments
 (0)