Skip to content

Commit

Permalink
Refine error handling and MIME types in MS Graph integration
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dgmjr committed Jan 18, 2024
1 parent 7ffb764 commit c33eb74
Show file tree
Hide file tree
Showing 19 changed files with 313 additions and 67 deletions.
2 changes: 1 addition & 1 deletion src/Configuration/AddAzureAppConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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. "
Expand Down
44 changes: 36 additions & 8 deletions src/MicrosoftGraph/Constants/MimeTypes.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
namespace Dgmjr.Graph.Constants;

using Dgmjr.Mime;

public static class MimeTypes
{
/// <value>msgraph</value>
private const string MsGraph = "msgraph";

/// <value><inheritdoc cref="MsGraph" path="/value" />-extension-properties</value>
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}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.Json.DisplayName" path="/value" /></value>
public const string MsGraphExtensionPropertiesListJson =
$"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Json.DisplayName}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.Xml.DisplayName" path="/value" /></value>
public const string MsGraphExtensionPropertiesListXml =
$"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Xml.DisplayName}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.Bson.DisplayName" path="/value" /></value>
public const string MsGraphExtensionPropertiesListBson =
$"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.Bson.DisplayName}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.MessagePack.DisplayName" path="/value" /></value>
public const string MsGraphExtensionPropertiesListMsgPack =
$"{Application.Base.DisplayName}/{MsGraphExtensionPropertiesList}{Suffixes.MessagePack.DisplayName}";

/// <value><inheritdoc cref="MsGraph" path="/value" />-user</value>
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}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.Json.DisplayName" path="/value" /></value>
public const string MsGraphUserJson =
$"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Json.DisplayName}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.Xml.DisplayName" path="/value" /></value>
public const string MsGraphUserXml =
$"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Xml.DisplayName}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.Bson.DisplayName" path="/value" /></value>
public const string MsGraphUserBson =
$"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.Bson.DisplayName}";

/// <value><inheritdoc cref="Application.Base.DisplayName" path="/value" />/<inheritdoc cref="MsGraphExtensionPropertiesList" path="/value" /><inheritdoc cref="Suffixes.MessagePack.DisplayName" path="/value" /></value>
public const string MsGraphUserMsgPack =
$"{Application.Base.DisplayName}/{MsGraphUser}{Suffixes.MessagePack.DisplayName}";
}
12 changes: 7 additions & 5 deletions src/MicrosoftGraph/Constants/Uris.cs
Original file line number Diff line number Diff line change
@@ -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";
}
37 changes: 37 additions & 0 deletions src/MicrosoftGraph/Controllers/DirectoryObjectsController.cs
Original file line number Diff line number Diff line change
@@ -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. <[email protected]>
*
* 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<DirectoryObjectsController> logger,
IServiceProvider services
) : MsGraphController(logger, services)
{
private IDirectoryObjectsService DirectoryObjectsService =>
services.GetRequiredService<IDirectoryObjectsService>();

[HttpGet(Uris.ExtensionProperties)]
public async Task<IActionResult> GetExtensionPropertiesAsync()
{
Logger.PageVisited(Http.Get, Request.Path);
return Ok(
(
await DirectoryObjectsService.GetExtensionPropertiesAsync(default)
).Cast<DGraphExtensionProperty>()
);
}
}
16 changes: 9 additions & 7 deletions src/MicrosoftGraph/Controllers/MeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MeController> logger, IServiceProvider services) : ControllerBase, ILog, IHaveAGraphClient
[AuthorizeForScopes(Scopes = [MsGraphScopes.User.Read.Base])]
[RequiredScope([MsGraphScopes.User.Read.Base])]
[Route($"{MsGraphApi}{Me}")]
public class MeController(ILogger<MeController> logger, IServiceProvider services) : MsGraphController(logger, services)
{
public ILogger Logger => logger;
public GraphServiceClient Graph => services.GetRequiredService<GraphServiceClient>();
private readonly IUsersService _users = services.GetRequiredService<IUsersService>();

[HttpGet]
Expand All @@ -28,7 +30,7 @@ public async Task<IActionResult> Get()
[ProducesResponseType(typeof(long), Status200OK)]
public async Task<IActionResult> 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];
Expand All @@ -39,7 +41,7 @@ public async Task<IActionResult> Get([FromRoute] string property)
[Produces(MsGraphUserJson, MsGraphUserXml, MsGraphUserBson, MsGraphUserMsgPack)]
public async Task<IActionResult> 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));
Expand Down
25 changes: 25 additions & 0 deletions src/MicrosoftGraph/Controllers/MsGraphController.cs
Original file line number Diff line number Diff line change
@@ -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. <[email protected]>
*
* 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<GraphServiceClient>();
}
41 changes: 30 additions & 11 deletions src/MicrosoftGraph/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,72 @@
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<UsersController> logger, IServiceProvider services) : ControllerBase, ILog
[Route($"{MsGraphApi}{Users}")]
public class UsersController(ILogger<UsersController> logger, IServiceProvider services)
: MsGraphController(logger, services)
{
public ILogger Logger => logger;
private readonly IUsersService _users = services.GetRequiredService<IUsersService>();

[HttpGet("{userId:guid}")]
[ProducesResponseType(typeof(User), Status200OK)]
[Produces(MsGraphUserJson, MsGraphUserXml, MsGraphUserBson, MsGraphUserMsgPack)]
public async Task<IActionResult> 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<IActionResult> 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);
}

[HttpPost("{userId}/{property}")]
[Produces(MsGraphUserJson, MsGraphUserXml, MsGraphUserBson, MsGraphUserMsgPack)]
public async Task<IActionResult> Post([FromRoute] guid userId, [FromRoute] string property, [FromQuery] string value)
public async Task<IActionResult> 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<IActionResult> GetExtensionProperties()
{
Logger.PageVisited(Http.Get, $"{Users}/{Uris.ExtensionProperties}");
return Ok((await _users.GetExtensionPropertiesAsync(default)).Cast<DGraphExtensionProperty>());
Logger.PageVisited(Http.Get, Request.Path);
return Ok(
(await _users.GetExtensionPropertiesAsync(default)).Cast<DGraphExtensionProperty>()
);
}
}
2 changes: 2 additions & 0 deletions src/MicrosoftGraph/Dgmjr.Graph.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
<PackageReference Include="Dgmjr.Abstractions" />
<PackageReference Include="Dgmjr.Configuration.Extensions" />
<PackageReference Include="Dgmjr.Extensions.Caching" />
<PackageReference Include="Dgmjr.Logging.Extensions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../DownstreamApis/Dgmjr.Web.DownstreamApis.csproj" />
<PackageReference Include="Dgmjr.RegexDtoGenerator" IncludeAssets="Analyzers;Build" ExcludeAssets="Native;BuildTransitive;BuildMultitargeting;ContentFiles;Compile;Runtime" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Using Include="Dgmjr.Configuration.Extensions" />
<Using Include="Dgmjr.Abstractions" />
<Using Include="Dgmjr.Graph.Abstractions" />
<Using Include="Dgmjr.Graph.Constants.CacheKeys" Static="true" />
<Using Include="Dgmjr.Graph.Constants.MimeTypes" Static="true" />
Expand Down
26 changes: 13 additions & 13 deletions src/MicrosoftGraph/Dgmjr.Graph.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
23 changes: 19 additions & 4 deletions src/MicrosoftGraph/Extensions/LoggingExtensions.cs
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit c33eb74

Please sign in to comment.