From 1af158330252d56e5828687b74506839febdbd33 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sat, 24 Jan 2015 16:20:44 -0800 Subject: [PATCH] Copy `[Remote]` and supporting classes from MVC 5.2 - #439 (1 of 3) --- .../AreaReference.cs | 20 + .../ModelClientValidationRemoteRule.cs | 26 + .../RemoteAttribute.cs | 158 ++++++ .../RemoteAttributeTest.cs | 467 ++++++++++++++++++ 4 files changed, 671 insertions(+) create mode 100644 src/Microsoft.AspNet.Mvc.Core/AreaReference.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Internal/ModelClientValidationRemoteRule.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/RemoteAttribute.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/RemoteAttributeTest.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/AreaReference.cs b/src/Microsoft.AspNet.Mvc.Core/AreaReference.cs new file mode 100644 index 0000000000..c4fccb132f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AreaReference.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Web.Mvc +{ + /// + /// Controls interpretation of a controller name when constructing a . + /// + public enum AreaReference + { + /// + /// Find the controller in the current area. + /// + UseCurrent = 0, + + /// + /// Find the controller in the root area. + /// + UseRoot = 1, + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/ModelClientValidationRemoteRule.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/ModelClientValidationRemoteRule.cs new file mode 100644 index 0000000000..374da299a4 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Internal/ModelClientValidationRemoteRule.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System.Web.Mvc +{ + [TypeForwardedFrom("System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")] + public class ModelClientValidationRemoteRule : ModelClientValidationRule + { + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "The value is a not a regular URL since it may contain ~/ ASP.NET-specific characters")] + public ModelClientValidationRemoteRule(string errorMessage, string url, string httpMethod, string additionalFields) + { + ErrorMessage = errorMessage; + ValidationType = "remote"; + ValidationParameters["url"] = url; + + if (!String.IsNullOrEmpty(httpMethod)) + { + ValidationParameters["type"] = httpMethod; + } + + ValidationParameters["additionalfields"] = additionalFields; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/RemoteAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/RemoteAttribute.cs new file mode 100644 index 0000000000..21868ee211 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/RemoteAttribute.cs @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Web.Mvc.Properties; +using System.Web.Routing; + +namespace System.Web.Mvc +{ + [AttributeUsage(AttributeTargets.Property)] + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "The constructor parameters are used to feed RouteData, which is public.")] + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is designed to be a base class for other attributes.")] + public class RemoteAttribute : ValidationAttribute, IClientValidatable + { + private string _additionalFields; + private string[] _additonalFieldsSplit = new string[0]; + + protected RemoteAttribute() + : base(MvcResources.RemoteAttribute_RemoteValidationFailed) + { + RouteData = new RouteValueDictionary(); + } + + public RemoteAttribute(string routeName) + : this() + { + if (String.IsNullOrWhiteSpace(routeName)) + { + throw new ArgumentException(MvcResources.Common_NullOrEmpty, "routeName"); + } + + RouteName = routeName; + } + + public RemoteAttribute(string action, string controller) + : + this(action, controller, null /* areaName */) + { + } + + public RemoteAttribute(string action, string controller, string areaName) + : this() + { + if (String.IsNullOrWhiteSpace(action)) + { + throw new ArgumentException(MvcResources.Common_NullOrEmpty, "action"); + } + if (String.IsNullOrWhiteSpace(controller)) + { + throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controller"); + } + + RouteData["controller"] = controller; + RouteData["action"] = action; + + if (!String.IsNullOrWhiteSpace(areaName)) + { + RouteData["area"] = areaName; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The route name. + /// The name of the controller. + /// + /// Find the controller in the root if . Otherwise look in the current area. + /// + public RemoteAttribute(string action, string controller, AreaReference areaReference) + : this(action, controller) + { + if (areaReference == AreaReference.UseRoot) + { + RouteData["area"] = null; + } + } + + public string HttpMethod { get; set; } + + public string AdditionalFields + { + get { return _additionalFields ?? String.Empty; } + set + { + _additionalFields = value; + _additonalFieldsSplit = AuthorizeAttribute.SplitString(value); + } + } + + protected RouteValueDictionary RouteData { get; private set; } + + protected string RouteName { get; set; } + + protected virtual RouteCollection Routes + { + get { return RouteTable.Routes; } + } + + public string FormatAdditionalFieldsForClientValidation(string property) + { + if (String.IsNullOrEmpty(property)) + { + throw new ArgumentException(MvcResources.Common_NullOrEmpty, "property"); + } + + string delimitedAdditionalFields = FormatPropertyForClientValidation(property); + + foreach (string field in _additonalFieldsSplit) + { + delimitedAdditionalFields += "," + FormatPropertyForClientValidation(field); + } + + return delimitedAdditionalFields; + } + + public static string FormatPropertyForClientValidation(string property) + { + if (String.IsNullOrEmpty(property)) + { + throw new ArgumentException(MvcResources.Common_NullOrEmpty, "property"); + } + return "*." + property; + } + + [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "The value is a not a regular URL since it may contain ~/ ASP.NET-specific characters")] + protected virtual string GetUrl(ControllerContext controllerContext) + { + var pathData = Routes.GetVirtualPathForArea(controllerContext.RequestContext, + RouteName, + RouteData); + + if (pathData == null) + { + throw new InvalidOperationException(MvcResources.RemoteAttribute_NoUrlFound); + } + + return pathData.VirtualPath; + } + + public override string FormatErrorMessage(string name) + { + return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name); + } + + public override bool IsValid(object value) + { + return true; + } + + public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) + { + yield return new ModelClientValidationRemoteRule(FormatErrorMessage(metadata.GetDisplayName()), GetUrl(context), HttpMethod, FormatAdditionalFieldsForClientValidation(metadata.PropertyName)); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/RemoteAttributeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/RemoteAttributeTest.cs new file mode 100644 index 0000000000..9b7ff69921 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/RemoteAttributeTest.cs @@ -0,0 +1,467 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Web.Routing; +using Microsoft.TestCommon; +using Moq; + +namespace System.Web.Mvc.Test +{ + public class RemoteAttributeTest + { + // Good route name, bad route name + // Controller + Action + + [Fact] + public void GuardClauses() + { + // Act & Assert + Assert.ThrowsArgumentNullOrEmpty( + () => new RemoteAttribute(null, "controller"), + "action"); + Assert.ThrowsArgumentNullOrEmpty( + () => new RemoteAttribute("action", null), + "controller"); + Assert.ThrowsArgumentNullOrEmpty( + () => new RemoteAttribute(null), + "routeName"); + Assert.ThrowsArgumentNullOrEmpty( + () => RemoteAttribute.FormatPropertyForClientValidation(String.Empty), + "property"); + Assert.ThrowsArgumentNullOrEmpty( + () => new RemoteAttribute("foo").FormatAdditionalFieldsForClientValidation(String.Empty), + "property"); + } + + [Fact] + public void IsValidAlwaysReturnsTrue() + { + // Act & Assert + Assert.True(new RemoteAttribute("RouteName", "ParameterName").IsValid(null)); + Assert.True(new RemoteAttribute("ActionName", "ControllerName", "ParameterName").IsValid(null)); + } + + [Fact] + public void BadRouteNameThrows() + { + // Arrange + ControllerContext context = new ControllerContext(); + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(object)); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("RouteName"); + + // Act & Assert + Assert.Throws( + () => new List(attribute.GetClientValidationRules(metadata, context)), + "A route named 'RouteName' could not be found in the route collection.\r\nParameter name: name"); + } + + [Fact] + public void NoRouteWithActionControllerThrows() + { + // Arrange + ControllerContext context = new ControllerContext(); + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller"); + + // Act & Assert + Assert.Throws( + () => new List(attribute.GetClientValidationRules(metadata, context)), + "No url for remote validation could be found."); + } + + [Fact] + public void GoodRouteNameReturnsCorrectClientData() + { + // Arrange + string url = null; + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("RouteName"); + attribute.RouteTable.Add("RouteName", new Route("my/url", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = attribute.GetClientValidationRules(metadata, GetMockControllerContext(url)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("'Length' is invalid.", rule.ErrorMessage); + Assert.Equal(2, rule.ValidationParameters.Count); + Assert.Equal("/my/url", rule.ValidationParameters["url"]); + } + + [Fact] + public void ActionControllerReturnsCorrectClientDataWithoutNamedParameters() + { + // Arrange + string url = null; + + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller"); + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = attribute.GetClientValidationRules(metadata, GetMockControllerContext(url)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("'Length' is invalid.", rule.ErrorMessage); + Assert.Equal(2, rule.ValidationParameters.Count); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + Assert.Equal("*.Length", rule.ValidationParameters["additionalfields"]); + Assert.Throws( + () => rule.ValidationParameters["type"], + "The given key was not present in the dictionary."); + } + + [Fact] + public void ActionControllerReturnsCorrectClientDataWithNamedParameters() + { + // Arrange + string url = null; + + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, typeof(string), "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller"); + attribute.HttpMethod = "POST"; + attribute.AdditionalFields = "Password,ConfirmPassword"; + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = attribute.GetClientValidationRules(metadata, GetMockControllerContext(url)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("'Length' is invalid.", rule.ErrorMessage); + Assert.Equal(3, rule.ValidationParameters.Count); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + Assert.Equal("*.Length,*.Password,*.ConfirmPassword", rule.ValidationParameters["additionalfields"]); + Assert.Equal("POST", rule.ValidationParameters["type"]); + } + + // Current area is root in this case. + [Fact] + public void ActionController_RemoteFindsControllerInCurrentArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller"); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContext(url: null)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + } + + [Fact] + public void ActionControllerArea_RemoteFindsControllerInNamedArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", "Test"); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContext(url: null)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Test/Controller/Action", rule.ValidationParameters["url"]); + } + + // Current area is root in this case. + [Fact] + public void ActionControllerArea_WithEmptyArea_RemoteFindsControllerInCurrentArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", ""); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContext(url: null)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + } + + // Current area is root in this case. + [Fact] + public void ActionControllerAreaReference_WithUseCurrent_RemoteFindsControllerInCurrentArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", AreaReference.UseCurrent); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContext(url: null)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + } + + [Fact] + public void ActionControllerAreaReference_WithUseRoot_RemoteFindsControllerInRoot() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", AreaReference.UseRoot); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContext(url: null)).Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + } + + // Current area is Test in this case. + [Fact] + public void ActionController_InArea_RemoteFindsControllerInCurrentArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller"); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContextWithArea(url: null, areaName: "Test")) + .Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Test/Controller/Action", rule.ValidationParameters["url"]); + } + + // Explicit reference to the Test area. + [Fact] + public void ActionControllerArea_InSameArea_RemoteFindsControllerInNamedArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", "Test"); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContextWithArea(url: null, areaName: "Test")) + .Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Test/Controller/Action", rule.ValidationParameters["url"]); + } + + [Fact] + public void ActionControllerArea_InArea_RemoteFindsControllerInNamedArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", "AnotherArea"); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + context = new AreaRegistrationContext("AnotherArea", attribute.RouteTable); + context.MapRoute(name: null, url: "AnotherArea/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContextWithArea(url: null, areaName: "Test")) + .Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/AnotherArea/Controller/Action", rule.ValidationParameters["url"]); + } + + // Current area is Test in this case. + [Fact] + public void ActionControllerArea_WithEmptyAreaInArea_RemoteFindsControllerInCurrentArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", ""); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContextWithArea(url: null, areaName: "Test")) + .Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Test/Controller/Action", rule.ValidationParameters["url"]); + } + + // Current area is Test in this case. + [Fact] + public void ActionControllerAreaReference_WithUseCurrentInArea_RemoteFindsControllerInCurrentArea() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", AreaReference.UseCurrent); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContextWithArea(url: null, areaName: "Test")) + .Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Test/Controller/Action", rule.ValidationParameters["url"]); + } + + [Fact] + public void ActionControllerAreaReference_WithUseRootInArea_RemoteFindsControllerInRoot() + { + // Arrange + ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor: null, + containerType: typeof(string), propertyName: "Length"); + TestableRemoteAttribute attribute = new TestableRemoteAttribute("Action", "Controller", AreaReference.UseRoot); + attribute.HttpMethod = "POST"; + + var context = new AreaRegistrationContext("Test", attribute.RouteTable); + context.MapRoute(name: null, url: "Test/{controller}/{action}"); + + attribute.RouteTable.Add(new Route("{controller}/{action}", new MvcRouteHandler())); + + // Act + ModelClientValidationRule rule = + attribute.GetClientValidationRules(metadata, GetMockControllerContextWithArea(url: null, areaName: "Test")) + .Single(); + + // Assert + Assert.Equal("remote", rule.ValidationType); + Assert.Equal("/Controller/Action", rule.ValidationParameters["url"]); + } + + private ControllerContext GetMockControllerContext(string url) + { + Mock context = new Mock(); + context.Setup(c => c.HttpContext.Request.ApplicationPath) + .Returns("/"); + context.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny())) + .Callback(vpath => url = vpath) + .Returns(() => url); + + return context.Object; + } + + private ControllerContext GetMockControllerContextWithArea(string url, string areaName) + { + Mock context = new Mock(); + context.Setup(c => c.HttpContext.Request.ApplicationPath) + .Returns("/"); + context.Setup(c => c.HttpContext.Response.ApplyAppPathModifier(It.IsAny())) + .Callback(vpath => url = vpath) + .Returns(() => url); + + var controllerContext = context.Object; + + controllerContext.RequestContext.RouteData.DataTokens.Add("area", areaName); + + return controllerContext; + } + + private class TestableRemoteAttribute : RemoteAttribute + { + public RouteCollection RouteTable = new RouteCollection(); + + public TestableRemoteAttribute(string action, string controller, AreaReference areaReference) + : base(action, controller, areaReference) + { + } + + public TestableRemoteAttribute(string action, string controller, string areaName) + : base(action, controller, areaName) + { + } + + public TestableRemoteAttribute(string action, string controller) + : base(action, controller) + { + } + + public TestableRemoteAttribute(string routeName) + : base(routeName) + { + } + + protected override RouteCollection Routes + { + get { return RouteTable; } + } + } + } +}