WebApi validation ignores custom Error Message in Required Data Annotation attribute.
Given the code:
View Model:
```
public class InputVM : IValidatableObject
{
#region Properties
/// <summary>
/// Duration period
/// </summary>
[Required(ErrorMessage = "Custom duration required message")]
public int? Duration { get; set; }
(...)
}
```
Controller:
```
public class SampleApiController : ApiController
{
// GET /api/dummyoutput/
public HttpResponseMessage GetDummyOutput([FromUri]InputVM vm)
{
if(ModelState.IsValid == false)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
}
var service = new DummyDataService();
var vm = service.GetDummyOutput(vm);
return Request.CreateResponse<CalculatorVM>(HttpStatusCode.OK, vm);
}
```
WebApi Config:
```
config.Services.RemoveAll(typeof(System.Web.Http.Validation.ModelValidatorProvider),
v => v is InvalidModelValidatorProvider);
config.Formatters.Remove(config.Formatters.XmlFormatter);
var json = config.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = false;
json.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
json.SerializerSettings.Formatting = Formatting.Indented;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.LocalOnly;
config.Routes.MapHttpRoute(
name: "DummyoutputApi",
routeTemplate: "api/dummyoutput",
defaults: new { controller = "SampleApi", action = "GetDummyOutput" }
);
```
When i miss the Duration field in query string, it will give me back JSON / XML with:
"The Duration property is required." instead of my custom error.
It's really hard to live with it with a localization requirement. :)
Hint: it only concerns Required validator - as a workaround i have written RequiredLocalized validator which does the same job - and it works:
Workaround validator:
```
/// <summary>
/// Localized Required validation for Web Api.
/// By default, the returned Error Message is hardcoded (bug).
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class RequiredLocalizedValidator : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return (value == null) == false;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
ValidationType = "requiredlocalized"
};
}
}
```
Usage on view model:
```
public class InputVM : IValidatableObject
{
#region Properties
/// <summary>
/// Duration period
/// </summary>
//[Required(ErrorMessage = "Custom duration required message")]
[RequiredLocalizedValidator(ErrorMessage = "Custom duration required message")]
public int? Duration { get; set; }
(...)
}
```
Best Regards,
Tom
Comments: We are going to take a look, this seems to be broken only when using modelbinding (from URL). But either way using ErrorMessage= is not supporting Localization. You should use ErrorMessageResourceName instead. (of course after we make it work for URL, or in your custom property).
Given the code:
View Model:
```
public class InputVM : IValidatableObject
{
#region Properties
/// <summary>
/// Duration period
/// </summary>
[Required(ErrorMessage = "Custom duration required message")]
public int? Duration { get; set; }
(...)
}
```
Controller:
```
public class SampleApiController : ApiController
{
// GET /api/dummyoutput/
public HttpResponseMessage GetDummyOutput([FromUri]InputVM vm)
{
if(ModelState.IsValid == false)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
}
var service = new DummyDataService();
var vm = service.GetDummyOutput(vm);
return Request.CreateResponse<CalculatorVM>(HttpStatusCode.OK, vm);
}
```
WebApi Config:
```
config.Services.RemoveAll(typeof(System.Web.Http.Validation.ModelValidatorProvider),
v => v is InvalidModelValidatorProvider);
config.Formatters.Remove(config.Formatters.XmlFormatter);
var json = config.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = false;
json.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
json.SerializerSettings.Formatting = Formatting.Indented;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.LocalOnly;
config.Routes.MapHttpRoute(
name: "DummyoutputApi",
routeTemplate: "api/dummyoutput",
defaults: new { controller = "SampleApi", action = "GetDummyOutput" }
);
```
When i miss the Duration field in query string, it will give me back JSON / XML with:
"The Duration property is required." instead of my custom error.
It's really hard to live with it with a localization requirement. :)
Hint: it only concerns Required validator - as a workaround i have written RequiredLocalized validator which does the same job - and it works:
Workaround validator:
```
/// <summary>
/// Localized Required validation for Web Api.
/// By default, the returned Error Message is hardcoded (bug).
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class RequiredLocalizedValidator : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return (value == null) == false;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
ValidationType = "requiredlocalized"
};
}
}
```
Usage on view model:
```
public class InputVM : IValidatableObject
{
#region Properties
/// <summary>
/// Duration period
/// </summary>
//[Required(ErrorMessage = "Custom duration required message")]
[RequiredLocalizedValidator(ErrorMessage = "Custom duration required message")]
public int? Duration { get; set; }
(...)
}
```
Best Regards,
Tom
Comments: We are going to take a look, this seems to be broken only when using modelbinding (from URL). But either way using ErrorMessage= is not supporting Localization. You should use ErrorMessageResourceName instead. (of course after we make it work for URL, or in your custom property).