using System; using System.Linq; using System.Threading.Tasks; using EmbedIO.Utilities; using Swan; namespace EmbedIO.WebApi { /// /// Specifies that a parameter of a controller method will receive the value of a field, /// obtained by deserializing a request URL query. /// The parameter carrying this attribute can be either a simple type or a one-dimension array. /// If multiple values are present for the field, a non-array parameter will receive the last specified value, /// while an array parameter will receive an array of field values converted to the element type of the /// parameter. /// If a single value is present for the field, a non-array parameter will receive the value converted /// to the type of the parameter, while an array parameter will receive an array of length 1, containing /// the value converted to the element type of the parameter /// If no values are present for the field and the property is /// , a 400 Bad Request response will be sent to the client, with a message /// specifying the name of the missing field. /// If no values are present for the field and the property is /// , a non-array parameter will receive the default value for its type, while /// an array parameter will receive an array of length 0. /// This class cannot be inherited. /// /// /// /// [AttributeUsage(AttributeTargets.Parameter)] public sealed class QueryFieldAttribute : Attribute, IRequestDataAttribute, IRequestDataAttribute, IRequestDataAttribute { /// /// Initializes a new instance of the class. /// The name of the query field to extract will be equal to the name of the parameter /// carrying this attribute. /// public QueryFieldAttribute() : this(false, null) { } /// /// Initializes a new instance of the class. /// /// The name of the query field to extract. /// is . /// is the empty string (""). public QueryFieldAttribute(string fieldName) : this(false, Validate.NotNullOrEmpty(nameof(fieldName), fieldName)) { } /// /// Initializes a new instance of the class. /// The name of the query field to extract will be equal to the name of the parameter /// carrying this attribute. /// /// If set to , a 400 Bad Request /// response will be sent to the client if no values are found for the field; if set to /// , a default value will be assumed. public QueryFieldAttribute(bool badRequestIfMissing) : this(badRequestIfMissing, null) { } /// /// Initializes a new instance of the class. /// /// The name of the query field to extract. /// is . /// is the empty string (""). /// If set to , a 400 Bad Request /// response will be sent to the client if no values are found for the field; if set to /// , a default value will be assumed. public QueryFieldAttribute(string fieldName, bool badRequestIfMissing) : this(badRequestIfMissing, Validate.NotNullOrEmpty(nameof(fieldName), fieldName)) { } private QueryFieldAttribute(bool badRequestIfMissing, string? fieldName) { BadRequestIfMissing = badRequestIfMissing; FieldName = fieldName; } /// /// Gets the name of the query field that this attribute will extract, /// or if the name of the parameter carrying this /// attribute is to be used as field name. /// public string? FieldName { get; } /// /// Gets or sets a value indicating whether to send a 400 Bad Request response /// to the client if the URL query contains no values for the field. /// If this property is and the URL query /// contains no values for the field, the 400 Bad Request response sent /// to the client will contain a reference to the missing field. /// If this property is and the URL query /// contains no values for the field, the default value for the parameter /// (or a zero-length array if the parameter is of an array type) /// will be passed to the controller method. /// public bool BadRequestIfMissing { get; } Task IRequestDataAttribute.GetRequestDataAsync( WebApiController controller, string parameterName) { var data = controller.HttpContext.GetRequestQueryData(); var fieldName = FieldName ?? parameterName; if (!data.ContainsKey(fieldName) && BadRequestIfMissing) throw HttpException.BadRequest($"Missing query field {fieldName}."); return Task.FromResult(data.GetValues(fieldName)?.LastOrDefault()); } Task IRequestDataAttribute.GetRequestDataAsync( WebApiController controller, string parameterName) { var data = controller.HttpContext.GetRequestQueryData(); var fieldName = FieldName ?? parameterName; if (!data.ContainsKey(fieldName) && BadRequestIfMissing) throw HttpException.BadRequest($"Missing query field {fieldName}."); return Task.FromResult(data.GetValues(fieldName) ?? Array.Empty()); } Task IRequestDataAttribute.GetRequestDataAsync( WebApiController controller, Type type, string parameterName) { var data = controller.HttpContext.GetRequestQueryData(); var fieldName = FieldName ?? parameterName; if (!data.ContainsKey(fieldName) && BadRequestIfMissing) throw HttpException.BadRequest($"Missing query field {fieldName}."); if (type.IsArray) { var fieldValues = data.GetValues(fieldName) ?? Array.Empty(); if (!FromString.TryConvertTo(type, fieldValues, out var result)) throw HttpException.BadRequest($"Cannot convert field {fieldName} to an array of {type.GetElementType().Name}."); return Task.FromResult(result); } else { var fieldValue = data.GetValues(fieldName)?.LastOrDefault(); if (fieldValue == null) return Task.FromResult(type.IsValueType ? Activator.CreateInstance(type) : null); if (!FromString.TryConvertTo(type, fieldValue, out var result)) throw HttpException.BadRequest($"Cannot convert field {fieldName} to {type.Name}."); return Task.FromResult(result); } } } }