diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..bcb2323
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,18 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "swashbuckle.aspnetcore.cli": {
+ "version": "6.5.0",
+ "commands": [
+ "swagger"
+ ]
+ },
+ "refitter": {
+ "version": "0.9.7",
+ "commands": [
+ "refitter"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index b5f39e6..d9748ca 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -104,7 +104,7 @@ csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:warning
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:silent
# Code-block preferences
csharp_prefer_braces = true:silent
@@ -256,31 +256,31 @@ dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.interfaces.required_modifiers =
+dotnet_naming_symbols.interfaces.required_modifiers =
dotnet_naming_symbols.enums.applicable_kinds = enum
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.enums.required_modifiers =
+dotnet_naming_symbols.enums.required_modifiers =
dotnet_naming_symbols.events.applicable_kinds = event
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.events.required_modifiers =
+dotnet_naming_symbols.events.required_modifiers =
dotnet_naming_symbols.methods.applicable_kinds = method
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.methods.required_modifiers =
+dotnet_naming_symbols.methods.required_modifiers =
dotnet_naming_symbols.properties.applicable_kinds = property
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.properties.required_modifiers =
+dotnet_naming_symbols.properties.required_modifiers =
dotnet_naming_symbols.public_fields.applicable_kinds = field
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
-dotnet_naming_symbols.public_fields.required_modifiers =
+dotnet_naming_symbols.public_fields.required_modifiers =
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
-dotnet_naming_symbols.private_fields.required_modifiers =
+dotnet_naming_symbols.private_fields.required_modifiers =
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
@@ -288,15 +288,15 @@ dotnet_naming_symbols.private_static_fields.required_modifiers = static
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.types_and_namespaces.required_modifiers =
+dotnet_naming_symbols.types_and_namespaces.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.non_field_members.required_modifiers =
+dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
-dotnet_naming_symbols.type_parameters.required_modifiers =
+dotnet_naming_symbols.type_parameters.required_modifiers =
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
@@ -304,7 +304,7 @@ dotnet_naming_symbols.private_constant_fields.required_modifiers = const
dotnet_naming_symbols.local_variables.applicable_kinds = local
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
-dotnet_naming_symbols.local_variables.required_modifiers =
+dotnet_naming_symbols.local_variables.required_modifiers =
dotnet_naming_symbols.local_constants.applicable_kinds = local
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
@@ -312,7 +312,7 @@ dotnet_naming_symbols.local_constants.required_modifiers = const
dotnet_naming_symbols.parameters.applicable_kinds = parameter
dotnet_naming_symbols.parameters.applicable_accessibilities = *
-dotnet_naming_symbols.parameters.required_modifiers =
+dotnet_naming_symbols.parameters.required_modifiers =
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
@@ -328,37 +328,36 @@ dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readon
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
-dotnet_naming_symbols.local_functions.required_modifiers =
+dotnet_naming_symbols.local_functions.required_modifiers =
# Naming styles
-dotnet_naming_style.pascalcase.required_prefix =
-dotnet_naming_style.pascalcase.required_suffix =
-dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
dotnet_naming_style.pascalcase.capitalization = pascal_case
dotnet_naming_style.ipascalcase.required_prefix = I
-dotnet_naming_style.ipascalcase.required_suffix =
-dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
dotnet_naming_style.ipascalcase.capitalization = pascal_case
dotnet_naming_style.tpascalcase.required_prefix = T
-dotnet_naming_style.tpascalcase.required_suffix =
-dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
dotnet_naming_style.tpascalcase.capitalization = pascal_case
dotnet_naming_style._camelcase.required_prefix = _
-dotnet_naming_style._camelcase.required_suffix =
-dotnet_naming_style._camelcase.word_separator =
+dotnet_naming_style._camelcase.required_suffix =
+dotnet_naming_style._camelcase.word_separator =
dotnet_naming_style._camelcase.capitalization = camel_case
-dotnet_naming_style.camelcase.required_prefix =
-dotnet_naming_style.camelcase.required_suffix =
-dotnet_naming_style.camelcase.word_separator =
+dotnet_naming_style.camelcase.required_prefix =
+dotnet_naming_style.camelcase.required_suffix =
+dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_style.s_camelcase.required_prefix = s_
-dotnet_naming_style.s_camelcase.required_suffix =
-dotnet_naming_style.s_camelcase.word_separator =
-dotnet_naming_style.s_camelcase.capitalization = camel_case
-
+dotnet_naming_style.s_camelcase.required_suffix =
+dotnet_naming_style.s_camelcase.word_separator =
+dotnet_naming_style.s_camelcase.capitalization = camel_case
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..d6c6091
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+ $(SolutionDir)openapi.yaml
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebApiBootstrap.sln b/WebApiBootstrap.sln
index 99d06e3..e0290eb 100644
--- a/WebApiBootstrap.sln
+++ b/WebApiBootstrap.sln
@@ -7,6 +7,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3E83E620-E6C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiBootstrap.Api", "src\WebApiBootstrap.Api\WebApiBootstrap.Api.csproj", "{5C60CE8C-0E08-40F2-B428-3F942AC49065}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiBootstrap.Contracts", "src\WebApiBootstrap.Contracts\WebApiBootstrap.Contracts.csproj", "{97D2070C-40DD-46D6-BF73-6A037960C745}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiBootstrap.Client", "src\WebApiBootstrap.Client\WebApiBootstrap.Client.csproj", "{B09D031D-33D4-4CA4-B242-011EE829FAEB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -20,8 +24,18 @@ Global
{5C60CE8C-0E08-40F2-B428-3F942AC49065}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C60CE8C-0E08-40F2-B428-3F942AC49065}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C60CE8C-0E08-40F2-B428-3F942AC49065}.Release|Any CPU.Build.0 = Release|Any CPU
+ {97D2070C-40DD-46D6-BF73-6A037960C745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {97D2070C-40DD-46D6-BF73-6A037960C745}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {97D2070C-40DD-46D6-BF73-6A037960C745}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {97D2070C-40DD-46D6-BF73-6A037960C745}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B09D031D-33D4-4CA4-B242-011EE829FAEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B09D031D-33D4-4CA4-B242-011EE829FAEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B09D031D-33D4-4CA4-B242-011EE829FAEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B09D031D-33D4-4CA4-B242-011EE829FAEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5C60CE8C-0E08-40F2-B428-3F942AC49065} = {3E83E620-E6C9-48B9-B8BE-02B4CDD07FE5}
+ {97D2070C-40DD-46D6-BF73-6A037960C745} = {3E83E620-E6C9-48B9-B8BE-02B4CDD07FE5}
+ {B09D031D-33D4-4CA4-B242-011EE829FAEB} = {3E83E620-E6C9-48B9-B8BE-02B4CDD07FE5}
EndGlobalSection
EndGlobal
diff --git a/openapi.yaml b/openapi.yaml
new file mode 100644
index 0000000..60b3bc0
--- /dev/null
+++ b/openapi.yaml
@@ -0,0 +1,38 @@
+openapi: 3.0.1
+info:
+ title: 'WebApiBootstrap.Api, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
+ version: '1.0'
+paths:
+ /weatherforecast:
+ get:
+ tags:
+ - 'WebApiBootstrap.Api, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
+ operationId: GetWeatherForecast
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/WeatherForecast'
+components:
+ schemas:
+ WeatherForecast:
+ type: object
+ properties:
+ date:
+ type: string
+ format: date
+ temperatureC:
+ type: integer
+ format: int32
+ temperatureF:
+ type: integer
+ format: int32
+ nullable: true
+ summary:
+ type: string
+ nullable: true
+ additionalProperties: false
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Api/Convert.cs b/src/WebApiBootstrap.Api/Convert.cs
new file mode 100644
index 0000000..5e9a732
--- /dev/null
+++ b/src/WebApiBootstrap.Api/Convert.cs
@@ -0,0 +1,22 @@
+namespace WebApiBootstrap.Api;
+
+internal static class Convert
+{
+ ///
+ /// Converts an object to a data transfer object (DTO).
+ ///
+ /// The object to convert.
+ /// The type of DTO.
+ /// The DTO.
+ ///
+ /// This method is a shorthand for . It allows using the method group syntax:
+ /// var dtos = models.Select(Convert.ToDto);
+ /// instead of the lambda syntax:
+ /// var dtos = models.Select(model => model.ToDto());
+ ///
+ ///
+ public static T ToDto(this ISerializableAs serializable)
+ {
+ return serializable.ToDto();
+ }
+}
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Api/GlobalUsings.cs b/src/WebApiBootstrap.Api/GlobalUsings.cs
new file mode 100644
index 0000000..08c54ef
--- /dev/null
+++ b/src/WebApiBootstrap.Api/GlobalUsings.cs
@@ -0,0 +1,9 @@
+// Allows using "Dto" as a namespace prefix instead of suffix, so "Dto.WeatherForecast" instead of "WeatherForecastDto"
+
+global using Dto = WebApiBootstrap.Contracts;
+
+// Unless we explicitly use the "Dto." prefix, we assume that we're referring to the Models namespace.
+global using WebApiBootstrap.Api.Models;
+
+// Allows using "Convert.ToDto" instead of "Dto.Convert.ToDto"
+global using Convert = WebApiBootstrap.Api.Convert;
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Api/ISerializableAs.cs b/src/WebApiBootstrap.Api/ISerializableAs.cs
new file mode 100644
index 0000000..45710a1
--- /dev/null
+++ b/src/WebApiBootstrap.Api/ISerializableAs.cs
@@ -0,0 +1,10 @@
+namespace WebApiBootstrap.Api;
+
+///
+/// An object that can be converted to a data transfer object (DTO) of type .
+///
+/// The type of DTO.
+internal interface ISerializableAs
+{
+ T ToDto();
+}
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Api/Models/WeatherForecast.cs b/src/WebApiBootstrap.Api/Models/WeatherForecast.cs
new file mode 100644
index 0000000..76226e8
--- /dev/null
+++ b/src/WebApiBootstrap.Api/Models/WeatherForecast.cs
@@ -0,0 +1,17 @@
+namespace WebApiBootstrap.Api.Models;
+
+internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
+ : ISerializableAs
+{
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+
+ public static WeatherForecast FromDto(Dto.WeatherForecast dto)
+ {
+ return new WeatherForecast(dto.Date, dto.TemperatureC, dto.Summary);
+ }
+
+ public Dto.WeatherForecast ToDto()
+ {
+ return new Dto.WeatherForecast(Date, TemperatureC, TemperatureF, Summary);
+ }
+}
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Api/Program.cs b/src/WebApiBootstrap.Api/Program.cs
index 00ff539..7cab004 100644
--- a/src/WebApiBootstrap.Api/Program.cs
+++ b/src/WebApiBootstrap.Api/Program.cs
@@ -1,44 +1,42 @@
-var builder = WebApplication.CreateBuilder(args);
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
+WebApplication app = builder.Build();
-var app = builder.Build();
+app.UseSwagger();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
- app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
-
-var summaries = new[]
+string[] summaries = new[]
{
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering",
+ "Scorching"
};
-app.MapGet("/weatherforecast", () =>
-{
- var forecast = Enumerable.Range(1, 5).Select(index =>
- new WeatherForecast
- (
- DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
- Random.Shared.Next(-20, 55),
- summaries[Random.Shared.Next(summaries.Length)]
- ))
- .ToArray();
- return forecast;
-})
-.WithName("GetWeatherForecast")
-.WithOpenApi();
+app.MapGet(
+ "/weatherforecast",
+ () =>
+ {
+ Dto.WeatherForecast[] forecast = Enumerable.Range(1, 5)
+ .Select(
+ index => new WeatherForecast(
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ Random.Shared.Next(-20, 55),
+ summaries[Random.Shared.Next(summaries.Length)]))
+ .Select(Convert.ToDto)
+ .ToArray();
-app.Run();
+ return forecast;
+ })
+ .WithName("GetWeatherForecast")
+ .WithOpenApi();
-record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
-{
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-}
+app.Run();
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Api/WebApiBootstrap.Api.csproj b/src/WebApiBootstrap.Api/WebApiBootstrap.Api.csproj
index 9b01f30..0c7d4a7 100644
--- a/src/WebApiBootstrap.Api/WebApiBootstrap.Api.csproj
+++ b/src/WebApiBootstrap.Api/WebApiBootstrap.Api.csproj
@@ -1,16 +1,24 @@
-
- net8.0
- enable
- enable
- true
- WebApiBootstrap
-
+
+ net8.0
+ enable
+ enable
+ true
+ WebApiBootstrap.Api
+
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Client/RefitterInterface.g.cs b/src/WebApiBootstrap.Client/RefitterInterface.g.cs
new file mode 100644
index 0000000..60b3d53
--- /dev/null
+++ b/src/WebApiBootstrap.Client/RefitterInterface.g.cs
@@ -0,0 +1,27 @@
+//
+// This code was generated by Refitter.
+//
+
+
+using Refit;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+using WebApiBootstrap.Contracts;
+
+namespace WebApiBootstrap.Client
+{
+ [System.CodeDom.Compiler.GeneratedCode("Refitter", "0.9.7.0")]
+ public partial interface IGetWeatherForecastEndpoint
+ {
+ /// OK
+ /// Thrown when the request returns a non-success status code.
+ [Headers("Accept: application/json")]
+ [Get("/weatherforecast")]
+ Task> Execute(CancellationToken cancellationToken = default);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Client/WebApiBootstrap.Client.csproj b/src/WebApiBootstrap.Client/WebApiBootstrap.Client.csproj
new file mode 100644
index 0000000..5cc5830
--- /dev/null
+++ b/src/WebApiBootstrap.Client/WebApiBootstrap.Client.csproj
@@ -0,0 +1,46 @@
+
+
+
+ netstandard2.0
+ disable
+ enable
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %5E
+
+
+
+
+ %5C
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Contracts/WeatherForecast.cs b/src/WebApiBootstrap.Contracts/WeatherForecast.cs
new file mode 100644
index 0000000..7e5eaf4
--- /dev/null
+++ b/src/WebApiBootstrap.Contracts/WeatherForecast.cs
@@ -0,0 +1,5 @@
+using System;
+
+namespace WebApiBootstrap.Contracts;
+
+public record WeatherForecast(DateOnly Date, int TemperatureC, int? TemperatureF, string? Summary);
\ No newline at end of file
diff --git a/src/WebApiBootstrap.Contracts/WebApiBootstrap.Contracts.csproj b/src/WebApiBootstrap.Contracts/WebApiBootstrap.Contracts.csproj
new file mode 100644
index 0000000..a06fe4e
--- /dev/null
+++ b/src/WebApiBootstrap.Contracts/WebApiBootstrap.Contracts.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netstandard2.0
+ disable
+ annotations
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file