Introducing Controller.TryUpdateModel
This changeset reintroduces some of the overloads for Controller.TryUpdateModel. Fixes #415
This commit is contained in:
parent
da0bf6f7d8
commit
24f74222f5
|
|
@ -20,6 +20,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
get
|
||||
{
|
||||
if (ActionContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ActionContext.HttpContext;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +33,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
get
|
||||
{
|
||||
if (ViewData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ViewData.ModelState;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +48,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
[Activate]
|
||||
public IUrlHelper Url { get; set; }
|
||||
|
||||
[Activate]
|
||||
public IActionBindingContextProvider BindingContextProvider { get; set; }
|
||||
|
||||
public IPrincipal User
|
||||
{
|
||||
get
|
||||
|
|
@ -438,5 +451,72 @@ namespace Microsoft.AspNet.Mvc
|
|||
OnActionExecuted(await next());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using values from the controller's current value provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <returns>true if the update is successful; otherwise, false.</returns>
|
||||
[NonAction]
|
||||
public virtual Task<bool> TryUpdateModelAsync<TModel>([NotNull] TModel model)
|
||||
where TModel : class
|
||||
{
|
||||
return TryUpdateModelAsync(model, prefix: typeof(TModel).Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using values from the controller's current value provider
|
||||
/// and a prefix.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the value provider.</param>
|
||||
/// <returns>true if the update is successful; otherwise, false.</returns>
|
||||
[NonAction]
|
||||
public virtual async Task<bool> TryUpdateModelAsync<TModel>([NotNull] TModel model,
|
||||
[NotNull] string prefix)
|
||||
where TModel : class
|
||||
{
|
||||
if (BindingContextProvider == null)
|
||||
{
|
||||
var message = Resources.FormatPropertyOfTypeCannotBeNull("BindingContextProvider", GetType().FullName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
|
||||
return await TryUpdateModelAsync(model, prefix, bindingContext.ValueProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using the value provider and a prefix.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the value provider.</param>
|
||||
/// <param name="valueProvider">The value provider used for looking up values.</param>
|
||||
/// <returns>true if the update is successful; otherwise, false.</returns>
|
||||
[NonAction]
|
||||
public virtual async Task<bool> TryUpdateModelAsync<TModel>([NotNull] TModel model,
|
||||
[NotNull] string prefix,
|
||||
[NotNull] IValueProvider valueProvider)
|
||||
where TModel : class
|
||||
{
|
||||
if (BindingContextProvider == null)
|
||||
{
|
||||
var message = Resources.FormatPropertyOfTypeCannotBeNull("BindingContextProvider", GetType().FullName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
|
||||
return await ModelBindingHelper.TryUpdateModelAsync(model,
|
||||
prefix,
|
||||
ActionContext.HttpContext,
|
||||
ModelState,
|
||||
bindingContext.MetadataProvider,
|
||||
bindingContext.ModelBinder,
|
||||
valueProvider,
|
||||
bindingContext.ValidatorProviders);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
<Compile Include="Formatters\OutputFormatterContext.cs" />
|
||||
<Compile Include="Formatters\JsonOutputFormatter.cs" />
|
||||
<Compile Include="Formatters\OutputFormatter.cs" />
|
||||
<Compile Include="ParameterBinding\ModelBindingHelper.cs" />
|
||||
<Compile Include="ReflectedActionDescriptor.cs" />
|
||||
<Compile Include="ReflectedActionDescriptorProvider.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\IReflectedApplicationModelConvention.cs" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public static class ModelBindingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using the specified binder and value provider and
|
||||
/// executes validation using the specified sequence of validator providers.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the value provider.</param>
|
||||
/// <param name="httpContext">The context for the current executing request.</param>
|
||||
/// <param name="modelState">The ModelStateDictionary used for maintaining state and
|
||||
/// results of model-binding validation.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The model binder used for binding.</param>
|
||||
/// <param name="valueProvider">The value provider used for looking up values.</param>
|
||||
/// <param name="validatorProviders">The validator providers used for executing validation
|
||||
/// on the model instance.</param>
|
||||
/// <returns>A Task with a value representing if the the update is successful.</returns>
|
||||
public static async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[NotNull] TModel model,
|
||||
[NotNull] string prefix,
|
||||
[NotNull] HttpContext httpContext,
|
||||
[NotNull] ModelStateDictionary modelState,
|
||||
[NotNull] IModelMetadataProvider metadataProvider,
|
||||
[NotNull] IModelBinder modelBinder,
|
||||
[NotNull] IValueProvider valueProvider,
|
||||
[NotNull] IEnumerable<IModelValidatorProvider> validatorProviders)
|
||||
where TModel : class
|
||||
{
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
||||
modelAccessor: null,
|
||||
modelType: typeof(TModel));
|
||||
|
||||
var modelBindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelMetadata = modelMetadata,
|
||||
ModelName = prefix,
|
||||
Model = model,
|
||||
ModelState = modelState,
|
||||
ModelBinder = modelBinder,
|
||||
ValueProvider = valueProvider,
|
||||
ValidatorProviders = validatorProviders,
|
||||
MetadataProvider = metadataProvider,
|
||||
FallbackToEmptyPrefix = true,
|
||||
HttpContext = httpContext
|
||||
};
|
||||
|
||||
if (await modelBinder.BindModelAsync(modelBindingContext))
|
||||
{
|
||||
return modelState.IsValid;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNet.Testing;
|
|||
using Moq;
|
||||
#endif
|
||||
using Xunit;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Test
|
||||
{
|
||||
|
|
@ -25,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
return typeof(Controller).GetTypeInfo()
|
||||
.DeclaredMethods
|
||||
.Where(method => method.IsPublic && !method.IsSpecialName)
|
||||
.Select(method => new [] { method });
|
||||
.Select(method => new[] { method });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -541,6 +542,133 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
await ActionFilterAttributeTests.ActionFilter_Calls_OnActionExecuted(
|
||||
new Mock<Controller>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_UsesModelTypeNameIfNotSpecified()
|
||||
{
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback((ModelBindingContext b) =>
|
||||
{
|
||||
Assert.Equal(typeof(MyModel).Name, b.ModelName);
|
||||
Assert.Same(valueProvider, b.ValueProvider);
|
||||
})
|
||||
.Returns(Task.FromResult(false))
|
||||
.Verifiable();
|
||||
var model = new MyModel();
|
||||
var actionContext = new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder.Object,
|
||||
valueProvider,
|
||||
Mock.Of<IInputFormatterProvider>(),
|
||||
Enumerable.Empty<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
|
||||
.Returns(Task.FromResult(bindingContext));
|
||||
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
|
||||
var controller = new Controller
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
BindingContextProvider = bindingContextProvider.Object,
|
||||
ViewData = viewData
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await controller.TryUpdateModelAsync(model);
|
||||
|
||||
// Assert
|
||||
binder.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_UsesModelTypeNameIfSpecified()
|
||||
{
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
var binder = new Mock<IModelBinder>();
|
||||
var modelName = "mymodel";
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback((ModelBindingContext b) =>
|
||||
{
|
||||
Assert.Equal(modelName, b.ModelName);
|
||||
Assert.Same(valueProvider, b.ValueProvider);
|
||||
})
|
||||
.Returns(Task.FromResult(false))
|
||||
.Verifiable();
|
||||
var model = new MyModel();
|
||||
var actionContext = new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder.Object,
|
||||
valueProvider,
|
||||
Mock.Of<IInputFormatterProvider>(),
|
||||
Enumerable.Empty<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
|
||||
.Returns(Task.FromResult(bindingContext));
|
||||
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
|
||||
var controller = new Controller
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
BindingContextProvider = bindingContextProvider.Object,
|
||||
ViewData = viewData
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await controller.TryUpdateModelAsync(model, modelName);
|
||||
|
||||
// Assert
|
||||
binder.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_UsesModelValueProviderIfSpecified()
|
||||
{
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
var binder = new Mock<IModelBinder>();
|
||||
var modelName = "mymodel";
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback((ModelBindingContext b) =>
|
||||
{
|
||||
Assert.Equal(modelName, b.ModelName);
|
||||
Assert.Same(valueProvider, b.ValueProvider);
|
||||
})
|
||||
.Returns(Task.FromResult(false))
|
||||
.Verifiable();
|
||||
var model = new MyModel();
|
||||
var actionContext = new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder.Object,
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterProvider>(),
|
||||
Enumerable.Empty<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
|
||||
.Returns(Task.FromResult(bindingContext));
|
||||
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
|
||||
var controller = new Controller
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
BindingContextProvider = bindingContextProvider.Object,
|
||||
ViewData = viewData
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await controller.TryUpdateModelAsync(model, modelName, valueProvider);
|
||||
|
||||
// Assert
|
||||
binder.Verify();
|
||||
}
|
||||
#endif
|
||||
|
||||
private class MyModel
|
||||
{
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
<Compile Include="ExpiringFileInfoCacheTest.cs" />
|
||||
<Compile Include="DefaultActionDiscoveryConventionsTests.cs" />
|
||||
<Compile Include="Formatters\OutputFormatterTests.cs" />
|
||||
<Compile Include="ParameterBinding\ModelBindingHelperTest.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedParameterModelTests.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedActionModelTests.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedControllerModelTests.cs" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if NET45
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Core.Test
|
||||
{
|
||||
public class ModelBindingHelperTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_ReturnsFalse_IfBinderReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||
metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny<Type>()))
|
||||
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
||||
.Verifiable();
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
var model = new MyModel();
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
null,
|
||||
Mock.Of<HttpContext>(),
|
||||
new ModelStateDictionary(),
|
||||
metadataProvider.Object,
|
||||
GetCompositeBinder(binder.Object),
|
||||
Mock.Of<IValueProvider>(),
|
||||
Enumerable.Empty<IModelValidatorProvider>());
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(model.MyProperty);
|
||||
metadataProvider.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_ReturnsFalse_IfModelValidationFails()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
{
|
||||
new TypeConverterModelBinder(),
|
||||
new ComplexModelDtoModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider();
|
||||
var model = new MyModel();
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
var values = new Dictionary<string, object>
|
||||
{
|
||||
{ "", null }
|
||||
};
|
||||
var valueProvider = new DictionaryBasedValueProvider(values);
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
"",
|
||||
Mock.Of<HttpContext>(),
|
||||
modelStateDictionary,
|
||||
new DataAnnotationsModelMetadataProvider(),
|
||||
GetCompositeBinder(binders),
|
||||
valueProvider,
|
||||
new[] { validator });
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Equal("The MyProperty field is required.",
|
||||
modelStateDictionary["MyProperty"].Errors[0].ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
{
|
||||
new TypeConverterModelBinder(),
|
||||
new ComplexModelDtoModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider();
|
||||
var model = new MyModel { MyProperty = "Old-Value" };
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
var values = new Dictionary<string, object>
|
||||
{
|
||||
{ "", null },
|
||||
{ "MyProperty", "MyPropertyValue" }
|
||||
};
|
||||
var valueProvider = new DictionaryBasedValueProvider(values);
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
"",
|
||||
Mock.Of<HttpContext>(),
|
||||
modelStateDictionary,
|
||||
new DataAnnotationsModelMetadataProvider(),
|
||||
GetCompositeBinder(binders),
|
||||
valueProvider,
|
||||
new[] { validator });
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal("MyPropertyValue", model.MyProperty);
|
||||
}
|
||||
|
||||
private static IModelBinder GetCompositeBinder(params IModelBinder[] binders)
|
||||
{
|
||||
var binderProvider = new Mock<IModelBinderProvider>();
|
||||
binderProvider.SetupGet(p => p.ModelBinders)
|
||||
.Returns(binders);
|
||||
return new CompositeModelBinder(binderProvider.Object);
|
||||
}
|
||||
|
||||
private class MyModel
|
||||
{
|
||||
[Required]
|
||||
public string MyProperty { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Loading…
Reference in New Issue