Display Name is problem on Data Annotation ErrorMessage (The {0} field is required.) with Localization - asp.net-core

My aim is very simple. I wanna get "Display Name" in Required Error Message. So I used it '{0}' that string format
Example: sqlLocalization
[Required(ErrorMessage="The {0} field is required.")]
public class AttributeField
{
[Display(Name = "AttributeFeatureCode")]
[Required(ErrorMessage = "The {0} field is required.")]
public string AttributeFeatureCode { get; set; }
...
}
Result: The true field is required
data-val-required="The {0} field is required"
So I researched on web and I see that there is an error on GetErrorMessage
I guesss something is wrong in here... And I have to write override on my project.
public class RequiredAttributeAdapter : AttributeAdapterBase<RequiredAttribute>
{
public RequiredAttributeAdapter(RequiredAttribute attribute, IStringLocalizer stringLocalizer): base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-required", GetErrorMessage(context));
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
var errorMessage = GetErrorMessage(validationContext.ModelMetadata);
return string.Format(errorMessage, validationContext.ModelMetadata.GetDisplayName());
}
}
When I use it similar like this
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
result is The {0} field is required
If I change it with override, that's working
var errorMessage = GetErrorMessage(validationContext.ModelMetadata);
return string.Format(errorMessage, validationContext.ModelMetadata.GetDisplayName());
What do you think ? is there any error on GetErrorMessage ?
Why I can get {0} ? and
why cant I get display name for {0}?
thanks a lot

there is a problem on SqlStringLocalizer.cs and line 40
so I changed it
public LocalizedString this[string name, params object[] arguments]
{
get
{
var str = this[name];
if (arguments.Length > 0)
str = this[string.Format(str, arguments.Select(x => x.ToString()).ToArray())];
return str;
}
}
if string contain string format I mean this {} so it will work for string format. I think my logic will work

Related

Passing data from custom validation attribute to page/view

Let's say I have a custom validation attribute:
public class CustomAttribute : ValidationAttribute
{
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var customErrorString = "You did something wrong!"; //How to pass this to the localized string defined in the resource file?
if(SomethingIsInvalid())
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
return ValidationResult.Success;
}
}
public class CustomAttributeAdapter : AttributeAdapterBase<CustomAttribute>
{
public CustomAttributeAdapter(CustomAttribute attribute, IStringLocalizer stringLocalizer)
: base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
How can I pass, let's say, a string from the data annotation to the validation tag? For example:
[Custom(ErrorMessage = "CustomValidationMessage")]
public string ValidateThis { get; set; }
CustomValidationMessage is defined in a resource file and results to "This is invalid:"
Now my question is, how can I pass customErrorString from the validation attribute to the localized string so it shows on the validation tag like this:
<span id="validation-span" asp-validation-for="#Model.ValidateThis" class="text-danger">This is invalid: You did something wrong!</span>
I hope my question is clear. If not, feel free to ask for more details.
EDIT: I got it to work:
public class CustomAttribute : ValidationAttribute
{
//Create property to hold our custom error string
public string CustomErrorString { get; set; }
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//Set the custom error string property.
CustomErrorString = "You did something wrong!";
if(SomethingIsInvalid())
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
return ValidationResult.Success;
}
}
public class CustomAttributeAdapter : AttributeAdapterBase<CustomAttribute>
{
//Declare class variable to hold the attribute's custom error string.
private string _customErrorString = string.empty;
public CustomAttributeAdapter(CustomAttribute attribute, IStringLocalizer stringLocalizer)
: base(attribute, stringLocalizer)
{
//Set the adapter's custom error string according to the attribute's custom error string
if(!string.IsNullOrEmpty(attribute.CustomErrorString))
_customErrorString = attribute.CustomErrorString;
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
//Pass the custom error string instead of member name
return GetErrorMessage(validationContext.ModelMetadata, _customErrorString);
}
}
After you do all of this, you can then set your resource string like this:
[Custom(ErrorMessage = "CustomValidationMessage")]
public string ValidateThis { get; set; }
Where CustomValidationMessage results in "This is invalid: {0}"
Normally, {0} would result in the localized member name of the property. But because we passed the custom error string in the adapter, it will be set to the custom error message.
It might be a little dirty, but it gets the job done.
You can't pass it back. Anything set on the attribute is static, because attributes are instantiated in place, i.e. there's no opportunity to modify anything there later. Typically, the error message would be passed as a format string ("This is invalid: {0}"), and then the validation code would use that along with something like string.Format to fill in the member name.
Localization is not an issue the attribute needs to worry about. You simply need to add the data annotations localizer:
services.AddControllers()
.AddDataAnnotationsLocalization();

XML Serialization - Required attribute

I have a class that should represent a controller's action parameter and I'd like its properties to be "required" (meaning, you get a status code 400 or something in case it's passed as null). I managed to get it done using System.ComponentModel.DataAnnotations, but the ErrorMessage that I pass to the constructor of the Required attribute is never shown.
[XmlRoot(ElementName = "root")]
public class Request
{
[XmlElement(ElementName = "prop")]
[Required(ErrorMessage = "The property is required.")]
public string Property { get; set; }
[XmlElement(ElementName = "another")]
[Required(ErrorMessage = "The property is required.")]
public string Another { get; set; }
}
Action:
[HttpPost]
public IActionResult Post([FromBody] Request value)
{
return Ok(value); //ignore this, it's just for testing purposes...
}
However, if I don't pass the Property value, I get a 400 that doesn't contain the ErrorMessage I passed earlier. Am I missing something here?
<ValidationProblemDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>One or more validation errors occurred.</Title>
<Status>400</Status>
</ValidationProblemDetails>
My Startup has Xml formatters added to it:
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.InputFormatters.Insert(0, new XmlSerializerInputFormatter(options));
options.OutputFormatters.Insert(0, new XmlSerializerOutputFormatter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
The body of the request looks like this, and it doesn't have "Property":
<root>
<another>Test</another>
<!-- Property "Property" is missing here -->
</root>
Kudos to Code Rethinked for the huge help - Customizing automatic HTTP 400 error response in ASP.NET Core Web APIs.
An approach that I managed to figure out eventually includes the use of services.Configure in my Startup.ConfigureServices method.
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
return new OkObjectResult(new CustomResponse(someStatusCode, context))
{
ContentTypes = { "application/xml" }
};
};
});
So, I made a class named CustomResponse that holds the status code I want to retrieve and all the validation errors (including the ones where my Required property was not passed to the API).
[XmlRoot(ElementName = "rcemsTrxSubReqAck")]
public class CustomResponse
{
[XmlElement(ElementName = "Status")]
public string Status { get; set; }
[XmlArray(ElementName = "Errors"), XmlArrayItem(ElementName = "Error")]
public string[] Errors { get; set; }
public CustomResponse(int status, ActionContext context)
{
Status = status;
Errors = ConstructErrorMessages(context);
}
private string[] ConstructErrorMessages(ActionContext context)
{
if (context == null)
{
return null;
}
string[] arr = new string[context.ModelState.ErrorCount];
int i = 0;
foreach (var keyModelStatePair in context.ModelState)
{
var key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
if (errors != null && errors.Count > 0)
{
if (errors.Count == 1)
{
var errorMessage = GetErrorMessage(errors[0]);
arr[i] = $"{key}: {errorMessage}";
}
else
{
var errorMessages = new string[errors.Count];
for (var j = 0; j < errors.Count; j++)
{
errorMessages[j] = GetErrorMessage(errors[j]);
}
arr[i] = $"{key}: {errorMessages.ToString()}";
}
i++;
}
}
return arr;
}
private string GetErrorMessage(ModelError error)
{
return string.IsNullOrEmpty(error.ErrorMessage) ? "The input was not valid." : error.ErrorMessage;
}
}

Use IMarkupExtension together with StringFormat

I'm using the TranslateExtension from Xamarin. Is it possible to add a StringFormat to the call?
Currently, I have
<Label Text="{i18n:Translate User}" />
but I would need something like this
<Label Text="{i18n:Translate User, StringFormat='{0}:'}" />
If I do the latter, I get
Xamarin.Forms.Xaml.XamlParseException: Cannot assign property "StringFormat": Property does not exists, or is not assignable, or mismatching type between value and property
I know I could add another translation with a colon, but it would be nice to have a different option.
A bit late to the party, but doing it with the standard extension and just XAML, go like this:
<Label Text="{Binding YourDynamicValue, StringFormat={i18n:Translate KeyInResources}}"/>
Your translation should look something like: Static text {0}. Where {0} is replaced by the value you bind to.
The problem is that the Translate extension just gets your string out of the resources, and doesn't have a StringFormat property etc. But you can assign the retrieved resource value to the StringFormat of the Binding.
You can add a parameter property to TranslateExtension.
My TranslateExtension looks like this. You can take the Parameter parts and add it to the one from the Xamarin sample.
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
public string Text { get; set; }
public string Parameter { get; set; }
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
try
{
if (Text == null)
return null;
var culture = new CultureInfo(CultureHelper.CurrentIsoLanguage);
var result = LocalizationResources.ResourceManager.GetString(Text, culture);
if (string.IsNullOrWhiteSpace(Parameter))
{
return string.IsNullOrWhiteSpace(result) ? "__TRANSLATE__" : result;
}
return string.IsNullOrWhiteSpace(result) ? "__TRANSLATE__" : string.Format(result, Parameter);
}
catch (Exception ex)
{
TinyInsights.TrackErrorAsync(ex);
return "__TRANSLATE__";
}
}
}
Here I have updated the Xamarin sample:
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
readonly CultureInfo ci = null;
const string ResourceId = "UsingResxLocalization.Resx.AppResources";
static readonly Lazy<ResourceManager> ResMgr = new Lazy<ResourceManager>(() => new ResourceManager(ResourceId, IntrospectionExtensions.GetTypeInfo(typeof(TranslateExtension)).Assembly));
public string Text { get; set; }
public string StringFormat {get;set;}
public TranslateExtension()
{
if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Android)
{
ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
}
}
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
return string.Empty;
var translation = ResMgr.Value.GetString(Text, ci);
if (translation == null)
{
#if DEBUG
throw new ArgumentException(
string.Format("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, ci.Name),
"Text");
#else
translation = Text; // HACK: returns the key, which GETS DISPLAYED TO THE USER
#endif
}
if(!string.IsNullOrWhitespace(StringFormat)
return string.Format(StringFormat, translation);
return translation;
}
}

MVC WebAPI Data Annotation Error Message Empty String

I have implemented an OWIN self-hosted webapi and am trying to use data annotations and an ActionFilterAttribute to return formatted errors to the user. I have set custom error messages on the data annotation but when I try to retrieve the message from the ModelState it is always an empty string (shown in image below).
Model:
public class JobPointer
{
[Required(ErrorMessage = "JobId Required")]
public Guid JobId { get; set; }
}
Filter:
public class ModelValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid) return;
string errors = actionContext.ModelState.SelectMany(state => state.Value.Errors).Aggregate("", (current, error) => current + (error.ErrorMessage + ". "));
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, errors);
}
}
Endpoint:
[HttpPost]
public HttpResponseMessage DescribeJob(JobPointer jobId)
{
Job job = _jobhelper.GetJob(jobId.JobId);
return Request.CreateResponse(HttpStatusCode.OK, job);
}
Request Body:
{
}
Response:
Status Code: 400
{
"Message": ". "
}
If I change error.Message in ModelValidationFilter to error.Exception.Message I get back the default validation error:
Status Code: 400
{
"Message": "Required property 'JobId' not found in JSON. Path '', line 3, position 2.. "
}
I know this is an old question, but I just had this problem and found the solution myself.
As you no doubt discovered, as Guid is a non-nullable type [Required] produces an unfriendly error message (I assume because the JSON parser picks it up before actually getting the model validation).
You can get around this by making the Guid nullable...
public class JobPointer
{
[Required(ErrorMessage = "JobId Required")]
public Guid? JobId { get; set; }
}
... however, this is not a viable option in all cases (as in my case), so I ended up writing my own validation attribute that would check the property against it's Empty declaration...
public class IsNotEmptyAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null) return false;
var valueType = value.GetType();
var emptyField = valueType.GetField("Empty");
if (emptyField == null) return true;
var emptyValue = emptyField.GetValue(null);
return !value.Equals(emptyValue);
}
}
You could then implement like...
public class JobPointer
{
[IsNotEmpty(ErrorMessage = "JobId Required")]
public Guid JobId { get; set; }
}

Value type field required in Razor View

I have an enum type field called Title.
[Serializable]
public enum Title
{
NotSet,
Miss = 4,
Mr = 1,
Mrs = 3,
Ms = 2
}
I want to bind a property of type Title to the Razor View but I don't want it to be a required field. However, on tabbing out or OnBlur, it is showing as required, although I have not specified this as required.
Is there any way I can get around this?
create
namespace YourApplicationName.Helper
{
public class ModelValueListProvider : IEnumerable<SelectListItem>
{
List<KeyValuePair<string, string>> innerList = new List<KeyValuePair<string, string>>();
public static readonly ModelValueListProvider TitleList = new TitleListProvider();
protected void Add(string value, string text)
{
string innerValue = null, innerText = null;
if (value != null)
innerValue = value.ToString();
if (text != null)
innerText = text.ToString();
if (innerList.Exists(kvp => kvp.Key == innerValue))
throw new ArgumentException("Value must be unique", "value");
innerList.Add(new KeyValuePair<string, string>(innerValue, innerText));
}
public IEnumerator<SelectListItem> GetEnumerator()
{
return new ModelValueListProviderEnumerator(innerList.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private struct ModelValueListProviderEnumerator : IEnumerator<SelectListItem>
{
private IEnumerator<KeyValuePair<string, string>> innerEnumerator;
public ModelValueListProviderEnumerator(IEnumerator<KeyValuePair<string, string>> enumerator)
{
innerEnumerator = enumerator;
}
public SelectListItem Current
{
get
{
var current = innerEnumerator.Current;
return new SelectListItem { Value = current.Key, Text = current.Value };
}
}
public void Dispose()
{
try
{
innerEnumerator.Dispose();
}
catch (Exception)
{
}
}
object System.Collections.IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
return innerEnumerator.MoveNext();
}
public void Reset()
{
innerEnumerator.Reset();
}
}
private class TitleListProvider : ModelValueListProvider
{
public TitleListProvider (string defaultText = null)
{
if (!string.IsNullOrEmpty(defaultText))
Add(string.Empty, defaultText);
Add(Title.NotSet, "NotSet");
Add(Title.Miss , "Miss");
Add(Title.Mr , "Mr");
Add(Title.Mrs , "Mrs");
Add(Title.MS, "MS");
}
public void Add(Title value, string text)
{
Add(value.ToString("d"), text);
}
}
}
}
in your model
public Title? Titleformation { get; set; }
public string[] SelectedTitle { get; set; }
in your view, also add the name space to your view
#using YourApplicationName.Helper;
#Html.ListBoxFor(m => m.SelectedTitle , new SelectList(ModelValueListProvider.TitleList, "Value", "Text"))
hope this help you
Enums require values, and cannot be null (aka not set) despite what someone commented above. What I do for salutations is have a "none" member of the enum, and whenever I print this out, I just check in the code to see if the value of the enum is > 0 (aka, the none option) and don't print it.
public enum Salutation { none,
[Description("Mr.")] Mr,
[Description("Mrs.")] Mrs,
[Description("Ms.")]Ms,
[Description("Miss")] Miss }
Use a class rather than enum ie:
public class Title
{
NotSet;
Miss = 4;
Mr = 1;
Mrs = 3;
Ms = 2;
}