From c33eb74d8dc9b06267e9ad4d9af6e4569b762b21 Mon Sep 17 00:00:00 2001 From: "David G. Moore, Jr." Date: Wed, 17 Jan 2024 22:55:50 -0500 Subject: [PATCH] Refine error handling and MIME types in MS Graph integration Refactor several components to enhance the Microsoft Graph integration: - Improve exception clarity when Azure AppConfig connection strings are missing by using a more descriptive check. - Streamline the definition of Microsoft Graph MIME types through XML documentation and interpolated strings, aiding future maintenance and readability. - Standardize URI paths with leading slashes and add a new route for DirectoryObjects to align with API design conventions. - Introduce DirectoryObjectsController with functionality to retrieve extension properties, expanding the API surface. - Refine MeController and UsersController by adjusting authorization scopes and resource access patterns, ensuring consistency and restricting permissions to the minimal required. - Abstract common functionality into a new MsGraphController base class, reducing code duplication and separating concerns. - Update project configurations, adding a logging extension and including new auxiliary classes for MVC patterns. - Perform various code cleanups like modifying internal visibility, enhancing logging extension methods, and fixing minor inconsistencies. These changes solidify the foundation of Microsoft Graph features in the application and streamline future extension and maintenance efforts. --- .../AddAzureAppConfigExtensions.cs | 2 +- src/MicrosoftGraph/Constants/MimeTypes.cs | 44 +++++++++++++++---- src/MicrosoftGraph/Constants/Uris.cs | 12 ++--- .../Controllers/DirectoryObjectsController.cs | 37 ++++++++++++++++ .../Controllers/MeController.cs | 16 ++++--- .../Controllers/MsGraphController.cs | 25 +++++++++++ .../Controllers/UsersController.cs | 41 ++++++++++++----- src/MicrosoftGraph/Dgmjr.Graph.csproj | 2 + src/MicrosoftGraph/Dgmjr.Graph.sln | 26 +++++------ .../Extensions/LoggingExtensions.cs | 23 ++++++++-- ...crosoftGraphServiceCollectionExtensions.cs | 25 +++++++---- src/MicrosoftGraph/Services/MsGraphService.cs | 5 ++- src/Mvc/ApiControllerBase.cs | 21 +++++++++ src/Mvc/ControllerExtensions.cs | 22 ++++++++++ src/Mvc/Result.cs | 43 ++++++++++++++++++ src/Mvc/ViewControllerBase.cs | 21 +++++++++ .../Filters/EnumsAsStringsSchemaFilter.cs | 3 +- src/Swagger/SwaggerAutoConfigurator.cs | 10 +++-- src/Swagger/SwaggerExtensions/UseSwaggerUI.cs | 2 +- 19 files changed, 313 insertions(+), 67 deletions(-) create mode 100644 src/MicrosoftGraph/Controllers/DirectoryObjectsController.cs create mode 100644 src/MicrosoftGraph/Controllers/MsGraphController.cs create mode 100644 src/Mvc/ApiControllerBase.cs create mode 100644 src/Mvc/ControllerExtensions.cs create mode 100644 src/Mvc/Result.cs create mode 100644 src/Mvc/ViewControllerBase.cs diff --git a/src/Configuration/AddAzureAppConfigExtensions.cs b/src/Configuration/AddAzureAppConfigExtensions.cs index 1acbaf03..a0089507 100644 --- a/src/Configuration/AddAzureAppConfigExtensions.cs +++ b/src/Configuration/AddAzureAppConfigExtensions.cs @@ -31,7 +31,7 @@ // /*** Add Azure AppConfig ***/ // // Retrieve the connection string // var connectionString = builder.Configuration[connectionStringKey]; -// if (connectionString.IsNullOrEmpty()) +// if (connectionIsNullOrEmpty()) // { // throw new ArgumentException( // $"The connection string for Azure App Configuration was not found. " diff --git a/src/MicrosoftGraph/Constants/MimeTypes.cs b/src/MicrosoftGraph/Constants/MimeTypes.cs index 1715ee3e..34621b39 100644 --- a/src/MicrosoftGraph/Constants/MimeTypes.cs +++ b/src/MicrosoftGraph/Constants/MimeTypes.cs @@ -1,19 +1,47 @@ namespace Dgmjr.Graph.Constants; + using Dgmjr.Mime; public static class MimeTypes { + /// msgraph private const string MsGraph = "msgraph"; + /// -extension-properties public const string MsGraphExtensionPropertiesList = $"{MsGraph}-extension-properties"; - public const string MsGraphExtensionPropertiesListJson = $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Json.DisplayName}"; - public const string MsGraphExtensionPropertiesListXml = $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Xml.DisplayName}"; - public const string MsGraphExtensionPropertiesListBson = $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Bson.DisplayName}"; - public const string MsGraphExtensionPropertiesListMsgPack = $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.MessagePack.DisplayName}"; + /// / + public const string MsGraphExtensionPropertiesListJson = + $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Json.DisplayName}"; + + /// / + public const string MsGraphExtensionPropertiesListXml = + $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Xml.DisplayName}"; + + /// / + public const string MsGraphExtensionPropertiesListBson = + $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Bson.DisplayName}"; + + /// / + public const string MsGraphExtensionPropertiesListMsgPack = + $"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.MessagePack.DisplayName}"; + + /// -user public const string MsGraphUser = $"{MsGraph}-user"; - public const string MsGraphUserJson = $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Json.DisplayName}"; - public const string MsGraphUserXml = $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Xml.DisplayName}"; - public const string MsGraphUserBson = $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Bson.DisplayName}"; - public const string MsGraphUserMsgPack = $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.MessagePack.DisplayName}"; + + /// / + public const string MsGraphUserJson = + $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Json.DisplayName}"; + + /// / + public const string MsGraphUserXml = + $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Xml.DisplayName}"; + + /// / + public const string MsGraphUserBson = + $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Bson.DisplayName}"; + + /// / + public const string MsGraphUserMsgPack = + $"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.MessagePack.DisplayName}"; } diff --git a/src/MicrosoftGraph/Constants/Uris.cs b/src/MicrosoftGraph/Constants/Uris.cs index 2cce190a..1ad41cbe 100644 --- a/src/MicrosoftGraph/Constants/Uris.cs +++ b/src/MicrosoftGraph/Constants/Uris.cs @@ -1,12 +1,14 @@ namespace Dgmjr.Graph.Constants; + using Dgmjr.Mime; public static class Uris { - public const string Api = "api"; - public const string MsGraph = "msgraph"; - public const string Users = "users"; - public const string MsGraphApi = $"{Api}/{MsGraph}"; - public const string Me = "me"; + public const string Api = "/api"; + public const string MsGraph = "/msgraph"; + public const string Users = "/users"; + public const string MsGraphApi = $"{Api}{MsGraph}"; + public const string Me = "/me"; + public const string DirectoryObjects = "/directoryObjects"; public const string ExtensionProperties = "extensionProperties"; } diff --git a/src/MicrosoftGraph/Controllers/DirectoryObjectsController.cs b/src/MicrosoftGraph/Controllers/DirectoryObjectsController.cs new file mode 100644 index 00000000..dcf6ad0e --- /dev/null +++ b/src/MicrosoftGraph/Controllers/DirectoryObjectsController.cs @@ -0,0 +1,37 @@ +/* + * DirectoryObjectsController.cs + * + * Created: 2024-55-16T14:55:21-05:00 + * Modified: 2024-55-16T14:55:21-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.Graph.Controllers; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +[Route($"{MsGraphApi}{DirectoryObjects}")] +public class DirectoryObjectsController( + ILogger logger, + IServiceProvider services +) : MsGraphController(logger, services) +{ + private IDirectoryObjectsService DirectoryObjectsService => + services.GetRequiredService(); + + [HttpGet(Uris.ExtensionProperties)] + public async Task GetExtensionPropertiesAsync() + { + Logger.PageVisited(Http.Get, Request.Path); + return Ok( + ( + await DirectoryObjectsService.GetExtensionPropertiesAsync(default) + ).Cast() + ); + } +} diff --git a/src/MicrosoftGraph/Controllers/MeController.cs b/src/MicrosoftGraph/Controllers/MeController.cs index 8f0453eb..f4c99346 100644 --- a/src/MicrosoftGraph/Controllers/MeController.cs +++ b/src/MicrosoftGraph/Controllers/MeController.cs @@ -3,13 +3,15 @@ namespace Dgmjr.Graph.Controllers; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Application = Dgmjr.Mime.Application; +using Dgmjr.Abstractions; +using Microsoft.Extensions.Logging; +using Microsoft.Identity.Web.Resource; -[Route($"{MsGraphApi}/{Me}")] -[AuthorizeForScopes(Scopes = [MsGraphScopes.User.ReadWrite.All])] -public class MeController(ILogger logger, IServiceProvider services) : ControllerBase, ILog, IHaveAGraphClient +[AuthorizeForScopes(Scopes = [MsGraphScopes.User.Read.Base])] +[RequiredScope([MsGraphScopes.User.Read.Base])] +[Route($"{MsGraphApi}{Me}")] +public class MeController(ILogger logger, IServiceProvider services) : MsGraphController(logger, services) { - public ILogger Logger => logger; - public GraphServiceClient Graph => services.GetRequiredService(); private readonly IUsersService _users = services.GetRequiredService(); [HttpGet] @@ -28,7 +30,7 @@ public async Task Get() [ProducesResponseType(typeof(long), Status200OK)] public async Task Get([FromRoute] string property) { - Logger.PageVisited(Http.Get, $"{Me}/{property}"); + Logger.PageVisited(Http.Get, Request.Path); var propertyFullName = new DGraphExtensionProperty(property).Name; var result = await Graph.Me.Request().Select(u => u.AdditionalData[propertyFullName]).GetAsync(); var value = result.AdditionalData[new DGraphExtensionProperty(property).Name]; @@ -39,7 +41,7 @@ public async Task Get([FromRoute] string property) [Produces(MsGraphUserJson, MsGraphUserXml, MsGraphUserBson, MsGraphUserMsgPack)] public async Task Post([FromRoute] string property, [FromQuery] string value) { - Logger.PageVisited(Http.Post, $"{Me}/{property}"); + Logger.PageVisited(Http.Post, Request.Path); var me = await Graph.Me.Request().GetAsync(); me.AdditionalData[property] = value; return Ok(await Graph.Me.Request().UpdateAsync(me)); diff --git a/src/MicrosoftGraph/Controllers/MsGraphController.cs b/src/MicrosoftGraph/Controllers/MsGraphController.cs new file mode 100644 index 00000000..375ce749 --- /dev/null +++ b/src/MicrosoftGraph/Controllers/MsGraphController.cs @@ -0,0 +1,25 @@ +/* + * MsGraphController.cs + * + * Created: 2024-55-16T14:55:53-05:00 + * Modified: 2024-55-16T14:55:54-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.Graph.Controllers; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +[Route(MsGraphApi)] +public abstract class MsGraphController(ILogger logger, IServiceProvider services) + : ControllerBase, + IHaveAGraphClient +{ + public ILogger Logger => logger; + public GraphServiceClient Graph => services.GetRequiredService(); +} diff --git a/src/MicrosoftGraph/Controllers/UsersController.cs b/src/MicrosoftGraph/Controllers/UsersController.cs index 88a1a83a..7c084642 100644 --- a/src/MicrosoftGraph/Controllers/UsersController.cs +++ b/src/MicrosoftGraph/Controllers/UsersController.cs @@ -1,14 +1,16 @@ namespace Dgmjr.Graph.Controllers; + using Dgmjr.Graph.Abstractions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Application = Dgmjr.Mime.Application; +using Dgmjr.Abstractions; -[Route($"{MsGraphApi}/{Users}")] [AuthorizeForScopes(ScopeKeySection = DownstreamApis_MicrosoftGraph_Scopes)] -public class UsersController(ILogger logger, IServiceProvider services) : ControllerBase, ILog +[Route($"{MsGraphApi}{Users}")] +public class UsersController(ILogger logger, IServiceProvider services) + : MsGraphController(logger, services) { - public ILogger Logger => logger; private readonly IUsersService _users = services.GetRequiredService(); [HttpGet("{userId:guid}")] @@ -16,18 +18,24 @@ public class UsersController(ILogger logger, IServiceProvider s [Produces(MsGraphUserJson, MsGraphUserXml, MsGraphUserBson, MsGraphUserMsgPack)] public async Task Get([FromRoute] guid userId) { - Logger.PageVisited(Http.Get, $"{Users}/{userId}"); + Logger.PageVisited(Http.Get, Request.Path); return Ok(await _users.GetAsync(userId.ToString())); } [HttpGet("{property}")] - [Produces(Text.Plain.DisplayName, Application.Json.DisplayName, Application.Xml.DisplayName, Application.Bson.DisplayName, Application.MessagePack.DisplayName)] + [Produces( + Text.Plain.DisplayName, + Application.Json.DisplayName, + Application.Xml.DisplayName, + Application.Bson.DisplayName, + Application.MessagePack.DisplayName + )] [ProducesResponseType(typeof(string), Status200OK)] [ProducesResponseType(typeof(int), Status200OK)] [ProducesResponseType(typeof(long), Status200OK)] public async Task Get([FromRoute] string property) { - Logger.PageVisited(Http.Get, $"{Users}/{property}"); + Logger.PageVisited(Http.Get, Request.Path); var result = await _users.GetAsync((await _users.GetMyIdAsync()).ToString(), property); var value = result.AdditionalData[new DGraphExtensionProperty(property).Name]; return Ok(value); @@ -35,19 +43,30 @@ public async Task Get([FromRoute] string property) [HttpPost("{userId}/{property}")] [Produces(MsGraphUserJson, MsGraphUserXml, MsGraphUserBson, MsGraphUserMsgPack)] - public async Task Post([FromRoute] guid userId, [FromRoute] string property, [FromQuery] string value) + public async Task Post( + [FromRoute] guid userId, + [FromRoute] string property, + [FromQuery] string value + ) { - Logger.PageVisited(Http.Post, $"{Users}/{userId}/{property}"); + Logger.PageVisited(Http.Post, Request.Path); var user = await _users.UpdateAsync(userId.ToString(), property, value); return Ok(user); } [HttpGet(Uris.ExtensionProperties)] [ProducesResponseType(typeof(DGraphExtensionProperty[]), Status200OK)] - [Produces(MsGraphExtensionPropertiesListJson, MsGraphExtensionPropertiesListXml, MsGraphExtensionPropertiesListBson, MsGraphExtensionPropertiesListMsgPack)] + [Produces( + MsGraphExtensionPropertiesListJson, + MsGraphExtensionPropertiesListXml, + MsGraphExtensionPropertiesListBson, + MsGraphExtensionPropertiesListMsgPack + )] public async Task GetExtensionProperties() { - Logger.PageVisited(Http.Get, $"{Users}/{Uris.ExtensionProperties}"); - return Ok((await _users.GetExtensionPropertiesAsync(default)).Cast()); + Logger.PageVisited(Http.Get, Request.Path); + return Ok( + (await _users.GetExtensionPropertiesAsync(default)).Cast() + ); } } diff --git a/src/MicrosoftGraph/Dgmjr.Graph.csproj b/src/MicrosoftGraph/Dgmjr.Graph.csproj index 1a712776..6ea36ced 100644 --- a/src/MicrosoftGraph/Dgmjr.Graph.csproj +++ b/src/MicrosoftGraph/Dgmjr.Graph.csproj @@ -8,6 +8,7 @@ + @@ -15,6 +16,7 @@ + diff --git a/src/MicrosoftGraph/Dgmjr.Graph.sln b/src/MicrosoftGraph/Dgmjr.Graph.sln index 071b83f4..7e98e5e5 100644 --- a/src/MicrosoftGraph/Dgmjr.Graph.sln +++ b/src/MicrosoftGraph/Dgmjr.Graph.sln @@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Web.DownstreamApis", "..\DownstreamApis\Dgmjr.Web.DownstreamApis.csproj", "{0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Graph", "Dgmjr.Graph.csproj", "{29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dgmjr.Graph", "Dgmjr.Graph.csproj", "{D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -34,18 +34,18 @@ Global {0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}.Production|Any CPU.Build.0 = Local|Any CPU {0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EB63FC8-4D68-471C-BF25-FF7FBE57A33F}.Release|Any CPU.Build.0 = Release|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Local|Any CPU.ActiveCfg = Local|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Local|Any CPU.Build.0 = Local|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Testing|Any CPU.ActiveCfg = Testing|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Testing|Any CPU.Build.0 = Testing|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Staging|Any CPU.ActiveCfg = Staging|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Staging|Any CPU.Build.0 = Staging|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Production|Any CPU.ActiveCfg = Local|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Production|Any CPU.Build.0 = Local|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29B1DE18-9B2F-4B4A-8B52-48D59A1FF395}.Release|Any CPU.Build.0 = Release|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Local|Any CPU.ActiveCfg = Local|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Local|Any CPU.Build.0 = Local|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Testing|Any CPU.ActiveCfg = Testing|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Testing|Any CPU.Build.0 = Testing|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Staging|Any CPU.ActiveCfg = Staging|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Staging|Any CPU.Build.0 = Staging|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Production|Any CPU.ActiveCfg = Local|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Production|Any CPU.Build.0 = Local|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6E3A1A7-6CE2-4CB6-AB20-AD3C525E5E23}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/MicrosoftGraph/Extensions/LoggingExtensions.cs b/src/MicrosoftGraph/Extensions/LoggingExtensions.cs index 237e0c78..86036235 100644 --- a/src/MicrosoftGraph/Extensions/LoggingExtensions.cs +++ b/src/MicrosoftGraph/Extensions/LoggingExtensions.cs @@ -1,9 +1,24 @@ namespace Dgmjr.Graph; -public static partial class LoggingExtensions +internal static partial class LoggingExtensions { - [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "{Method} {Path}", EventName = nameof(PageVisited))] - public static partial void PageVisited(this ILogger logger, System.Net.Http.HttpMethod method, string path); - [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "{Method} {Path}", EventName = nameof(PageVisited))] + [LoggerMessage( + EventId = 1, + Level = LogLevel.Information, + Message = "{Method} {Path}", + EventName = nameof(PageVisited) + )] + public static partial void PageVisited( + this ILogger logger, + System.Net.Http.HttpMethod method, + string path + ); + + [LoggerMessage( + EventId = 1, + Level = LogLevel.Information, + Message = "{Method} {Path}", + EventName = nameof(PageVisited) + )] public static partial void PageVisited(this ILogger logger, string method, string path); } diff --git a/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs b/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs index 8c68358f..1ebb9da2 100644 --- a/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs +++ b/src/MicrosoftGraph/Extensions/MicrosoftGraphServiceCollectionExtensions.cs @@ -2,24 +2,31 @@ namespace Microsoft.Extensions.DependencyInjection; public static class MicrosoftGraphServiceCollectionExtensions { - public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, IConfiguration config) + public static IServiceCollection AddMicrosoftGraph( + this IServiceCollection services, + IConfiguration config + ) { - services.AddMicrosoftGraph(options => config.Bind(options)) + var configSection = config.GetSection(DownstreamApis_MicrosoftGraph); + services + .AddMicrosoftGraph(options => config.Bind(options)) .AddMicrosoftIdentityConsentHandler() - .ConfigureDownstreamApi( - MicrosoftGraph, - config.GetSection(DownstreamApis_MicrosoftGraph) - ); + .ConfigureDownstreamApi(MicrosoftGraph, configSection); services.AddScoped(); - services.Configure(config.GetSection(DownstreamApis_MicrosoftGraph)); + services.Configure(configSection); services.AddScoped(); services.AddSingleton(); return services; } - public static IServiceCollection AddPassphraseGenerator(this IServiceCollection services, IConfiguration config) + public static IServiceCollection AddPassphraseGenerator( + this IServiceCollection services, + IConfiguration config + ) { - services.Configure(config.GetSection(PassphraseGeneratorOptions.AppSettingsKey)); + services.Configure( + config.GetSection(PassphraseGeneratorOptions.AppSettingsKey) + ); services.AddSingleton(); return services; } diff --git a/src/MicrosoftGraph/Services/MsGraphService.cs b/src/MicrosoftGraph/Services/MsGraphService.cs index c086b691..a42ea2e0 100644 --- a/src/MicrosoftGraph/Services/MsGraphService.cs +++ b/src/MicrosoftGraph/Services/MsGraphService.cs @@ -1,6 +1,7 @@ namespace Dgmjr.Graph.Services; +using global::Dgmjr.Abstractions; -public interface IMsGraphService : ILog, IHaveAGraphClient +public interface IMsGraphService : IHaveAGraphClient { /// The client ID of the extensions application guid ExtensionsAppClientId { get; } @@ -21,7 +22,7 @@ public interface IMsGraphService : ILog, IHaveAGraphClient Task GetExtensionsApplicationAsync(); } -public class MsGraphService(GraphServiceClient graph, ILogger logger, IOptionsMonitor options, IOptionsMonitor msidOptions, IDistributedCache cache) : ILog, IMsGraphService +public class MsGraphService(GraphServiceClient graph, ILogger logger, IOptionsMonitor options, IOptionsMonitor msidOptions, IDistributedCache cache) : IMsGraphService { private static readonly duration CacheDuration = duration.FromDays(1000); private static readonly DateTimeOffset CacheExpiration = DateTimeOffset.UtcNow.Add(CacheDuration); diff --git a/src/Mvc/ApiControllerBase.cs b/src/Mvc/ApiControllerBase.cs new file mode 100644 index 00000000..0e16889e --- /dev/null +++ b/src/Mvc/ApiControllerBase.cs @@ -0,0 +1,21 @@ +/* + * ControllerBase.cs + * + * Created: 2024-10-16T04:10:42-05:00 + * Modified: 2024-10-16T04:10:42-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.Mvc; + +using Microsoft.AspNetCore.Mvc; + +public class ApiControllerBase : ControllerBase +{ + public IActionResult Result(T value, string contentType) => + ControllerExtensions.Result(this, value, contentType); +} diff --git a/src/Mvc/ControllerExtensions.cs b/src/Mvc/ControllerExtensions.cs new file mode 100644 index 00000000..f27d8e04 --- /dev/null +++ b/src/Mvc/ControllerExtensions.cs @@ -0,0 +1,22 @@ +/* + * ControllerExtensions.cs + * + * Created: 2024-13-16T04:13:05-05:00 + * Modified: 2024-13-16T04:13:05-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Microsoft.AspNetCore.Mvc; + +public static class ControllerExtensions +{ + public static IActionResult Result( + this ControllerBase controller, + T value, + string contentType + ) => new Result(value, contentType).Convert(); +} diff --git a/src/Mvc/Result.cs b/src/Mvc/Result.cs new file mode 100644 index 00000000..ab017492 --- /dev/null +++ b/src/Mvc/Result.cs @@ -0,0 +1,43 @@ +/* + * Result.cs + * + * Created: 2024-48-16T04:48:01-05:00 + * Modified: 2024-48-16T04:48:01-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.Mvc; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +using OneOf; + +public class Result : OneOfBase, IConvertToActionResult +{ + public Result(T value, string contentType) + : base(value is string ? new ContentResult { Content = value as string, ContentType = contentType, StatusCode = Status200OK } : + new ObjectResult(value) { ContentTypes = [contentType], StatusCode = Status200OK, DeclaredType = typeof(T) }) { } + + protected Result(ContentResult contentResult) : base(contentResult) { } + protected Result(ObjectResult contentResult) : base(contentResult) { } + + public IActionResult Convert() => IsT0 ? AsT0 : AsT1; + + public static implicit operator Result(ContentResult contentResult) => new(contentResult); + + public static implicit operator Result(ObjectResult objectResult) => new(objectResult); + + public static implicit operator ActionResult(Result result) => result.Convert() as ActionResult; +} + +public class Result : Result +{ + public Result(object value, string contentType) : base(value, contentType) { } + public Result(ContentResult contentResult) : base(contentResult) { } + public Result(ObjectResult contentResult) : base(contentResult) { } +} diff --git a/src/Mvc/ViewControllerBase.cs b/src/Mvc/ViewControllerBase.cs new file mode 100644 index 00000000..4f4fac32 --- /dev/null +++ b/src/Mvc/ViewControllerBase.cs @@ -0,0 +1,21 @@ +/* + * ViewControllerBase.cs + * + * Created: 2024-12-16T04:12:28-05:00 + * Modified: 2024-12-16T04:12:28-05:00 + * + * Author: David G. Moore, Jr. + * + * Copyright © 2024 David G. Moore, Jr., All Rights Reserved + * License: MIT (https://opensource.org/licenses/MIT) + */ + +namespace Dgmjr.AspNetCore.Mvc; + +using Microsoft.AspNetCore.Mvc; + +public class ViewControllerBase : Controller +{ + public IActionResult Result(T value, string contentType) => + ControllerExtensions.Result(this, value, contentType); +} diff --git a/src/Swagger/Filters/EnumsAsStringsSchemaFilter.cs b/src/Swagger/Filters/EnumsAsStringsSchemaFilter.cs index 86e845cf..fee7f7a7 100644 --- a/src/Swagger/Filters/EnumsAsStringsSchemaFilter.cs +++ b/src/Swagger/Filters/EnumsAsStringsSchemaFilter.cs @@ -26,8 +26,7 @@ public void Apply(OpenApiSchema model, SchemaFilterContext context) .OfType() .FirstOrDefault(); string label = - enumMemberAttribute == null - || string.IsNullOrWhiteSpace(enumMemberAttribute.Value) + enumMemberAttribute == null || IsNullOrWhiteSpace(enumMemberAttribute.Value) ? enumName : enumMemberAttribute.Value; model.Enum.Add(new OpenApiString(label)); diff --git a/src/Swagger/SwaggerAutoConfigurator.cs b/src/Swagger/SwaggerAutoConfigurator.cs index 99ca40f8..6caddff6 100644 --- a/src/Swagger/SwaggerAutoConfigurator.cs +++ b/src/Swagger/SwaggerAutoConfigurator.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Builder; +namespace Microsoft.Extensions.DependencyInjection; -namespace Dgmjr.AspNetCore.Swagger; +using Microsoft.AspNetCore.Builder; -public class SwaggerAutoConfigurator : IConfigureIHostApplicationBuilder, IConfigureIApplicationBuilder +public class SwaggerAutoConfigurator + : IConfigureIHostApplicationBuilder, + IConfigureIApplicationBuilder { - public ConfigurationOrder Order => throw new NotImplementedException(); + public ConfigurationOrder Order => ConfigurationOrder.AnyTime; public void Configure(IHostApplicationBuilder builder) { diff --git a/src/Swagger/SwaggerExtensions/UseSwaggerUI.cs b/src/Swagger/SwaggerExtensions/UseSwaggerUI.cs index 9d512022..a481326d 100644 --- a/src/Swagger/SwaggerExtensions/UseSwaggerUI.cs +++ b/src/Swagger/SwaggerExtensions/UseSwaggerUI.cs @@ -26,7 +26,7 @@ namespace Microsoft.Extensions.DependencyInjection; -public static partial class UseSwaggerUIExtensions +internal static partial class UseSwaggerUIExtensions { private static readonly Assembly Assembly = typeof(UseSwaggerUIExtensions).Assembly;