2 次代碼提交 63028cc695 ... ad7ce05d05

作者 SHA1 備註 提交日期
  Lukas Angerer ad7ce05d05 Better abstraction for the version information 1 年之前
  Lukas Angerer b930ae3ce1 Cleanup and small improvements 1 年之前

+ 4 - 3
WebTemplate/AppServer.cs

@@ -18,14 +18,15 @@ public class AppServer
     /// </summary>
     private readonly IList<IAppConfigurationModule> _modules = new List<IAppConfigurationModule>
     {
-        new JsonModule(),
         new AuthModule(),
         new CorsModule(),
+        new JsonModule(),
         new SwaggerModule(),
-        new SpaRoutingModule(),
         new ValidationModule(),
-        new StatusEndpointModule(),
+        new StatusEndpointModule(_ => new GitVersionInfo()),
         new ControllersModule(),
+        // Keep the SpaRoutingModule at the very end
+        new SpaRoutingModule(),
     };
 
     public void Start(string[] args)

+ 24 - 0
WebTemplate/GitVersionInfo.cs

@@ -0,0 +1,24 @@
+using System.Reflection;
+
+namespace WebTemplate;
+
+public record GitVersionInfo : IVersionInfo
+{
+    public string AssemblyName { get; }
+    public string AssemblyVersion { get; }
+    public string Version { get; }
+    public string Commit { get; }
+    public string Branch { get; }
+    public bool IsDirty { get; }
+    
+    public GitVersionInfo()
+    {
+        var entryAssembly = Assembly.GetEntryAssembly();
+        AssemblyName = entryAssembly?.GetName().Name ?? "<unknown>";
+        AssemblyVersion = entryAssembly?.GetName().Version?.ToString() ?? "<unknown>";
+        Version = ThisAssembly.Git.Tag;
+        Commit = ThisAssembly.Git.Sha;
+        Branch = ThisAssembly.Git.Branch;
+        IsDirty = ThisAssembly.Git.IsDirty;
+    }
+}

+ 11 - 0
WebTemplate/IVersionInfo.cs

@@ -0,0 +1,11 @@
+namespace WebTemplate;
+
+public interface IVersionInfo
+{
+    string AssemblyName { get; }
+    string AssemblyVersion { get; }
+    string Version { get; }
+    string Commit { get; }
+    string Branch { get; }
+    bool IsDirty { get; }
+}

+ 2 - 4
WebTemplate/Program.cs

@@ -1,9 +1,7 @@
-
 using WebTemplate;
 
-var server = new AppServer();
-server.Start(args);
-
 // After launching the application, you can access
 // * https://localhost:7169/swagger/
 // * https://localhost:7169/v1/status
+var server = new AppServer();
+server.Start(args);

+ 2 - 12
WebTemplate/Properties/launchSettings.json

@@ -1,22 +1,12 @@
 {
   "$schema": "http://json.schemastore.org/launchsettings.json",
   "profiles": {
-    "http": {
-      "commandName": "Project",
-      "dotnetRunMessages": true,
-      "launchBrowser": true,
-      "launchUrl": "http://localhost:5292/v1/status",
-      "applicationUrl": "http://localhost:5292",
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      }
-    },
     "https": {
       "commandName": "Project",
       "dotnetRunMessages": true,
       "launchBrowser": true,
-      "launchUrl": "https://localhost:7169/v1/status",
-      "applicationUrl": "https://localhost:7169;http://localhost:5292",
+      "launchUrl": "https://localhost:7169/swagger",
+      "applicationUrl": "https://localhost:7169",
       "environmentVariables": {
         "ASPNETCORE_ENVIRONMENT": "Development"
       }

+ 2 - 2
WebTemplate/ServerAspects/Controllers/ControllersModule.cs

@@ -1,4 +1,4 @@
-namespace WebTemplate.ServerAspects.Controllers;
+namespace WebTemplate.ServerAspects.Controllers;
 
 public class ControllersModule : IAppConfigurationModule
 {
@@ -12,4 +12,4 @@ public class ControllersModule : IAppConfigurationModule
     {
         app.MapControllers();
     }
-}
+}

+ 2 - 2
WebTemplate/ServerAspects/Json/JsonModule.cs

@@ -1,4 +1,4 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace WebTemplate.ServerAspects.Json;
 
@@ -17,4 +17,4 @@ public class JsonModule : IAppConfigurationModule
     public void ConfigureApplication(WebApplication app)
     {
     }
-}
+}

+ 6 - 0
WebTemplate/ServerAspects/Spa/SpaRoutingModule.cs

@@ -1,5 +1,11 @@
 namespace WebTemplate.ServerAspects.Spa;
 
+/// <summary>
+/// IMPORTANT: The <see cref="SpaDefaultPageMiddleware"/> _by design_ MUST map ALL previously unmapped routes
+/// to the default page which makes this module very susceptible to ordering issues. Any middleware that is registered
+/// AFTER the SPA default page middleware is not actually going to get a chance to add anything to the routing process
+/// if at the point of the SPA default page middleware the "GetEndpoint" method returns null.
+/// </summary>
 public class SpaRoutingModule : IAppConfigurationModule
 {
 	public void ConfigureServices(IServiceCollection services, IConfigurationRoot config)

+ 2 - 2
WebTemplate/ServerAspects/Swagger/StringEnumSchemaFilter.cs

@@ -1,4 +1,4 @@
-using System.ComponentModel;
+using System.ComponentModel;
 using System.Reflection;
 using Microsoft.OpenApi.Any;
 using Microsoft.OpenApi.Models;
@@ -37,4 +37,4 @@ public class StringEnumSchemaFilter : ISchemaFilter
             schema.Description = description.ToString();
         }
     }
-}
+}

+ 2 - 2
WebTemplate/ServerAspects/Validation/ValidationFilter.cs

@@ -1,4 +1,4 @@
-using FluentValidation;
+using FluentValidation;
 
 namespace WebTemplate.ServerAspects.Validation;
 
@@ -36,4 +36,4 @@ public class ValidationFilter<T> : IEndpointFilter
         
         return Results.Problem("Could not find type to validate");
     }
-}
+}

+ 2 - 2
WebTemplate/ServerAspects/Validation/ValidationModule.cs

@@ -1,4 +1,4 @@
-using FluentValidation;
+using FluentValidation;
 
 namespace WebTemplate.ServerAspects.Validation;
 
@@ -21,4 +21,4 @@ public class ValidationModule : IAppConfigurationModule
     public void ConfigureApplication(WebApplication app)
     {
     }
-}
+}

+ 7 - 0
WebTemplate/Status/IStatusReportProvider.cs

@@ -0,0 +1,7 @@
+namespace WebTemplate.Status;
+
+public interface IStatusReportProvider
+{
+    public string Key { get; }
+    public object StatusReport();
+}

+ 2 - 2
WebTemplate/Status/ServiceStatus.cs

@@ -6,5 +6,5 @@ namespace WebTemplate.Status;
 /// <param name="Status">The value "OK" if everything is OK. Otherwise this service will not actually return a 200
 /// HTTP result</param>
 /// <param name="Version">Detailed version information</param>
-/// <param name="Environment">Collection of environment variables. Only enabled in _Development_ mode</param>
-public record ServiceStatus(string Status, VersionInfo Version, EnvironmentInfo Environment);
+/// <param name="Context">Collection of contextual information provided by different modules</param>
+public record ServiceStatus(string Status, IVersionInfo Version, Dictionary<string, object> Context);

+ 13 - 3
WebTemplate/Status/StatusEndpointModule.cs

@@ -2,23 +2,33 @@ namespace WebTemplate.Status;
 
 public class StatusEndpointModule : IAppConfigurationModule
 {
+    private readonly Func<IServiceProvider, IVersionInfo> _versionProvider;
+
+    public StatusEndpointModule(Func<IServiceProvider, IVersionInfo> versionProvider)
+    {
+        _versionProvider = versionProvider;
+    }
+    
     public void ConfigureServices(IServiceCollection services, IConfigurationRoot config)
     {
+        services.AddTransient<IVersionInfo>(_versionProvider);
     }
 
     public void ConfigureApplication(WebApplication app)
     {
-        app.MapGet("/v1/status", () => new ServiceStatus("OK", new VersionInfo(), new EnvironmentInfo(app.Environment)))
+        var reporter = new StatusReporter();
+
+        app.MapGet("v1/status", (IVersionInfo version, IServiceProvider serviceProvider) => new ServiceStatus("OK", version, reporter.StatusReport(serviceProvider)))
             .WithName("StatusService")
             .WithOpenApi(operation =>
             {
                 operation.Description =
                     """
-                    Returns the overall service status, including version and environment information. This can be used
+                    Returns the overall service status, including version and context information. This can be used
                     as a health check endpoint.
                     """;
 
                 return operation;
             });
     }
-}
+}

+ 17 - 0
WebTemplate/Status/StatusReporter.cs

@@ -0,0 +1,17 @@
+namespace WebTemplate.Status;
+
+public class StatusReporter
+{
+    public Dictionary<string, object> StatusReport(IServiceProvider serviceProvider)
+    {
+        var result = new Dictionary<string, object>();
+        var providers = serviceProvider.GetServices<IStatusReportProvider>().OrderBy(p => p.Key).ToList();
+
+        foreach (var provider in providers)
+        {
+            result[provider.Key] = provider.StatusReport();
+        }
+
+        return result;
+    }
+}

+ 0 - 19
WebTemplate/Status/VersionInfo.cs

@@ -1,19 +0,0 @@
-namespace WebTemplate.Status;
-
-public record VersionInfo
-{
-    public string AssemblyVersion { get; }
-    public string Version { get; }
-    public string Commit { get; }
-    public string Branch { get; }
-    public bool IsDirty { get; }
-    
-    public VersionInfo()
-    {
-        AssemblyVersion = typeof(VersionInfo).Assembly.GetName().Version?.ToString() ?? "<unknown>";
-        Version = ThisAssembly.Git.Tag;
-        Commit = ThisAssembly.Git.Sha;
-        Branch = ThisAssembly.Git.Branch;
-        IsDirty = ThisAssembly.Git.IsDirty;
-    }
-}

+ 1 - 2
WebTemplate/WebTemplate.csproj

@@ -9,12 +9,11 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="FluentValidation" Version="11.9.0" />
-    <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.0" />
     <PackageReference Include="GitInfo" Version="3.3.4">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
+    <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
     <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />