diff --git a/.gitignore b/.gitignore index 78f7cacc25..06cfaa0cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ PublishProfiles/ .vs/ bower_components/ node_modules/ -**/wwwroot/lib/ debugSettings.json project.lock.json *.user diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs index f62bb9fc5c..0cc85d70d2 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Theory] - [InlineData("", "\"/RemoteAttribute_Verify/IsIdAvailable rejects Joe1.\"")] + [InlineData("", "\"/RemoteAttribute_Verify/IsIdAvailable rejects UserId1: 'Joe1'.\"")] [InlineData("/Area1", "false")] [InlineData("/Area2", "\"/Area2/RemoteAttribute_Verify/IsIdAvailable rejects 'Joe4' with 'Joe1', 'Joe2', and 'Joe3'.\"")] @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Theory] - [InlineData("", "\"/RemoteAttribute_Verify/IsIdAvailable rejects Jane1.\"")] + [InlineData("", "\"/RemoteAttribute_Verify/IsIdAvailable rejects UserId1: 'Jane1'.\"")] [InlineData("/Area1", "false")] public async Task RemoteAttribute_VerificationAction_PostReturnsExpectedJson( string pathSegment, diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html index a00fd67818..1759bc1d2a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html @@ -1,4 +1,4 @@ - + @@ -40,7 +40,7 @@
- +
@@ -48,7 +48,7 @@
- +
@@ -56,7 +56,7 @@
- +
@@ -64,11 +64,19 @@
- +
+
+ +
+ + +
+
+
@@ -83,10 +91,10 @@
- + - - + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html index 700624bd94..38b5724974 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html @@ -1,4 +1,4 @@ - + @@ -40,7 +40,7 @@
- +
@@ -48,7 +48,7 @@
- +
@@ -56,7 +56,7 @@
- +
@@ -64,11 +64,19 @@
- +
+
+ +
+ + +
+
+
@@ -83,10 +91,10 @@
- + - - + + \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml b/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml index 7367c6d941..3fa298b308 100644 --- a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml +++ b/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml @@ -55,6 +55,14 @@
+
+ @Html.LabelFor(model => model.UserId5, htmlAttributes: new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.UserId5, new { htmlAttributes = new { @class = "form-control" } }) + @Html.ValidationMessageFor(model => model.UserId5, "", new { @class = "text-danger" }) +
+
+
@@ -68,8 +76,6 @@
@section Scripts { - @* Until script helpers are available, add script references manually. *@ - @* @Scripts.Render("~/bundles/jqueryval") *@ - - + + } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml b/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml index 21bd3f1eca..5212c90af3 100644 --- a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml +++ b/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml @@ -61,6 +61,16 @@ @Html.DisplayFor(model => model.UserId4) + +
+
+ @Html.DisplayNameFor(model => model.UserId5) +
+ +
+ @Html.DisplayFor(model => model.UserId5) +
+

diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml b/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml index ebd9c640d5..3da0702694 100644 --- a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml +++ b/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml @@ -25,7 +25,7 @@ @RenderBody() - + @RenderSection("scripts", required: false) \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs b/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs index 9ba6a0967a..5e75011496 100644 --- a/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs +++ b/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs @@ -20,7 +20,6 @@ namespace BasicWebSite.Controllers [HttpPost] public IActionResult Create(RemoteAttributeUser user) { - ModelState.Remove("id"); if (!ModelState.IsValid) { return View(user); diff --git a/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs b/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs index 0f8e458f30..1641d9870a 100644 --- a/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs +++ b/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs @@ -1,18 +1,44 @@ // 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 BasicWebSite.Models; using Microsoft.AspNetCore.Mvc; namespace BasicWebSite.Controllers { - [Route("[controller]/[action]")] public class RemoteAttribute_VerifyController : Controller { - // This action is overloaded and may receive requests to validate either UserId1 or UserId2. + // This action is overloaded and may receive requests to validate UserId1, UserId2 or UserId5. [AcceptVerbs("Get", "Post")] - public IActionResult IsIdAvailable(string userId1, string userId2) + [Route("[controller]/[action]", Name = "VerifyRoute")] + public IActionResult IsIdAvailable(string userId1, string userId2, string userId5) { - return Json(data: string.Format("/RemoteAttribute_Verify/IsIdAvailable rejects {0}.", userId1 ?? userId2)); + string name; + string value; + if (userId1 != null) + { + name = nameof(RemoteAttributeUser.UserId1); + value = userId1; + } + else if (userId2 != null) + { + + name = nameof(RemoteAttributeUser.UserId2); + value = userId2; + } + else if (userId5 != null) + { + + name = nameof(RemoteAttributeUser.UserId5); + value = userId5; + } + else + { + name = "unknown"; + value = string.Empty; + } + + return Json(data: $"/RemoteAttribute_Verify/IsIdAvailable rejects {name}: '{value}'."); } } } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs b/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs index 88bce4a8ec..754e0a5212 100644 --- a/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs +++ b/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs @@ -1,6 +1,7 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; namespace BasicWebSite.Models @@ -10,25 +11,33 @@ namespace BasicWebSite.Models public int Id { get; set; } // Controller in current area. + [Required(ErrorMessage = "UserId1 is required")] [Remote(action: "IsIdAvailable", controller: "RemoteAttribute_Verify")] public string UserId1 { get; set; } // Controller in root area. + [Required(ErrorMessage = "UserId2 is required")] [Remote(action: "IsIdAvailable", controller: "RemoteAttribute_Verify", areaName: null, HttpMethod = "Post")] public string UserId2 { get; set; } + [Required(ErrorMessage = "UserId3 is required")] [Remote( action: "IsIdAvailable", controller: "RemoteAttribute_Verify", - areaName:"Area1", + areaName: "Area1", ErrorMessage = "/Area1/RemoteAttribute_Verify/IsIdAvailable rejects you.")] public string UserId3 { get; set; } + [Required(ErrorMessage = "UserId4 is required")] [Remote( action:"IsIdAvailable", controller: "RemoteAttribute_Verify", areaName: "Area2", AdditionalFields = "UserId1, UserId2, UserId3")] public string UserId4 { get; set; } + + [Required(ErrorMessage = "UserId5 is required")] + [Remote(routeName: "VerifyRoute", HttpMethod = "Post")] + public string UserId5 { get; set; } } } diff --git a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml b/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml index 7367c6d941..3fa298b308 100644 --- a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml +++ b/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml @@ -55,6 +55,14 @@ +

+ @Html.LabelFor(model => model.UserId5, htmlAttributes: new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.UserId5, new { htmlAttributes = new { @class = "form-control" } }) + @Html.ValidationMessageFor(model => model.UserId5, "", new { @class = "text-danger" }) +
+
+
@@ -68,8 +76,6 @@
@section Scripts { - @* Until script helpers are available, add script references manually. *@ - @* @Scripts.Render("~/bundles/jqueryval") *@ - - + + } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Details.cshtml b/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Details.cshtml index 21bd3f1eca..5212c90af3 100644 --- a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Details.cshtml +++ b/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Details.cshtml @@ -61,6 +61,16 @@ @Html.DisplayFor(model => model.UserId4) + +
+
+ @Html.DisplayNameFor(model => model.UserId5) +
+ +
+ @Html.DisplayFor(model => model.UserId5) +
+

diff --git a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml b/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml index ebd9c640d5..3da0702694 100644 --- a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml +++ b/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml @@ -25,7 +25,7 @@ @RenderBody() - + @RenderSection("scripts", required: false) \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/_bower.json b/test/WebSites/BasicWebSite/_bower.json new file mode 100644 index 0000000000..7f3bcb8b86 --- /dev/null +++ b/test/WebSites/BasicWebSite/_bower.json @@ -0,0 +1,14 @@ +{ + "name": "BasicWebSite", + "description": "Web site demonstrating various validations.", + "private": true, + "dependencies": { + "jquery-validation-unobtrusive": "3.2.6" + }, + "exportsOverride": { + "jquery-validation-unobtrusive": { + "": "jquery.validate.unobtrusive.min.js" + }, + "*": { } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/_bower.readme b/test/WebSites/BasicWebSite/_bower.readme new file mode 100644 index 0000000000..a4fdaba27e --- /dev/null +++ b/test/WebSites/BasicWebSite/_bower.readme @@ -0,0 +1,7 @@ +To recreate minified JavaScript file, + +1. Update jquery-validation-unobtrusive version in _bower.json if necessary. +2. Rename or copy _bower.json to bower.json, _gruntfile.js to gruntfile.js, and _package.json to package.json. +3. Run build from the repo root. (Minimum command is `./build.sh --quiet run-grunt`.) +4. Remove bower.json, gruntfile.js and package.json. +5. Check in _bower.json and new jquery.validate.unobtrusive.min.js file. \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/_gruntfile.js b/test/WebSites/BasicWebSite/_gruntfile.js new file mode 100644 index 0000000000..d0ae191963 --- /dev/null +++ b/test/WebSites/BasicWebSite/_gruntfile.js @@ -0,0 +1,20 @@ +module.exports = function (grunt) { + grunt.initConfig({ + bower: { + install: { + options: { + targetDir: "wwwroot/lib", + layout: "byComponent", + cleanTargetDir: false + } + } + } + }); + + // This command registers the default task which will install bower packages into wwwroot/lib + grunt.registerTask("default", ["bower:install"]); + + // The following line loads the grunt plugins. + // This line needs to be at the end of this this file. + grunt.loadNpmTasks("grunt-bower-task"); +}; \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/_package.json b/test/WebSites/BasicWebSite/_package.json new file mode 100644 index 0000000000..a0d2a37b7a --- /dev/null +++ b/test/WebSites/BasicWebSite/_package.json @@ -0,0 +1,10 @@ +{ + "version": "0.0.0", + "name": "BasicWebSite", + "description": "Web site demonstrating various validations.", + "private": true, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-bower-task": "^0.4.0" + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/project.json b/test/WebSites/BasicWebSite/project.json index 6ec0915077..4cac56dfdb 100644 --- a/test/WebSites/BasicWebSite/project.json +++ b/test/WebSites/BasicWebSite/project.json @@ -36,7 +36,8 @@ "include": [ "Areas/Area1/Views", "Views", - "web.config" + "web.config", + "wwwroot" ] }, "tools": { diff --git a/test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js b/test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js new file mode 100644 index 0000000000..be9a38a4cf --- /dev/null +++ b/test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js @@ -0,0 +1,5 @@ +/* +** Unobtrusive validation support library for jQuery and jQuery Validate +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ +!function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("

  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function m(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=p.unobtrusive.options||{},m=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),m("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),m("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),m("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var u,p=a.validator,v="unobtrusiveValidation";p.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=m(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){p.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=m(this);a&&a.attachValidation()})}},u=p.unobtrusive.adapters,u.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},u.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},u.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},u.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},p.addMethod("__dummy__",function(a,e,n){return!0}),p.addMethod("regex",function(a,e,n){var t;return this.optional(e)?!0:(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),p.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),p.methods.extension?(u.addSingleVal("accept","mimtype"),u.addSingleVal("extension","extension")):u.addSingleVal("extension","extension","accept"),u.addSingleVal("regex","pattern"),u.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),u.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),u.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),u.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),u.add("required",function(a){("INPUT"!==a.element.tagName.toUpperCase()||"CHECKBOX"!==a.element.type.toUpperCase())&&e(a,"required",!0)}),u.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),u.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),a(function(){p.unobtrusive.parse(document)})}(jQuery); \ No newline at end of file