Is there any way to set Data Annotation messages using formatted string instead of direct constant,
I need to set required field ErrorMessage like in following code, but it gives me an error.
[Required(ErrorMessage = string.Format(SystemMessages.Required, "First Name"))]
public string FirstName { get; set; }
SystemMessages is a constant file and it has following code,
public const string Required = "{0} is required.";
Is there any way to set attributes like this?
string.Format result is not a compile time constant and can't bee evaluated by the compiler. Attribute values are limited to
constant expression, typeof expression or array creation expression of an attribute parameter type.
More formally, you can find description of limitations at msdn. Formatting a string doesn't fit any of them, which are:
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of -one of the above types.)
One-dimensional arrays of any of the above types
At best you can try something similar to:
[Required(ErrorMessage = "First Name" + Required)]
public string FirstName { get; set; }
public const string Required = " is required.";
Just use DisplayAttribute, if you want to output display name instead of property name:
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
Please implement IValidatableObject which use Validate method. It will look like:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var memberNames = new List<string>();
if (string.IsNullOrEmpty(this.FirstName )
{
memberNames.Add("FirstName");
yield return new ValidationResult(string.Format(Resources.Required, "First Name"), memberNames);
}
}
If it is not clear please just read about IValidatableObject. Good luck!
Related
I have to send string with xml containing enums serialized to xml.
Those enums are in nuget package, so I can't edit them.
All enums have name, value and description attribute.
public enum FileExtenstion
[Description("XML extension")]
xml = 1,
[Description("TXT extension")]
txt = 2
end enum
My function to get xml from this enum looks like this:
public static string GetFileExtension()
{
var _cache = System.Enum.GetValues(typeof(FileExtension))
.Cast<FileExtension>()
.Select(enumValue => new EnumToXmlSerializer
{
Name = enumValue.ToString(),
Value = (int)enumValue,
Description = enumValue.GetDescription()
}).ToList();
(...)
}
public class EnumToXmlSerializer
{
public string Name { get; set; }
public int Value { get; set; }
public string Description { get; set; }
}
Works well.
But I have a lot of enums to convert and better way will be to prepare some parametrized function with convert string with name of enum ("FileExtension") to type.
I haven't idea how to convert enum name (not enum every value name but enum type name) and convert its to type. In stackoverflow I found only conversion for enum values not all types.
I'm looking for solution to prepare function like this:
public string GetXmlFromEnumTypeName("FileExtension")
to get:
<enums>
<enum><name>xml</name><value>1</value><description>XML extension</description></enum>
<enum><name>txt</name><value>2</value><description>TXT extension</description></enum>
</enums>
I suppose there will be one line to change my GetFileExtension function but I usually get it wrong about things like that.
I have custom attribute defined like so:
[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayAttribute : Attribute
{
public string Description { get; private set; }
public string Code { get; private set; }
public EnumDisplayAttribute(string description = null, string code = null)
{
Description = description;
Code = code;
}
}
Both constructor parameters are optional.
When using this attribute on a field like so
public enum TransactionType
{
[EnumDisplay(code: "B")]
Bill,
[EnumDisplay(description: null, code: "C")]
CashReceipt,
}
I don't see any squigglies in the code editor but I see a vague error without any File Line number of column. The error message is:
error CS0182: An attribute argument must be a constant expression, typeof expression
or array creation expression of an attribute parameter type
Clicking on the error does nothing. That is, you don't get navigated to the error site (obviously, since there is no line number and column).
even if I set up the attribute like so:
[EnumDisplay("This is a Bill")]
The compiler doesn't like it.
Effectively, I am forced to provide both parameters (named or not) in order to use this attribute as an attribute.
Of course if I use this attribute as a regular class like so:
var enumDisplayAttribute = new EnumDisplayAttribute();
enumDisplayAttribute = new EnumDisplayAttribute(description: "This is a Bill");
enumDisplayAttribute = new EnumDisplayAttribute(code: "B");
enumDisplayAttribute = new EnumDisplayAttribute(description: "This is a Bill", code: "B");
enumDisplayAttribute = new EnumDisplayAttribute("This is a Bill", "B");
enumDisplayAttribute = new EnumDisplayAttribute("This is a Bill");
The compiler will accept any one of the above "styles".
Surely, I'm missing something or my brain is just not working.
Optional parameters were added to C# after optional values for attributes already existed in C#. Therefore, for optional attribute parameters, you should fall back to the attribute-specific syntax:
[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayAttribute : Attribute
{
public string Description { get; set; }
public string Code { get; set; }
public EnumDisplayAttribute()
{
}
}
public enum TransactionType
{
[EnumDisplay(Code = "B")]
Bill,
[EnumDisplay(Description = null, Code = "C")]
CashReceipt,
}
As you see, the end-result is effectively the same, but instead of using named arguments, you are using named properties (where syntax like [EnumDisplay(Description = null, Code = "C")] is only possible in attribute declarations).
Another way to think of it is that attribute declarations "borrowed" its syntax from method/constructor invocations, but attribute declarations are not in themselves method invocations, so they don't get all the same features as methods.
If you do want to push values into your attribute using a constructor (e.g. if some of your attribute's properties are mandatory or to perform some kind of processing on them) you can always go old school and overload the constructor.
For example:
[AttributeUsage(AttributeTargets.Field)]
public class SampleAttribute : Attribute
{
public string MandatoryProperty { get; private set; }
public string OptionalProperty { get; private set; }
// we use an overload here instead of optional parameters because
// C# does not currently support optional constructor parameters in attributes
public SampleAttribute(string mandatoryProperty)
: this(mandatoryProperty, null)
{
}
public SampleAttribute(string mandatoryProperty, string optionalProperty)
{
MandatoryProperty = mandatoryProperty;
OptionalProperty = optionalProperty;
}
}
Optional parameters are not really optional, the method signature has all arguments in it and attributes are special (existed before optional parameters and have different rules when applied as an attribute (eg consider who calls the attribute constructor)). I imagine however that support will be added in the future.
For now, if you wish to achieve the optional effect try the following:
[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayAttribute : Attribute
{
public string Description { get; set; }
public string Code { get; set; }
}
And apply as so:
[EnumDisplay(Description = null, Code = "C")]
private object _aField;
In an Entity Framework data model, I have a list of strings:
public List<string> Comments { get; set; }
I'm trying to restrict the size of the strings within the above list. I wish to do this by using data annotations as all other restrictions are expressed that way throughout the model.
This is what I have tried:
[StringLength(200, MinimumLength = 1)]
public List<string> Comments { get; set; }
The above code does does not work, as the StringLength constraint is applied to the list itself and not to the elements within. This results in the following exception:
System.Data.Entity.Validation.DbUnexpectedValidationException: An unexpected exception was thrown during validation of 'Comments' when invoking System.ComponentModel.DataAnnotations.StringLengthAttribute.IsValid.
[...]
System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.List`1[System.String]' to type 'System.String'.
How can I constraint a string's length, in cases where the string is composed by a collection such as a list, using a data annotations?
One idea is that it might be possible to write a custom annotation -- that would be a valid answer.
One option, although I don't feel it is very elegant is to create a new type that functions like the string type and decorate that with your annotations.
public class Comment
{
[StringLength(200, MinimumLength = 1)]
private readonly string _value;
public Comment(string value)
{
this._value = value;
}
public static implicit operator string(Comment s)
{
return s.ToString();
}
public static implicit operator Comment(string s)
{
return new Comment(s);
}
}
then your property would be updated to:
public List<Comment> Comments { get; set; }
You can use it like a string type:
var foo = new YourClass();
foo.Comments = new List<Comment>();
foo.Comments.Add("bar"); // note that it works *like* a string
// the following is the equivalent, but just looks like a usual class initializer
foo.Comments.Add(new Comment("bar"));
I'm using ServiceStack v 3.9.71 and the ServiceStack.Text.EnumMemberSerializer assembly to serialize enums into readable text.
This works great, my enum values are serialized into the name I've specified using the EnumMemberAttribute.
The problem, though, is Swagger does not use my names. My guess is it just calls the .ToString() method on the enum values rather than the EnumMemberAttribute value.
Here is the order in which I setup the serialization. (In AppHost):
new EnumSerializerConfigurator()
.WithEnumTypes(new Type[] { typeof(MyEnum) })
.Configure();
Plugins.Add(new SwaggerFeature());
It doesn't seem to matter if the enum serializer is set before or after the swagger feature is added.
You are correct that the Swagger code does not use ServiceStack.Text.EnumMemberSerializer when parsing enum values. It only uses an Enum.GetValues here. Note that this is still the same in v4.
You can submit a pull request to make this change, but I'm not familiar with EnumMemberSerialzer and how it allows for retrieving the list of enum options. You may instead be able to use a string property decorated with ApiAllowableValues to achieve the affect.
Here is the solution I came up with (with the help of bpruitt-goddard, thanks mate):
The enum:
public enum MyEnum
{
[EnumMember(Value = "Value One")]
Value1 = 1,
[EnumMember(Value = "Value Two")]
Value2 = 2,
[EnumMember(Value = "Value Three")]
Value3 = 3
}
The client object:
public class MyClientObject
{
[Description("The name")]
public string Name {get;set;}
[Description("The client object type")]
[ApiAllowableValues("MyEnum", "Value One", "Value Two", "Value Three")]
public MyEnum MyEnum { get; set; }
}
Inside the AppHost:
new EnumSerializerConfigurator()
.WithEnumTypes(new Type[] { typeof(MyEnum) })
.Configure();
Now the enum is serialized properly and the Swagger documentation is correct. The only issue with this is having the names in two different places. Perhaps there is a way to check the names match via a unit test.
I came up with, in my opinion, a better solution. I wrote a class that extends the ApiAllowableValuesAttribute:
public class ApiAllowableValues2Attribute : ApiAllowableValuesAttribute
{
public ApiAllowableValues2Attribute(string name, Type enumType)
: base(name)
{
List<string> values = new List<string>();
var enumTypeValues = Enum.GetValues(enumType);
// loop through each enum value
foreach (var etValue in enumTypeValues)
{
// get the member in order to get the enumMemberAttribute
var member = enumType.GetMember(
Enum.GetName(enumType, etValue)).First();
// get the enumMember attribute
var enumMemberAttr = member.GetCustomAttributes(
typeof(System.Runtime.Serialization.EnumMemberAttribute), true).First();
// get the enumMember attribute value
var enumMemberValue = ((System.Runtime.Serialization.EnumMemberAttribute)enumMemberAttr).Value;
values.Add(enumMemberValue);
}
Values = values.ToArray();
}
}
The client object:
public class MyClientObject
{
[Description("The name")]
public string Name {get;set;}
[Description("The client object type")]
[ApiAllowableValues2("MyEnum", typeof(MyEnum))]
public MyEnum MyEnum { get; set; }
}
Now you don't have to specify the names again or worry about a name change breaking your Swagger documentation.
Why can't I do like this?
[Required(ErrorMessage = "*")]
[RegularExpression("^[a-zA-Z0-9_]*$", ErrorMessage = Resources.RegistrationModel.UsernameError)]
public string Username { get; set; }
What is the error message telling me?
An attribute argument must be a
constant expression , typeof
expression or array creation
expression of an attribute parameter
type.
When you are using the ErrorMessage property only constant strings or string literal can be assigned to it.
Use the ErrorMessageResourceType and ErrorMessageResourceName instead to specity your resources.
[RegularExpression(
"^[a-zA-Z0-9_]*$",
ErrorMessageResourceType=typeof(Resources.RegistrationModel),
ErrorMessageResourceName= "UsernameError"
)]
Note that the resources must be public (can be set in the resource editor).
Please see this link: http://code.msdn.microsoft.com/Getting-Started-WCF-RIA-1469cbe2/sourcecode?fileId=19242&pathId=774666288 (link broken, but left for attribution purposes)
public sealed partial class RegistrationData
{
[Key]
[Required(ErrorMessageResourceName = "ValidationErrorRequiredField", ErrorMessageResourceType = typeof(ErrorResources))]
[Display(Order = 0, Name = "UserNameLabel", ResourceType = typeof(RegistrationDataResources))]
[RegularExpression("^[a-zA-Z0-9_]*$", ErrorMessageResourceName = "ValidationErrorInvalidUserName", ErrorMessageResourceType = typeof(ErrorResources))]
[StringLength(255, MinimumLength = 4, ErrorMessageResourceName = "ValidationErrorBadUserNameLength", ErrorMessageResourceType = typeof(ErrorResources))]
public string UserName { get; set; }
Try FluentModelMetaDataProvider.
Managed to use resources for error messages in strongly typed fashion.
Looks like this:
using System.Web.Mvc.Extensibility;
namespace UI.Model
{
public class StoreInputMetadata : ModelMetadataConfigurationBase<StoreInput>
{
public StoreInputMetadata()
{
Configure(m => m.Id)
.Hide();
Configure(model => model.Name)
.Required(Resources.Whatever.StoreIsRequired)
.MaximumLength(64, Resources.Whatever.StoreNameLengthSomething);
}
}
}
What is the error message telling me?
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
It's already self explanatory. C# isn't dynamic language like Ruby where You can write classes that inherits random base class at runtime. :)
Here's what Skeet says about this.
It means that you cannot determine the value of the argument you are passing into the attribute at runtime, it must be at compile time so the value is embedded into the assembly.
The error message value needs to be a constant expression.
For information, attribute arguments can only be of types bool, byte, char, short, int, long, float, double, string, System.Type, and enums.
You should instead look at the ErrorMessageResourceName and ErrorMessageResourceType properties of this attribute. They do allow the error message to be pulled from a resource.
We can now use nameof for strongly typed error messages:
[RegularExpression("^[a-zA-Z0-9_]*$",
ErrorMessageResourceType=typeof(Resources.RegistrationModel),
ErrorMessageResourceName=nameof(Resources.RegistrationModel.UsernameError)
)]