diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index 2df59735a7..1c80049c47 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -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());
}
}
+
+ ///
+ /// Updates the specified model instance using values from the controller's current value provider.
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// true if the update is successful; otherwise, false.
+ [NonAction]
+ public virtual Task TryUpdateModelAsync([NotNull] TModel model)
+ where TModel : class
+ {
+ return TryUpdateModelAsync(model, prefix: typeof(TModel).Name);
+ }
+
+ ///
+ /// Updates the specified model instance using values from the controller's current value provider
+ /// and a prefix.
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the value provider.
+ /// true if the update is successful; otherwise, false.
+ [NonAction]
+ public virtual async Task TryUpdateModelAsync([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);
+ }
+
+ ///
+ /// Updates the specified model instance using the value provider and a prefix.
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the value provider.
+ /// The value provider used for looking up values.
+ /// true if the update is successful; otherwise, false.
+ [NonAction]
+ public virtual async Task TryUpdateModelAsync([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);
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
index 5b4e537161..ee88bb41c8 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
+++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
@@ -47,6 +47,7 @@
+
diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
new file mode 100644
index 0000000000..67846420d4
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.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
+ {
+ ///
+ /// Updates the specified model instance using the specified binder and value provider and
+ /// executes validation using the specified sequence of validator providers.
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the value provider.
+ /// The context for the current executing request.
+ /// The ModelStateDictionary used for maintaining state and
+ /// results of model-binding validation.
+ /// The provider used for reading metadata for the model type.
+ /// The model binder used for binding.
+ /// The value provider used for looking up values.
+ /// The validator providers used for executing validation
+ /// on the model instance.
+ /// A Task with a value representing if the the update is successful.
+ public static async Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ [NotNull] string prefix,
+ [NotNull] HttpContext httpContext,
+ [NotNull] ModelStateDictionary modelState,
+ [NotNull] IModelMetadataProvider metadataProvider,
+ [NotNull] IModelBinder modelBinder,
+ [NotNull] IValueProvider valueProvider,
+ [NotNull] IEnumerable 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
index c7c24f2a64..bdf3bbb3c5 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
@@ -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());
}
+
+ [Fact]
+ public async Task TryUpdateModel_UsesModelTypeNameIfNotSpecified()
+ {
+ var metadataProvider = new DataAnnotationsModelMetadataProvider();
+ var valueProvider = Mock.Of();
+ var binder = new Mock();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .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(), new RouteData(), new ActionDescriptor());
+ var bindingContext = new ActionBindingContext(actionContext,
+ metadataProvider,
+ binder.Object,
+ valueProvider,
+ Mock.Of(),
+ Enumerable.Empty());
+ var bindingContextProvider = new Mock();
+ 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();
+ var binder = new Mock();
+ var modelName = "mymodel";
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .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(), new RouteData(), new ActionDescriptor());
+ var bindingContext = new ActionBindingContext(actionContext,
+ metadataProvider,
+ binder.Object,
+ valueProvider,
+ Mock.Of(),
+ Enumerable.Empty());
+ var bindingContextProvider = new Mock();
+ 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();
+ var binder = new Mock();
+ var modelName = "mymodel";
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .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(), new RouteData(), new ActionDescriptor());
+ var bindingContext = new ActionBindingContext(actionContext,
+ metadataProvider,
+ binder.Object,
+ Mock.Of(),
+ Mock.Of(),
+ Enumerable.Empty());
+ var bindingContextProvider = new Mock();
+ 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; }
+ }
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
index 8e1dd889bb..557de45ae2 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
@@ -44,6 +44,7 @@
+
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
new file mode 100644
index 0000000000..b5c4815582
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.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();
+ metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny()))
+ .Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
+ .Verifiable();
+
+ var binder = new Mock();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Returns(Task.FromResult(false));
+ var model = new MyModel();
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ null,
+ Mock.Of(),
+ new ModelStateDictionary(),
+ metadataProvider.Object,
+ GetCompositeBinder(binder.Object),
+ Mock.Of(),
+ Enumerable.Empty());
+
+ // 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
+ {
+ { "", null }
+ };
+ var valueProvider = new DictionaryBasedValueProvider(values);
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ "",
+ Mock.Of(),
+ 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
+ {
+ { "", null },
+ { "MyProperty", "MyPropertyValue" }
+ };
+ var valueProvider = new DictionaryBasedValueProvider(values);
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ "",
+ Mock.Of(),
+ 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();
+ binderProvider.SetupGet(p => p.ModelBinders)
+ .Returns(binders);
+ return new CompositeModelBinder(binderProvider.Object);
+ }
+
+ private class MyModel
+ {
+ [Required]
+ public string MyProperty { get; set; }
+ }
+ }
+}
+#endif
\ No newline at end of file