diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index e0fd035e68..72661425a0 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -1030,6 +1030,57 @@ namespace Microsoft.AspNet.Mvc
predicate);
}
+ ///
+ /// Validates the specified instance.
+ ///
+ /// The model to validate.
+ /// true if the is valid; false otherwise.
+ [NonAction]
+ public virtual bool TryValidateModel([NotNull] object model)
+ {
+ return TryValidateModel(model, prefix: null);
+ }
+
+ ///
+ /// Validates the specified instance.
+ ///
+ /// The model to validate.
+ /// The key to use when looking up information in .
+ ///
+ /// true if the is valid;false otherwise.
+ [NonAction]
+ public virtual bool TryValidateModel([NotNull] object model, string prefix)
+ {
+ if (BindingContext == null)
+ {
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
+ throw new InvalidOperationException(message);
+ }
+
+ var modelMetadata = MetadataProvider.GetMetadataForType(
+ modelAccessor: () => model,
+ modelType: model.GetType());
+
+ var validationContext = new ModelValidationContext(
+ MetadataProvider,
+ BindingContext.ValidatorProvider,
+ ModelState,
+ modelMetadata,
+ containerMetadata: null);
+
+ var modelName = prefix ?? string.Empty;
+
+ var validationNode = new ModelValidationNode(modelMetadata, modelName)
+ {
+ ValidateAllProperties = true
+ };
+ validationNode.Validate(validationContext);
+
+ return ModelState.IsValid;
+ }
+
public void Dispose()
{
Dispose(disposing: true);
@@ -1040,4 +1091,4 @@ namespace Microsoft.AspNet.Mvc
{
}
}
-}
\ 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 0fbdee98f4..23e0ac6f1e 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
@@ -1237,6 +1237,101 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.True(controller.DisposeCalled);
}
+ [Fact]
+ public void TryValidateModelWithValidModel_ReturnsTrue()
+ {
+ // Arrange
+ var binder = new Mock();
+ var controller = GetController(binder.Object, provider: null);
+ controller.BindingContext.ValidatorProvider = Mock.Of();
+ var model = new TryValidateModelModel();
+
+ // Act
+ var result = controller.TryValidateModel(model);
+
+ // Assert
+ Assert.True(result);
+ Assert.True(controller.ModelState.IsValid);
+ }
+
+ [Fact]
+ public void TryValidateModelWithInvalidModelWithPrefix_ReturnsFalse()
+ {
+ // Arrange
+ var model = new TryValidateModelModel();
+ var validationResult = new []
+ {
+ new ModelValidationResult(string.Empty, "Out of range!")
+ };
+
+ var validator1 = new Mock();
+
+ validator1.Setup(v => v.Validate(It.IsAny()))
+ .Returns(validationResult);
+
+ var provider = new Mock();
+ provider.Setup(v => v.GetValidators(It.IsAny()))
+ .Returns(new[] { validator1.Object });
+
+ var binder = new Mock();
+ var controller = GetController(binder.Object, provider: null);
+ controller.BindingContext.ValidatorProvider = provider.Object;
+
+ // Act
+ var result = controller.TryValidateModel(model, "Prefix");
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(1, controller.ModelState.Count);
+ var error = Assert.Single(controller.ModelState["Prefix.IntegerProperty"].Errors);
+ Assert.Equal("Out of range!", error.ErrorMessage);
+ }
+
+ [Fact]
+ public void TryValidateModelWithInvalidModelNoPrefix_ReturnsFalse()
+ {
+ // Arrange
+ var model = new TryValidateModelModel();
+ var validationResult = new[]
+ {
+ new ModelValidationResult(string.Empty, "Out of range!")
+ };
+
+ var validator1 = new Mock();
+
+ validator1.Setup(v => v.Validate(It.IsAny()))
+ .Returns(validationResult);
+
+ var provider = new Mock();
+ provider.Setup(v => v.GetValidators(It.IsAny()))
+ .Returns(new[] { validator1.Object });
+
+ var binder = new Mock();
+ var controller = GetController(binder.Object, provider: null);
+ controller.BindingContext.ValidatorProvider = provider.Object;
+
+ // Act
+ var result = controller.TryValidateModel(model);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(1, controller.ModelState.Count);
+ var error = Assert.Single(controller.ModelState["IntegerProperty"].Errors);
+ Assert.Equal("Out of range!", error.ErrorMessage);
+ }
+
+ [Fact]
+ public void TryValidateModelEmptyBindingContextThrowsException()
+ {
+ // Arrange
+ var controller = new Controller();
+ var model = new TryValidateModelModel();
+
+ // Act & Assert
+ var exception = Assert.Throws(() => controller.TryValidateModel(model));
+ Assert.Equal("The 'BindingContext' property of 'Microsoft.AspNet.Mvc.Controller' must not be null.", exception.Message);
+ }
+
private static Controller GetController(IModelBinder binder, IValueProvider provider)
{
var metadataProvider = new DataAnnotationsModelMetadataProvider();
@@ -1287,6 +1382,11 @@ namespace Microsoft.AspNet.Mvc.Test
public int Zip { get; set; }
}
+ private class TryValidateModelModel
+ {
+ public int IntegerProperty { get; set; }
+ }
+
private class DisposableController : Controller
{
public bool DisposeCalled { get; private set; }
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs
new file mode 100644
index 0000000000..23d9364d79
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs
@@ -0,0 +1,73 @@
+// 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;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.TestHost;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.FunctionalTests
+{
+ public class TryValidateModelTest
+ {
+ private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(FormatterWebSite));
+ private readonly Action _app = new FormatterWebSite.Startup().Configure;
+
+ [Fact]
+ public async Task TryValidateModel_SimpleModelInvalidProperties()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("http://localhost/TryValidateModel/GetInvalidUser");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var json = JsonConvert.DeserializeObject>(
+ await response.Content.ReadAsStringAsync());
+ Assert.Equal("The field Id must be between 1 and 2000.", json["Id"][0]);
+ Assert.Equal(
+ "The field Name must be a string or array type with a minimum length of '5'.", json["Name"][0]);
+ }
+
+ [Fact]
+ public async Task TryValidateModel_DerivedModelInvalidType()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("http://localhost/TryValidateModel/GetInvalidAdminWithPrefix");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var json = JsonConvert.DeserializeObject>(
+ await response.Content.ReadAsStringAsync());
+ Assert.Equal("AdminAccessCode property does not have the right value", json["admin"][0]);
+ }
+
+ [Fact]
+ public async Task TryValidateModel_ValidDerivedModel()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("http://localhost/TryValidateModel/GetValidAdminWithPrefix");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("Admin user created successfully", await response.Content.ReadAsStringAsync());
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs b/test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs
new file mode 100644
index 0000000000..d041232b94
--- /dev/null
+++ b/test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs
@@ -0,0 +1,64 @@
+// 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 Microsoft.AspNet.Mvc;
+
+namespace FormatterWebSite
+{
+ public class TryValidateModelController : Controller
+ {
+ [HttpGet]
+ public IActionResult GetInvalidUser()
+ {
+ var user = new User
+ {
+ Id = 0,
+ Name = "x"
+ };
+
+ // If ModelState.InValid is false return BadRequestOjectResult; else return empty string.
+ if (!TryValidateModel(user))
+ {
+ return new BadRequestObjectResult(ModelState);
+ }
+
+ return Content(string.Empty);
+ }
+
+ [HttpGet]
+ public IActionResult GetInvalidAdminWithPrefix()
+ {
+ var admin = new Administrator()
+ {
+ Id = 1,
+ Name = "John Doe",
+ Designation = "Administrator",
+ AdminAccessCode = 0
+ };
+ if (!TryValidateModel(admin,"admin"))
+ {
+ return new BadRequestObjectResult(ModelState);
+ }
+
+ return Content(string.Empty);
+ }
+
+ [HttpGet]
+ public IActionResult GetValidAdminWithPrefix()
+ {
+ var admin = new Administrator()
+ {
+ Id = 1,
+ Name = "John Doe",
+ Designation = "Administrator",
+ AdminAccessCode = 1
+ };
+ if (!TryValidateModel(admin, "admin"))
+ {
+ return new BadRequestObjectResult(ModelState);
+ }
+
+ return Content("Admin user created successfully");
+ }
+ }
+}
diff --git a/test/WebSites/FormatterWebSite/Models/AdminValidator.cs b/test/WebSites/FormatterWebSite/Models/AdminValidator.cs
new file mode 100644
index 0000000000..03ebcf4b4a
--- /dev/null
+++ b/test/WebSites/FormatterWebSite/Models/AdminValidator.cs
@@ -0,0 +1,22 @@
+// 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.ComponentModel.DataAnnotations;
+
+namespace FormatterWebSite
+{
+ public class AdminValidator : ValidationAttribute
+ {
+ protected override ValidationResult IsValid(object value, ValidationContext validationContext)
+ {
+ var admin = (Administrator)value;
+
+ if (admin.AdminAccessCode != 1)
+ {
+ return new ValidationResult ("AdminAccessCode property does not have the right value");
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/FormatterWebSite/Models/Administrator.cs b/test/WebSites/FormatterWebSite/Models/Administrator.cs
new file mode 100644
index 0000000000..ce82dd9ded
--- /dev/null
+++ b/test/WebSites/FormatterWebSite/Models/Administrator.cs
@@ -0,0 +1,13 @@
+// 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;
+
+namespace FormatterWebSite
+{
+ [AdminValidator]
+ public class Administrator : User
+ {
+ public int AdminAccessCode { get; set; }
+ }
+}
\ No newline at end of file