This is the problem that we're facing in .NET MVC 2.
We're trying to use DataAnnotations to take care of the Model Validation for us, like it's supposed to. The only problem that we're having is that we don't want the standard error messages (because we have multiple languages on our website).
We want to localize this, but the way the site is setup, is that all text comes from a database. So we'd like to have our error messages in the database as well.
So we wrote a custom RequiredAttribute, like this:
public class LocalizedRequiredAttribute : RequiredAttribute
{
public string LocalizedErrorMessage
{
get
{
return ErrorMessage;
}
set
{
ErrorMessage = value.Translate();
}
}
}
We wrote an extension to the String class to add the "Translate()" method, which does the necessary database lookup for the correct localized version.
We use our attribute like this:
[LocalizedRequired(LocalizedErrorMessage = "Naam is required")]
public string Name {get; set; }
This works, but only once.
If you visit the site in French first, you'll see the French error message stating that you're supposed to enter a value. If you visit the English site later, you'll still see the French error on the English page. The Setter seems to be called only once.
What can we do to prevent this behavior and refresh the error message every time the validation is run / the model is populated with values?
Thanks for any help you can give me.
Couldn't you fix this by moving your .Translate() from your setter to your getter? It makes sense that your setter is called only once.
Edit:
I assumed ErrorMessage was a virtual message, which is not the case.
Your only option might be to create Resource class (you don't need a resource file) that retrieves your values from the database.
[Required(ErrorMesageResourceName="FirstName", ErrorMessageResourceType=typeof(ABCResourceClass))]
public string Name {get; set; }
class ABCResourceClass{
public static String FirstName{
get{
return Translate("FirstName");
}
}
}
As you can infer from the example, the annotations framework calls the property with the name that matches the string you provide to ErrorMessageResourceName.
You could resort to some kindof code generation technique to create the ABCResourceClass if you have a lot of properties.
Just use method FormatErrorMessage() (whis is called everytime) to set ErrorMessage property
But it's hackish
public class ErrorLocalizedRequiredAttribute : RequiredAttribute
{
public ErrorLocalizedRequiredAttribute(string name)
{
Name = name;
}
public string Name
{ get; set; }
public override string FormatErrorMessage(string name)
{
//get translation from DB by Name
ErrorMessage = Localization.Translate(Name);
return base.FormatErrorMessage(name);
}
}
.
.
.
[ErrorLocalizedRequiredAttribute("EmailIsRequired")]
public string Email
{
get; set;
}
Related
We are building a class library that provides certain structured types for the view model, e.g. InlineImage.
I need to be able to define default HTML output for such instance when used with #Html.DisplayFor(...), basically ...
When /Views/DisplayTemplates/InlineImage.cshtml is available, then use that template
When not available, it should output the default
However ...
When I override just ToString() of that class, it gives me the correct output, but it gets HTML encoded
I found out analyzing MVC source code that I am able to disable encoding by adding [DisplayFormat(HtmlEncode = false)] to the CLASS
BUT ... the attribute does not target class, so I hack it by wrapping it to another attribute which I add to the class. It is nasty, but at least works :)
My code currently looks like this:
[AttributeUsage(AttributeTargets.Class)]
class DisableHtmlEncodeAttribute : DisplayFormatAttribute
{
public DisableHtmlEncodeAttribute()
{
HtmlEncode = false;
}
}
[DisableHtmlEncode]
internal class InlineImage : IInlineImage
{
public string AltText { get; set; }
public string Src { get; set; }
public override string ToString()
{
return $"<figure><img src=\"{Src}\" alt=\"{AltText}\"></figure>";
}
}
It works for the default display, but when display template is provided in file system, it is not used. Probably because something along the way cuts it of because of that data annotation.
I have already tried several other approaches similar to this:
Using first property with Html data annotation
Using display property with Html data annotation
Implementing IHtmlString
But the framework seems to check the metadata only for the class itself, but not for its properties in this case. And IHtmlString is completely ignored.
I am looking for any hints how to provide default display template for the given class from a class library, that could be overriden just by placing standard display template to views folder.
So it turned out that I was closer than I thought and with the last trial I made it working.
The trick is similar to what I did with the DisableHtmlEncode attribute, but with UIHint attribute. This way you can apparently tell the engine to apply display template to a class, and it is stronger than the Format attribute.
Here is the final code that provides the ability for default HTML markup, and when display template is provided in FS, it uses that template:
[AttributeUsage(AttributeTargets.Class)]
class DisableHtmlEncodeAttribute : DisplayFormatAttribute
{
public DisableHtmlEncodeAttribute()
{
HtmlEncode = false;
}
}
[AttributeUsage(AttributeTargets.Class)]
class UseDisplayTemplateAttribute : UIHintAttribute
{
public UseDisplayTemplateAttribute(string uiHint)
: base(uiHint)
{
}
}
[DisableHtmlEncode]
[UseDisplayTemplate("InlineImage")]
internal class InlineImage : IInlineImage
{
public string AltText { get; set; }
public string Src { get; set; }
public override string ToString()
{
return $"<figure><img src=\"{Src}\" alt=\"{AltText}\"></figure>";
}
}
I am not particularly proud about the hacks, but as they say "it ain't stupid if it works".
If anyone has got a better idea, please share ...
I've got one method, which take a model [AccountLinkRequest] as a parameter with url-encoded data. It's uses Json.NET by default, and also, I can't use the setting UseDataContractJsonSerializer = true cause I have generic output response model (in other methods)
[HttpPost]
public SomeResponse Link(AccountLinkRequest request)
{
if (request.CustomerId == null)
throw new Exception("Deserialization error here, pls help!");
// other actions
}
Here is my model class:
[DataContract]
[JsonObject]
public class AlertAccountLinkRequest
{
[DataMember(Name = "id")]
public string id { get; set; }
[DataMember(Name = "customer_id")]
[JsonProperty("customer_id")]
public string CustomerId { get; set; }
}
The problem: request.CustomerId is allways null. The request is pretty simple:
web_service_URL/link?customer_id=customer_id&id=id (url-encoded)
if I use Customer_Id instead of CustomerId, everything will be fine, but I'm on a jedy-way. Thank you!
There is not a simple answer how to achieve that. For more details please read this:
How to bind to custom objects in action signatures in MVC/WebAPI
Summary:
Manually call the parse function inside of your Action
Use a TypeConverter to make the complex type be simple
Use a custom model binder
So, if you for instance create your 'SmartBinder' which is able to consume some attributes, you can get what you want. Out fo the box there is no functionality for that, just the naming conventions...
I wish to give a Person as defined below, the ability to print a vCard out of my system. To provide the user with privacy options, the user can select whether to show/hide certain properties. In it's simplest form, I need to have a separate table that would hold the user's choices.
I was wondering if it was possible to build this configurator table using reflection. As shown in the Person model below, I could decorate properties with a custom attribute, and then using those properties, construct and persist a model that would have a bool property for every decorated Person property.
public class Person
{
public string UserName { get; set; }
public string FirstName { get; set; }
[DisplayOnVCard]
public string LastName { get; set; }
[DisplayOnVCard]
public string Email { get; set; }
[DisplayOnVCard]
public string MobilePhone { get; set; }
}
** where [DisplayOnVCard] is a custom attribute.*
At the end of this, I expect a table in the db that would correspond to this:
public class VCardConfigurator
{
public bool LastName { get; set; }
public bool Email { get; set; }
public bool MobilePhone { get; set; }
}
This is just a sample representation of what is actually a huge entity. Which is why I hope to avoid manually mapping a bool field to each optional property.
I believe this problem domain is quite similar to how, for instance, privacy settings work on social networking sites, yes?
While I was typing this, I did ponder upon the possibility that if down the line I was to remove the attribute from one of the properties, what implications that might have. Needs some thought!
Further reading for self:
Programmatically adding properties to an MVC model at runtime
There is a huge possibility that I am galloping down a totally wrong path! If that is the case, please advice so!
#1 Update
I am not sure its possible to add or remove attributes for an instance since attributes are at the class level, but their property values can be changed (Since they are instances).
My suggested solusion
I am not sure what you mean in "I expect a table in the db that would correspond to this",
since you can't have a table in the database that contains only the columns of the non-privacy properties for each user.
You will need a dedicated table for this mapping (Lets say 'PrivacyMappings' table), with these columns:
UserId, PropertyName, IsPrivate.
When a user is added, all the properties will be added to this table with a default privacy settings (for instance, all properties are non-private by default).
You can add the properties by iterating over them and insert them as you said.
You can use the following class in entity framework:
public class PrivacyMapping
{
public int UserId {get;set;}
public string PropertyName {get;set;}
public bool IsPrivate {get;set;}
}
Adding the default privacy settings when a user being added:
// retrieve user model properties.
foreach (property in properties)
{
//iterrate over the user Properties.
context.PrivacyMapping.Add(new PrivacyMapping(user.userId, propertyName, isPrivate);
}
context.SaveChanges()
Now you can take all the user non-private properties by
context.PrivacyMapping.Where(p=>p.UserId == user.id && !IsPrivate).Select(p=>p.PropertyName);
And now you can deal with information any way you want.
For example, you can have a VCardItems class, that receive an user id/object in its c'tor and stores a dictionary of the allowed properties by their names.
public class VCardItems{
private Dictionary<string, object> properties{get;set;}
public VCardItems(User user)
{
// initiate values..
}
public object this[string name] {
get
{
if (properties.ContainsKey(name))
{
return properties[name];
}
// A private property.
return null;
}
set
{
properties[name] = value;
}
}
}
There is other options of how to use the data, for example with ActionFilter that in this case sets the private properties to null or storing the non-private data in the HttpContext.Items dictionary,
but it really up to you.
First message
Before we get into details, I wonder how you expect to use this class.
If a view (or whatever going to handle it), going to receive have a runtime-generated class for example, how you gonna handle it?
How you gonna know what properties this model has?
I'm still learning, but with the stackoverflow commnuties help, I've been able to get closer and closer.
What I have right now is a View "Index.aspx":
System.Web.Mvc.ViewPage<Data.Models.GetDealsModel>
The Model:
public class GetDealsModel
{
// set up the model
public string DealId { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Logo { get; set; }
public string Website { get; set; }
public string TotalRows { get; set; }
}
And the controller:
public ActionResult Index()
{
LinqToDealsDataContext db = new LinqToDealsDataContext();
XElement xmlTree = XElement.Parse("<Request><ZipCode>92612</ZipCode></Request>");
var deals = db.spSearchDeals(xmlTree);
return View(deals);
}
And with this configuration I'm now getting this error:
The model item passed into the dictionary is of type 'System.Data.Linq.SqlClient.SqlProvider+SingleResult`1[Data.Models.spSearchDealsResult]', but this dictionary requires a model item of type 'Data.Models.GetDealsModel'.
I'm guessing that there's an issue connecting my Controller to my Model... I'm not sure why. PLEASE help me connect this final peice.
NOTE: I do understand that eventually I should separate my logic in the controller into a Repository Pattern, but for now, this will do.
You need to translate the data coming back from this call:
var deals = db.spSearchDeals(xmlTree);
into a GetDealsModel type. So something like:
GetDealsModel dealsModel = new GetDealsModel()
{
DealId = deals.DealId,
StreetAddress = deals.StreetAddress,
....
};
return View(dealsModel);
The reason being that your View is strongly typed to take a GetDealsModel, but your deals variable is not of that type and it gives you that exception when you pass it to the View.
You should create object of type GetDealsModel, but your DB Query returns object of type Data.Models.spSearchDealsResult. Try something like:
return new GetDealsModel
{
DealId = deals.Id,
// other fields here
}
Add to your learning curve list the following items:
Repository Pattern
Ask yourself the following question: Why do I need a service layer?
Read Steven Sanderson's book. It teaches you to think in MVC.
The above applies to your problems because your issues are clearly related to having code in your Controllers that should be in your Model (ie, data access code should be in a repository class). Ie, you are not thinking in MVC.
Your model should include the necessary repository classes, eg, DealRepository.
You need a Service class to map the objects your repository digs out of your database to your model class: that way conversion problems are encapsulated into the Service Layer code.
If you do this, you can then write in your controller:
public ActionResult Index()
{
return(DealService.GetByZipcode(92612));
}
Where DealService.GetByZipcode basically just maps DealRepository.GetByZipcode(92612) to your model class and returns the mapping result.
The DealRepository.GetByZipcode method would be roughly:
public static DealEntity GetByZipcode(string zip)
{
LinqToDealsDataContext db = new LinqToDealsDataContext();
XElement xmlTree = XElement.Parse("<Request><ZipCode>" + zip + "</ZipCode></Request>");
var deals = db.spSearchDeals(xmlTree);
return deals;
}
The DealEntity class is just whatever Linq gives you for your table.
The reason WHY for all this:
The reason for this structure is as follows:
a. All you data access code is in one place: DealRepository. You can test and debug that independently of everything else.
b. The mapping code is all in one place: DealService. You can test and debug that independently of everything else.
c. In other words, you need to properly separate your concerns.
The problem with your existing code is precisely that you have NOT separated concerns. Ie, you have taken a dash of MVC and put it in a food processor and ended up with mush full of problems that are way more difficult to deal with than they need be.
Your model is mixed into your controller, there is no repository, no service layer.
So hold your horses just a while and take the time to read Steve Sanderson's book.
I would also try modelling a simpler problem. That xml parsing makes my head hurt even on a good day.
NOTE:
You could seriously improve your naming conventions. LinqToDealsDataContext? You're kidding, right?
When I use UpdateModel or TryUpdateModel, the MVC framework is smart enough to know if you are trying to pass in a null into a value type (e.g. the user forgets to fill out the required Birth Day field) .
Unfortunately, I don't know how to override the default message, "A value is required." in the summary into something more meaningful ("Please enter in your Birth Day").
There has to be a way of doing this (without writing too much work-around code), but I can't find it. Any help?
EDIT
Also, I guess this would also be an issue for invalid conversions, e.g. BirthDay = "Hello".
Make your own ModelBinder by extending DefaultModelBinder:
public class LocalizationModelBinder : DefaultModelBinder
Override SetProperty:
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
foreach (var error in bindingContext.ModelState[propertyDescriptor.Name].Errors.
Where(e => IsFormatException(e.Exception)))
{
if (propertyDescriptor.Attributes[typeof(TypeErrorMessageAttribute)] != null)
{
string errorMessage =
((TypeErrorMessageAttribute)propertyDescriptor.Attributes[typeof(TypeErrorMessageAttribute)]).GetErrorMessage();
bindingContext.ModelState[propertyDescriptor.Name].Errors.Remove(error);
bindingContext.ModelState[propertyDescriptor.Name].Errors.Add(errorMessage);
break;
}
}
Add the function bool IsFormatException(Exception e) to check if an Exception is a FormatException:
if (e == null)
return false;
else if (e is FormatException)
return true;
else
return IsFormatException(e.InnerException);
Create an Attribute class:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public class TypeErrorMessageAttribute : Attribute
{
public string ErrorMessage { get; set; }
public string ErrorMessageResourceName { get; set; }
public Type ErrorMessageResourceType { get; set; }
public TypeErrorMessageAttribute()
{
}
public string GetErrorMessage()
{
PropertyInfo prop = ErrorMessageResourceType.GetProperty(ErrorMessageResourceName);
return prop.GetValue(null, null).ToString();
}
}
Add the attribute to the property you wish to validate:
[TypeErrorMessage(ErrorMessageResourceName = "IsGoodType", ErrorMessageResourceType = typeof(AddLang))]
public bool IsGood { get; set; }
AddLang is a resx file and IsGoodType is the name of the resource.
And finally add this into Global.asax.cs Application_Start:
ModelBinders.Binders.DefaultBinder = new LocalizationModelBinder();
Cheers!
With the DefaultModelBinder it is possible to override the default required error message but unfortunately it would apply globally which IMHO renders it completely useless. But in case you decide to do it here's how:
Add the App_GlobalResources folder to your ASP.NET site
Add a resources file called Messages.resx
Inside the resources file declare a new string resource with the key PropertyValueRequired and some value
In Application_Start add the following line:
DefaultModelBinder.ResourceClassKey = "Messages";
As you can see there's no link between the model property you are validating and the error message.
In conclusion it is better to write custom validation logic to handle this scenario. One way would be to use a nullable type (System.Nullable<TValueType>) and then:
if (model.MyProperty == null ||
/** Haven't tested if this condition is necessary **/
!model.MyProperty.HasValue)
{
ModelState.AddModelError("MyProperty", "MyProperty is required");
}
I've been using the awesome xVal validation framework. It lets me do all my validation in the model (Even LINQ-SQL :)). It also emits the javascript required for client side validation.
EDIT: Sorry left out the link for how to get it working for LINQ-SQL
The basic workflow goes something like this.
public partial class YourClass
{
[Required(ErrorMessage = "Property is required.")]
[StringLength(200)]
public string SomeProperty{ get; set; }
}
try
{
// Validate the instance of your object
var obj = new YourClass() { SomeProperty = "" }
var errors = DataAnnotationsValidationRunner.GetErrors(obj);
// Do some more stuff e.g. Insert into database
}
catch (RulesException ex)
{
// e.g. control name 'Prefix.Title'
ex.AddModelStateErrors(ModelState, "Prefix");
ModelState.SetModelValue("Prefix.Title", new ValueProviderResult(ValueProvider["Prefix.Title"].AttemptedValue, collection["Prefix.Title"], System.Globalization.CultureInfo.CurrentCulture));
}
how about this?
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$",
ErrorMessage = "Characters are not allowed.")]
That should allow you to tag properties with specific error messages for whatever MVC validators you want to use...
In ASP.NET MVC 1, I met this problem too.
In my project, there is a model or business object named "Entry", and its primary key EntryId is int? type, and the value of EntryId can be allowd to input by users.
So the problem is, when the field is blank or zero or some integer value that has existed, the custom error messages can be shown well, but if the value is some non-integer value like "a", i can not find a way to use the custom message to replace the default message like "The value 'a' is invalid".
when i track the error message in ModelState, i found when the value is non-integer, there will be two errors related to EntryId, and the first item's error message is blank...
Now i have to use such an ugly code to hack the problem.
if (ModelState["EntryId"].Errors.Count > 1)
{
ModelState["EntryId"].Errors.Clear(); //should not use ModelState["EntryId"].remove();
ModelState.AddModelError("EntryId", "必须为大于0的整数"); //必须为大于0的整数 means "it should be an integer value and great than 0"
}
but this makes controller fat, hope there is a real solution to solve it.
Look up ModelState.AddError.
yes, there is a way, you must use System.ComponentModel.DataAnnotations in combination with xVal and you are going to be able to set validation rules and messages (u can even use resource files for localization) for each of your property using Attributes
look here http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/