I'm writing C# Lambda functions to be exposed by the API Gateway service, using the AWS Annotation Framework.
I successfully registered application services in the ConfigureServices(IServiceCollection services)
method of the Startup.cs file.
In order to add some API configurations in the header of all incoming requests (Authorization header, etc.), I registered a middleware via the Configure(IApplicationBuilder app, IWebHostEnvironment env)
method of the the Startup.cs file.
The problem is, the application is completely ignoring the middleware; in other terms, the application never passes through the middleware.
Here is my code:
Lambda function (in Function.cs file):
using Amazon.Lambda.Core;
using Amazon.Lambda.Annotations;
using TbTypes.Model.Api.Requests;
using Microsoft.AspNetCore.Mvc;
using FromServicesAttribute = Amazon.Lambda.Annotations.FromServicesAttribute;
using FromBodyAttribute = Microsoft.AspNetCore.Mvc.FromBodyAttribute;
using TbTypes.Services.Thingsboard;
using TbTypes.Model.Api.Reponses;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace TbInterface;
public class Function
{
/// <summary>
/// Function for requesting TB Devices visible by a User
/// </summary>
[LambdaFunction()]
[HttpGet("/user/devices/")]
public async Task<DevicesPage> FetchDevices(
[FromBody] UserDevicesRequestBody body,
ILambdaContext context,
[FromServices] IDeviceService service)
{
// you can replace IDeviceService by a dummy service when reproducing the issue
return await service.FetchDevices(body.claims.TbUserID, body.claims.CognitoUserID);
}
}
My Startups.cs file with services registration in ConfigureServices() and middleware registration in Configure() method:
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Lambda.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Diagnostics;
using TbInterface.Configuration;
using TbInterface.Middlewares;
using TbInterface.Repositories;
using TbInterface.Services.Cache;
using TbInterface.Services.Database;
using TbInterface.Services.Thingsboard;
using TbTypes.Configuration;
using TbTypes.Repositories;
using TbTypes.Services.Cache;
using TbTypes.Services.Thingsboard;
namespace TbInterface
{
[LambdaStartup]
public class Startup
{
private IConfiguration Configuration;
public Startup()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
// Application Configurations
services.AddSingleton<IConfiguration>(implementationInstance: Configuration);
services.AddSingleton<ITbConfiguration>(sp => {
var settings = sp.GetRequiredService<IConfiguration>();
return new TbConfiguration(settings);
});
// Cache Service: AWS Elasti Cache
services.AddSingleton<IElastiCacheService, ElastiCacheService>();
// Database Service: AWS DynamoDB
services.AddSingleton<IAmazonDynamoDB, DynamoDB>();
services.AddAWSService<IAmazonDynamoDB>();
//services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<Amazon.S3.IAmazonS3>();
services.AddAWSService<IAmazonDynamoDB>();
// Repositories
services.AddSingleton<IUserTokenRepository, UserTokenRepository>();
// Thingsboard API services
services.AddSingleton<IAuthTokenService, AuthTokenService>();
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<IDeviceService, DeviceService>();
services.AddTransient<TbApiConfigurationMiddleware>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Here's how I resgistered the middleware
app.UseMiddleware<TbApiConfigurationMiddleware>();
}
}
}
The middleware itself - TbApiConfigurationMiddleware.cs :
using Microsoft.AspNetCore.Http;
using System.Net;
using Newtonsoft.Json;
using TbAPIClient.Generated.Client;
using TbAPIClient.Generated.Model;
using TbClientConfiguration = TbAPIClient.Generated.Client.Configuration;
using TbTypes.Model.Api.Requests;
using TbTypes.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace TbInterface.Middlewares
{
public class TbApiConfigurationMiddleware : IMiddleware
{
/// <summary>
/// Custom logic to be executed before the next middleware
/// </summary>
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var authService = context.RequestServices.GetService<IAuthTokenService>();
BaseApiRequestBody? body = extractRequestBody(context);
if (body == null || body.claims == null) {
await handleBadRequestBody(context, "Bad request body format");
return;
}
//JwtPair? token = await _authService.RetrieveOrRequestUserToken(body.claims.TbUserID!);
JwtPair? token = await authService!.FetchUserTokenAsync(body.claims.TbUserID!);
if (token == null)
{
await handleUnauthorizedUser(context);
return;
}
var tbConfiguration = context.RequestServices.GetService<ITbConfiguration>();
ConfigureTbApiToken(token!, tbConfiguration!);
await next(context);
}
/// <summary>
/// Extract request body to perform basic format validation
/// </summary>
/// <param name="context">HTTP Context</param>
/// <returns></returns>
private BaseApiRequestBody? extractRequestBody(HttpContext context) {
var rawBody = context.Request.Body.ToString();
if (rawBody == null)
{
return null;
}
return JsonConvert.DeserializeObject<BaseApiRequestBody>(rawBody);
}
/// <summary>
/// Handling bad request body
/// </summary>
/// <param name="context">HTTP Context</param>
/// <returns></returns>
private async Task handleBadRequestBody(HttpContext context, string message) {
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Response.ContentType = "application/json";
var body = new ApiException(context.Response.StatusCode, message);
await context.Response.WriteAsync(JsonConvert.SerializeObject(body));
}
/// <summary>
/// Configuring middleware response in case of Unauthorized User
/// </summary>
/// <param name="context">HTTP Context</param>
/// <returns></returns>
private async Task handleUnauthorizedUser(HttpContext context)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.ContentType = "application/json";
var body = new ApiException(context.Response.StatusCode, "Unauthorized user");
await context.Response.WriteAsync(JsonConvert.SerializeObject(body));
}
/// <summary>
/// Method for configuring Thingsboard API Auth Token
/// </summary>
/// <param name="token">Token definition: {Token, RefreshToken} </param>
/// <param name="tbConfiguration">Application configs </param>
/// <returns></returns>
private void ConfigureTbApiToken(JwtPair token, ITbConfiguration tbConfiguration)
{
TbClientConfiguration.Default.ApiKeyPrefix[tbConfiguration.TokenHeaderKey] = tbConfiguration.TokenType;
TbClientConfiguration.Default.ApiKey[tbConfiguration.TokenHeaderKey] = (string)token.Token;
}
}
}