// 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.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc
{
///
/// A 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.
///
/// Does no server-side validation of the final form submission.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RemoteAttribute : ValidationAttribute, IClientModelValidator
{
private string _additionalFields = string.Empty;
private string[] _additionalFieldsSplit = new string[0];
///
/// Initializes a new instance of the class.
///
///
/// Intended for subclasses that support URL generation with no route, action, or controller names.
///
protected RemoteAttribute()
: base(Resources.RemoteAttribute_RemoteValidationFailed)
{
RouteData = new RouteValueDictionary();
}
///
/// Initializes a new instance of the class.
///
///
/// The route name used when generating the URL where client should send a validation request.
///
///
/// Finds the in any area of the application.
///
public RemoteAttribute(string routeName)
: this()
{
RouteName = routeName;
}
///
/// Initializes a new instance of the class.
///
///
/// The action name used when generating the URL where client should send a validation request.
///
///
/// The controller name used when generating the URL where client should send a validation request.
///
///
///
/// If either or is null, uses the corresponding
/// ambient value.
///
/// Finds the in the current area.
///
public RemoteAttribute(string action, string controller)
: this()
{
if (action != null)
{
RouteData["action"] = action;
}
if (controller != null)
{
RouteData["controller"] = controller;
}
}
///
/// Initializes a new instance of the class.
///
///
/// The action name used when generating the URL where client should send a validation request.
///
///
/// The controller name used when generating the URL where client should send a validation request.
///
/// The name of the area containing the .
///
///
/// If either or is null, uses the corresponding
/// ambient value.
///
/// If is null, finds the in the root area.
/// Use the overload find the in
/// the current area. Or explicitly pass the current area's name as the argument to
/// this overload.
///
public RemoteAttribute(string action, string controller, string areaName)
: this(action, controller)
{
RouteData["area"] = areaName;
}
///
/// Gets or sets the HTTP method ("Get" or "Post") client should use when sending a validation
/// request.
///
public string HttpMethod { get; set; }
///
/// Gets or sets the comma-separated names of fields the client should include in a validation request.
///
public string AdditionalFields
{
get { return _additionalFields; }
set
{
_additionalFields = value ?? string.Empty;
_additionalFieldsSplit = SplitAndTrimPropertyNames(value)
.Select(field => FormatPropertyForClientValidation(field))
.ToArray();
}
}
///
/// Gets the used when generating the URL where client should send a
/// validation request.
///
protected RouteValueDictionary RouteData { get; }
///
/// Gets or sets the route name used when generating the URL where client should send a validation request.
///
protected string RouteName { get; set; }
///
/// Formats and for use in generated HTML.
///
///
/// Name of the property associated with this instance.
///
/// Comma-separated names of fields the client should include in a validation request.
///
/// Excludes any whitespace from in the return value.
/// Prefixes each field name in the return value with "*.".
///
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;
}
///
/// Formats for use in generated HTML.
///
/// One field name the client should include in a validation request.
/// Name of a field the client should include in a validation request.
/// Returns with a "*." prefix.
public static string FormatPropertyForClientValidation(string property)
{
if (string.IsNullOrEmpty(property))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(property));
}
return "*." + property;
}
///
/// Returns the URL where the client should send a validation request.
///
/// The used to generate the URL.
/// The URL where the client should send a validation request.
protected virtual string GetUrl(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var urlHelper = context.RequestServices.GetRequiredService();
var url = urlHelper.RouteUrl(new UrlRouteContext()
{
RouteName = this.RouteName,
Values = RouteData,
});
if (url == null)
{
throw new InvalidOperationException(Resources.RemoteAttribute_NoUrlFound);
}
return url;
}
///
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
}
///
///
/// Always returns true since this does no validation itself.
/// Related validations occur only when the client sends a validation request.
///
public override bool IsValid(object value)
{
return true;
}
///
///
/// Thrown if unable to generate a target URL for a validation request.
///
public virtual IEnumerable GetClientValidationRules(
ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var metadata = context.ModelMetadata;
var rule = new ModelClientValidationRemoteRule(
FormatErrorMessage(metadata.GetDisplayName()),
GetUrl(context),
HttpMethod,
FormatAdditionalFieldsForClientValidation(metadata.PropertyName));
return new[] { rule };
}
private static IEnumerable SplitAndTrimPropertyNames(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
var split = original.Split(',')
.Select(piece => piece.Trim())
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
return split;
}
}
}