This might be more of a question related to how .NET Framework works, than looking for an actual solution. Reason is I would like to know if this is something I should pursue in fixing, or try something else entirely. I did some searching, but couldn't find the right answer in my opinion.
I am working on an ASP.NET MVC5 application that utilizes a translation provider with an XML file as it source. In some scenarios I use a DisplayNameAttribute extension to decorate model properties to provide translations. It is made by referencing the solution here: https://stackoverflow.com/a/9723620/1501132
This is my implementation:
[AttributeUsage(AttributeTargets.Property)]
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string key, string page = null) : base(FormatMessage(key, page))
{
}
private static string FormatMessage(string key, string page = null)
{
if (!string.IsNullOrWhiteSpace(key) && string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key);
}
else if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key, page);
}
else
{
return string.Empty;
}
}
}
The "TextGetter" is a separate library that handles fetching strings from the XML data source.
The attribute extension is used like so:
[LocalizedDisplayName("Timestamp", "/report")]
public DateTimeOffset Timestamp { get; set; }
The website also has a feature where a super user can edit the translation XML file, in case some translations are wrong or missing. Usually an edit in the XML file is visible immediately, except for properties with this particular attribute. I know that normally when using DisplayName attribute with a hardcoded value can not be changed because it is compiled, though I was under the assumption that since this uses an XML file as reference, I believed that if the XML was changed it would be reflected immediately in this case as well. But that seems not to happen.
Being able to change translations on the fly is an important feature; should I seek some other solution? I can set the property names with translations in the views, which is working, but that will entail a LOT of refactoring, and keeping it as annotations is just more neat.
I don't really know where to take it from here.
Found a solution in the meantime, and just putting it out there if anyone stumbles across it. So this is what you should do, if you want to make an attribute that derives from DisplayName used for localization, and on top of that have a localization source that can change and update during runtime:
[AttributeUsage(AttributeTargets.Property)]
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string _key;
private readonly string _page;
public LocalizedDisplayNameAttribute(string key, string page = null) : base(key)
{
this._key = key;
this._page = page;
}
public override string DisplayName => this.FormatMessage(this._key, this._page);
private string FormatMessage(string key, string page = null)
{
if (!string.IsNullOrWhiteSpace(key) && string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key);
}
else if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key, page);
}
else
{
return string.Empty;
}
}
}
The contents of "FormatMessage" can be whatever you want really, just insert there whatever code you need to fetch your translated string.
Related
I have a class like below (simplified) and I like to replace the default validation error with my own messages. I know I can set the message directly when I am adding the attribute, but it is too much pain and it is error prune to add the same message 10,000 time in the same code.
Public class CreateUser
{
[Required(ErrorMessage = "A specific message")]//Don't like to do this
[MinLength(10)]// Like to write specific text
public string Name { get; set; }
}
I have looked into their code and apparently they are using ResourceManager to set the actual message from the key. The documentation just describes the way to handle windows applications. How do I add the resources in Asp.net core application and overwrite the default values?
Update
From the code it seems like a small change if one knows how to set the ResourceManager, but from the comments it seems like a hard job. I don't understand why. For MinLenghtAttribute we have
public class MinLengthAttribute : ValidationAttribute
{
public MinLengthAttribute(int length)
: base(SR.MinLengthAttribute_ValidationError)
{
Length = length;
}
// The rest of the code
}
The SR is as below :
namespace System
{
internal static partial class SR
{
internal static string MinLengthAttribute_ValidationError =>
GetResourceString("MinLengthAttribute_ValidationError");
// The rest of the code
}
}
One should be able to chance the value in GetResourceString and achieve my goal. Right?
I am trying to handle multiple languages in an ASP.NET Webforms (.NET 4.5, C#) application of mine.
Basically, some of my entities in my SQL Server 2012 database have properties like Name or Description which exist in three languages - German, French, Italian.
Those are stored as columns Name_De (German), Name_Fr (French), and Name_It (Italian).
When I create my .NET objects from the database, of course, I also get those three properties in my entity class. But for displaying on screen, in a grid for instance, it would be nice if I could somehow "magically" always show the "right" language. This should be based on the Thread.Current.CurrentUICulture.TwoLetterISOLanguageName (which returns de, fr or it, depending on the browser's language preferences).
So I was hoping to somehow be able to create e.g. a .NET attribute that would allow me to do something like this:
Base "Module" entity - generated from existing SQL Server database:
public partial class Module
{
public string ModuleCode { get; set; }
public string Name_De { get; set; }
public string Name_Fr { get; set; }
public string Name_It { get; set; }
... // other properties
}
Partial extension in a separate file
public partial class Module
{
[Multilingual]
public string Name { get; set; }
}
The base idea is: I can access the Module.Name property, and depending on the current setting of CurrentUICulture, either the value of Name_De, Name_Fr or Name_It would be fetched, when I access the getter of the Name property.
Can something like this be done in C# ? I have looked at a lot of custom attribute implementations, but nothing ever seemed to be doing something like this...
Assuming you are using two separate entities (one generated by your SQL entities and one "business entity" which only contains a Name property), are you open to using something like AutoMapper ?
If you are, then you could tweak your resolve function to map the entity depending on the current thread culture.
switch(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
{
case "DE":
return dto.Name_De;
case "FR":
return dto.Name_Fr;
// ...
default :
return String.Empty;
}
which would work for your scenario.
If this is a solution that could work for you, I think this question is very close to what you're looking for : Automapper Mapping for Localization Resolver in a Multi-Language Website
If you do go down the custom attribute route, you will have to deal with Reflection stuff and string parsing I'm afraid. AFAIK, there is no built in way to do this with the localization functions provided by .NET. AutoMapper will hide that from you.
The problem with custom attributes in this case is that you are still trying to access the Name property. You are trying to "shadow" the default behaviour of the property by making it access other properties. If I understand correctly you want the Multilingual custom attribute to turn your property into :
public String Name
{
get
{ switch(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
{
case "DE":
return dto.Name_De;
case "FR":
return dto.Name_Fr;
// ...
default :
return String.Empty;
}
}
}
If that's correct, then you won't be able to do that easily with attributes, simply because the attribute will never be aware of the existence of the Name_De property.
Other option that still isn't quite what you're looking for :
void Main()
{
Module mod = new Module();
mod.Name_De = "name";
mod.Name_Fr = "nom";
// This is the unfortunate nasty bit. I address the property using its name
// in a String which is just bad. I don't think there is a way
// you will be able to address the ".Name" property directly and have
// it return the localized value through your custom attribute though
String localizedValue = mod.GetLocalizedProperty("Name");
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class MultilingualAttribute : Attribute
{
public MultilingualAttribute()
{
}
}
public static class ModuleExtensions
{
public static String GetLocalizedProperty(this Module module, String propName)
{
var type = typeof(Module);
var propInfo = type.GetProperty(propName);
// Make sure the prop really is "Multilingual"
if(Attribute.IsDefined(propInfo, typeof(MultilingualAttribute)))
{
String localizedPropName = propInfo.Name;
switch(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
{
case "DE":
localizedPropName += "_De";
return type.GetProperty(localizedPropName).GetValue(module, null).ToString();
case "FR":
localizedPropName += "_Fr";
return type.GetProperty(localizedPropName).GetValue(module, null).ToString();
}
}
return String.Empty;
}
}
public class Module
{
public String Name_De {get; set;}
public String Name_Fr {get; set;}
[Multilingual]
public String Name {get; set;}
public Module()
{
}
}
I don't know of a more powerful way to use custom attributes for what you're looking for unfortunately. Quite frankly, I don't think this is a good solution, only posted because I was trying to see what I could do with custom attributes. There is no real point in using this code over a more "normal" property which would do the same thing in a clearer way (without attributes). As you say in your original question, your goal is to intercept the call to the Name property and this doesn't achieve it.
I have a ViewModel that I can decorate with the [Required] attribute (see below). I've come to the point where I need to let the client control which fields are required or not. They can configure this trough XML and all this info is stored in the Model when it's first created. Now I have fields that are not decorated with [Required] but still need to get validated (as per "user settings") before submitting (for example the Phone field).
public class MyBusinessObjectViewModel
{
[Required]
public string Email { get; set; } //compulsory
public string Phone { get; set; } //not (yet) compulsory, but might become
}
If the user will not enter the Phone number, the data will still get posted. Wanting not to mess with custom validators, I just add the "data-val" and "data-val-required" attributes to the Html, like this:
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("data-val", "true");
dict.Add("data-val-required", "This field is required.");
#Html.TextBoxFor(x => x, dict);
This forces the client side validation for all the properties that are dynamically set as required. Is this good practice? What kind of side effects can I expect?
You should look into extending the meta model framework with your own metadata provider to do the actual binding between your site's configuration and the model metadata. You can actually set the required property flag to true on the property model metadata during the metadata creation process. I can't remember for sure whether this causes the built in editor templates to generate the attribute, but I think it does. Worst case scenario you can actually create and attach a new RequiredAttribute to the property, which is a tad bit kluggy, but works pretty well in certain scenarios.
You could also do this with IMetadataAware attributes, especially if Required is the only metadata aspect your users can customize, but the implementation really depends on what you're trying to do.
One major advantage of using a custom ModelMetadataProvider in your specific case is that you can use dependency injection (via ModelMetadataProviders) to get your customer settings persistence mechanism into scope, whereas with the data attribute you only get to write an isolated method that runs immediately after the metadata model is created.
Here is a sample implementation of a custom model metadata provider, you'd just have to change the client settings to whatever you wanted to use.
UPDATED but not tested at all
public class ClientSettingsProvider
{
public ClientSettingsProvider(/* db info */) { /* init */ }
public bool IsPropertyRequired(string propertyIdentifier)
{
// check the property identifier here and return status
}
}
public ClientRequiredAttribute : Attribute
{
string _identifier;
public string Identifier { get { return _identifer; } }
public ClientRequiredAttribute(string identifier)
{ _identifier = identifier; }
}
public class RequiredModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
ClientSettings _clientSettings;
public RequiredModelMetadataProvider(ClientSettings clientSettings)
{
_clientSettings = clientSettings;
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// alternatively here is where you could 'inject' a RequiredAttribute into the attributes list
var clientRequiredAttribute = attributes.OfType<ClientRequiredAttribute>().SingleOrDefault();
if(clientRequiredAttribute != null && _clientSettings.IsPropertyRequired(clientRequiredAttribute.Identifier))
{
// By injecting the Required attribute here it will seem to
// the base provider we are extending as if the property was
// marked with [Required]. Your data validation attributes should
// be added, provide you are using the default editor templates in
// you view.
attributes = attributes.Union(new [] { new RequiredAttribute() });
}
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// REMOVED, this is another way but I'm not 100% sure it will add your attributes
// Use whatever attributes you need here as parameters...
//if (_clientSettings.IsPropertyRequired(containerType, propertyName))
//{
// metadata.IsRequired = true;
//}
return metadata;
}
}
USAGE
public class MyModel
{
[ClientRequired("CompanyName")]
public string Company { get; set; }
}
public class MyOtherModel
{
[ClientRequired("CompanyName")]
public string Name { get; set; }
public string Address { get; set; }
}
Both of these models would validate the string "CompanyName" against your client settings provider.
Not wanting to mess with custom validators, so you messed in the View obfuscating the logic of your validation by removing it from the place where it is expected to be found.
Really, don't be afraid of creating a custom attribute validator. What you are doing right now is getting a technical debt.
Many forms in my project have a part number input. Currently in the controller I often times test the part number to see if the part exists or user has access or if the part is obsolete, then based on the condition I reload the view passing a message through a string parameter then viewbag for display to show why the form submit failed. I'm trying to clean this up and instead use model property validation.
I have a few working well however one of the validations I want to test is if the part is obsolete and has a suggested alternate part number to use. Based on the property value (part number) I have a service layer method that will return a bool of if the part is obsolete and another one that will return the suggested use part number. If possible I'd like to trigger the validation on the bool check, then pass that alternate part number into the validation message that gets generated.
Here is the validation code in it's current form:
public class PartAlternateValidation : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
GP10Service gp10svc = new GP10Service();
//string altPart = gp10svc.GetPartAlternate(value.ToString());
return gp10svc.CheckPartAlternate(value.ToString());
}
}
Did quite a bit of searching around but couldn't find anything that specifically discussed this. Thinking maybe I could have an out string parameter with the bool in IsValid, but not sure how to then pass that to the message (or call FormatErrorMessage method from the IsValid method?). Thinking maybe there is a way using ModelState.AddModelError, however I believe the key on these is tied to the property, correct? So I'm fuzzy on how I could detect when the property fails a particular validation and use the property value as a variable in generating the message that way.
Probably something simple, has been a good excuse to do more research and I will continue reading but any suggestions or tips would be welcome.
Thanks.
Please see below tip helps..
Model:
namespace Mvc4Test.Models
{
public class Part
{
[AlternateValidation(Suggetion = "Please Use 123 (This is a Suggestion)")]
public string PartNumber { get; set; }
}
public class AlternateValidation : ValidationAttribute
{
public string Suggetion { get; set; }
protected override ValidationResult IsValid(object value,ValidationContext validationContext)
{
if (value != null)
{
if (value.ToString() == "123")
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(Suggetion);
}
}else
return new ValidationResult("Value is Null");
}
}
}
View:
#Html.EditorFor(model => model.PartNumber)
#Html.ValidationMessageFor(model => model.PartNumber)
For the past couple of weeks I've been working on developing a cross platform app (IOS/Android/WP7) using the MVVMCross framework. Today I ran into a problem I don't really know how to solve, so hopefully you can push me in the right direction.
In the IOS I have the following construction for navigating to another page (the code below is located in a ViewModel):
KeyValuePair<string,string> kvpAct1 = new KeyValuePair<string, string>("short", ".countertest5");
public IMvxCommand BeckhoffActuator1
{
get
{
return new MvxRelayCommand<Type>((type) => this.RequestNavigate<Beckhoff.BeckhoffActuatorViewModel>(kvpAct1));
}
}
When this IMvxCommand is fired (button pressed) the next View is loaded, in this case the BeckhoffActuatorViewModel. In the code of the BeckhoffActuatorView I use the keyvaluepair from above:
public class BeckhoffActuatorView : MvxTouchDialogViewController<BeckhoffActuatorViewModel>
{
ICollection<string> icol;
public BeckhoffActuatorView(MvxShowViewModelRequest request) : base(request, UITableViewStyle.Grouped, null, true)
{
icol = request.ParameterValues.Values;
}
public override void ViewDidLoad()
{
//Code
}
}
This construction is working fine in IOS, but I would like to use the same construction in my android App.
The code in the ViewModel hasn't changed since that's the whole idea of MVVM. But the code of the BackhoffActuatorView is different for Android:
public class BeckhoffActuatorView : MvxBindingActivityView<BeckhoffSensorViewModel>
{
public ICollection<string> icol;
public BeckhoffActuatorView()
{
Debug.WriteLine("Standard");
}
public BeckhoffActuatorView(MvxShowViewModelRequest request)
{
Debug.WriteLine("Custom");
icol = request.ParameterValues.Values;
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.BeckhoffActuatorView);
}
}
The code above isn't working, the MvxBindingActivityView doesn't seem to implement something similar to the ViewController I use in IOS. The code only come in the standard constructor, and when I leave that one out completely it won't compile/run.
Does anyone know know I can access the keyvaluepair I send with the RequestNavigate? Thank you!
MVVMCross is very convention based - and it works on the idea of passing messages between ViewModels wherever possible.
If you navigate to a ViewModel using:
KeyValuePair<string,string> kvpAct1 = new KeyValuePair<string, string>("short", ".countertest5");
public IMvxCommand BeckhoffActuator1
{
get
{
return new MvxRelayCommand<Type>((type) => this.RequestNavigate<Beckhoff.BeckhoffActuatorViewModel>(kvpAct1));
}
}
then you should be able to pick that up in the BeckhoffActuatorViewModel using the constructor:
public class BeckhoffActuatorViewModel : MvxViewModel
{
public BeckhoffActuatorViewModel(string short)
{
ShortValue = short;
}
private string _shortValue;
public string ShortValue
{
get
{
return _shortValue;
}
set
{
_shortValue = value;
FirePropertyChanged("ShortValue");
}
}
}
And your views can then access ViewModel.ShortValue (for iOS this can be done after base.ViewDidLoad(), for Android after OnCreate() and for WP7 after OnNavigatedTo)
For an example of this, take a look at the TwitterSearch example:
https://github.com/slodge/MvvmCrossTwitterSearch
This has a HomeViewModel which calls navigate using:
private void DoSearch()
{
RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });
}
and a TwitterViewModel which receives the searchTerm using the constructor:
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
Please note that only strings are allowed in this message passing at present - but you can always serialise your own objects using JSON.Net - or you can extend the framework - it's open source.
Please note that only strings, ints, doubles and bools are allowed in this constructor parameter passing at present - this is due to serialisation requirements for Xaml Urls and for Android Intents. If you want to experiment with navigation using your own custom serialised objects, then please see http://slodge.blogspot.co.uk/2013/01/navigating-between-viewmodels-by-more.html.
Also, note that if you want to use the anonymous object navigation (RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });) then you will need to make sure that an InternalsVisibleTo attribute is set - see https://github.com/slodge/MvvmCrossTwitterSearch/blob/master/TwitterSearch.Core/Properties/AssemblyInfo.cs:
[assembly: InternalsVisibleTo("Cirrious.MvvmCross")]
Further... not for the faint-hearted... and this isn't "good mvvm code"... but if you really want/need to access the MvxShowViewModelRequest data inside an Android activity, then you can extract it from the incoming Intent - there's an Extras string containing the request (see the deserialisation in CreateViewModelFromIntent in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Android/Views/MvxAndroidViewsContainer.cs)