Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Add BindingSourceMetadataProvider #5750

Merged
merged 6 commits into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;

namespace Microsoft.AspNetCore.Mvc.ModelBinding
Expand Down Expand Up @@ -86,6 +87,24 @@ public class BindingSource : IEquatable<BindingSource>
isGreedy: true,
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>
/// Creates a new <see cref="BindingSource"/>.
/// </summary>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,10 @@
<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>
</data>
<data name="BindingSource_Special" xml:space="preserve">
<value>Special</value>
</data>
<data name="BindingSource_FormFile" xml:space="preserve">
<value>FormFile</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ public void Configure(MvcOptions options)
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider());
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
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.Same


// 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
});
}
}
}
18 changes: 18 additions & 0 deletions test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@ public void Setup_SetsUpMetadataDetailsProviders()
provider => Assert.IsType<DefaultBindingMetadataProvider>(provider),
provider => Assert.IsType<DefaultValidationMetadataProvider>(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);
Assert.Equal(typeof(Type), excludeFilter.Type);
Expand Down