Skip to content

Commit

Permalink
Ability to use IFormFile or IFormFileCollection in HTTP middleware. C…
Browse files Browse the repository at this point in the history
…loses GH-926
  • Loading branch information
jeremydmiller committed Jun 13, 2024
1 parent 466d1b9 commit b6bbb42
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Alba;
using IntegrationTests;
using Marten;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Options;
using Wolverine.Attributes;
using Wolverine.Marten;

namespace Wolverine.Http.Tests.Bugs;

public class Bug_926_form_file_used_by_middleware
{
[Fact]
public async Task should_only_codegen_the_form_file_once()
{
var builder = WebApplication.CreateBuilder([]);

builder.Services.AddMarten(opts =>
{
// Establish the connection string to your Marten database
opts.Connection(Servers.PostgresConnectionString);
opts.DatabaseSchemaName = "myapp";
// Specify that we want to use STJ as our serializer
opts.UseSystemTextJsonForSerialization();
opts.Policies.AllDocumentsSoftDeleted();
opts.Policies.AllDocumentsAreMultiTenanted();
opts.DisableNpgsqlLogging = true;
}).IntegrateWithWolverine();

builder.Host.UseWolverine(opts =>
{
opts.Discovery.DisableConventionalDiscovery();
opts.ApplicationAssembly = GetType().Assembly;
});

// This is using Alba, which uses WebApplicationFactory under the covers
await using var host = await AlbaHost.For(builder, app =>
{
app.MapWolverineEndpoints();
});

await host.Scenario(x =>
{
x.Post.Url("/api/files/upload").ContentType("application/x-www-form-urlencoded");
});
}
}

public static class FileUploadEndpoint
{
[Middleware(
typeof(FileLengthValidationMiddleware),
typeof(FileExtensionValidationMiddleware)
)]
[WolverinePost("/api/files/upload")]
public static async Task<Ok> HandleAsync(IFormFile file)
{
// todo, generate filename, write mapping to table
// todo, create sideeffect to write file with new filename

return TypedResults.Ok(); // return new filename
}
}

public static class FileLengthValidationMiddleware
{
public static void Before(IFormFile file)
{
// todo, return ProblemDetail if validation fails
}
}

public static class FileExtensionValidationMiddleware
{
public static void Before(IFormFile file)
{
// todo, return ProblemDetail if validation fails
}
}
31 changes: 26 additions & 5 deletions src/Http/Wolverine.Http/CodeGen/FromFileStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,40 @@ public bool TryMatch(HttpChain chain, IContainer container, ParameterInfo parame
{
chain.FileParameters.Add(parameter);

var existing = chain.ChainVariables.FirstOrDefault(x => x.VariableType == typeof(IFormFile));
if (existing != null)
{
variable = existing;
return true;
}

var frame = new FromFileValue(parameter);
chain.Middleware.Add(frame);
variable = frame.Variable;
chain.Middleware.Add(frame);
variable = frame.Variable;
chain.ChainVariables.Add(variable);

return true;
} else if (parameter.ParameterType == typeof(IFormFileCollection))
}

if (parameter.ParameterType == typeof(IFormFileCollection))
{
chain.FileParameters.Add(parameter);

var existing = chain.ChainVariables.FirstOrDefault(x => x.VariableType == typeof(IFormFileCollection));
if (existing != null)
{
variable = existing;
return true;
}

var frame = new FromFileValues(parameter);
chain.Middleware.Add(frame);
variable = frame.Variable;
chain.Middleware.Add(frame);
variable = frame.Variable;
chain.ChainVariables.Add(variable);

return true;
}

variable = null;
return false;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Http/Wolverine.Http/HttpChain.Codegen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core;
using JasperFx.Core.Reflection;
using Microsoft.AspNetCore.Http;
Expand All @@ -17,6 +18,13 @@ namespace Wolverine.Http;

public partial class HttpChain
{
/// <summary>
/// Used to cache variables like for IFormFile or IFormFileCollection
/// that might be reused between middleware and handler methods, but should
/// not be created more than once
/// </summary>
public List<Variable> ChainVariables { get; } = new();

internal string? SourceCode => _generatedType?.SourceCode;

private readonly object _locker = new();
Expand Down

0 comments on commit b6bbb42

Please sign in to comment.