Add PageRemoteAttribute (#8324)
* Add PageRemoteAttribute Fixes https://github.com/aspnet/Mvc/issues/8245
This commit is contained in:
parent
d603735818
commit
98c10b6879
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Resources;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="RemoteAttributeBase"/> for razor page handler which configures Unobtrusive validation
|
||||||
|
/// to send an Ajax request to the web site. The invoked handler should return JSON indicating
|
||||||
|
/// whether the value is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Does no server-side validation of the final form submission.</remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class PageRemoteAttribute : RemoteAttributeBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The handler name used when generating the URL where client should send a validation request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If not set the ambient value will be used when generating the URL.
|
||||||
|
/// </remarks>
|
||||||
|
public string PageHandler { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The page name used when generating the URL where client should send a validation request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If not set the ambient value will be used when generating the URL.
|
||||||
|
/// </remarks>
|
||||||
|
public string PageName { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string GetUrl(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var services = context.ActionContext.HttpContext.RequestServices;
|
||||||
|
var factory = services.GetRequiredService<IUrlHelperFactory>();
|
||||||
|
var urlHelper = factory.GetUrlHelper(context.ActionContext);
|
||||||
|
|
||||||
|
var url = urlHelper.Page(PageName, PageHandler, RouteData);
|
||||||
|
|
||||||
|
if (url == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.RemoteAttribute_NoUrlFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,34 +2,21 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Localization;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Resources;
|
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Resources;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="ValidationAttribute"/> which configures Unobtrusive validation to send an Ajax request to the
|
/// A <see cref="RemoteAttributeBase"/> for controllers which configures Unobtrusive validation to send an Ajax request to the
|
||||||
/// web site. The invoked action should return JSON indicating whether the value is valid.
|
/// web site. The invoked action should return JSON indicating whether the value is valid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Does no server-side validation of the final form submission.</remarks>
|
/// <remarks>Does no server-side validation of the final form submission.</remarks>
|
||||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class RemoteAttribute : ValidationAttribute, IClientModelValidator
|
public class RemoteAttribute : RemoteAttributeBase
|
||||||
{
|
{
|
||||||
private string _additionalFields = string.Empty;
|
|
||||||
private string[] _additionalFieldsSplit = Array.Empty<string>();
|
|
||||||
private bool _checkedForLocalizer;
|
|
||||||
private IStringLocalizer _stringLocalizer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
|
/// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -37,10 +24,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// Intended for subclasses that support URL generation with no route, action, or controller names.
|
/// Intended for subclasses that support URL generation with no route, action, or controller names.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected RemoteAttribute()
|
protected RemoteAttribute()
|
||||||
: base(errorMessageAccessor: () => Resources.RemoteAttribute_RemoteValidationFailed)
|
{ }
|
||||||
{
|
|
||||||
RouteData = new RouteValueDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
|
/// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
|
||||||
|
|
@ -112,90 +96,14 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
RouteData["area"] = areaName;
|
RouteData["area"] = areaName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the HTTP method (<c>"Get"</c> or <c>"Post"</c>) client should use when sending a validation
|
|
||||||
/// request.
|
|
||||||
/// </summary>
|
|
||||||
public string HttpMethod { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the comma-separated names of fields the client should include in a validation request.
|
|
||||||
/// </summary>
|
|
||||||
public string AdditionalFields
|
|
||||||
{
|
|
||||||
get => _additionalFields;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_additionalFields = value ?? string.Empty;
|
|
||||||
_additionalFieldsSplit = SplitAndTrimPropertyNames(value)
|
|
||||||
.Select(field => FormatPropertyForClientValidation(field))
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="RouteValueDictionary"/> used when generating the URL where client should send a
|
|
||||||
/// validation request.
|
|
||||||
/// </summary>
|
|
||||||
protected RouteValueDictionary RouteData { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the route name used when generating the URL where client should send a validation request.
|
/// Gets or sets the route name used when generating the URL where client should send a validation request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected string RouteName { get; set; }
|
protected string RouteName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Formats <paramref name="property"/> and <see cref="AdditionalFields"/> for use in generated HTML.
|
protected override string GetUrl(ClientModelValidationContext context)
|
||||||
/// </summary>
|
|
||||||
/// <param name="property">
|
|
||||||
/// Name of the property associated with this <see cref="RemoteAttribute"/> instance.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>Comma-separated names of fields the client should include in a validation request.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Excludes any whitespace from <see cref="AdditionalFields"/> in the return value.
|
|
||||||
/// Prefixes each field name in the return value with <c>"*."</c>.
|
|
||||||
/// </remarks>
|
|
||||||
public string FormatAdditionalFieldsForClientValidation(string property)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(property))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(property));
|
|
||||||
}
|
|
||||||
|
|
||||||
var delimitedAdditionalFields = string.Join(",", _additionalFieldsSplit);
|
|
||||||
if (!string.IsNullOrEmpty(delimitedAdditionalFields))
|
|
||||||
{
|
|
||||||
delimitedAdditionalFields = "," + delimitedAdditionalFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formattedString = FormatPropertyForClientValidation(property) + delimitedAdditionalFields;
|
|
||||||
|
|
||||||
return formattedString;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Formats <paramref name="property"/> for use in generated HTML.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="property">One field name the client should include in a validation request.</param>
|
|
||||||
/// <returns>Name of a field the client should include in a validation request.</returns>
|
|
||||||
/// <remarks>Returns <paramref name="property"/> with a <c>"*."</c> prefix.</remarks>
|
|
||||||
public static string FormatPropertyForClientValidation(string property)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(property))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(property));
|
|
||||||
}
|
|
||||||
|
|
||||||
return "*." + property;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the URL where the client should send a validation request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The <see cref="ClientModelValidationContext"/> used to generate the URL.</param>
|
|
||||||
/// <returns>The URL where the client should send a validation request.</returns>
|
|
||||||
protected virtual string GetUrl(ClientModelValidationContext context)
|
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
|
|
@ -219,101 +127,5 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string FormatErrorMessage(string name)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <remarks>
|
|
||||||
/// Always returns <c>true</c> since this <see cref="ValidationAttribute"/> does no validation itself.
|
|
||||||
/// Related validations occur only when the client sends a validation request.
|
|
||||||
/// </remarks>
|
|
||||||
public override bool IsValid(object value)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void AddValidation(ClientModelValidationContext context)
|
|
||||||
{
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
MergeAttribute(context.Attributes, "data-val", "true");
|
|
||||||
|
|
||||||
CheckForLocalizer(context);
|
|
||||||
var errorMessage = GetErrorMessage(context.ModelMetadata.GetDisplayName());
|
|
||||||
MergeAttribute(context.Attributes, "data-val-remote", errorMessage);
|
|
||||||
|
|
||||||
MergeAttribute(context.Attributes, "data-val-remote-url", GetUrl(context));
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(HttpMethod))
|
|
||||||
{
|
|
||||||
MergeAttribute(context.Attributes, "data-val-remote-type", HttpMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
var additionalFields = FormatAdditionalFieldsForClientValidation(context.ModelMetadata.PropertyName);
|
|
||||||
MergeAttribute(context.Attributes, "data-val-remote-additionalfields", additionalFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void MergeAttribute(IDictionary<string, string> attributes, string key, string value)
|
|
||||||
{
|
|
||||||
if (!attributes.ContainsKey(key))
|
|
||||||
{
|
|
||||||
attributes.Add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> SplitAndTrimPropertyNames(string original)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(original))
|
|
||||||
{
|
|
||||||
return Array.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var split = original
|
|
||||||
.Split(',')
|
|
||||||
.Select(piece => piece.Trim())
|
|
||||||
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
|
|
||||||
|
|
||||||
return split;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckForLocalizer(ClientModelValidationContext context)
|
|
||||||
{
|
|
||||||
if (!_checkedForLocalizer)
|
|
||||||
{
|
|
||||||
_checkedForLocalizer = true;
|
|
||||||
|
|
||||||
var services = context.ActionContext.HttpContext.RequestServices;
|
|
||||||
var options = services.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
|
||||||
var factory = services.GetService<IStringLocalizerFactory>();
|
|
||||||
|
|
||||||
var provider = options.Value.DataAnnotationLocalizerProvider;
|
|
||||||
if (factory != null && provider != null)
|
|
||||||
{
|
|
||||||
_stringLocalizer = provider(
|
|
||||||
context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType,
|
|
||||||
factory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetErrorMessage(string displayName)
|
|
||||||
{
|
|
||||||
if (_stringLocalizer != null &&
|
|
||||||
!string.IsNullOrEmpty(ErrorMessage) &&
|
|
||||||
string.IsNullOrEmpty(ErrorMessageResourceName) &&
|
|
||||||
ErrorMessageResourceType == null)
|
|
||||||
{
|
|
||||||
return _stringLocalizer[ErrorMessage, displayName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormatErrorMessage(displayName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Resources;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ValidationAttribute"/> which configures Unobtrusive validation to send an Ajax request to the
|
||||||
|
/// web site. The invoked endpoint should return JSON indicating whether the value is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Does no server-side validation of the final form submission.</remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
|
public abstract class RemoteAttributeBase : ValidationAttribute, IClientModelValidator
|
||||||
|
{
|
||||||
|
private string _additionalFields = string.Empty;
|
||||||
|
private string[] _additionalFieldsSplit = Array.Empty<string>();
|
||||||
|
private bool _checkedForLocalizer;
|
||||||
|
private IStringLocalizer _stringLocalizer;
|
||||||
|
|
||||||
|
protected RemoteAttributeBase()
|
||||||
|
: base(errorMessageAccessor: () => Resources.RemoteAttribute_RemoteValidationFailed)
|
||||||
|
{
|
||||||
|
RouteData = new RouteValueDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="RouteValueDictionary"/> used when generating the URL where client should send a
|
||||||
|
/// validation request.
|
||||||
|
/// </summary>
|
||||||
|
protected RouteValueDictionary RouteData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the HTTP method (<c>"Get"</c> or <c>"Post"</c>) client should use when sending a validation
|
||||||
|
/// request.
|
||||||
|
/// </summary>
|
||||||
|
public string HttpMethod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the comma-separated names of fields the client should include in a validation request.
|
||||||
|
/// </summary>
|
||||||
|
public string AdditionalFields
|
||||||
|
{
|
||||||
|
get => _additionalFields;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_additionalFields = value ?? string.Empty;
|
||||||
|
_additionalFieldsSplit = SplitAndTrimPropertyNames(value)
|
||||||
|
.Select(field => FormatPropertyForClientValidation(field))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats <paramref name="property"/> and <see cref="AdditionalFields"/> for use in generated HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property">
|
||||||
|
/// Name of the property associated with this <see cref="RemoteAttribute"/> instance.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Comma-separated names of fields the client should include in a validation request.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Excludes any whitespace from <see cref="AdditionalFields"/> in the return value.
|
||||||
|
/// Prefixes each field name in the return value with <c>"*."</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public string FormatAdditionalFieldsForClientValidation(string property)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(property))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
var delimitedAdditionalFields = string.Join(",", _additionalFieldsSplit);
|
||||||
|
if (!string.IsNullOrEmpty(delimitedAdditionalFields))
|
||||||
|
{
|
||||||
|
delimitedAdditionalFields = "," + delimitedAdditionalFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formattedString = FormatPropertyForClientValidation(property) + delimitedAdditionalFields;
|
||||||
|
|
||||||
|
return formattedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats <paramref name="property"/> for use in generated HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property">One field name the client should include in a validation request.</param>
|
||||||
|
/// <returns>Name of a field the client should include in a validation request.</returns>
|
||||||
|
/// <remarks>Returns <paramref name="property"/> with a <c>"*."</c> prefix.</remarks>
|
||||||
|
public static string FormatPropertyForClientValidation(string property)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(property))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
return "*." + property;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the URL where the client should send a validation request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The <see cref="ClientModelValidationContext"/> used to generate the URL.</param>
|
||||||
|
/// <returns>The URL where the client should send a validation request.</returns>
|
||||||
|
protected abstract string GetUrl(ClientModelValidationContext context);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string FormatErrorMessage(string name)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// Always returns <c>true</c> since this <see cref="ValidationAttribute"/> does no validation itself.
|
||||||
|
/// Related validations occur only when the client sends a validation request.
|
||||||
|
/// </remarks>
|
||||||
|
public override bool IsValid(object value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds Unobtrusive validation HTML attributes to <see cref="ClientModelValidationContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">
|
||||||
|
/// <see cref="ClientModelValidationContext"/> to add Unobtrusive validation HTML attributes to.
|
||||||
|
/// </param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Calls derived <see cref="ValidationAttribute"/> implementation of <see cref="GetUrl(ClientModelValidationContext)"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual void AddValidation(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeAttribute(context.Attributes, "data-val", "true");
|
||||||
|
|
||||||
|
CheckForLocalizer(context);
|
||||||
|
var errorMessage = GetErrorMessage(context.ModelMetadata.GetDisplayName());
|
||||||
|
MergeAttribute(context.Attributes, "data-val-remote", errorMessage);
|
||||||
|
|
||||||
|
MergeAttribute(context.Attributes, "data-val-remote-url", GetUrl(context));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(HttpMethod))
|
||||||
|
{
|
||||||
|
MergeAttribute(context.Attributes, "data-val-remote-type", HttpMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
var additionalFields = FormatAdditionalFieldsForClientValidation(context.ModelMetadata.PropertyName);
|
||||||
|
MergeAttribute(context.Attributes, "data-val-remote-additionalfields", additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MergeAttribute(IDictionary<string, string> attributes, string key, string value)
|
||||||
|
{
|
||||||
|
if (!attributes.ContainsKey(key))
|
||||||
|
{
|
||||||
|
attributes.Add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> SplitAndTrimPropertyNames(string original)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(original))
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = original
|
||||||
|
.Split(',')
|
||||||
|
.Select(piece => piece.Trim())
|
||||||
|
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
|
||||||
|
|
||||||
|
return split;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForLocalizer(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
if (!_checkedForLocalizer)
|
||||||
|
{
|
||||||
|
_checkedForLocalizer = true;
|
||||||
|
|
||||||
|
var services = context.ActionContext.HttpContext.RequestServices;
|
||||||
|
var options = services.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
var factory = services.GetService<IStringLocalizerFactory>();
|
||||||
|
|
||||||
|
var provider = options.Value.DataAnnotationLocalizerProvider;
|
||||||
|
if (factory != null && provider != null)
|
||||||
|
{
|
||||||
|
_stringLocalizer = provider(
|
||||||
|
context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType,
|
||||||
|
factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetErrorMessage(string displayName)
|
||||||
|
{
|
||||||
|
if (_stringLocalizer != null &&
|
||||||
|
!string.IsNullOrEmpty(ErrorMessage) &&
|
||||||
|
string.IsNullOrEmpty(ErrorMessageResourceName) &&
|
||||||
|
ErrorMessageResourceType == null)
|
||||||
|
{
|
||||||
|
return _stringLocalizer[ErrorMessage, displayName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatErrorMessage(displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -42,5 +42,9 @@
|
||||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper<T0> : Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper, Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<T0>",
|
"TypeId": "public class Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper<T0> : Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper, Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<T0>",
|
||||||
"MemberId": "public .ctor(Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator htmlGenerator, Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine viewEngine, Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider metadataProvider, Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope bufferScope, System.Text.Encodings.Web.HtmlEncoder htmlEncoder, System.Text.Encodings.Web.UrlEncoder urlEncoder, Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ExpressionTextCache expressionTextCache)",
|
"MemberId": "public .ctor(Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator htmlGenerator, Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine viewEngine, Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider metadataProvider, Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope bufferScope, System.Text.Encodings.Web.HtmlEncoder htmlEncoder, System.Text.Encodings.Web.UrlEncoder urlEncoder, Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ExpressionTextCache expressionTextCache)",
|
||||||
"Kind": "Removal"
|
"Kind": "Removal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeId": "public class Microsoft.AspNetCore.Mvc.RemoteAttribute : System.ComponentModel.DataAnnotations.ValidationAttribute, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator",
|
||||||
|
"Kind": "Removal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Resources;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
public class PageRemoteAttributeTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetUrl_CallsUrlHelperWithExpectedValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testableAttribute = new TestablePageRemoteAttribute
|
||||||
|
{
|
||||||
|
PageName = "Foo",
|
||||||
|
PageHandler = "Bar"
|
||||||
|
};
|
||||||
|
|
||||||
|
var ambientValues = new RouteValueDictionary()
|
||||||
|
{
|
||||||
|
["page"] = "/Foo"
|
||||||
|
};
|
||||||
|
|
||||||
|
var routeData = new RouteData(ambientValues)
|
||||||
|
{
|
||||||
|
Routers = { Mock.Of<IRouter>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var urlHelper = new MockUrlHelper(url: "/Foo?handler=Bar")
|
||||||
|
{
|
||||||
|
ActionContext = GetActionContext(new ServiceCollection().BuildServiceProvider(), routeData)
|
||||||
|
};
|
||||||
|
|
||||||
|
var validationContext = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testableAttribute.InvokeGetUrl(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
||||||
|
|
||||||
|
Assert.Equal(2, routeDictionary.Count);
|
||||||
|
Assert.Equal("/Foo", routeDictionary["page"] as string);
|
||||||
|
Assert.Equal("Bar", routeDictionary["handler"] as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUrl_WhenUrlHelperReturnsNull_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testableAttribute = new TestablePageRemoteAttribute
|
||||||
|
{
|
||||||
|
PageName = "Foo",
|
||||||
|
PageHandler = "Bar"
|
||||||
|
};
|
||||||
|
|
||||||
|
var ambientValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
["page"] = "/Page"
|
||||||
|
};
|
||||||
|
|
||||||
|
var routeData = new RouteData(ambientValues)
|
||||||
|
{
|
||||||
|
Routers = { Mock.Of<IRouter>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var urlHelper = new MockUrlHelper(url: null)
|
||||||
|
{
|
||||||
|
ActionContext = GetActionContext(new ServiceCollection().BuildServiceProvider(), routeData)
|
||||||
|
};
|
||||||
|
|
||||||
|
var validationContext = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
|
// Act && Assert
|
||||||
|
ExceptionAssert.Throws<InvalidOperationException>(
|
||||||
|
() => testableAttribute.InvokeGetUrl(validationContext),
|
||||||
|
Resources.RemoteAttribute_NoUrlFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUrl_WhenPageNameIsNotSet_WillUsePageNameFromAmbientValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testableAttribute = new TestablePageRemoteAttribute()
|
||||||
|
{
|
||||||
|
PageHandler = "Handler"
|
||||||
|
};
|
||||||
|
|
||||||
|
var ambientValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
["page"] = "/Page"
|
||||||
|
};
|
||||||
|
|
||||||
|
var routeData = new RouteData(ambientValues)
|
||||||
|
{
|
||||||
|
Routers = { Mock.Of<IRouter>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var urlHelper = new MockUrlHelper(url: "/Page?handler=Handler")
|
||||||
|
{
|
||||||
|
ActionContext = GetActionContext(new ServiceCollection().BuildServiceProvider(), routeData)
|
||||||
|
};
|
||||||
|
|
||||||
|
var validationContext = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualUrl = testableAttribute.InvokeGetUrl(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Page?handler=Handler", actualUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUrl_WhenPageNameAndPageHandlerIsNotSet_WillUseAmbientValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testableAttribute = new TestablePageRemoteAttribute();
|
||||||
|
|
||||||
|
var ambientValues = new RouteValueDictionary
|
||||||
|
{
|
||||||
|
["page"] = "/Page",
|
||||||
|
["handler"] = "Handler"
|
||||||
|
};
|
||||||
|
|
||||||
|
var routeData = new RouteData(ambientValues)
|
||||||
|
{
|
||||||
|
Routers = { Mock.Of<IRouter>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var urlHelper = new MockUrlHelper(url: "/Page?handler=Handler")
|
||||||
|
{
|
||||||
|
ActionContext = GetActionContext(new ServiceCollection().BuildServiceProvider(), routeData)
|
||||||
|
};
|
||||||
|
|
||||||
|
var validationContext = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualUrl = testableAttribute.InvokeGetUrl(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Page?handler=Handler", actualUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientModelValidationContext GetValidationContext(IUrlHelper urlHelper, RouteData routeData = null)
|
||||||
|
{
|
||||||
|
var serviceCollection = GetServiceCollection();
|
||||||
|
var factory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
||||||
|
|
||||||
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
var actionContext = GetActionContext(serviceProvider, routeData);
|
||||||
|
|
||||||
|
factory
|
||||||
|
.Setup(f => f.GetUrlHelper(actionContext))
|
||||||
|
.Returns(urlHelper);
|
||||||
|
|
||||||
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
var metadata = metadataProvider.GetMetadataForProperty(
|
||||||
|
containerType: typeof(string),
|
||||||
|
propertyName: nameof(string.Length));
|
||||||
|
|
||||||
|
return new ClientModelValidationContext(
|
||||||
|
actionContext,
|
||||||
|
metadata,
|
||||||
|
metadataProvider,
|
||||||
|
new AttributeDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ServiceCollection GetServiceCollection()
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection
|
||||||
|
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
|
||||||
|
.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
|
||||||
|
|
||||||
|
serviceCollection.AddOptions();
|
||||||
|
serviceCollection.AddRouting();
|
||||||
|
|
||||||
|
serviceCollection.AddSingleton<IInlineConstraintResolver>(
|
||||||
|
provider => new DefaultInlineConstraintResolver(provider.GetRequiredService<IOptions<RouteOptions>>(), provider));
|
||||||
|
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionContext GetActionContext(IServiceProvider serviceProvider, RouteData routeData)
|
||||||
|
{
|
||||||
|
// Set IServiceProvider properties because TemplateRoute gets services (e.g. an ILoggerFactory instance)
|
||||||
|
// through the HttpContext.
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
RequestServices = serviceProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (routeData == null)
|
||||||
|
{
|
||||||
|
routeData = new RouteData
|
||||||
|
{
|
||||||
|
Routers = { Mock.Of<IRouter>(), },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActionContext(httpContext, routeData, new ActionDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestablePageRemoteAttribute : PageRemoteAttribute
|
||||||
|
{
|
||||||
|
public string InvokeGetUrl(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
return base.GetUrl(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockUrlHelper : IUrlHelper
|
||||||
|
{
|
||||||
|
private readonly string _url;
|
||||||
|
|
||||||
|
public MockUrlHelper(string url)
|
||||||
|
{
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionContext ActionContext { get; set; }
|
||||||
|
|
||||||
|
public object RouteValues { get; private set; }
|
||||||
|
|
||||||
|
public string Action(UrlActionContext actionContext)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Content(string contentPath)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLocalUrl(string url)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Link(string routeName, object values)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RouteUrl(UrlRouteContext routeContext)
|
||||||
|
{
|
||||||
|
RouteValues = routeContext.Values;
|
||||||
|
|
||||||
|
return _url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,539 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Test.Resources;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
public class RemoteAttributeBaseTest
|
||||||
|
{
|
||||||
|
// Null or empty property names are invalid. (Those containing just whitespace are legal.)
|
||||||
|
public static TheoryData<string> NullOrEmptyNames
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<string>
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValidAlwaysReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var attribute = new TestableRemoteAttributeBase();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.True(attribute.IsValid(value: null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ErrorMessageProperties_HaveExpectedDefaultValues()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var attribute = new TestableRemoteAttributeBase();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(attribute.ErrorMessage);
|
||||||
|
Assert.Null(attribute.ErrorMessageResourceName);
|
||||||
|
Assert.Null(attribute.ErrorMessageResourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void FormatErrorMessage_ReturnsDefaultErrorMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// See ViewFeatures.Resources.RemoteAttribute_RemoteValidationFailed.
|
||||||
|
var expected = "'Property1' is invalid.";
|
||||||
|
var attribute = new TestableRemoteAttributeBase();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = attribute.FormatErrorMessage("Property1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FormatErrorMessage_UsesOverriddenErrorMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Property1' from override.";
|
||||||
|
var attribute = new TestableRemoteAttributeBase()
|
||||||
|
{
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = attribute.FormatErrorMessage("Property1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void FormatErrorMessage_UsesErrorMessageFromResource()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Property1' from resources.";
|
||||||
|
var attribute = new TestableRemoteAttributeBase()
|
||||||
|
{
|
||||||
|
ErrorMessageResourceName = nameof(Resources.RemoteAttribute_Error),
|
||||||
|
ErrorMessageResourceType = typeof(Resources)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = attribute.FormatErrorMessage("Property1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
|
public void FormatAdditionalFieldsForClientValidation_WithInvalidPropertyName_Throws(string property)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var attribute = new TestableRemoteAttributeBase();
|
||||||
|
var expectedMessage = "Value cannot be null or empty.";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
() => attribute.FormatAdditionalFieldsForClientValidation(property),
|
||||||
|
"property",
|
||||||
|
expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FormatAdditionalFieldsForClientValidation_WillFormat_AdditionalFields()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var attribute = new TestableRemoteAttributeBase
|
||||||
|
{
|
||||||
|
AdditionalFields = "FieldOne, FieldTwo"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = attribute.FormatAdditionalFieldsForClientValidation("Property");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var expected = "*.Property,*.FieldOne,*.FieldTwo";
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
|
public void FormatPropertyForClientValidation_WithInvalidPropertyName_Throws(string property)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Value cannot be null or empty.";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
() => RemoteAttributeBase.FormatPropertyForClientValidation(property),
|
||||||
|
"property",
|
||||||
|
expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessage_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from override.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContext();
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageAndLocalizerFactory_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from override.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var localizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict).Object;
|
||||||
|
var context = GetValidationContext(localizerFactory);
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
// IStringLocalizerFactory existence alone is insufficient to change error message.
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageAndLocalizerProvider_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from override.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContext();
|
||||||
|
var attribute = new TestableRemoteAttributeBase(url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
// Non-null DataAnnotationLocalizerProvider alone is insufficient to change error message.
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageLocalizerFactoryAndLocalizerProvider_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from localizer.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var localizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict).Object;
|
||||||
|
var context = GetValidationContext(localizerFactory);
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
var localizedString = new LocalizedString("Fred", expected);
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
localizer
|
||||||
|
.Setup(l => l["Error about '{0}' from override.", "Length"])
|
||||||
|
.Returns(localizedString)
|
||||||
|
.Verifiable();
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
localizer.VerifyAll();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void AddValidation_WithErrorResourcesLocalizerFactoryAndLocalizerProvider_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from resources.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessageResourceName = nameof(Resources.RemoteAttribute_Error),
|
||||||
|
ErrorMessageResourceType = typeof(Resources),
|
||||||
|
};
|
||||||
|
|
||||||
|
var localizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict).Object;
|
||||||
|
var context = GetValidationContext(localizerFactory);
|
||||||
|
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
// Configuring the attribute using ErrorMessageResource* trumps available IStringLocalizer etc.
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageAndDisplayName_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Display Length' from override.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
metadataProvider
|
||||||
|
.ForProperty(typeof(string), nameof(string.Length))
|
||||||
|
.DisplayDetails(d => d.DisplayName = () => "Display Length");
|
||||||
|
var context = GetValidationContext(localizerFactory: null, metadataProvider: metadataProvider);
|
||||||
|
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageLocalizerFactoryLocalizerProviderAndDisplayName_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from localizer.";
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
metadataProvider
|
||||||
|
.ForProperty(typeof(string), nameof(string.Length))
|
||||||
|
.DisplayDetails(d => d.DisplayName = () => "Display Length");
|
||||||
|
var localizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict).Object;
|
||||||
|
var context = GetValidationContext(localizerFactory, metadataProvider);
|
||||||
|
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
var localizedString = new LocalizedString("Fred", expected);
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
localizer
|
||||||
|
.Setup(l => l["Error about '{0}' from override.", "Display Length"])
|
||||||
|
.Returns(localizedString)
|
||||||
|
.Verifiable();
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
localizer.VerifyAll();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WillSetAttributes_ToExpectedValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var attribute = new TestableRemoteAttributeBase(dummyGetUrlReturnValue: url)
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
AdditionalFields = "Password,ConfirmPassword",
|
||||||
|
ErrorMessage = "Error"
|
||||||
|
};
|
||||||
|
var context = GetValidationContext();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("Error", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length,*.Password,*.ConfirmPassword", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientModelValidationContext GetValidationContext(
|
||||||
|
IStringLocalizerFactory localizerFactory = null,
|
||||||
|
IModelMetadataProvider metadataProvider = null)
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddOptions();
|
||||||
|
if (localizerFactory != null)
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<IStringLocalizerFactory>(localizerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
RequestServices = serviceProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = new ActionContext(
|
||||||
|
httpContext,
|
||||||
|
routeData: new Mock<RouteData>().Object,
|
||||||
|
actionDescriptor: new ActionDescriptor());
|
||||||
|
|
||||||
|
var emptyMetadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
|
if (metadataProvider == null)
|
||||||
|
{
|
||||||
|
metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = metadataProvider.GetMetadataForProperty(
|
||||||
|
containerType: typeof(string),
|
||||||
|
propertyName: nameof(string.Length));
|
||||||
|
|
||||||
|
return new ClientModelValidationContext(
|
||||||
|
actionContext,
|
||||||
|
metadata,
|
||||||
|
metadataProvider,
|
||||||
|
new AttributeDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestableRemoteAttributeBase : RemoteAttributeBase
|
||||||
|
{
|
||||||
|
private readonly string _dummyGetUrlReturnValue;
|
||||||
|
|
||||||
|
public TestableRemoteAttributeBase()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public TestableRemoteAttributeBase(string dummyGetUrlReturnValue)
|
||||||
|
{
|
||||||
|
_dummyGetUrlReturnValue = dummyGetUrlReturnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string GetUrl(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
return _dummyGetUrlReturnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ using System;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
|
@ -13,24 +12,17 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Localization;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Test.Resources;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
public class RemoteAttributeTest
|
public class RemoteAttributeTest
|
||||||
{
|
{
|
||||||
private static readonly IModelMetadataProvider _metadataProvider = new EmptyModelMetadataProvider();
|
|
||||||
private static readonly ModelMetadata _metadata = _metadataProvider.GetMetadataForProperty(
|
|
||||||
typeof(string),
|
|
||||||
nameof(string.Length));
|
|
||||||
|
|
||||||
public static TheoryData<string> SomeNames
|
public static TheoryData<string> SomeNames
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -57,15 +49,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IsValidAlwaysReturnsTrue()
|
|
||||||
{
|
|
||||||
// Act & Assert
|
|
||||||
Assert.True(new RemoteAttribute("RouteName", "ParameterName").IsValid(value: null));
|
|
||||||
Assert.True(new RemoteAttribute("ActionName", "ControllerName", "ParameterName").IsValid(value: null));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_WithNullAction_IgnoresArgument()
|
public void Constructor_WithNullAction_IgnoresArgument()
|
||||||
{
|
{
|
||||||
|
|
@ -157,516 +141,74 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
Assert.Equal(areaName, resultName);
|
Assert.Equal(areaName, resultName);
|
||||||
Assert.Null(attribute.RouteName);
|
Assert.Null(attribute.RouteName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ErrorMessageProperties_HaveExpectedDefaultValues()
|
public void GetUrl_WithBadRouteName_Throws()
|
||||||
{
|
|
||||||
// Arrange & Act
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Null(attribute.ErrorMessage);
|
|
||||||
Assert.Null(attribute.ErrorMessageResourceName);
|
|
||||||
Assert.Null(attribute.ErrorMessageResourceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[ReplaceCulture]
|
|
||||||
public void FormatErrorMessage_ReturnsDefaultErrorMessage()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// See ViewFeatures.Resources.RemoteAttribute_RemoteValidationFailed.
|
var testableAttribute = new TestableRemoteAttribute("nonexistentRoute");
|
||||||
var expected = "'Property1' is invalid.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var message = attribute.FormatErrorMessage("Property1");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FormatErrorMessage_UsesOverriddenErrorMessage()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Property1' from override.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var message = attribute.FormatErrorMessage("Property1");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[ReplaceCulture]
|
|
||||||
public void FormatErrorMessage_UsesErrorMessageFromResource()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Property1' from resources.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
ErrorMessageResourceName = nameof(Resources.RemoteAttribute_Error),
|
|
||||||
ErrorMessageResourceType = typeof(Resources),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var message = attribute.FormatErrorMessage("Property1");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
|
||||||
public void FormatAdditionalFieldsForClientValidation_WithInvalidPropertyName_Throws(string property)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var attribute = new RemoteAttribute(routeName: "default");
|
|
||||||
var expectedMessage = "Value cannot be null or empty.";
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
ExceptionAssert.ThrowsArgument(
|
|
||||||
() => attribute.FormatAdditionalFieldsForClientValidation(property),
|
|
||||||
"property",
|
|
||||||
expectedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
|
||||||
public void FormatPropertyForClientValidation_WithInvalidPropertyName_Throws(string property)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Value cannot be null or empty.";
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
ExceptionAssert.ThrowsArgument(
|
|
||||||
() => RemoteAttribute.FormatPropertyForClientValidation(property),
|
|
||||||
"property",
|
|
||||||
expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithBadRouteName_Throws()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var attribute = new RemoteAttribute("nonexistentRoute");
|
|
||||||
var context = GetValidationContextWithArea(currentArea: null);
|
var context = GetValidationContextWithArea(currentArea: null);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => attribute.AddValidation(context));
|
var exception = Assert.Throws<InvalidOperationException>(() => testableAttribute.InvokeGetUrl(context));
|
||||||
Assert.Equal("No URL for remote validation could be found.", exception.Message);
|
Assert.Equal("No URL for remote validation could be found.", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithRoute_CallsUrlHelperWithExpectedValues()
|
||||||
public void AddValidation_WithRoute_CallsUrlHelperWithExpectedValues()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var routeName = "RouteName";
|
var routeName = "RouteName";
|
||||||
var attribute = new RemoteAttribute(routeName);
|
var testableRemoteAttribute = new TestableRemoteAttribute(routeName);
|
||||||
var url = "/my/URL";
|
var url = "/my/URL";
|
||||||
var urlHelper = new MockUrlHelper(url, routeName);
|
var urlHelper = new MockUrlHelper(url, routeName);
|
||||||
var context = GetValidationContext(urlHelper);
|
var context = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableRemoteAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal(url, actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
|
|
||||||
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
||||||
Assert.Empty(routeDictionary);
|
Assert.Empty(routeDictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionController_CallsUrlHelperWithExpectedValues()
|
||||||
public void AddValidation_WithActionController_CallsUrlHelperWithExpectedValues()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller");
|
var testableRemoteAttribute = new TestableRemoteAttribute("Action", "Controller");
|
||||||
var url = "/Controller/Action";
|
var url = "/Controller/Action";
|
||||||
var urlHelper = new MockUrlHelper(url, routeName: null);
|
var urlHelper = new MockUrlHelper(url, routeName: null);
|
||||||
var context = GetValidationContext(urlHelper);
|
var context = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableRemoteAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal(url, actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
|
|
||||||
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
||||||
Assert.Equal(2, routeDictionary.Count);
|
Assert.Equal(2, routeDictionary.Count);
|
||||||
Assert.Equal("Action", routeDictionary["action"] as string);
|
Assert.Equal("Action", routeDictionary["action"] as string);
|
||||||
Assert.Equal("Controller", routeDictionary["controller"] as string);
|
Assert.Equal("Controller", routeDictionary["controller"] as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerArea_CallsUrlHelperWithExpectedValues()
|
||||||
public void AddValidation_WithActionController_PropertiesSet_CallsUrlHelperWithExpectedValues()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller", "Test");
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
AdditionalFields = "Password,ConfirmPassword",
|
|
||||||
};
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var urlHelper = new MockUrlHelper(url, routeName: null);
|
|
||||||
var context = GetValidationContext(urlHelper);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length,*.Password,*.ConfirmPassword", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
|
|
||||||
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
|
||||||
Assert.Equal(2, routeDictionary.Count);
|
|
||||||
Assert.Equal("Action", routeDictionary["action"] as string);
|
|
||||||
Assert.Equal("Controller", routeDictionary["controller"] as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithErrorMessage_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Length' from override.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var context = GetValidationContext(url);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithErrorMessageAndLocalizerFactory_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Length' from override.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var context = GetValidationContextWithLocalizerFactory(url);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
// IStringLocalizerFactory existence alone is insufficient to change error message.
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithErrorMessageAndLocalizerProvider_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Length' from override.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var context = GetValidationContext(url);
|
|
||||||
|
|
||||||
var options = context.ActionContext.HttpContext.RequestServices
|
|
||||||
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
|
||||||
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
|
||||||
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
// Non-null DataAnnotationLocalizerProvider alone is insufficient to change error message.
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithErrorMessageLocalizerFactoryAndLocalizerProvider_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Length' from localizer.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var context = GetValidationContextWithLocalizerFactory(url);
|
|
||||||
|
|
||||||
var localizedString = new LocalizedString("Fred", expected);
|
|
||||||
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
|
||||||
localizer
|
|
||||||
.Setup(l => l["Error about '{0}' from override.", "Length"])
|
|
||||||
.Returns(localizedString)
|
|
||||||
.Verifiable();
|
|
||||||
var options = context.ActionContext.HttpContext.RequestServices
|
|
||||||
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
|
||||||
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
localizer.VerifyAll();
|
|
||||||
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[ReplaceCulture]
|
|
||||||
public void AddValidation_WithErrorResourcesLocalizerFactoryAndLocalizerProvider_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Length' from resources.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessageResourceName = nameof(Resources.RemoteAttribute_Error),
|
|
||||||
ErrorMessageResourceType = typeof(Resources),
|
|
||||||
};
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var context = GetValidationContextWithLocalizerFactory(url);
|
|
||||||
|
|
||||||
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
|
||||||
var options = context.ActionContext.HttpContext.RequestServices
|
|
||||||
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
|
||||||
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
// Configuring the attribute using ErrorMessageResource* trumps available IStringLocalizer etc.
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithErrorMessageAndDisplayName_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Display Length' from override.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
metadataProvider
|
|
||||||
.ForProperty(typeof(string), nameof(string.Length))
|
|
||||||
.DisplayDetails(d => d.DisplayName = () => "Display Length");
|
|
||||||
var context = GetValidationContext(url, metadataProvider);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddValidation_WithErrorMessageLocalizerFactoryLocalizerProviderAndDisplayName_SetsAttributesAsExpected()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "Error about 'Length' from localizer.";
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
ErrorMessage = "Error about '{0}' from override.",
|
|
||||||
};
|
|
||||||
|
|
||||||
var url = "/Controller/Action";
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
metadataProvider
|
|
||||||
.ForProperty(typeof(string), nameof(string.Length))
|
|
||||||
.DisplayDetails(d => d.DisplayName = () => "Display Length");
|
|
||||||
var context = GetValidationContextWithLocalizerFactory(url, metadataProvider);
|
|
||||||
|
|
||||||
var localizedString = new LocalizedString("Fred", expected);
|
|
||||||
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
|
||||||
localizer
|
|
||||||
.Setup(l => l["Error about '{0}' from override.", "Display Length"])
|
|
||||||
.Returns(localizedString)
|
|
||||||
.Verifiable();
|
|
||||||
var options = context.ActionContext.HttpContext.RequestServices
|
|
||||||
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
|
||||||
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
attribute.AddValidation(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
localizer.VerifyAll();
|
|
||||||
|
|
||||||
Assert.Collection(
|
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote", kvp.Key);
|
|
||||||
Assert.Equal(expected, kvp.Value);
|
|
||||||
},
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[ReplaceCulture]
|
|
||||||
public void AddValidation_WithActionControllerArea_CallsUrlHelperWithExpectedValues()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var attribute = new RemoteAttribute("Action", "Controller", "Test")
|
|
||||||
{
|
|
||||||
HttpMethod = "POST",
|
|
||||||
};
|
|
||||||
var url = "/Test/Controller/Action";
|
var url = "/Test/Controller/Action";
|
||||||
var urlHelper = new MockUrlHelper(url, routeName: null);
|
var urlHelper = new MockUrlHelper(url, routeName: null);
|
||||||
var context = GetValidationContext(urlHelper);
|
var context = GetValidationContext(urlHelper);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal(url, actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
|
||||||
Assert.Equal("*.Length", kvp.Value);
|
|
||||||
},
|
|
||||||
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
|
||||||
|
|
||||||
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
var routeDictionary = Assert.IsType<RouteValueDictionary>(urlHelper.RouteValues);
|
||||||
Assert.Equal(3, routeDictionary.Count);
|
Assert.Equal(3, routeDictionary.Count);
|
||||||
|
|
@ -677,179 +219,109 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
// Root area is current in this case.
|
// Root area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionController_FindsControllerInCurrentArea()
|
||||||
public void AddValidation_WithActionController_FindsControllerInCurrentArea()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller");
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller");
|
||||||
var context = GetValidationContextWithArea(currentArea: null);
|
var context = GetValidationContextWithArea(currentArea: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerInArea_FindsControllerInCurrentArea()
|
||||||
public void AddValidation_WithActionControllerInArea_FindsControllerInCurrentArea()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller");
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller");
|
||||||
var context = GetValidationContextWithArea(currentArea: "Test");
|
var context = GetValidationContextWithArea(currentArea: "Test");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/Test/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/Test/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit reference to the (current) root area.
|
// Explicit reference to the (current) root area.
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerArea_FindsControllerInRootArea(string areaName)
|
||||||
public void AddValidation_WithActionControllerArea_FindsControllerInRootArea(string areaName)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller", areaName);
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller", areaName);
|
||||||
var context = GetValidationContextWithArea(currentArea: null);
|
var context = GetValidationContextWithArea(currentArea: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerAreaInArea_FindsControllerInRootArea(string areaName)
|
||||||
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInRootArea(string areaName)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller", areaName);
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller", areaName);
|
||||||
var context = GetValidationContextWithArea(currentArea: "Test");
|
var context = GetValidationContextWithArea(currentArea: "Test");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root area is current in this case.
|
// Root area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerArea_FindsControllerInNamedArea()
|
||||||
public void AddValidation_WithActionControllerArea_FindsControllerInNamedArea()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller", "Test");
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller", "Test");
|
||||||
var context = GetValidationContextWithArea(currentArea: null);
|
var context = GetValidationContextWithArea(currentArea: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/Test/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/Test/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit reference to the current (Test) area.
|
// Explicit reference to the current (Test) area.
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerAreaInArea_FindsControllerInNamedArea()
|
||||||
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInNamedArea()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller", "Test");
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller", "Test");
|
||||||
var context = GetValidationContextWithArea(currentArea: "Test");
|
var context = GetValidationContextWithArea(currentArea: "Test");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/Test/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/Test/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
public void GetUrl_WithActionControllerAreaInArea_FindsControllerInDifferentArea()
|
||||||
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInDifferentArea()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var attribute = new RemoteAttribute("Action", "Controller", "AnotherArea");
|
var testableAttribute = new TestableRemoteAttribute("Action", "Controller", "AnotherArea");
|
||||||
var context = GetValidationContextWithArea(currentArea: "Test");
|
var context = GetValidationContextWithArea(currentArea: "Test");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
attribute.AddValidation(context);
|
var actualUrl = testableAttribute.InvokeGetUrl(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(
|
Assert.Equal("/AnotherArea/Controller/Action", actualUrl);
|
||||||
context.Attributes,
|
|
||||||
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote", kvp.Key); Assert.Equal("'Length' is invalid.", kvp.Value); },
|
|
||||||
kvp => { Assert.Equal("data-val-remote-additionalfields", kvp.Key); Assert.Equal("*.Length", kvp.Value); },
|
|
||||||
kvp =>
|
|
||||||
{
|
|
||||||
Assert.Equal("data-val-remote-url", kvp.Key);
|
|
||||||
Assert.Equal("/AnotherArea/Controller/Action", kvp.Value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
|
|
@ -883,29 +355,16 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal("original", kvp.Value); });
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal("original", kvp.Value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClientModelValidationContext GetValidationContext(
|
private static ClientModelValidationContext GetValidationContext(string url)
|
||||||
string url,
|
|
||||||
IModelMetadataProvider metadataProvider = null)
|
|
||||||
{
|
{
|
||||||
var urlHelper = new MockUrlHelper(url, routeName: null);
|
var urlHelper = new MockUrlHelper(url, routeName: null);
|
||||||
return GetValidationContext(urlHelper, localizerFactory: null, metadataProvider: metadataProvider);
|
return GetValidationContext(urlHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClientModelValidationContext GetValidationContextWithLocalizerFactory(
|
|
||||||
string url,
|
|
||||||
IModelMetadataProvider metadataProvider = null)
|
|
||||||
{
|
|
||||||
var urlHelper = new MockUrlHelper(url, routeName: null);
|
|
||||||
var localizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
|
|
||||||
return GetValidationContext(urlHelper, localizerFactory.Object, metadataProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClientModelValidationContext GetValidationContext(
|
private static ClientModelValidationContext GetValidationContext(
|
||||||
IUrlHelper urlHelper,
|
IUrlHelper urlHelper)
|
||||||
IStringLocalizerFactory localizerFactory = null,
|
|
||||||
IModelMetadataProvider metadataProvider = null)
|
|
||||||
{
|
{
|
||||||
var serviceCollection = GetServiceCollection(localizerFactory);
|
var serviceCollection = GetServiceCollection();
|
||||||
var factory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
var factory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
||||||
|
|
||||||
|
|
@ -915,17 +374,12 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
factory
|
factory
|
||||||
.Setup(f => f.GetUrlHelper(actionContext))
|
.Setup(f => f.GetUrlHelper(actionContext))
|
||||||
.Returns(urlHelper);
|
.Returns(urlHelper);
|
||||||
|
|
||||||
var metadata = _metadata;
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
if (metadataProvider == null)
|
var metadata = metadataProvider.GetMetadataForProperty(
|
||||||
{
|
containerType: typeof(string),
|
||||||
metadataProvider = _metadataProvider;
|
propertyName: nameof(string.Length));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
metadata = metadataProvider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClientModelValidationContext(
|
return new ClientModelValidationContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
metadata,
|
metadata,
|
||||||
|
|
@ -935,7 +389,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
private static ClientModelValidationContext GetValidationContextWithArea(string currentArea)
|
private static ClientModelValidationContext GetValidationContextWithArea(string currentArea)
|
||||||
{
|
{
|
||||||
var serviceCollection = GetServiceCollection(localizerFactory: null);
|
var serviceCollection = GetServiceCollection();
|
||||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
var routeCollection = GetRouteCollectionWithArea(serviceProvider);
|
var routeCollection = GetRouteCollectionWithArea(serviceProvider);
|
||||||
var routeData = new RouteData
|
var routeData = new RouteData
|
||||||
|
|
@ -968,10 +422,15 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
serviceProvider = serviceCollection.BuildServiceProvider();
|
serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
actionContext.HttpContext.RequestServices = serviceProvider;
|
actionContext.HttpContext.RequestServices = serviceProvider;
|
||||||
|
|
||||||
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
var metadata = metadataProvider.GetMetadataForProperty(
|
||||||
|
containerType: typeof(string),
|
||||||
|
propertyName: nameof(string.Length));
|
||||||
|
|
||||||
return new ClientModelValidationContext(
|
return new ClientModelValidationContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
_metadata,
|
metadata,
|
||||||
_metadataProvider,
|
metadataProvider,
|
||||||
new AttributeDictionary());
|
new AttributeDictionary());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -987,15 +446,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IRouter GetRouteCollectionWithNoController(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
var builder = GetRouteBuilder(serviceProvider);
|
|
||||||
builder.MapRoute("default", "static/route");
|
|
||||||
|
|
||||||
return builder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RouteBuilder GetRouteBuilder(IServiceProvider serviceProvider)
|
private static RouteBuilder GetRouteBuilder(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
var app = new Mock<IApplicationBuilder>(MockBehavior.Strict);
|
var app = new Mock<IApplicationBuilder>(MockBehavior.Strict);
|
||||||
|
|
@ -1034,7 +485,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
return new ActionContext(httpContext, routeData, new ActionDescriptor());
|
return new ActionContext(httpContext, routeData, new ActionDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServiceCollection GetServiceCollection(IStringLocalizerFactory localizerFactory)
|
private static ServiceCollection GetServiceCollection()
|
||||||
{
|
{
|
||||||
var serviceCollection = new ServiceCollection();
|
var serviceCollection = new ServiceCollection();
|
||||||
serviceCollection
|
serviceCollection
|
||||||
|
|
@ -1047,11 +498,6 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
serviceCollection.AddSingleton<IInlineConstraintResolver>(
|
serviceCollection.AddSingleton<IInlineConstraintResolver>(
|
||||||
provider => new DefaultInlineConstraintResolver(provider.GetRequiredService<IOptions<RouteOptions>>(), provider));
|
provider => new DefaultInlineConstraintResolver(provider.GetRequiredService<IOptions<RouteOptions>>(), provider));
|
||||||
|
|
||||||
if (localizerFactory != null)
|
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<IStringLocalizerFactory>(localizerFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1135,6 +581,11 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
return base.RouteData;
|
return base.RouteData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string InvokeGetUrl(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
return base.GetUrl(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue