Use IMarkupExtension together with StringFormat - xaml

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;
}
}

Related

Deserialize enums using the EnumMember value in AspNet [FromQuery] model binding

I have an endpoint in .NET 6 Microsoft.NET.Sdk.Web project that deserialize query strings into a .NET object by using the standard [FromQuery]
[Route("[controller]")]
public class SamplesController
: ControllerBase
{
[HttpGet]
public IActionResult Get([FromQuery]QueryModel queryModel)
{
if (!queryModel.Status.HasValue)
{
return BadRequest("Problem in deserialization");
}
return Ok(queryModel.Status.Value.GetEnumDisplayName());
}
}
The model contains an enum
public class QueryModel
{
/// <summary>
/// The foo parameter
/// </summary>
/// <example>bar</example>
public string? Foo { get; init; } = null;
/// <summary>
/// The status
/// </summary>
/// <example>on-hold</example>
public Status? Status { get; set; } = null;
}
And the enum has EnumMember attributes which value I want to use to deserialize from.
public enum Status
{
[EnumMember(Value = "open")]
Open,
[EnumMember(Value = "on-hold")]
OnHold
}
By default, .NET 6 does not take into consideration the EnumMember when deserializing.
The goal is to be able to send requests such as
http://localhost:5000/Samples?Foo=bar&Status=on-hold
and have the controller's action deserialize the QueryModel with the proper Status.OnHold value by using its EnumMember
I have tried without luck an extensions library that contains a converter, but the converter is not getting triggered when using [FromQuery]. See https://github.com/Macross-Software/core/issues/30
I have added a project to reproduce problem and as a sandbox to provide a solution**
https://gitlab.com/sunnyatticsoftware/issues/string-to-enum-mvc/-/tree/feature/1-original-problem
NOTE: I would need a solution where the Enum and the does not require any external dependency (just .NET sdk).
A custom Enum converter might be your choice. By leveraging the existing EnumConverter class what we need is to have a customized ConvertFrom method:
public class CustomEnumConverter : EnumConverter
{
public CustomEnumConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type type) : base(type)
{
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string strValue)
{
try
{
foreach (var name in Enum.GetNames(EnumType))
{
var field = EnumType.GetField(name);
if (field != null)
{
var enumMember = (EnumMemberAttribute)(field.GetCustomAttributes(typeof(EnumMemberAttribute), true).Single());
if (strValue.Equals(enumMember.Value, StringComparison.OrdinalIgnoreCase))
{
return Enum.Parse(EnumType, name, true);
}
}
}
}
catch (Exception e)
{
throw new FormatException((string)value, e);
}
}
return base.ConvertFrom(context, culture, value);
}
}
And then decorate the converter to your Model class:
[TypeConverter(typeof(CustomEnumConverter))]
public enum Status
{
[EnumMember(Value = "open")]
Open,
[EnumMember(Value = "on-hold")]
OnHold
}
then we can get the "on-hold" parsed. You might also want to override the ConverTo() for printing the EnumMember value to swagger. It is a bit hacky, but if you want a pure .NET solution this should be one of the minimal viable solutions.
Following the documentation guide Custom Model Binding in ASP.NET Core, you can create your own versions of Microsoft's classes EnumTypeModelBinderProvider, EnumTypeModelBinder (and base class SimpleTypeModelBinder) that replace incoming enum value names that have been renamed via EnumMemberAttribute with the original enum names before binding:
// Begin code for enum model binding
public class EnumMemberEnumTypeModelBinderProvider : IModelBinderProvider
{
public EnumMemberEnumTypeModelBinderProvider(MvcOptions options) { }
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsEnum)
{
var enumType = context.Metadata.UnderlyingOrModelType;
Debug.Assert(enumType.IsEnum);
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
if (EnumExtensions.TryGetEnumMemberOverridesToOriginals(enumType, out var overridesToOriginals))
return new EnumMemberEnumTypeModelBinder(suppressBindingUndefinedValueToEnumType: true, enumType, loggerFactory, overridesToOriginals);
}
return null;
}
}
public class EnumMemberEnumTypeModelBinder : ExtensibleSimpleTypeModelBinder
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/EnumTypeModelBinder.cs#L58
readonly Type enumType;
readonly bool isFlagged;
readonly Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals;
readonly TypeConverter typeConverter;
public EnumMemberEnumTypeModelBinder(bool suppressBindingUndefinedValueToEnumType, Type modelType, ILoggerFactory loggerFactory, Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals) : base(modelType, loggerFactory)
{
this.enumType = Nullable.GetUnderlyingType(modelType) ?? modelType;
if (!this.enumType.IsEnum)
throw new ArgumentException();
this.isFlagged = Attribute.IsDefined(enumType, typeof(FlagsAttribute));
this.overridesToOriginals = overridesToOriginals ?? throw new ArgumentNullException(nameof(overridesToOriginals));
this.typeConverter = TypeDescriptor.GetConverter(this.enumType);
}
protected override string? GetValueFromBindingContext(ValueProviderResult valueProviderResult) =>
EnumExtensions.ReplaceRenamedEnumValuesToOriginals(base.GetValueFromBindingContext(valueProviderResult), isFlagged, overridesToOriginals);
protected override void CheckModel(ModelBindingContext bindingContext, ValueProviderResult valueProviderResult, object? model)
{
if (model == null)
{
base.CheckModel(bindingContext, valueProviderResult, model);
}
else if (IsDefinedInEnum(model, bindingContext))
{
bindingContext.Result = ModelBindingResult.Success(model);
}
else
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(
valueProviderResult.ToString()));
}
}
private bool IsDefinedInEnum(object model, ModelBindingContext bindingContext)
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/EnumTypeModelBinder.cs#L58
var modelType = bindingContext.ModelMetadata.UnderlyingOrModelType;
// Check if the converted value is indeed defined on the enum as EnumTypeConverter
// converts value to the backing type (ex: integer) and does not check if the value is defined on the enum.
if (bindingContext.ModelMetadata.IsFlagsEnum)
{
var underlying = Convert.ChangeType(
model,
Enum.GetUnderlyingType(modelType),
CultureInfo.InvariantCulture).ToString();
var converted = model.ToString();
return !string.Equals(underlying, converted, StringComparison.OrdinalIgnoreCase);
}
return Enum.IsDefined(modelType, model);
}
}
public class ExtensibleSimpleTypeModelBinder : IModelBinder
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/SimpleTypeModelBinder.cs
private readonly TypeConverter _typeConverter;
private readonly ILogger _logger;
public ExtensibleSimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory) : this(type, loggerFactory, null) { }
public ExtensibleSimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory, TypeConverter? typeConverter)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
_typeConverter = typeConverter ?? TypeDescriptor.GetConverter(type);
_logger = loggerFactory.CreateLogger<ExtensibleSimpleTypeModelBinder>();
}
protected virtual string? GetValueFromBindingContext(ValueProviderResult valueProviderResult) => valueProviderResult.FirstValue;
/// <inheritdoc />
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
//_logger.AttemptingToBindModel(bindingContext);
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
//_logger.FoundNoValueInRequest(bindingContext);
// no entry
//_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
try
{
var value = GetValueFromBindingContext(valueProviderResult);
object? model;
if (bindingContext.ModelType == typeof(string))
{
// Already have a string. No further conversion required but handle ConvertEmptyStringToNull.
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value))
model = null;
else
model = value;
}
else if (string.IsNullOrWhiteSpace(value))
{
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
model = null;
}
else
{
model = _typeConverter.ConvertFrom(context: null,culture: valueProviderResult.Culture, value: value);
}
CheckModel(bindingContext, valueProviderResult, model);
//_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
catch (Exception exception)
{
var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null)
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we capture the inner exception.
exception = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName,exception, bindingContext.ModelMetadata);
// Were able to find a converter for the type but conversion failed.
return Task.CompletedTask;
}
}
/// <inheritdoc/>
protected virtual void CheckModel(
ModelBindingContext bindingContext,
ValueProviderResult valueProviderResult,
object? model)
{
// When converting newModel a null value may indicate a failed conversion for an otherwise required
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
// current bindingContext. If not, an error is logged.
if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
valueProviderResult.ToString()));
}
else
{
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
// End code for enum model binding
/********************************************************/
// Begin general enum parsing code
public class CharMemoryComparer : IEqualityComparer<ReadOnlyMemory<char>>
{
public static CharMemoryComparer OrdinalIgnoreCase { get; } = new CharMemoryComparer(StringComparison.OrdinalIgnoreCase);
public static CharMemoryComparer Ordinal { get; } = new CharMemoryComparer(StringComparison.Ordinal);
readonly StringComparison comparison;
CharMemoryComparer(StringComparison comparison) => this.comparison = comparison;
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y) => MemoryExtensions.Equals(x.Span, y.Span, comparison);
public int GetHashCode(ReadOnlyMemory<char> obj) => String.GetHashCode(obj.Span, comparison);
}
public static partial class EnumExtensions
{
public const char FlagSeparatorChar = ',';
public const string FlagSeparatorString = ", ";
public static bool TryGetEnumMemberOverridesToOriginals(Type enumType, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out Dictionary<ReadOnlyMemory<char>, string>? overridesToOriginals)
{
if (enumType == null)
throw new ArgumentNullException(nameof(enumType));
if (!enumType.IsEnum)
throw new ArgumentException(nameof(enumType));
overridesToOriginals = null;
foreach (var name in Enum.GetNames(enumType))
{
if (TryGetEnumAttribute<EnumMemberAttribute>(enumType, name, out var attr) && !string.IsNullOrWhiteSpace(attr.Value))
{
overridesToOriginals = overridesToOriginals ?? new(CharMemoryComparer.OrdinalIgnoreCase);
overridesToOriginals.Add(attr.Value.AsMemory(), name);
}
}
return overridesToOriginals != null;
}
public static bool TryGetEnumAttribute<TAttribute>(Type type, string name, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out TAttribute? attribute) where TAttribute : System.Attribute
{
var member = type.GetMember(name).SingleOrDefault();
attribute = member?.GetCustomAttribute<TAttribute>(false);
return attribute != null;
}
public static string? ReplaceRenamedEnumValuesToOriginals(string? value, bool isFlagged, Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals)
{
if (string.IsNullOrWhiteSpace(value))
return value;
var trimmed = value.AsMemory().Trim();
if (overridesToOriginals.TryGetValue(trimmed, out var #override))
value = #override;
else if (isFlagged && trimmed.Length > 0)
{
var sb = new StringBuilder();
bool replaced = false;
foreach (var n in trimmed.Split(EnumExtensions.FlagSeparatorChar, StringSplitOptions.TrimEntries))
{
ReadOnlySpan<char> toAppend;
if (overridesToOriginals.TryGetValue(n, out var #thisOverride))
{
toAppend = thisOverride.AsSpan();
replaced = true;
}
else
toAppend = n.Span;
sb.Append(sb.Length == 0 ? null : EnumExtensions.FlagSeparatorString).Append(toAppend);
}
if (replaced)
value = sb.ToString();
}
return value;
}
}
public static class StringExtensions
{
public static IEnumerable<ReadOnlyMemory<char>> Split(this ReadOnlyMemory<char> chars, char separator, StringSplitOptions options = StringSplitOptions.None)
{
int index;
while ((index = chars.Span.IndexOf(separator)) >= 0)
{
var slice = chars.Slice(0, index);
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
slice = slice.Trim();
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || slice.Length > 0)
yield return slice;
chars = chars.Slice(index + 1);
}
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
chars = chars.Trim();
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || chars.Length > 0)
yield return chars;
}
}
Then add the binder in ConfigureServices() like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new EnumMemberEnumTypeModelBinderProvider(options));
});
}
Notes:
EnumTypeModelBinder and base class SimpleTypeModelBinder provide no useful extension points to customize the parsing of the incoming value string, thus it was necessary to copy some of their logic.
Precisely emulating the logic of SimpleTypeModelBinder is somewhat difficult because it supports both numeric and textual enum values -- including mixtures of both for flags enums. The binder above retains that capability, but at a cost of also allowing original enum names to be bound successfully. Thus the values on-hold and onhold will be bound to Status.OnHold.
Conversely, if you do not want to support binding of numeric values for enums, you could adapt the code of JsonEnumMemberStringEnumConverter from this answer to System.Text.Json: How do I specify a custom name for an enum value?. Demo fiddle here. This approach also avoids binding to the original, unrenamed enum names.
Matching of override names with original enum names is case-insensitive, so override names that differ only in case are not supported.

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();

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

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

How to send object which contains IEnumerable via Refit on NetCore?

I have to send a request object via Refit which contains 2 IEnumerable and one string, but for some reason I can't send the object forward.
I've tried to use all the paramets from the interface. Ex: [Query(CollectionFormat.Csv)] or Multi / Pipes but no success.
I've also tried to create my own CustomUrlParameterFormatter but unfortunately here I'm stuck, because I don't see a good way to retrieve the name of the property from the object request that I'm sending.
The code for CustomUrlParameterFormatter
public class CustomUrlParameterFormatter : IUrlParameterFormatter
{
public string Format(object value, ParameterInfo parameterInfo)
{
if(value is IEnumerable enumerable)
{
var result = ToQueryString(enumerable, parameterInfo.Name);
return result;
}
return string.Empty;
}
public static string ToQueryString(IEnumerable query, string parameterName)
{
var values = query.Cast<object>().Select(ToString).ToArray();
var separator = parameterName + "=";
return values.Any() ? separator + string.Join("&" + separator, values) : "";
}
public static string ToString(object value)
{
var json = JsonConvert.SerializeObject(value).Replace("\\\"", "\"").Trim('"');
return Uri.EscapeUriString(json);
}
}
The Call from the IService that I'm using
[Get("/TestMethod")]
Task<HttpResponseMessage> TestMethod([Query]TestRequestDTO requestDTO, [Header("X-Correlation-ID")] string correlationId);
The Request object
public class TestRequestDTO
{
public IEnumerable<long> EnumOne { get; set; }
public IEnumerable<long> EnumTwo { get; set; }
public string MethodString { get; set; }
}
Also the RefitClient configuration
var refitSettings = new RefitSettings();
refitSettings.UrlParameterFormatter = new CustomUrlParameterFormatter();
services.AddRefitClient<IService>(refitSettings)
.ConfigureHttpClient(c => c.BaseAddress = new Uri(settings.Services.IService));
What I'm trying to achieve is something like
TestMethod?EnumOne =123&EnumOne =321&EnumTwo=123&EnumTwo=321&methodString=asdsaa
and instead I'm receiving other behavior
without CustomUrlParameterFormatter()
TestMethod?EnumOne=System.Collections.Generic.List`1%5BSystem.Int64%5D&EnumTwo=System.Collections.Generic.List`1%5BSystem.Int64%5D&MethodString=sdf

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;
}