parent
366dbde378
commit
927e75870d
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
@ -86,6 +87,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
isGreedy: true,
|
isGreedy: true,
|
||||||
isFromRequest: false);
|
isFromRequest: false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="BindingSource"/> for special parameter types that are not user input.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly BindingSource Special = new BindingSource(
|
||||||
|
"Special",
|
||||||
|
Resources.BindingSource_Special,
|
||||||
|
isGreedy: true,
|
||||||
|
isFromRequest: false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="BindingSource"/> for <see cref="IFormFile"/> and <see cref="IFormCollection"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly BindingSource FormFile = new BindingSource(
|
||||||
|
"FormFile",
|
||||||
|
Resources.BindingSource_FormFile,
|
||||||
|
isGreedy: true,
|
||||||
|
isFromRequest: true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="BindingSource"/>.
|
/// Creates a new <see cref="BindingSource"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,38 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
|
||||||
return GetString("BindingSource_Services");
|
return GetString("BindingSource_Services");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Special
|
||||||
|
/// </summary>
|
||||||
|
internal static string BindingSource_Special
|
||||||
|
{
|
||||||
|
get { return GetString("BindingSource_Special"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Special
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatBindingSource_Special()
|
||||||
|
{
|
||||||
|
return GetString("BindingSource_Special");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FormFile
|
||||||
|
/// </summary>
|
||||||
|
internal static string BindingSource_FormFile
|
||||||
|
{
|
||||||
|
get { return GetString("BindingSource_FormFile"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FormFile
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatBindingSource_FormFile()
|
||||||
|
{
|
||||||
|
return GetString("BindingSource_FormFile");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ModelBinding
|
/// ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -168,4 +168,10 @@
|
||||||
<data name="BindingSource_MustBeGreedy" xml:space="preserve">
|
<data name="BindingSource_MustBeGreedy" xml:space="preserve">
|
||||||
<value>The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.</value>
|
<value>The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BindingSource_Special" xml:space="preserve">
|
||||||
|
<value>Special</value>
|
||||||
|
</data>
|
||||||
|
<data name="BindingSource_FormFile" xml:space="preserve">
|
||||||
|
<value>FormFile</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -79,6 +79,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider());
|
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider());
|
||||||
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
||||||
|
|
||||||
|
options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(CancellationToken), BindingSource.Special));
|
||||||
|
options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile));
|
||||||
|
options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile));
|
||||||
|
|
||||||
// Set up validators
|
// Set up validators
|
||||||
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());
|
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
{
|
||||||
|
public class BindingSourceMetadataProvider : IBindingMetadataProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="BindingSourceMetadataProvider"/> for the given <paramref name="type"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">
|
||||||
|
/// The <see cref="Type"/>. The provider sets <see cref="BindingSource"/> of the given <see cref="Type"/> or
|
||||||
|
/// anything assignable to the given <see cref="Type"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="bindingSource">
|
||||||
|
/// The <see cref="BindingSource"/> to assign to the given <paramref name="type"/>.
|
||||||
|
/// </param>
|
||||||
|
public BindingSourceMetadataProvider(Type type, BindingSource bindingSource)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
Type = type;
|
||||||
|
BindingSource = bindingSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type Type { get; }
|
||||||
|
public BindingSource BindingSource { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CreateBindingMetadata(BindingMetadataProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Type.IsAssignableFrom(context.Key.ModelType))
|
||||||
|
{
|
||||||
|
context.BindingMetadata.BindingSource = BindingSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
{
|
||||||
|
public class BindingSourceMetadataProviderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateBindingMetadata_ForMatchingType_SetsBindingSource()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new BindingSourceMetadataProvider(typeof(Test), BindingSource.Special);
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(typeof(Test));
|
||||||
|
|
||||||
|
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(BindingSource.Special, context.BindingMetadata.BindingSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Test
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
|
{
|
||||||
|
public class BindingSourceMetadataProviderIntegrationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task BindParameter_WithCancellationToken_BindingSourceSpecial()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new MvcOptions();
|
||||||
|
var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
|
||||||
|
|
||||||
|
options.ModelBinderProviders.Insert(0, new CancellationTokenModelBinderProvider());
|
||||||
|
|
||||||
|
setup.Configure(options);
|
||||||
|
|
||||||
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(options);
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "Parameter1",
|
||||||
|
BindingInfo = new BindingInfo(),
|
||||||
|
ParameterType = typeof(CancellationTokenBundle),
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.Form = new FormCollection(new Dictionary<string, StringValues>
|
||||||
|
{
|
||||||
|
{ "name", new[] { "Fred" } }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
var token = testContext.HttpContext.RequestAborted;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// ModelBindingResult
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
// Model
|
||||||
|
var boundPerson = Assert.IsType<CancellationTokenBundle>(modelBindingResult.Model);
|
||||||
|
Assert.NotNull(boundPerson);
|
||||||
|
Assert.Equal("Fred", boundPerson.Name);
|
||||||
|
Assert.Equal(token, boundPerson.Token);
|
||||||
|
|
||||||
|
// ModelState
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CancellationTokenBundle
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public CancellationToken Token { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindParameter_WithFormFile_BindingSourceFormFile()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new MvcOptions();
|
||||||
|
var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
|
||||||
|
|
||||||
|
options.ModelBinderProviders.Insert(0, new FormFileModelBinderProvider());
|
||||||
|
|
||||||
|
setup.Configure(options);
|
||||||
|
|
||||||
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(options);
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "Parameter1",
|
||||||
|
BindingInfo = new BindingInfo(),
|
||||||
|
ParameterType = typeof(FormFileBundle),
|
||||||
|
};
|
||||||
|
|
||||||
|
var data = "Some Data Is Better Than No Data.";
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||||
|
request =>
|
||||||
|
{
|
||||||
|
request.QueryString = QueryString.Create("Name", "Fred");
|
||||||
|
UpdateRequest(request, data, "File");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// ModelBindingResult
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
// Model
|
||||||
|
var boundPerson = Assert.IsType<FormFileBundle>(modelBindingResult.Model);
|
||||||
|
Assert.Equal("Fred", boundPerson.Name);
|
||||||
|
Assert.Equal("text.txt", boundPerson.File.FileName);
|
||||||
|
|
||||||
|
// ModelState
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FormFileBundle
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public IFormFile File { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRequest(HttpRequest request, string data, string name)
|
||||||
|
{
|
||||||
|
const string fileName = "text.txt";
|
||||||
|
var fileCollection = new FormFileCollection();
|
||||||
|
var formCollection = new FormCollection(new Dictionary<string, StringValues>(), fileCollection);
|
||||||
|
|
||||||
|
request.Form = formCollection;
|
||||||
|
request.ContentType = "multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(data) || string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
// Leave the submission empty.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Headers["Content-Disposition"] = $"form-data; name={name}; filename={fileName}";
|
||||||
|
|
||||||
|
var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(data));
|
||||||
|
fileCollection.Add(new FormFile(memoryStream, 0, data.Length, name, fileName)
|
||||||
|
{
|
||||||
|
Headers = request.Headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -162,6 +162,24 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
provider => Assert.IsType<DefaultBindingMetadataProvider>(provider),
|
provider => Assert.IsType<DefaultBindingMetadataProvider>(provider),
|
||||||
provider => Assert.IsType<DefaultValidationMetadataProvider>(provider),
|
provider => Assert.IsType<DefaultValidationMetadataProvider>(provider),
|
||||||
provider =>
|
provider =>
|
||||||
|
{
|
||||||
|
var specialParameter = Assert.IsType<BindingSourceMetadataProvider>(provider);
|
||||||
|
Assert.Equal(typeof(CancellationToken), specialParameter.Type);
|
||||||
|
Assert.Equal(BindingSource.Special, specialParameter.BindingSource);
|
||||||
|
},
|
||||||
|
provider =>
|
||||||
|
{
|
||||||
|
var formFileParameter = Assert.IsType<BindingSourceMetadataProvider>(provider);
|
||||||
|
Assert.Equal(typeof(IFormFile), formFileParameter.Type);
|
||||||
|
Assert.Equal(BindingSource.FormFile, formFileParameter.BindingSource);
|
||||||
|
},
|
||||||
|
provider =>
|
||||||
|
{
|
||||||
|
var formCollectionParameter = Assert.IsType<BindingSourceMetadataProvider>(provider);
|
||||||
|
Assert.Equal(typeof(IFormCollection), formCollectionParameter.Type);
|
||||||
|
Assert.Equal(BindingSource.FormFile, formCollectionParameter.BindingSource);
|
||||||
|
},
|
||||||
|
provider =>
|
||||||
{
|
{
|
||||||
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
|
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
|
||||||
Assert.Equal(typeof(Type), excludeFilter.Type);
|
Assert.Equal(typeof(Type), excludeFilter.Type);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue