Skip to content

Commit

Permalink
Startup enhancements (#11)
Browse files Browse the repository at this point in the history
* WithDataService

* WithDefaultNameRoleProvisioningService

* WithDefaultDeepLinkingService

* WithDefaultPlatformService

* WithDefaultTokenService

* WithDefaultAssignmentGradeService

* Simplify Routing Configuring

* Rename public service interfaces to include Lti13

* Rename services to include lti13, default, and config

* Update Default Services to use Configuration settings
  • Loading branch information
ninjapiratica authored Nov 2, 2024
1 parent 96209ed commit 23586c1
Show file tree
Hide file tree
Showing 40 changed files with 396 additions and 271 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class LineItemServiceEndpoints
}
}

public class ServiceEndpointsPopulator(LinkGenerator linkGenerator, ICoreDataService dataService, IAssignmentGradeService assignmentGradeService) : Populator<IServiceEndpoints>
public class ServiceEndpointsPopulator(LinkGenerator linkGenerator, ILti13CoreDataService dataService, ILti13AssignmentGradeConfigService assignmentGradeService) : Populator<IServiceEndpoints>
{
public override async Task PopulateAsync(IServiceEndpoints obj, MessageScope scope, CancellationToken cancellationToken = default)
{
Expand Down
54 changes: 39 additions & 15 deletions NP.Lti13Platform.AssignmentGradeServices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ The IMS [Assignment and Grade Services](https://www.imsglobal.org/spec/lti-ags/v

1. Add the nuget package to your project:

2. Add an implementation of the `IAssignmentGradeDataService` interface:
2. Add an implementation of the `ILti13AssignmentGradeDataService` interface:

```csharp
public class DataService: IAssignmentGradeDataService
public class DataService: ILti13AssignmentGradeDataService
{
...
}
Expand All @@ -26,9 +26,7 @@ public class DataService: IAssignmentGradeDataService
builder.Services
.AddLti13PlatformCore()
.AddLti13PlatformAssignmentGradeServices()
.AddDefaultAssignmentGradeService();

builder.Services.AddTransient<IAssignmentGradeDataService, DataService>();
.WithLti13AssignmentGradeDataService<DataService>();
```

4. Setup the routing for the LTI 1.3 platform endpoints:
Expand All @@ -37,11 +35,11 @@ builder.Services.AddTransient<IAssignmentGradeDataService, DataService>();
app.UseLti13PlatformAssignmentGradeServices();
```

## IAssignmentGradeDataService
## ILti13AssignmentGradeDataService

There is no default `IAssignmentGradeDataService` implementation to allow each project to store the data how they see fit.
There is no default `ILti13AssignmentGradeDataService` implementation to allow each project to store the data how they see fit.

The `IAssignmentGradeDataService` interface is used to manage the persistance of line items and grades.
The `ILti13AssignmentGradeDataService` interface is used to manage the persistance of line items and grades.

All of the internal services are transient and therefore the data service may be added at any scope (Transient, Scoped, Singleton).

Expand All @@ -55,18 +53,44 @@ Default routes are provided for all endpoints. Routes can be configured when cal
app.UseLti13PlatformAssignmentGradeServices(config => {
config.LineItemsUrl = "/lti13/{deploymentId}/{contextId}/lineItems"; // {deploymentId} and {contextId} are required
config.LineItemUrl = "/lti13/{deploymentId}/{contextId}/lineItems/{lineItemId}"; // {deploymentId}, {contextId}, and {lineItemId} are required
return config;
});
```

### IAssignmentGradeService
### ILti13AssignmentGradeConfigService

The `ILti13AssignmentGradeConfigService` interface is used to get the config for the assignment and grade service. The config is used to tell the tools how to request the members of a context.

The `IAssignmentGradeService` interface is used to get the config for the assignment and grade service. The config is used to tell the tools how to request the members of a context.
There is a default implementation of the `ILti13AssignmentGradeConfigService` interface that uses a configuration set up on app start.
It will be configured using the [`IOptions`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration) pattern and configuration.
The configuration path for the service is `Lti13Platform:AssignmentGradeServices`.

There is a default implementation of the `IAssignmentGradeService` interface that uses a configuration set up on app start. When calling the `AddDefaultAssignmentGradeService` method, the configuration can be setup at that time. A fallback to the current request scheme and host will be used if no ServiceEndpoint is configured. The Default implementation can be overridden by adding a new implementation of the `INameRoleProvisioningService` interface and not including the Default. This may be useful if the service URL is dynamic or needs to be determined at runtime.
Examples:
```json
{
"Lti13Platform": {
"AssignmentGradeServices": {
"ServiceAddress": "https://<mysite>"
}
}
}
```

```csharp
builder.Services
.AddLti13PlatformCore()
builder.Services.Configure<ServicesConfig>(x => { });
```

The Default implementation can be overridden by adding a new implementation of the `ILti13AssignmentGradeConfigService` interface.
This may be useful if the service URL is dynamic or needs to be determined at runtime.

```csharp
builder.AddLti13PlatformCore()
.AddLti13PlatformAssignmentGradeServices()
.AddDefaultAssignmentGradeService(x => { x.ServiceAddress = new Uri("https://<mysite>") });
```
.WithLti13AssignmentGradeConfigService<ConfigService>();
```

## Configuration

`ServiceAddress`

The base url used to tell tools where the service is located.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace NP.Lti13Platform.AssignmentGradeServices.Services
{
internal class AssignmentGradeService(IOptionsMonitor<ServicesConfig> config, IHttpContextAccessor httpContextAccessor) : IAssignmentGradeService
internal class DefaultAssignmentGradeConfigService(IOptionsMonitor<ServicesConfig> config, IHttpContextAccessor httpContextAccessor) : ILti13AssignmentGradeConfigService
{
public async Task<ServicesConfig> GetConfigAsync(string clientId, CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace NP.Lti13Platform.AssignmentGradeServices.Services
{
public interface IAssignmentGradeService
public interface ILti13AssignmentGradeConfigService
{
Task<ServicesConfig> GetConfigAsync(string clientId, CancellationToken cancellationToken = default);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace NP.Lti13Platform.AssignmentGradeServices.Services
{
public interface IAssignmentGradeDataService
public interface ILti13AssignmentGradeDataService
{
Task<LineItem?> GetLineItemAsync(string lineItemId, CancellationToken cancellationToken = default);
Task DeleteLineItemAsync(string lineItemId, CancellationToken cancellationToken = default);
Expand Down
35 changes: 21 additions & 14 deletions NP.Lti13Platform.AssignmentGradeServices/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.JsonWebTokens;
using NP.Lti13Platform.AssignmentGradeServices.Configs;
Expand All @@ -23,25 +24,31 @@ public static Lti13PlatformBuilder AddLti13PlatformAssignmentGradeServices(this
{
builder.ExtendLti13Message<IServiceEndpoints, ServiceEndpointsPopulator>();

builder.Services.AddOptions<ServicesConfig>().BindConfiguration("Lti13Platform:AssignmentGradeServices");
builder.Services.TryAddSingleton<ILti13AssignmentGradeConfigService, DefaultAssignmentGradeConfigService>();

return builder;
}

public static Lti13PlatformBuilder AddDefaultAssignmentGradeService(this Lti13PlatformBuilder builder, Action<ServicesConfig>? configure = null)
public static Lti13PlatformBuilder WithLti13AssignmentGradeDataService<T>(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : ILti13AssignmentGradeDataService
{
configure ??= (x) => { };
builder.Services.Add(new ServiceDescriptor(typeof(ILti13AssignmentGradeDataService), typeof(T), serviceLifetime));
return builder;
}

builder.Services.Configure(configure);
builder.Services.AddTransient<IAssignmentGradeService, AssignmentGradeService>();
public static Lti13PlatformBuilder WithLti13AssignmentGradeConfigService<T>(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : ILti13AssignmentGradeConfigService
{
builder.Services.Add(new ServiceDescriptor(typeof(ILti13AssignmentGradeConfigService), typeof(T), serviceLifetime));
return builder;
}

public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this IEndpointRouteBuilder app, Action<ServiceEndpointsConfig>? configure = null)
public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this IEndpointRouteBuilder app, Func<ServiceEndpointsConfig, ServiceEndpointsConfig>? configure = null)
{
var config = new ServiceEndpointsConfig();
configure?.Invoke(config);
ServiceEndpointsConfig config = new();
config = configure?.Invoke(config) ?? config;

app.MapGet(config.LineItemsUrl,
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string? resource_id, string? resource_link_id, string? tag, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string? resource_id, string? resource_link_id, string? tag, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) =>
{
var httpContext = httpContextAccessor.HttpContext!;
var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!;
Expand Down Expand Up @@ -106,7 +113,7 @@ public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this
});

app.MapPost(config.LineItemsUrl,
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, IAssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, LineItemRequest request, CancellationToken cancellationToken) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, LineItemRequest request, CancellationToken cancellationToken) =>
{
const string INVALID_CONTENT_TYPE = "Invalid Content-Type";
const string CONTENT_TYPE_REQUIRED = "Content-Type must be 'application/vnd.ims.lis.v2.lineitem+json'";
Expand Down Expand Up @@ -200,7 +207,7 @@ public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this
.DisableAntiforgery();

app.MapGet(config.LineItemUrl,
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, IAssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) =>
{
var httpContext = httpContextAccessor.HttpContext!;
var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!;
Expand Down Expand Up @@ -249,7 +256,7 @@ public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this
});

app.MapPut(config.LineItemUrl,
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, IAssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, LineItemRequest request, CancellationToken cancellationToken) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, LineItemRequest request, CancellationToken cancellationToken) =>
{
const string INVALID_CONTENT_TYPE = "Invalid Content-Type";
const string CONTENT_TYPE_REQUIRED = "Content-Type must be 'application/vnd.ims.lis.v2.lineitem+json'";
Expand Down Expand Up @@ -343,7 +350,7 @@ public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this
.DisableAntiforgery();

app.MapDelete(config.LineItemUrl,
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, IAssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) =>
{
var httpContext = httpContextAccessor.HttpContext!;
var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!;
Expand Down Expand Up @@ -384,7 +391,7 @@ public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this
.DisableAntiforgery();

app.MapGet($"{config.LineItemUrl}/results",
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, IAssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, string? user_id, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, string? user_id, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) =>
{
var httpContext = httpContextAccessor.HttpContext!;
var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!;
Expand Down Expand Up @@ -454,7 +461,7 @@ public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this
});

app.MapPost($"{config.LineItemUrl}/scores",
async (IHttpContextAccessor httpContextAccessor, ICoreDataService coreDataService, IAssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, ScoreRequest request, CancellationToken cancellationToken) =>
async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, ScoreRequest request, CancellationToken cancellationToken) =>
{
const string RESULT_TOO_EARLY = "startDateTime";
const string RESULT_TOO_EARLY_DESCRIPTION = "lineItem startDateTime is in the future";
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Configs/Lti13PlatformCoreConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class Lti13PlatformTokenConfig
/// <summary>
/// A case-sensitive URL using the HTTPS scheme that contains: scheme, host; and, optionally, port number, and path components; and, no query or fragment components. The issuer identifies the platform to the tools.
/// </summary>
public string Issuer
public required string Issuer
{
get => _issuer;
set
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/LtiServicesAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace NP.Lti13Platform.Core
{
public class LtiServicesAuthHandler(ICoreDataService dataService, ITokenService tokenService, IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder)
public class LtiServicesAuthHandler(ILti13CoreDataService dataService, ILti13TokenConfigService tokenService, IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
public const string SchemeName = "NP.Lti13Platform.Services";
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Populators/CustomPopulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface ICustomMessage
public IDictionary<string, string>? Custom { get; set; }
}

public class CustomPopulator(IPlatformService platformService, ICoreDataService dataService) : Populator<ICustomMessage>
public class CustomPopulator(ILti13PlatformService platformService, ILti13CoreDataService dataService) : Populator<ICustomMessage>
{
private static readonly IEnumerable<string> LineItemAttemptGradeVariables = [
Lti13ResourceLinkVariables.AvailableUserStartDateTime,
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Populators/PlatformPopulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class ToolPlatform
}
}

public class PlatformPopulator(IPlatformService platformService) : Populator<IPlatformMessage>
public class PlatformPopulator(ILti13PlatformService platformService) : Populator<IPlatformMessage>
{
public override async Task PopulateAsync(IPlatformMessage obj, MessageScope scope, CancellationToken cancellationToken = default)
{
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Populators/RolesPopulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface IRolesMessage
public IEnumerable<string>? RoleScopeMentor { get; set; }
}

public class RolesPopulator(ICoreDataService dataService) : Populator<IRolesMessage>
public class RolesPopulator(ILti13CoreDataService dataService) : Populator<IRolesMessage>
{
public override async Task PopulateAsync(IRolesMessage obj, MessageScope scope, CancellationToken cancellationToken = default)
{
Expand Down
Loading

0 comments on commit 23586c1

Please sign in to comment.