Added RequestFormLimits filter.

[Fixes #5128] Overriding Request Form max upload limit
This commit is contained in:
Kiran Challa 2017-09-12 17:56:29 -07:00
parent e114911d77
commit 23b7d8f62a
9 changed files with 708 additions and 1 deletions

View File

@ -199,10 +199,11 @@ namespace Microsoft.Extensions.DependencyInjection
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
//
// RequestSizeLimit filters
// Request body limit filters
//
services.TryAddTransient<RequestSizeLimitFilter>();
services.TryAddTransient<DisableRequestSizeLimitFilter>();
services.TryAddTransient<RequestFormLimitsFilter>();
// Error description
services.TryAddSingleton<IErrorDescriptionFactory, DefaultErrorDescriptorFactory>();

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// A marker interface for filters which define a policy for limits on a request's body read as a form.
/// </summary>
public interface IRequestFormLimitsPolicy : IFilterMetadata
{
}
}

View File

@ -77,6 +77,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static readonly Action<ILogger, string, Exception> _maxRequestBodySizeSet;
private static readonly Action<ILogger, Exception> _requestBodySizeLimitDisabled;
private static readonly Action<ILogger, Exception> _cannotApplyRequestFormLimits;
private static readonly Action<ILogger, Exception> _appliedRequestFormLimits;
static MvcCoreLoggerExtensions()
{
_actionExecuting = LoggerMessage.Define<string>(
@ -268,6 +272,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
LogLevel.Debug,
3,
"The request body size limit has been disabled.");
_cannotApplyRequestFormLimits = LoggerMessage.Define(
LogLevel.Warning,
1,
"Unable to apply configured form options since the request form has already been read.");
_appliedRequestFormLimits = LoggerMessage.Define(
LogLevel.Debug,
2,
"Applied the configured form options on the current request.");
}
public static IDisposable ActionScope(this ILogger logger, ActionDescriptor action)
@ -568,6 +582,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_requestBodySizeLimitDisabled(logger, null);
}
public static void CannotApplyRequestFormLimits(this ILogger logger)
{
_cannotApplyRequestFormLimits(logger, null);
}
public static void AppliedRequestFormLimits(this ILogger logger)
{
_appliedRequestFormLimits(logger, null);
}
private class ActionLogScope : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly ActionDescriptor _action;

View File

@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
{
/// <summary>
/// A filter that configures <see cref="FormOptions"/> for the current request.
/// </summary>
public class RequestFormLimitsFilter : IAuthorizationFilter, IRequestFormLimitsPolicy
{
private readonly ILogger _logger;
public RequestFormLimitsFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<RequestFormLimitsFilter>();
}
public FormOptions FormOptions { get; set; }
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.IsEffectivePolicy<IRequestFormLimitsPolicy>(this))
{
var features = context.HttpContext.Features;
var formFeature = features.Get<IFormFeature>();
if (formFeature == null || formFeature.Form == null)
{
// Request form has not been read yet, so set the limits
features.Set<IFormFeature>(new FormFeature(context.HttpContext.Request, FormOptions));
_logger.AppliedRequestFormLimits();
}
else
{
_logger.CannotApplyRequestFormLimits();
}
}
}
}
}

View File

@ -0,0 +1,154 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Sets the specified limits to the <see cref="HttpRequest.Form"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequestFormLimitsAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <summary>
/// Gets the order value for determining the order of execution of filters. Filters execute in
/// ascending numeric value of the <see cref="Order"/> property.
/// </summary>
/// <remarks>
/// <para>
/// Filters are executed in an ordering determined by an ascending sort of the <see cref="Order"/> property.
/// </para>
/// <para>
/// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and
/// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
/// </para>
/// <para>
/// Look at <see cref="IOrderedFilter.Order"/> for more detailed info.
/// </para>
/// </remarks>
public int Order { get; set; } = 900;
/// <inheritdoc />
public bool IsReusable => true;
// Internal for unit testing
internal FormOptions FormOptions { get; } = new FormOptions();
/// <summary>
/// Enables full request body buffering. Use this if multiple components need to read the raw stream.
/// The default value is false.
/// </summary>
public bool BufferBody
{
get => FormOptions.BufferBody;
set => FormOptions.BufferBody = value;
}
/// <summary>
/// If <see cref="BufferBody"/> is enabled, this many bytes of the body will be buffered in memory.
/// If this threshold is exceeded then the buffer will be moved to a temp file on disk instead.
/// This also applies when buffering individual multipart section bodies.
/// </summary>
public int MemoryBufferThreshold
{
get => FormOptions.MemoryBufferThreshold;
set => FormOptions.MemoryBufferThreshold = value;
}
/// <summary>
/// If <see cref="BufferBody"/> is enabled, this is the limit for the total number of bytes that will
/// be buffered. Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
/// </summary>
public long BufferBodyLengthLimit
{
get => FormOptions.BufferBodyLengthLimit;
set => FormOptions.BufferBodyLengthLimit = value;
}
/// <summary>
/// A limit for the number of form entries to allow.
/// Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
/// </summary>
public int ValueCountLimit
{
get => FormOptions.ValueCountLimit;
set => FormOptions.ValueCountLimit = value;
}
/// <summary>
/// A limit on the length of individual keys. Forms containing keys that exceed this limit will
/// throw an <see cref="InvalidDataException"/> when parsed.
/// </summary>
public int KeyLengthLimit
{
get => FormOptions.KeyLengthLimit;
set => FormOptions.KeyLengthLimit = value;
}
/// <summary>
/// A limit on the length of individual form values. Forms containing values that exceed this
/// limit will throw an <see cref="InvalidDataException"/> when parsed.
/// </summary>
public int ValueLengthLimit
{
get => FormOptions.ValueLengthLimit;
set => FormOptions.ValueLengthLimit = value;
}
/// <summary>
/// A limit for the length of the boundary identifier. Forms with boundaries that exceed this
/// limit will throw an <see cref="InvalidDataException"/> when parsed.
/// </summary>
public int MultipartBoundaryLengthLimit
{
get => FormOptions.MultipartBoundaryLengthLimit;
set => FormOptions.MultipartBoundaryLengthLimit = value;
}
/// <summary>
/// A limit for the number of headers to allow in each multipart section. Headers with the same name will
/// be combined. Form sections that exceed this limit will throw an <see cref="InvalidDataException"/>
/// when parsed.
/// </summary>
public int MultipartHeadersCountLimit
{
get => FormOptions.MultipartHeadersCountLimit;
set => FormOptions.MultipartHeadersCountLimit = value;
}
/// <summary>
/// A limit for the total length of the header keys and values in each multipart section.
/// Form sections that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
/// </summary>
public int MultipartHeadersLengthLimit
{
get => FormOptions.MultipartHeadersLengthLimit;
set => FormOptions.MultipartHeadersLengthLimit = value;
}
/// <summary>
/// A limit for the length of each multipart body. Forms sections that exceed this limit will throw an
/// <see cref="InvalidDataException"/> when parsed.
/// </summary>
public long MultipartBodyLengthLimit
{
get => FormOptions.MultipartBodyLengthLimit;
set => FormOptions.MultipartBodyLengthLimit = value;
}
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var filter = serviceProvider.GetRequiredService<RequestFormLimitsFilter>();
filter.FormOptions = FormOptions;
return filter;
}
}
}

View File

@ -0,0 +1,142 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class RequestFormLimitsFilterTest
{
[Fact]
public void SetsRequestFormFeature_WhenFeatureIsNotPresent()
{
// Arrange
var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance);
requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter });
// Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null);
// Act
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
// Assert
var formFeature = authorizationFilterContext.HttpContext.Features.Get<IFormFeature>();
Assert.IsType<FormFeature>(formFeature);
}
[Fact]
public void SetsRequestFormFeature_WhenFeatureIsPresent_ButFormIsNull()
{
// Arrange
var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance);
requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter });
var oldFormFeature = new FormFeature(authorizationFilterContext.HttpContext.Request);
// Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(oldFormFeature);
// Act
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
// Assert
var actualFormFeature = authorizationFilterContext.HttpContext.Features.Get<IFormFeature>();
Assert.NotSame(oldFormFeature, actualFormFeature);
}
[Fact]
public void LogsCannotApplyRequestFormLimits()
{
// Arrange
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter });
authorizationFilterContext.HttpContext.Request.Form = new FormCollection(null);
// Act
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
// Assert
var write = Assert.Single(sink.Writes);
Assert.Equal(LogLevel.Warning, write.LogLevel);
Assert.Equal(
"Unable to apply configured form options since the request form has already been read.",
write.State.ToString());
}
[Fact]
public void LogsAppliedRequestFormLimits_WhenFormFeatureIsNull()
{
// Arrange
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter });
// Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null);
// Act
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
// Assert
var write = Assert.Single(sink.Writes);
Assert.Equal(LogLevel.Debug, write.LogLevel);
Assert.Equal(
"Applied the configured form options on the current request.",
write.State.ToString());
}
[Fact]
public void LogsAppliedRequestFormLimits_WhenFormFeatureIsPresent_ButFormIsNull()
{
// Arrange
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter });
// Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(
new FormFeature(authorizationFilterContext.HttpContext.Request));
// Act
requestFormLimitsFilter.OnAuthorization(authorizationFilterContext);
// Assert
var write = Assert.Single(sink.Writes);
Assert.Equal(LogLevel.Debug, write.LogLevel);
Assert.Equal(
"Applied the configured form options on the current request.",
write.State.ToString());
}
private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters)
{
return new AuthorizationFilterContext(CreateActionContext(), filters);
}
private static ActionContext CreateActionContext()
{
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
namespace Microsoft.AspNetCore.Mvc
{
public class RequestFormLimitsAttributeTest
{
[Fact]
public void AllPublicProperties_OfFormOptions_AreExposed()
{
// Arrange
var formOptionsProperties = GetProperties(typeof(FormOptions));
var formLimitsAttributeProperties = GetProperties(typeof(RequestFormLimitsAttribute));
// Act & Assert
foreach (var property in formOptionsProperties)
{
var formLimiAttributeProperty = formLimitsAttributeProperties
.Where(pi => property.Name == pi.Name && pi.PropertyType == property.PropertyType)
.SingleOrDefault();
Assert.NotNull(formLimiAttributeProperty);
}
}
[Fact]
public void CreatesFormOptions_WithDefaults()
{
// Arrange
var formOptionsProperties = GetProperties(typeof(FormOptions));
var formLimitsAttributeProperties = GetProperties(typeof(RequestFormLimitsAttribute));
var formOptions = new FormOptions();
// Act
var requestFormLimitsAttribute = new RequestFormLimitsAttribute();
// Assert
foreach (var formOptionsProperty in formOptionsProperties)
{
var formLimitsAttributeProperty = formLimitsAttributeProperties
.Where(pi => pi.Name == formOptionsProperty.Name && pi.PropertyType == formOptionsProperty.PropertyType)
.SingleOrDefault();
Assert.Equal(
formOptionsProperty.GetValue(formOptions),
formLimitsAttributeProperty.GetValue(requestFormLimitsAttribute));
}
}
[Fact]
public void UpdatesFormOptions_WithOverridenValues()
{
// Arrange
var requestFormLimitsAttribute = new RequestFormLimitsAttribute();
// Act
requestFormLimitsAttribute.BufferBody = true;
requestFormLimitsAttribute.BufferBodyLengthLimit = 0;
requestFormLimitsAttribute.KeyLengthLimit = 0;
requestFormLimitsAttribute.MemoryBufferThreshold = 0;
requestFormLimitsAttribute.MultipartBodyLengthLimit = 0;
requestFormLimitsAttribute.MultipartBoundaryLengthLimit = 0;
requestFormLimitsAttribute.MultipartHeadersCountLimit = 0;
requestFormLimitsAttribute.MultipartHeadersLengthLimit = 0;
requestFormLimitsAttribute.ValueCountLimit = 0;
requestFormLimitsAttribute.ValueLengthLimit = 0;
// Assert
Assert.True(requestFormLimitsAttribute.FormOptions.BufferBody);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.BufferBodyLengthLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.KeyLengthLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MemoryBufferThreshold);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartBodyLengthLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartBoundaryLengthLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartHeadersCountLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.MultipartHeadersLengthLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.ValueCountLimit);
Assert.Equal(0, requestFormLimitsAttribute.FormOptions.ValueLengthLimit);
}
private PropertyInfo[] GetProperties(Type type)
{
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
}
}
}

View File

@ -0,0 +1,170 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class RequestFormLimitsTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupRequestLimitSize>>
{
public RequestFormLimitsTest(MvcTestFixture<BasicWebSite.StartupRequestLimitSize> fixture)
{
Client = fixture.Client;
}
public HttpClient Client { get; }
[Fact]
public async Task RequestFormLimitCheckHappens_BeforeAntiforgeryTokenValidation()
{
// Arrange
var request = new HttpRequestMessage();
var kvps = new List<KeyValuePair<string, string>>();
// Controller has value count limit of 2
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
kvps.Add(new KeyValuePair<string, string>("key2", "value2"));
kvps.Add(new KeyValuePair<string, string>("key3", "value3"));
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
// Act
var response = await Client.PostAsync(
"RequestFormLimits/RequestFormLimitsBeforeAntiforgeryValidation",
new FormUrlEncodedContent(kvps));
// Assert
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.Contains(
"InvalidDataException: Form value count limit 2 exceeded.",
result);
}
[Fact]
public async Task OverridesControllerLevelLimits()
{
// Arrange
var expected = "{\"sampleInt\":10,\"sampleString\":null}";
var request = new HttpRequestMessage();
var kvps = new List<KeyValuePair<string, string>>();
// Controller has a value count limit of 2, but the action has a limit of 5
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
kvps.Add(new KeyValuePair<string, string>("key2", "value2"));
kvps.Add(new KeyValuePair<string, string>("SampleInt", "10"));
// Act
var response = await Client.PostAsync(
"RequestFormLimits/OverrideControllerLevelLimits",
new FormUrlEncodedContent(kvps));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.Equal(expected, result);
}
[Fact]
public async Task OverrideControllerLevelLimits_UsingDefaultLimits()
{
// Arrange
var expected = "{\"sampleInt\":50,\"sampleString\":null}";
var request = new HttpRequestMessage();
var kvps = new List<KeyValuePair<string, string>>();
// Controller has a key limit of 2, but the action has default limits
for (var i = 0; i < 10; i++)
{
kvps.Add(new KeyValuePair<string, string>($"key{i}", $"value{i}"));
}
kvps.Add(new KeyValuePair<string, string>("SampleInt", "50"));
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
// Act
var response = await Client.PostAsync(
"RequestFormLimits/OverrideControllerLevelLimitsUsingDefaultLimits",
new FormUrlEncodedContent(kvps));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.Equal(expected, result);
}
[Fact]
public async Task RequestSizeLimitCheckHappens_BeforeRequestFormLimits()
{
// Arrange
var request = new HttpRequestMessage();
var kvps = new List<KeyValuePair<string, string>>();
// Request size has a limit of 100 bytes
// Request form limits has a value count limit of 2
// Antiforgery validation is also present
kvps.Add(new KeyValuePair<string, string>("key1", new string('a', 1024)));
kvps.Add(new KeyValuePair<string, string>("key2", "value2"));
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
// Act
var response = await Client.PostAsync(
"RequestFormLimits/RequestSizeLimitBeforeRequestFormLimits",
new FormUrlEncodedContent(kvps));
// Assert
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.Contains(
"InvalidOperationException: Request content size is greater than the limit size",
result);
}
[Fact]
public async Task RequestFormLimitsCheckHappens_AfterRequestSizeLimit()
{
// Arrange
var request = new HttpRequestMessage();
var kvps = new List<KeyValuePair<string, string>>();
// Request size has a limit of 100 bytes
// Request form limits has a value count limit of 2
// Antiforgery validation is also present
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
kvps.Add(new KeyValuePair<string, string>("key1", "value2"));
kvps.Add(new KeyValuePair<string, string>("key1", "value3"));
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
// Act
var response = await Client.PostAsync(
"RequestFormLimits/RequestSizeLimitBeforeRequestFormLimits",
new FormUrlEncodedContent(kvps));
// Assert
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.Contains(
"InvalidDataException: Form value count limit 2 exceeded.",
result);
}
[Fact]
public async Task AntiforgeryValidationHappens_AfterRequestFormAndSizeLimitCheck()
{
// Arrange
var request = new HttpRequestMessage();
var kvps = new List<KeyValuePair<string, string>>();
// Request size has a limit of 100 bytes
// Request form limits has a value count limit of 2
// Antiforgery validation is also present
kvps.Add(new KeyValuePair<string, string>("key1", "value1"));
kvps.Add(new KeyValuePair<string, string>("RequestVerificationToken", "invalid-data"));
// Act
var response = await Client.PostAsync(
"RequestFormLimits/RequestSizeLimitBeforeRequestFormLimits",
new FormUrlEncodedContent(kvps));
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicWebSite.Models;
using Microsoft.AspNetCore.Mvc;
namespace BasicWebSite.Controllers
{
[RequestFormLimits(ValueCountLimit = 2)]
public class RequestFormLimitsController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult RequestFormLimitsBeforeAntiforgeryValidation(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Json(product);
}
[HttpPost]
[RequestFormLimits(ValueCountLimit = 5)]
public IActionResult OverrideControllerLevelLimits(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Json(product);
}
[HttpPost]
[RequestFormLimits]
public IActionResult OverrideControllerLevelLimitsUsingDefaultLimits(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Json(product);
}
[HttpPost]
[RequestFormLimits(ValueCountLimit = 2)]
[RequestSizeLimit(100)]
[ValidateAntiForgeryToken]
public IActionResult RequestSizeLimitBeforeRequestFormLimits(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Json(product);
}
}
}