Skip to content

Commit e29e9c6

Browse files
committed
Merge branch 'dotnet-7'
2 parents 1d7e03a + a95ac54 commit e29e9c6

File tree

200 files changed

+7783
-5299
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

200 files changed

+7783
-5299
lines changed

Directory.Build.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>5.2.0</VersionPrefix>
5+
<VersionPrefix>6.0.0</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>
@@ -12,6 +12,7 @@
1212
<RepositoryUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</RepositoryUrl>
1313
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
1414
<RootNamespace>Thinktecture</RootNamespace>
15+
<TargetFramework>net7.0</TargetFramework>
1516
<LangVersion>11.0</LangVersion>
1617
<Nullable>enable</Nullable>
1718
<ImplicitUsings>enable</ImplicitUsings>

README.md

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,14 @@ This library provides some interfaces, classes, [Roslyn Source Generators](https
1515

1616
See [wiki](https://github.com/PawelGerr/Thinktecture.Runtime.Extensions/wiki) for more documentation.
1717

18-
# Required SDK/Compiler/Language Version
19-
* Compiler version: 4.2.0
20-
* C# language version: 9 or higher
21-
22-
Verify the version by placing `#error version` into any of your cs-files and build the project/solution.
23-
The build output should display the compiler version:
24-
```
25-
MyFile.cs(15, 8): [CS1029] #error: 'version'
26-
MyFile.cs(15, 8): [CS8304] Compiler version: '4.2.0-4.22220.2 (1e40aa11)'. Language version: 10.0.
27-
```
28-
29-
## Update your IDE and SDK to newest version.
30-
Works/tested with:
31-
* SDK: 6.0.300 / 6.0.301 / 6.0.400
32-
* Visual Studio: 17.2.4 / 17.3.0
33-
* JetBrains Rider: 2022.1.2 / 2022.2.2
34-
35-
> Please note: For developers having both, JetBrains Rider and Visual Studio, please update Visual Studio as well, because Rider is using the SDK of Visual Studio by default.
36-
18+
# Requirements
19+
20+
* Version 6:
21+
* C# 11 (or higher) for generated code
22+
* SDK 7.0.102 (or higher) for building projects
23+
* Version 5:
24+
* C# 9 (or higher) for generated code
25+
* SDK 6.0.300 (or higher) for building projects
3726

3827
# Smart Enums
3928

Thinktecture.Runtime.Extensions.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{21EB22
2020
.github\workflows\main.yml = .github\workflows\main.yml
2121
.gitignore = .gitignore
2222
.github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml
23+
global.json = global.json
2324
EndProjectSection
2425
EndProject
2526
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E6F200ED-86D2-4D5C-9D5F-34592908B107}"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Formattable/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

samples/Newtonsoft.Json.AspNetCore.Samples/Thinktecture.Runtime.Extensions.Newtonsoft.Json.AspNetCore.Samples.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
3-
<TargetFramework>net7.0</TargetFramework>
43
<OutputType>EXE</OutputType>
54
<NoWarn>$(NoWarn);CA2007;CA2234</NoWarn>
65
</PropertyGroup>
76
<ItemGroup>
8-
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.2" />
7+
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.3" />
98
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
109
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
1110
</ItemGroup>

samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Controllers/DemoController.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ public IActionResult RoundTrip(ProductGroupWithJsonConverter group)
4141
}
4242

4343
[HttpGet("productType/{productType}")]
44-
public IActionResult RoundTrip(
45-
[FromRoute] ProductType productType,
46-
[FromQuery] ProductType? type)
44+
public IActionResult RoundTrip(ProductType productType)
45+
{
46+
return RoundTrip<ProductType, string>(productType);
47+
}
48+
49+
[HttpGet("productType")]
50+
public IActionResult RoundTripWithQueryString(ProductType productType)
4751
{
4852
return RoundTrip<ProductType, string>(productType);
4953
}
@@ -54,8 +58,6 @@ public IActionResult RoundTripPost([FromBody] ProductType productType)
5458
return RoundTrip<ProductType, string>(productType);
5559
}
5660

57-
public record ProductTypeWrapper(ProductType ProductType);
58-
5961
[HttpPost("productTypeWrapper")]
6062
public IActionResult RoundTripPost([FromBody] ProductTypeWrapper productType)
6163
{
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using Thinktecture.SmartEnums;
2+
3+
namespace Thinktecture;
4+
5+
public record ProductTypeWrapper(ProductType ProductType);

samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Program.cs

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
using System;
21
using System.Net.Http;
32
using System.Net.Http.Json;
43
using System.Text;
54
using System.Threading.Tasks;
65
using Microsoft.AspNetCore.Builder;
76
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Mvc;
89
using Microsoft.Extensions.DependencyInjection;
910
using Microsoft.Extensions.Hosting;
1011
using Microsoft.Extensions.Logging;
1112
using Serilog;
1213
using Serilog.Events;
1314
using Thinktecture.AspNetCore.ModelBinding;
15+
using Thinktecture.SmartEnums;
1416
using Thinktecture.Text.Json.Serialization;
17+
using Thinktecture.Validation;
18+
using Thinktecture.ValueObjects;
1519
using ILogger = Microsoft.Extensions.Logging.ILogger;
1620

1721
namespace Thinktecture;
@@ -21,8 +25,14 @@ public class Program
2125
// ReSharper disable once InconsistentNaming
2226
public static async Task Main()
2327
{
28+
var startMinimalWebApi = true;
29+
2430
var loggerFactory = CreateLoggerFactory();
25-
var server = StartServerAsync(loggerFactory);
31+
var app = startMinimalWebApi
32+
? StartMinimalWebApiAsync(loggerFactory)
33+
: StartServerAsync(loggerFactory); // MVC controllers
34+
35+
await app;
2636

2737
// calls
2838
// http://localhost:5000/api/category/fruits
@@ -38,23 +48,30 @@ public static async Task Main()
3848
// http://localhost:5000/api/productName/bread
3949
// http://localhost:5000/api/productName/a
4050
// http://localhost:5000/api/boundary
41-
await DoHttpRequestsAsync(loggerFactory.CreateLogger<Program>());
51+
await DoHttpRequestsAsync(loggerFactory.CreateLogger<Program>(), startMinimalWebApi);
4252

43-
await server;
53+
await Task.Delay(5000);
4454
}
4555

46-
private static async Task DoHttpRequestsAsync(ILogger logger)
56+
private static async Task DoHttpRequestsAsync(ILogger logger, bool forMinimalWebApi)
4757
{
4858
using var client = new HttpClient();
4959

5060
await DoRequestAsync(logger, client, "category/fruits");
5161
await DoRequestAsync(logger, client, "categoryWithConverter/fruits");
5262
await DoRequestAsync(logger, client, "group/1");
53-
await DoRequestAsync(logger, client, "group/42"); // invalid
63+
await DoRequestAsync(logger, client, "group/42"); // invalid
64+
await DoRequestAsync(logger, client, "group/invalid"); // invalid
5465
await DoRequestAsync(logger, client, "groupWithConverter/1");
5566
await DoRequestAsync(logger, client, "groupWithConverter/42"); // invalid
56-
await DoRequestAsync(logger, client, "productType/groceries?type=groceries");
57-
await DoRequestAsync(logger, client, "productType/invalid"); // invalid
67+
await DoRequestAsync(logger, client, "productType/groceries");
68+
await DoRequestAsync(logger, client, "productType?productType=groceries");
69+
await DoRequestAsync(logger, client, "productType", "groceries");
70+
await DoRequestAsync(logger, client, "productType/invalid"); // invalid
71+
72+
if (forMinimalWebApi)
73+
await DoRequestAsync(logger, client, "productTypeWithFilter?productType=invalid"); // invalid
74+
5875
await DoRequestAsync(logger, client, "productType", "invalid"); // invalid
5976
await DoRequestAsync(logger, client, "productTypeWrapper", new { ProductType = "invalid" }); // invalid
6077
await DoRequestAsync(logger, client, "productTypeWithJsonConverter/groceries");
@@ -124,7 +141,46 @@ private static Task StartServerAsync(ILoggerFactory loggerFactory)
124141
})
125142
.Build();
126143

127-
return webHost.RunAsync();
144+
return webHost.StartAsync();
145+
}
146+
147+
private static Task StartMinimalWebApiAsync(ILoggerFactory loggerFactory)
148+
{
149+
var builder = WebApplication.CreateBuilder();
150+
builder.Services
151+
.AddSingleton(loggerFactory)
152+
.ConfigureHttpJsonOptions(options => options.SerializerOptions.Converters.Add(new ValueObjectJsonConverterFactory()));
153+
154+
var app = builder.Build();
155+
156+
var group = app.MapGroup("/api");
157+
158+
group.MapGet("category/{category}", (ProductCategory category) => new { Value = category, category.IsValid });
159+
group.MapGet("categoryWithConverter/{category}", (ProductCategoryWithJsonConverter category) => new { Value = category, category.IsValid });
160+
group.MapGet("group/{group}", (ProductGroup group) => new { Value = group, group.IsValid });
161+
group.MapGet("groupWithConverter/{group}", (ProductGroupWithJsonConverter group) => new { Value = group, group.IsValid });
162+
group.MapGet("productType/{productType}", (ProductType productType) => productType);
163+
group.MapGet("productType", (ProductType productType) => productType);
164+
group.MapGet("productTypeWithFilter", (BoundValueObject<ProductType> productType) => ValueTask.FromResult(productType.Value))
165+
.AddEndpointFilter((context, next) =>
166+
{
167+
var value = context.GetArgument<IBoundParam>(0);
168+
169+
if (value.Error is not null)
170+
return new ValueTask<object?>(Results.BadRequest(value.Error));
171+
172+
return next(context);
173+
});
174+
group.MapPost("productType", ([FromBody] ProductType productType) => productType);
175+
group.MapPost("productTypeWrapper", ([FromBody] ProductTypeWrapper productType) => productType);
176+
group.MapGet("productTypeWithJsonConverter/{productType}", (ProductTypeWithJsonConverter productType) => productType);
177+
group.MapGet("productName/{name}", (ProductName name) => name);
178+
group.MapPost("productName", ([FromBody] ProductName name) => name);
179+
group.MapGet("otherProductName/{name}", (OtherProductName? name) => name);
180+
group.MapPost("otherProductName", ([FromBody] OtherProductName name) => name);
181+
group.MapPost("boundary", ([FromBody] BoundaryWithJsonConverter boundary) => boundary);
182+
183+
return app.StartAsync();
128184
}
129185

130186
private static ILoggerFactory CreateLoggerFactory()

samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples/Thinktecture.Runtime.Extensions.AspNetCore.Samples.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
3-
<TargetFramework>net7.0</TargetFramework>
43
<OutputType>EXE</OutputType>
54
<NoWarn>$(NoWarn);CA2007;CA2234</NoWarn>
65
</PropertyGroup>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.ComponentModel.DataAnnotations;
3+
4+
namespace Thinktecture.Validation;
5+
6+
public class BoundValueObject<T, TKey> : IBoundParam
7+
where T : IKeyedValueObject<T, TKey>
8+
where TKey : IParsable<TKey>
9+
{
10+
private readonly T? _item;
11+
public T? Item => Error is null ? _item : throw new ValidationException(Error);
12+
13+
public string? Error { get; }
14+
15+
private BoundValueObject(T? item)
16+
{
17+
_item = item;
18+
}
19+
20+
private BoundValueObject(string error)
21+
{
22+
Error = error ?? throw new ArgumentNullException(nameof(error));
23+
}
24+
25+
public static bool TryParse(string s, IFormatProvider? formatProvider, out BoundValueObject<T, TKey> value)
26+
{
27+
if (!TKey.TryParse(s, formatProvider, out var key))
28+
{
29+
value = new BoundValueObject<T, TKey>($"The value '{s}' cannot be converted to '{typeof(TKey).FullName}'.");
30+
}
31+
else
32+
{
33+
var validationResult = T.Validate(key, out var item);
34+
35+
if (validationResult is null || item is IValidatableEnum<TKey>)
36+
{
37+
value = new BoundValueObject<T, TKey>(item);
38+
}
39+
else
40+
{
41+
value = new BoundValueObject<T, TKey>(validationResult.ErrorMessage ?? $"The value '{s}' cannot be converted to '{typeof(T).FullName}'.");
42+
}
43+
}
44+
45+
return true;
46+
}
47+
}
48+
49+
public class BoundValueObject<T> : IBoundParam
50+
where T : IKeyedValueObject<T, string>
51+
{
52+
private readonly T? _item;
53+
public T? Value => Error is null ? _item : throw new ValidationException(Error);
54+
55+
public string? Error { get; }
56+
57+
private BoundValueObject(T? item)
58+
{
59+
_item = item;
60+
}
61+
62+
private BoundValueObject(string error)
63+
{
64+
Error = error ?? throw new ArgumentNullException(nameof(error));
65+
}
66+
67+
public static bool TryParse(string s, IFormatProvider? formatProvider, out BoundValueObject<T> value)
68+
{
69+
var validationResult = T.Validate(s, out var item);
70+
71+
if (validationResult is null || item is IValidatableEnum<string>)
72+
{
73+
value = new BoundValueObject<T>(item);
74+
}
75+
else
76+
{
77+
value = new BoundValueObject<T>(validationResult.ErrorMessage ?? $"The value '{s}' cannot be converted to '{typeof(T).FullName}'.");
78+
}
79+
80+
return true;
81+
}
82+
}

0 commit comments

Comments
 (0)