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(s) of a field in a HTML form, /// obtained by deserializing a request body with a content type of application/x-www-form-urlencoded. /// 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 FormFieldAttribute : Attribute, IRequestDataAttribute, IRequestDataAttribute, IRequestDataAttribute { /// /// Initializes a new instance of the class. /// The name of the form field to extract will be equal to the name of the parameter /// carrying this attribute. /// public FormFieldAttribute() : this(false, null) { } /// /// Initializes a new instance of the class. /// /// The name of the form field to extract. /// is . /// is the empty string (""). public FormFieldAttribute(string fieldName) : this(false, Validate.NotNullOrEmpty(nameof(fieldName), fieldName)) { } /// /// Initializes a new instance of the class. /// The name of the form 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 FormFieldAttribute(bool badRequestIfMissing) : this(badRequestIfMissing, null) { } /// /// Initializes a new instance of the class. /// /// The name of the form 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 FormFieldAttribute(string fieldName, bool badRequestIfMissing) : this(badRequestIfMissing, Validate.NotNullOrEmpty(nameof(fieldName), fieldName)) { } private FormFieldAttribute(bool badRequestIfMissing, string? fieldName) { BadRequestIfMissing = badRequestIfMissing; FieldName = fieldName; } /// /// Gets the name of the form 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 submitted form contains no values for the field. /// If this property is and the submitted form /// 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 submitted form /// 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; } async Task IRequestDataAttribute.GetRequestDataAsync( WebApiController controller, string parameterName) { var data = await controller.HttpContext.GetRequestFormDataAsync() .ConfigureAwait(false); var fieldName = FieldName ?? parameterName; if (!data.ContainsKey(fieldName) && BadRequestIfMissing) throw HttpException.BadRequest($"Missing form field {fieldName}."); return data.GetValues(fieldName)?.LastOrDefault(); } async Task IRequestDataAttribute.GetRequestDataAsync( WebApiController controller, string parameterName) { var data = await controller.HttpContext.GetRequestFormDataAsync() .ConfigureAwait(false); var fieldName = FieldName ?? parameterName; if (!data.ContainsKey(fieldName) && BadRequestIfMissing) throw HttpException.BadRequest($"Missing form field {fieldName}."); return data.GetValues(fieldName) ?? Array.Empty(); } async Task IRequestDataAttribute.GetRequestDataAsync( WebApiController controller, Type type, string parameterName) { var data = await controller.HttpContext.GetRequestFormDataAsync() .ConfigureAwait(false); var fieldName = FieldName ?? parameterName; if (!data.ContainsKey(fieldName) && BadRequestIfMissing) throw HttpException.BadRequest($"Missing form 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 result; } else { var fieldValue = data.GetValues(fieldName)?.LastOrDefault(); if (fieldValue == null) return 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 result; } } } }