How to use JSON.Net CamelCasePropertyNamesContractResolver with dot separated names? - asp.net-mvc

Our application uses validation attributes to make use of the ASP.NET model validation, however this gives dot separated names for validation errors. When passed through the CamelCasePropertyNamesContractResolver this only applies camelcase to before the first dot, whereas we would like the have camelcase applied to each section of the name.
For example we currently get the current json response:
{
"body.State": [
"The state field is required."
],
"body.LatestVersion": [
"The latestVersion field is required."
]
}
But desire to get out:
{
"body.state": [
"The state field is required."
],
"body.latestVersion": [
"The latestVersion field is required."
]
}
In our MVC setup we do have a line similar to
services.AddJsonOptions(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
We'd appreciate any solution, be that modifications to how we set up the resolver, or how we could modify the validation.
Edit: Just for reference the model structure for the request that is generating this request is as follows:
public sealed class RequestModel
{
[FromRoute, DisplayName("entity"), Required, MaximumLength(255)]
public string Entity { get; set; }
[FromBody, DisplayName("body"), Required]
public BodyModel Body { get; set; }
}
public sealed class BodyModel
{
[DisplayName("latestVersion"), Required, MaximumLength(255)]
public string LatestVersion { get; set; }
[DisplayName("state"), Required]
public ModelState State { get; set; }
}
and the request body being sent is:
{
}

Assuming that the validation errors are serialized as some sort of IDictionary<string, T> for some T, then the JSON property names corresponding to the dictionary keys can be piecewise camel-cased between each . character by creating a custom naming strategy.
Json.NET encapsulates the logic to algorithmically remap property names and dictionary keys (e.g. to camel case) in the NamingStrategy type, specifically CamelCaseNamingStrategy. To modify the logic of a naming strategy to apply to each portion of a property name between . characters, you can adopt the decorator pattern and create a decorator naming strategy that applies some inner strategy to each portion of the name like so:
public class PiecewiseNamingStrategy : NamingStrategy
{
readonly NamingStrategy baseStrategy;
public PiecewiseNamingStrategy(NamingStrategy baseStrategy)
{
if (baseStrategy == null)
throw new ArgumentNullException();
this.baseStrategy = baseStrategy;
}
protected override string ResolvePropertyName(string name)
{
return String.Join(".", name.Split('.').Select(n => baseStrategy.GetPropertyName(n, false)));
}
}
Then, configure MVC as follows:
options.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new PiecewiseNamingStrategy(new CamelCaseNamingStrategy())
{
OverrideSpecifiedNames = true, ProcessDictionaryKeys = true
},
};
This takes advantage of the fact that, as shown in the reference source, CamelCasePropertyNamesContractResolver is basically just a subclass of DefaultContractResolver that uses a CamelCaseNamingStrategy with ProcessDictionaryKeys = true and OverrideSpecifiedNames = true.
Notes:
Naming strategies were introduces in Json.NET 9.0.1 so this answer does not apply to earlier versions.
You may want to cache the contract resolver statically for best performance.
By setting NamingStrategy.ProcessDictionaryKeys to true, the naming strategy will be applied to all dictionary keys.

Related

Proper way to handle Nullable References and RequiredAttribute on model binding

In my web-api controllers I post query model (for paging, filtering and sorting) for my entities.
I have nullable references enabled solution-wise (all projects in csproj)
The base classes are:
public class SearchFilter
{
public string? q { get; set; } // <-- This should indicate that the "q" property is optional
public bool? fullResults { get; set; } // <-- This should indicate that the "fullResults" property is optional
}
public class Query<TFilter> where TFilter : SearchFilter, new()
{
public int skip { get; set; }
public int take { get; set; }
public List<SortDescriptor> sorting { get; set; } = new List<SortDescriptor>();
public TFilter? filter { get; set; } // <-- this indicates that the property "filter" is optional
}
So, for instance, a Query model for users would be:
class UserFilter: SearchFilter
{
bool? is_activated {get;set;} // etc.
}
class UserQuery: Query<UserFilter>{
}
When my client posts this request json
{
"skip":0,
"take":25,
"sorting":[{"field":"id","desc":true}]
}
The request completes successfully as expected. The "filter" property is optional (as indicated by the nullable property), and indeed the "filter" property is missing from the above json
When I try to filter my data by providing another value
{
"skip":0,
"take":25,
"sorting":[{"field":"id","desc":true}],
"filter": {
"is_avtivated": true
}
}
I get a bad request response
{
"errors": {
"filter.q": ["The q field is required."]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|fb9d87a2-47080df6ddd0a9dc."
}
What is going on here? Is not supposed "q" to be optional? I read that enabling nullable-references in c#8 will automatically put [Required] attributes to non-nullable properties, but as it seems here it also puts [Required] attributes to nullable properties too!
Is this a bug in .net core?
Or is this expected?
And how should I make my request work?
The request is clearly correct (it worked perfectly before enabling nullable references) and very clear. The code is also very clear.
"A filter is optional. If a filter exists, the q property of the filter is also optional".
This is what the code reads to.
As a note, I feel very frustrated that I don't have control over which property is actually required and which is not (nullable references is not the way to declare this intention). I use nullable references only for the warnings, to spot potentially problematic pieces of code. I would expect that enabling this feature wouldn't alter the application's behavior in any level.

FluentValidation collection properties not validated

This is the first time I'm trying to implement FluentValidation since I need to cover a complex validation scenario.
The class I'm trying to validate has a large quantity of properties, complex objects and several collections.
I didn't have troubles to validate properties of the main class or even checking if collections are not empty, but I do have problems while validating objects properties within each collection.
To implement this I followed the examples documented here (check under "Re-using Validators for Collections"):
http://fluentvalidation.codeplex.com/wikipage?title=creatingavalidator
These are my model classes (reduced to improve readability)
public class Caso
{
public int Id { get; set; }
public string Descripcion { get; set; }
public List<Medicamento> Medicamentos { get; set; }
}
public class Medicamento
{
public int Id { get; set; }
public string Nombre { get; set; }
}
These are the validator classes:
public class CasoValidator : AbstractValidator<CasoAdverso>
{
public CasoValidator()
{
RuleSet("Iniciar", () =>
{
// Validated OK
RuleFor(x => x.Descripcion).NotEmpty();
// Validated OK
RuleFor(x => x.Medicamentos).Must(x => x != null && x.Count > 0).WithMessage("No puede iniciar un caso sin medicamentos cargados");
RuleFor(x => x.Medicamentos).SetCollectionValidator(new MedicamentoValidator());
});
}
}
public class MedicamentoValidator : AbstractValidator<Medicamento>
{
public MedicamentoValidator()
{
// NOT Validated. Even if the object property is empty the error message doesn't appear. I also checked using "NotNull" and "NotEmpty" clauses
RuleFor(x => x.Nombre).NotNull().WithMessage("Debe especificar un nombre");
}
}
(Note: I'm using RuleSet because of different validation schemas which are dependent of the document status in the workflow)
I'm executing the validation manually from the controller (no MVC integration)
[HttpPost]
public ActionResult Iniciar(Caso c)
{
CasoValidator validator = new CasoValidator();
FluentValidation.Results.ValidationResult validate = validator.Validate(c, ruleSet: "Iniciar");
// ...
}
With this implementation properties of the main class are validated fine but I need also to validate each property of the "Medicamento" class within the collection.
Could I be missing something here?. Should this be validated using the RuleForEach clause available?
Any help will be appreciated.
It appears the RuleSet setting is applying to the child validator as well as the primary one.
I tested your code in an xUnit.net test, and confirmed it.
If you change your rulesets to execute you should find it works as expected:
CasoValidator validator = new CasoValidator();
FluentValidation.Results.ValidationResult validate = validator.Validate(c, ruleSet: "default,Iniciar");
The 'default' ruleset will work on the MedicamentoValidator rules.
I didn't find this in the documentation, only through testing.
This is the sample unit test:
[Fact]
public void Test1()
{
Caso c = new Caso()
{
Id = 1,
Descripcion = "none",
Medicamentos = new List<Medicamento>()
};
c.Medicamentos.Add(new Medicamento()
{
Id = 0,
Nombre= null
});
CasoValidator validator = new CasoValidator();
FluentValidation.Results.ValidationResult validate = validator.Validate(c, ruleSet: "default,Iniciar");
Assert.NotEmpty(validate.Errors);
}
Update: i found a reference by Jeremy Skinner for exactly this behavior:
http://fluentvalidation.codeplex.com/discussions/266920
Rulesets cascade to any child validators, so whichever ruleset is
selected for use by the top-level validator will also be used by the
child validator.
So if you ran the "Minimal" ruleset on the CreateProfileModelValidator, then only rules in the "Minimal" ruleset
will be run on both the CreateProfileModelValidator and the
ProfileValidator.
as a complementary:
a collection named GroupMemberIds should have AdminMemebrId:
RuleFor(r => new { r.GroupMemberIds, r.AdminMemberId }).Must(a => a.GroupMemberIds.Contains(a.AdminMemberId));

IgnoreDataMember attribute is skipping json object properties during **DE**serialization

According to the docs, the IgnoreDataMember attribute is only supposed to be considered during serialization.
From what I'm seeing, however, MVC model binding is using it during *de*serialization of json as well.
Consider the following class:
public class Tax
{
public Tax() { }
public int ID { get; set; }
[Required]
[Range(1, int.MaxValue)]
[IgnoreDataMember]
public int PropertyId { get; set; }
}
If POST/PUT the following json string to an action method:
{"Position":0,"Description":"State sales tax","Rate":5,"RateIsPercent":true,"PropertyId":1912}
I get the following validation error:
{
"Message": "The request is invalid.",
"ModelState": {
"newTax.PropertyId": [
"The field PropertyId must be between 1 and 2147483647."
]
}
}
Both the [Range(1, int.MaxValue)] and [Required] attributes are invalid.
If I remove the [IgnoreDataMember] attribute, everything works fine.
Is there a different attribute that can be used which will tell MVC binding not to ignore the property during deserialization?
This only happens when posting a json string. If I post a name/value string, everthing works fine.
The answer has to do with the behavior of Json.net. That's what the model binding is using and it's checking IgnoreDataMember for both serialization and deserialization making it useless for me (since I want to only use it for serialization).
The JsonIgnore attribute works exactly the same way.
Given that, I pulled all the ignore attributes off my properties and switched to using json.net's conditional serialization methods.
So basically add this for the above PropertyId field:
public bool ShouldSerializePropertyId() { return false; }
That allows deserialization to come in but blocks serialization from going out.

Change default "The {0} field is required" (ultimate solution?)

Good day!
I've the following ViewModel class I use for login form:
using System.ComponentModel.DataAnnotations;
...
public class UserLogin : IDataErrorInfo
{
[Required]
[DisplayName("Login")]
public string Login { get; set; }
[Required]
[DisplayName("Password")]
public string Password { get; set; }
[DisplayName("Remember Me")]
public bool RememberMe { get; set; }
#region IDataErrorInfo Members
// This will be a Model-level error
public string Error
{
get
{
if (!WebUser.CanLogin(Login, Password))
{
return Resources.ValidationErrors.InvalidLoginPassword;
}
else
{
return String.Empty;
}
}
}
// All is handled by DataAnnotation attributes, just a stub for interface
public string this[string columnName]
{
get
{
return string.Empty;
}
}
#endregion
}
And this in Global.asax:
DefaultModelBinder.ResourceClassKey = "BinderMessages";
ValidationExtensions.ResourceClassKey = "BinderMessages";
The resource file BinderMessages.resx is placed inside App_GlobalResources it has two keys InvalidPropertyValue (which works) and PropertyValueRequired which doesn't and gives me default message.
Question: Is it possible to modify this message, or it's tied to DataAnnotations?
I've found many posts about this, but without solution. For now I just fallback to this:
[Required(ErrorMessageResourceType = typeof(Resources.ValidationErrors), ErrorMessageResourceName = "Required")]
You can create a custom ValidationAttribute that extends RequiredAttribute and sets the values there. Something like:
public class MyRequiredAttribute : RequiredAttribute
{
public MyRequiredAttribute()
{
ErrorMessageResourceType = typeof(Resources.ValidationErrors);
ErrorMessageResourceName = "Required";
}
}
Then decorate your Model with your custom attribute.
The default message is compiled into the DataAnnotations assembly in the resource file under System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.resources and is RequiredAttribute_ValidationError=The {0} field is required.. So to answer your question, yes, that message is part of DataAnnotations.
Edit: PropertyValueRequired is used for errors on null values with non-nullable types. As mentioned below PropertyValueInvalid is used for type conversion errors.
I've done an approach using a singleton class to provide the translations. You still need to derive all attributes as suggested by #bmancini. The upside with my approach is that you can use multiple string tables (or switch translation source) without having to modify any other logic.
Since my blog entry is rather large, I'll just provide a link:
http://blog.gauffin.org/2010/11/simplified-localization-for-dataannotations/

asp.net mvc input / model validation multi language

I'm quite new to asp.net mvc, and right know I'm trying to find out
a good practise to do input validation.
In the project we're going to use entity framework, where you can add
data annotations to properties in the following way:
[Required(ErrorMessage = "Please enter a product name")]
[Column]
public string Name { get; set; }
This is quite nice, however we have a multi language website (like most websites),
so we can't only show the error messages in English.
What can be a way to solve this? Can I change this errormessage #runtime, depending on the user's language?
Should I use Jquery client side validation?
Thanks for the input.
Update I've tried the code on the website of Phil Haack
This will do the trick with static resources however, we use resources that come from a database not static resources.
If I fill in the following for the dataannotations:
[MetadataType(typeof(IncidentsMetaData))]
public partial class INCIDENTS
{
private class IncidentsMetaData
{
[Required(ErrorMessageResourceType = typeof(CustomResourceProviders.DBResourceProviderFactory),
ErrorMessageResourceName="1277")]
public string SUBJECT { get; set; }
}
}
Then I get the following error:
The resource type 'CustomResourceProviders.DBResourceProviderFactory' does not have an accessible static property named '1277'.
Of course there is no such property, it should be accessed by a function.
Any idea what I could do about this?
tnx
You can inherit custom attribute from RequiredAttribute and set your own localized message for property ErrorMessage. It can looks like this:
public class LocalizedRequiredAttribute : RequiredAttribute
{
public LocalizedRequiredAttribute()
: base()
{
// prefix for the selection of localized messages from datebase
// e.x. for "Required" string, localized messages will be: "RuRequired", "EnRequired"
var currentCulture = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
// logic to get value from datebase
// e.x. using Linq2Sql
using (var context = new dateBaseContext())
{
var query = (from x in context.LocalizedStrings
where x.NameKey == currentCulture + "Required"
select x.NameValue).SingleOrDefault();
if (query != null)
{
base.ErrorMessage = query;
}
else
{
base.ErrorMessage = "UndefinedName";
}
}
}
}
also and you inherit from DisplayNameAttribute and override DisplayName property:
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string displayNameKey)
: base(displayNameKey)
{
}
public override string DisplayName
{
get
{
if (!string.IsNullOrEmpty(base.DisplayName))
{
// prefix for the selection of localized messages from datebase
// e.x. if DisplayName is "Country", localized messages will be: "RuCountry", "EnCountry"
var currentCulture = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
// logic to get value from datebase
// e.x. using Linq2Sql
using (var context = new dateBaseContext())
{
var query = (from x in context.DisplayNames
where x.DisplayNameKey == currentCulture + base.DisplayName
select x.DisplayNameValue).SingleOrDefault();
if (query != null)
{
return query;
}
return base.DisplayName;
}
}
return "UndefinedName";
}
}
}
also you can create your custom validation attributes that inherits from ValidationAttribute class.
Take a look at this post, http://helios.ca/2010/02/17/asp-net-mvc-2-model-validation-with-localization/ good blog on the problem
Phil Haack has written a good blog post that covers how to do this. Essentially it is much the same except you use resource files to provide the messages.
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = Required")]
public string MyProperty{ get; set; }

Resources