Skip to content

Add metadata types for form reading and mapping options #49844

@captainsafia

Description

@captainsafia

Background and Motivation

To support full parity between the existing anti-forgery support in MVC and the new anti-forgery middleware, we need to support a metadata-based approach for configuring limits to be used when reading from a form. Also, we need to introduce a mapping options type to support configuring the binding experience for forms.

Proposed API

// Assembly: Microsoft.AspNetCore.Http.Abstractions;
namespace Microsoft.AspNetCore.Http.Metadata;

public class FormMappingOptionsMetadata(int maxCollectionSize, int maxRecursionDepth, int maxKeySize)
{
    public int MaxCollectionSize { get; } = maxCollectionSize;
    public int MaxRecursionDepth { get; } = maxRecursionDepth;
    public int MaxKeyBufferSize { get; } = maxKeySize;
}
// Assembly: Microsoft.AspNetCore.Http.Abstractions;
namespace Microsoft.AspNetCore.Http.Metadata;

public interface IRequestFormLimitsMetadata
{
    bool BufferBody { get; set; }
    int MemoryBufferThreshold { get; set; }
    long BufferBodyLengthLimit { get; set; }
    int ValueCountLimit { get; set; }
    int KeyLengthLimit { get; set; }
    int ValueLengthLimit { get; set; }
    int MultipartBoundaryLengthLimit { get; set; }
    int MultipartHeadersCountLimit { get; set; }
    int MultipartHeadersLengthLimit { get; set; }
    long MultipartBodyLengthLimit { get; set; }
}
// Assembly: Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Builder;

public static class RoutingEndpointConventionBuilderExtensions
{
  public static TBuilder WithFormMappingOptions<TBuilder>(this TBuilder builder,
    int maxCollectionSize = FormDataReader.DefaultValueCountLimit, // 1024
    int maxRecursionDepth = 64, // Same as STJ default
    int maxKeySize = FormReader.DefaultKeyLengthLimit /* 1024 * 2 */) where TBuilder : IEndpointConventionBuilder { }

  // Defaults map to pre-existing values throughout framework
  public static TBuilder WithRequestFormLimits<TBuilder>(this TBuilder builder,
    bool bufferBody = false,
    int memoryBufferThreshold = DefaultMemoryBufferThreshold, // 1024 * 64
    long bufferBodyLengthLimit = DefaultBufferBodyLengthLimit, // 1024 * 1024 * 128
    int valueCountLimit = FormReader.DefaultValueCountLimit, // 1024
    int keyLengthLimit = FormReader.DefaultKeyLengthLimit, // 1024 * 2
    int valueLengthLimit = FormReader.DefaultValueLengthLimit, // 1024 * 1024 * 4,
    int multipartBoundaryLengthLimit = DefaultMultipartBoundaryLengthLimit, // 128
    int multipartHeadersCountLimit = MultipartReader.DefaultHeadersCountLimit, // 16
    int multipartHeaderLengthLimit = MultipartReader.DefaultHeadersLengthLimit, // 1024 * 16,
    int multipartBodyLengthLimit = DefaultMultipartBodyLengthLimit, /* 1024 * 1024 * 128 */) where TBuilder : IEndpointConventionBuilder { }
}
// Assembly: Microsoft.AspNetCore.Mvc.Core;
namespace Microsoft.AspNetCore.Mvc;

- public class RequestFormLimitsAttribute : Attribute, IFilterFactory, IOrderedFilter {}
+ public class RequestFormLimitsAttribute : Attribute, IFilterFactory, IOrderedFilter, IRequestFormLimitsMetadata {}

Usage Examples

var app = WebApplication.Create();

app.MapPost("/todo-1", ([FromForm] Todo todo) => todo)
  .WithFormMappingOptions(10, 10, 2);

app.MapPost("/todo-2", ([FromForm] Todo todo) => todo)
  .WithFormMappingOptions(maxRecursionDepth: 5);

app.MapPost("/todo-3", ([FromForm] Todo todo) => todo)
  .WithFormMappingOptions(maxRecursionDepth: 5)
  .WithRequestFormLimits(valueCountLimit: 4);

var todos = app.MapGroup("/todos").WithRequestFormLimits(bufferBody: true);

todos.MapPost("/4", ([FromForm] Todo todo) => todo)
  .WithRequestFormLimits(valueLengthLimit: 1024, multipartHeadersCountLimit: 4)

app.Run();

Alternative Designs

For .NET 8, we're intentionally moving with the model of having separate form mapping options types for Blazor and minimal APIs, even though they have the same form mapping infrastructure. The goal is to unify these options types in .NET 9 into a new assembly, but for now, the FormMappingOptionsMetadata is defined only as a concrete implementation and not an interface since we don't anticipate evolving it further in .NET 8 before we unify the two types.

Risks

There are risks and costs to action.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcarea-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templates

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions