Why can't I use resources as ErrorMessage with DataAnnotations? - c#

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)
)]

Related

C# Method/Function Parameter Passing Similar to Named Parameters, But Using '=' instead of ':' [duplicate]

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;

How to resolve : An attribute argument must be a constant expression error? [duplicate]

Here is my custom attribute and a class I'm using it on:
[MethodAttribute(new []{new MethodAttributeMembers(), new MethodAttributeMembers()})]
public class JN_Country
{
}
public class MethodAttribute : Attribute
{
public MethodAttributeMembers[] MethodAttributeMembers { get; set; }
public MethodAttribute(MethodAttributeMembers[] methodAttributeMemberses)
{
MethodAttributeMembers = methodAttributeMemberses;
}
}
public class MethodAttributeMembers
{
public string MethodName { get; set; }
public string Method { get; set; }
public string MethodTitle { get; set; }
}
The syntax error, displayed on the first line above:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
Why does this error occur?
This supplements the information Simon already gave.
I found some documentation here: Attribute parameter types:
The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
The type object.
The type System.Type.
An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Attribute specification).
Single-dimensional arrays of the above types. (emphasis added by me)
A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.
The last bullet point explains your syntax error. You've defined a one-dimensional array, but it should only be of primitive types, string, etc. as listed in the previous bullet points.
Attribute arguments must be compile-time constant. That means that the compiler must be able to "bake in" the values of the arguments when the assembly is compiled. new ReferenceType() is not constant - it must be evaluated at runtime to determine what it is.
Interestingly, this is a little bit flimsy in that there are some edge cases to that rule. Other than those though, you cannot do what you're trying to do.
Let me add that the compiler can return this error without any particular file or line of code, if your attribute has a constructor that has a parameter that isn't a simple type and you use the constructor (i.e. your non-simple parameter has a default value).
[MyAttribute(MySimpleParameter: "Foo")]
public class MyObject
{
}
public class MyAttribute : Attribute
{
public string MySimpleProperty { get; set; }
public MyPropertyClass MyComplexProperty { get; set; }
public MethodAttribute(string MySimpleParameter, MyPropertyClass MyComplexParameter = null)
{
MySimpleProperty = MySimpleParameter;
MyComplexProperty = MyComplexParameter;
}
}
public class MyPropertyClass
{
public string Name { get; set; }
public string Method { get; set; }
}

ServiceStack.Text.EnumMemberSerializer not working with Swagger plugin

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.

string.Format in Data Annotation Validation Attributes

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!

Attribute help with Routing, compiler error

I have created a custom attribute called RouteAttribute:
[AttributeUsage(AttributeTargets.Property)]
public class RouteAttribute : Attribute
{
public string Url { get; set; }
public bool CheckPhysicalUrlAccess { get; set; }
public RouteValueDictionary Defaults { get; set; }
public RouteValueDictionary Constraints { get; set; }
public RouteValueDictionary DataTokens { get; set; }
}
It is used to add routing via attribute on my url helper class that contains a list of urls in my site, so i have a easy way to manage my site urls.
Having a problem with adding a default though, getting compiler error:
[Route("~/MyPage/Home.aspx", new RouteValueDictionary { { "query", "value" } })]
public string HomePage
{
get { return "Home" }
}
To avoid confusion, the value is set to the routeurl, physical url is from attribute,
reason for this is, I am converting an existing site, and rather than changing links everywhere, once I'm done with page, I go to my class and change the physical url to new page
Giving an error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
The argument values for an attribute constructor are stored in metadata. That puts severe restrictions on what you can specify. Just simple value types, a Type from typeof and a simple one dimensional array of these values. No code is allowed, which is what the compiler complains about, the new operator requires code.
There are no constrictions on what you can do in the body of the attribute constructor, that code runs later when reflection code inspects the attribute. Suggesting something similar to this:
public class RouteAttribute : Attribute
{
public RouteAttribute(string url, string query, string value) {
this.url = url;
this.dunno = new RouteValueDictionary(query, value);
}
// etc..
}
...
[Route("~/MyPage/Home.aspx", "query", "value")]
public string HomePage
{
get { return "Home" }
}
This obviously needs work, I have no idea what the dictionary means. Be careful about it having side-effects or requiring resources, you don't know the runtime state when the attribute gets constructed.
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
The error tells you exactly what the problem is.
As
new RouteValueDictionary { { "query", "value" } }
is not a constant expression, not a typeof expression and not an array creation expression, this is not legal.

Categories