Null fields on a partial update mutation on GraphQL .NET - c#

At work, we're using EFCore on our data layer and graphql-dotnet to manage APIs requests, I'm having a problem updating some of our big objects using GraphQL mutations. When the user sends a partial update on the model, we would like to update on our database only the fields that actually were changed by the mutation. The problem we're having is that as we directly map the input to the entity, wheather some field was purposefully passed as null, or the field was not specified on the mutation at all, we get the property value as null. This way we can't send the changes to the database otherwise we would incorrectly update a bunch of fields to null.
So, we need a way to identify which fields are sent in a mutation and only update those. In JS this is achieved by checking if the property value is undefined, if the value is null we know that it was passed as null on purpose.
Some workarounds we've been thinking were using reflection on a Dictionary to update only the specified fields. But we would need to spread reflection to every single mutation. Another solution was to have a isChanged property to every nullable property on our model and change ir on the refered property setter, but... cmon...
I'm providing some code as example of this situation bellow:
Human class:
public class Human
{
public Id { get; set; }
public string Name { get; set; }
public string HomePlanet { get; set; }
}
GraphQL Type:
public class HumanType : ObjectGraphType<Human>
{
public HumanType()
{
Name = "Human";
Field(h => h.Id).Description("The id of the human.");
Field(h => h.Name, nullable: true).Description("The name of the human.");
Field(h => h.HomePlanet, nullable: true).Description("The home planet of the human.");
}
}
Input Type:
public class HumanInputType : InputObjectGraphType
{
public HumanInputType()
{
Name = "HumanInput";
Field<NonNullGraphType<StringGraphType>>("name");
//The problematic field
Field<StringGraphType>("homePlanet");
}
}
Human Mutation:
/// Example JSON request for an update mutation without HomePlanet
/// {
/// "query": "mutation ($human:HumanInput!){ createHuman(human: $human) { id name } }",
/// "variables": {
/// "human": {
/// "name": "Boba Fett"
/// }
/// }
/// }
///
public class StarWarsMutation : ObjectGraphType<object>
{
public StarWarsMutation(StarWarsRepository data)
{
Name = "Mutation";
Field<HumanType>(
"createOrUpdateHuman",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<HumanInputType>> {Name = "human"}
),
resolve: context =>
{
//After conversion human.HomePlanet is null. But it was not informed, we should keep what is on the database at the moment
var human = context.GetArgument<Human>("human");
//On EFCore the Update method is equivalent to an InsertOrUpdate method
return data.Update(human);
});
}
}

You could use JsonConvert.PopulateObject from the Newtonsoft Json library. On the mutation resolver instead of using GetArgument with my type, I'm using GetArgument<dynamic> and serializing it using JsonConvert.SerializeObject then by calling JsonConvert.PopulateObject I'm able to update only the fields that were informed.
public StarWarsMutation(StarWarsRepository data)
{
Name = "Mutation";
Field<HumanType>(
"createOrUpdateHuman",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<HumanInputType>> {Name = "human"}
),
resolve: context =>
{
//After conversion human.HomePlanet is null. But it was not informed, we should keep what is on the database at the moment
var human = context.GetArgument<dynamic>("human");
var humanDb = data.GetHuman(human["id"]);
var json = JsonConvert.SerializeObject(human);
JsonConvert.PopulateObject(json, humanDb);
//On EFCore the Update method is equivalent to an InsertOrUpdate method
return data.Update(humanDb);
});
}

Related

Marking a property as Json Required with Newtonsoft just makes object null

I have a request object for my API and I want to enforce that my properties are always present in the body. Here is my object:
public class Code
{
[JsonProperty("id", Required = Required.Always)]
public string id { get; set; }
[JsonProperty("type", Required = Required.Always)]
public string type { get; set; }
}
However, when I don't pass in the type property in my request body, my Code object is null. Instead I would want a bad request error to get propagated back to the client. Will the Newtonsoft decorator not do that for me here? Or would I have to manually add checks to see if the properties are not null?
This following code throws for me as expected:
string serialized = #"{ 'noId': '123' }";
Code deserialized = JsonConvert.DeserializeObject<Code>(serialized);
Console.WriteLine(deserialized.Id);
My code class:
class Code
{
[JsonProperty("id", Required = Required.Always)]
public string Id { get; set; }
}
Can you confirm Newtonsoft.Json is used? If you are using ASP.NET Core 3.x or above, please refer to How to use Newtonsoft.Json as default in Asp.net Core Web Api? to set your project up to use Newtonsoft.Json.
Fluent Validation is the solution for you. So, you don't have to use JsonProperty attributes.
Usage:
First, create a validator for your class.
public class CodelValidator : AbstractValidator<Code>
{
public CodelValidator()
{
RuleFor(x => x.id).NotEmpty().WithMessage("id is required.");
RuleFor(x => x.type).NotEmpty().WithMessage("type is required.");
}
}
Inside your controller method:
public ActionResult TestMethod(Code code)
{
var validator = new CodeValidator();
var validationResult = await validator.ValidateAsync(code);
if (validationResult.IsValid == false)
{
var errorMessages = validationResult.Errors.Select(s => s.ErrorMessage);
// manage how you want to show the erros.
}
...
}
So, as you get all the errors. Now you can show however you want.
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1064
Currently the value for JsonProperty.Required only determines if the value is required - it does not allow you to indicate that a value may or may not be null.
Also in looking at the code it looks like all empty strings get converted to null
https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L282

FluentValidation validate Enum value

I have the following model:
public class ViewDataItem
{
public string viewName { get; set; }
public UpdateIndicator updateIndicator { get; set; }
}
With the following enum:
public enum UpdateIndicator
{
Original,
Update,
Delete
}
And the following Validator:
public class ViewValidator : AbstractValidator<ViewDataItem>
{
public ViewValidator()
{
RuleFor(x => x.viewName).NotEmpty().WithMessage("View name must be specified");
RuleFor(x => x.updateIndicator).SetValidator(new UpdateIndicatorEnumValidator<UpdateIndicator>());
}
}
public class UpdateIndicatorEnumValidator<T> : PropertyValidator
{
public UpdateIndicatorEnumValidator() : base("Invalid update indicator") {}
protected override bool IsValid(PropertyValidatorContext context)
{
UpdateIndicator enumVal = (UpdateIndicator)Enum.Parse(typeof(UpdateIndicator), context.PropertyValue.ToString());
if (!Enum.IsDefined(typeof(UpdateIndicator), enumVal))
return false;
return true;
}
}
The code is in a WebAPI that receives data via JSON, deserialize it to an object and then validates, but for some reason I can send whatever I please in the updateIndicator, so long as I don't put in an integer value larger than the max index in the enum (i.e 1,2 or 3 works fine, but 7 will generate an error).
How can I get this to validate the input of the data I receive to see if that value is actually in the Enum?
Try the built-in IsInEnum()
RuleFor(x => x.updateIndicator).IsInEnum();
This checks if the provided enum value is within the range of your enum, if not, the validation will fail:
"'updateIndicator' has a range of values which does not include '7'."
The problem arises from the fact that the API model builder will convert what is sent to an enum. If a value isn't found, it doesn't populate it, and the default value is used (as it would be with any other property data type that isn't populated).
In order to easily tell if the value sent is a valid enum value, you should make your property nullable. That way, if a value isn't able to be parsed, it will be set to null. If you want to ensure that the property is set, just have your validator not allow null values for it.
public class ViewDataItem
{
public string viewName { get; set; }
public UpdateIndicator? updateIndicator { get; set; }
}
public class ViewValidator : AbstractValidator<ViewDataItem>
{
public ViewValidator()
{
RuleFor(x => x.viewName).NotEmpty().WithMessage("View name must be specified");
RuleFor(x => x.updateIndicator).NotNull();
}
}
Without setting the property to null, your model will always have a valid value when you have it. Alternatively, you could have the first value of your enum be a dummy value, but that would be a code smell. A null model property makes far more sense.
If you want to find out what the actual value that was sent to the API endpoint was, you'll need to look at creating an HTTP Handler, which is beyond the scope of this question.

C# Entity Framework: Data validation between add to context and saveChanges()

I have a simple scenario using the Entity Framework in C#. I have an Entity Post:
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
In my PostManager I have these methods:
public int AddPost(string name, string description)
{
var post = new Post() { Name = name, Description = description };
using (var db = new DbContext())
{
var res = db.Posts.Add(post);
res.Validate();
db.SaveChanges();
return res.Id;
}
}
public void UpdatePost(int postId, string newName, string newDescription)
{
using (var db = new DbContext())
{
var data = (from post in db.Posts.AsEnumerable()
where post.Id == postId
select post).FirstOrDefault();
data.Name = newName;
data.Description = newDescription;
data.Validate();
db.SaveChanges();
}
}
The method validate() refers to class:
public static class Validator
{
public static void Validate(this Post post)
{
if ( // some control)
throw new someException();
}
I call the validate method before the savechanges() but after adding the object to the context. What's the best practice to validate data in this simple scenario? It's better validate the arguments instead? What's happen to object post if the validate method throw exception after adding the object to the context?
UPDATE:
I have to throw a custom set of exception depending on data validation error.
I strongly recommend you to (if at all possible) to modify your entity so the setters are private (don't worry, EF can still set them on proxy creation), mark the default constructor as protected (EF can still do lazy loading/proxy creation), and make the only public constructors available check the arguments.
This has several benefits:
You limit the number of places where the state of an entity can be changed, leading to less duplication
You protect your class' invariants. By forcing creation of an entity to go via a constructor, you ensure that it is IMPOSSIBLE for an object of your entity to exist in an invalid or unknown state.
You get higher cohesion. By putting the constraints on data closer to the data itself, it becomes easier to understand and reason about your classes.
You code becomes self-documenting to a higher degree. One never has to wonder "is it OK if I set a negative value on this int property?" if it is impossible to even do it in the first place.
Separation of concerns. Your manager shouldn't know how to validate an entity, this just leads to high coupling. I've seen many managers grow into unmaintainable monsters because they simply do everything. Persisting, loading, validation, error handling, conversion, mapping etc. This is basically the polar opposite of SOLID OOP.
I know it is really popular nowadays to just make all "models" into stupid property bags with getters and setters and only a default constructor because (bad) ORMs have forced us to do this, but this is no longer the case, and there are so many issues with this imo.
Code example:
public class Post
{
protected Post() // this constructor is only for EF proxy creation
{
}
public Post(string name, string description)
{
if (/* validation check, inline or delegate */)
throw new ArgumentException();
Name = name;
Description = description;
}
public int Id { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
}
Then your PostManager code becomes trivial:
using (var db = new DbContext())
{
var post = new Post(name, description); // possibly try-catch here
db.Posts.Add(post);
db.SaveChanges();
return post.Id;
}
If the creation/validation logic is extremely intricate this pattern lends itself very well for refactoring to a factory taking care of the creation.
I would also note that encapsulating data in entities exposing a minimal state-changing API leads to classes that are several orders of magnitude easier to test in isolation, if you care at all about that sort of thing.
As I mentioned in the comments above, you might want to check out .NET System.ComponentModel.DataAnnotations namespace.
Data Annotations (DA) allows you to specify attributes on properties to describe what values are acceptable. It's important to know that DA is completely independent of databases and ORM APIs such as Entity Framework so classes decorated with DA attributes can be used in any tier of your system whether it be the data tier; WCF; ASP.NET MVC or WPF.
In the example below, I define a Muppet class with a series of properties.
Name is required and has a max length of 50.
Scaryness takes an int but it must be in the range of {0...100}.
Email is decorated with an imaginary custom validator for validating strings that should contain an e-mail.
Example:
public class Muppet
{
[Required]
[StringLength(50)]
public string Name {get; set;}
public Color Color {get; set; }
[Range(0,100)]
public int Scaryness {get; set; }
[MyCustomEmailValidator]
public string Email {get;set; }
}
In my project I have to throw customException when i validate the data. It's possible do it using Data Annotations?
Yes you can. To validate this object at any time of your application (regardless of whether it has reached EF or not) just perform this:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
.
.
.
Post post = ... // fill it in
Validator.Validate(post);
public static class Validator
{
public static void Validate(this Post post)
{
// uses the extension method GetValidationErrors defined below
if (post.GetValidationErrors().Any())
{
throw new MyCustomException();
}
}
}
public static class ValidationHelpers
{
public static IEnumerable<ValidationResult> GetValidationErrors(this object obj)
{
var validationResults = new List<ValidationResult>();
var context = new ValidationContext(obj, null, null);
Validator.TryValidateObject(obj, context, validationResults, true);
return validationResults;
}
.
.
.
If you want to get the validation error messages you could use this method:
/// <summary>
/// Gets the validation error messages for column.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public static string GetValidationErrorMessages(this object obj)
{
var error = "";
var errors = obj.GetValidationErrors();
var validationResults = errors as ValidationResult[] ?? errors.ToArray();
if (!validationResults.Any())
{
return error;
}
foreach (var ee in validationResults)
{
foreach (var n in ee.MemberNames)
{
error += ee + "; ";
}
}
return error;
}
The free set of steak knives is that the validation attributes will be detected once the object reaches EF where it will be validated there as well in case you forget or the object is changed since.
I think you should be working with Data Annotation as #Micky says above. Your current approach is validating manually after it has been added.
using System.ComponentModel.DataAnnotations;
// Your class
public class Post
{
[Required]
public int Id { get; set; }
[Required,MaxLength(50)]
public string Name { get; set; }
[Required,MinLength(15),MyCustomCheck] // << Here is your custom validator
public string Description { get; set; }
}
// Your factory methods
public class MyFactory() {
public bool AddPost() {
var post = new Post() { Id = 1, Name = null, Description = "This is my test post"};
try {
using (var db = new DbContext()) {
db.Posts.Add(post);
db.SaveChanges();
return true;
}
} catch(System.Data.Entity.Validation.DbEntityValidationException e) {
Console.WriteLine("Something went wrong....");
} catch(MyCustomException e) {
Console.WriteLine(" a Custom Exception was triggered from a custom data annotation...");
}
return false;
}
}
// The custom attribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MyCustomCheckAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value instanceof string) {
throw new MyCustomException("The custom exception was just triggered....")
} else {
return true;
}
}
}
// Your custom exception
public class MyCustomException : Exception() {}
See also:
DbEntityValidationException class: https://msdn.microsoft.com/en-us/library/system.data.entity.validation.dbentityvalidationexception(v=vs.113).aspx
Default data annotations
http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx
Building your custom data annotations (validators):
https://msdn.microsoft.com/en-us/library/cc668224.aspx
I always use two validations:
client side - using jQuery Unobtrusive Validation in combination with Data Annotations
server side validation - and here it depends on application - validation is performed in controller actions or deeper in business logic. Nice place to do it is to override OnSave method in your context and do it there
Remember that you can write custom Data Annotation attributes which can validate whatever you need.
You can modify the code in this way:
public int AddPost(string name, string description)
{
var post = new Post() { Name = name, Description = description };
if(res.Validate())
{
using (var db = new DbContext())
{
var res = db.Posts.Add(post);
db.SaveChanges();
return res.Id;
}
}
else
return -1; //if not success
}
public static bool Validate(this Post post)
{
bool isValid=false;
//validate post and change isValid to true if success
if(isvalid)
return true;
}
else
return false;
}
After adding data to DbContext and before calling SaveChanges() you can call GetValidationErrors() method of DbContext and check its count to quiqckly check if there are any errors. You can further enumerate all of errors and get error details against each of them. I have bundled Error conversion from ICollection to string in GetValidationErrorsString() extension method.
if (db.GetValidationErrors().Count() > 0)
{
var errorString = db.GetValidationErrorsString();
}
public static string GetValidationErrorsString(this DbContext dbContext)
{
var validationErrors = dbContext.GetValidationErrors();
string errorString = string.Empty;
foreach (var error in validationErrors)
{
foreach (var innerError in error.ValidationErrors)
{
errorString += string.Format("Property: {0}, Error: {1}<br/>", innerError.PropertyName, innerError.ErrorMessage);
}
}
return errorString;
}

How to Ignore Null values while serializing OData response

I've a requirement to omit the null valued fields from the response altogether.
I can do this by modifying the JsonFormatter Serialization Setting for a normal webapi response.
config.Formatters.JsonFormatter.SerializationSettings
.NullValueHandling = NullValueHandling.Ignore;
But that does not seem to work once i switch to OData.
Here are my files:
WebApi.config:
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
var workerEntitySet = builder.EntitySet<Item>("Values");
config.Routes.MapODataRoute("Default", "api", builder.GetEdmModel());
}
Item Model:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string OptionalField { get; set; }
}
ValuesController:
public class ValuesController : EntitySetController<Item, int>
{
public static List<Item> items = new List<Item>()
{
new Item { Id = 1, Name = "name1", OptionalField = "Value Present" },
new Item { Id = 3, Name = "name2" }
};
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public override IQueryable<Item> Get()
{
return items.AsQueryable();
}
[Queryable]
protected override Item GetEntityByKey(int id)
{
return items.Single(i => i.Id == id);
}
}
Here is the response I get for GET: api/Values.
{
"odata.metadata":"http://localhost:28776/api/$metadata#Values",
"value":[
{
"Id":1,
"Name":"name1",
"OptionalField":"Value Present"
},
{
"Id":3,
"Name":"name2",
"OptionalField":null
}
]
}
But I do not need the elements with null values present in the response - in the response below, I need the "OptionalField" not to be present in the second item (As its value is null). I need to achieve it in my response, I do not want the users to query for non-null values only.
In ODataLib v7 things changed drastically around these sorts of customisations thanks to Depencency Injection (DI)
This advice is for anyone who has upgraded to ODataLib v7, who may have implemented the previously accepted answers.
If you have the Microsoft.OData.Core nuget package v7 or later then this applies to you :). If you are still using older versions then use the code provided by #stas-natalenko but please DO NOT stop inheriting from ODataController...
We can globally override the DefaultODataSerializer so that null values are omitted from all Entity and Complex value serialized outputs using the following steps:
Define your custom Serializer that will omit properties with null values
Inherit from Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSerializer
/// <summary>
/// OData Entity Serilizer that omits null properties from the response
/// </summary>
public class IngoreNullEntityPropertiesSerializer : ODataResourceSerializer
{
public IngoreNullEntityPropertiesSerializer(ODataSerializerProvider provider)
: base(provider) { }
/// <summary>
/// Only return properties that are not null
/// </summary>
/// <param name="structuralProperty">The EDM structural property being written.</param>
/// <param name="resourceContext">The context for the entity instance being written.</param>
/// <returns>The property be written by the serilizer, a null response will effectively skip this property.</returns>
public override Microsoft.OData.ODataProperty CreateStructuralProperty(Microsoft.OData.Edm.IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)
{
var property = base.CreateStructuralProperty(structuralProperty, resourceContext);
return property.Value != null ? property : null;
}
}
Define a Provider that will determine when to use our custom Serializer
Inherit from Microsoft.AspNet.OData.Formatter.Serialization.DefaultODataSerializerProvider
/// <summary>
/// Provider that selects the IngoreNullEntityPropertiesSerializer that omits null properties on resources from the response
/// </summary>
public class IngoreNullEntityPropertiesSerializerProvider : DefaultODataSerializerProvider
{
private readonly IngoreNullEntityPropertiesSerializer _entityTypeSerializer;
public IngoreNullEntityPropertiesSerializerProvider(IServiceProvider rootContainer)
: base(rootContainer) {
_entityTypeSerializer = new IngoreNullEntityPropertiesSerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(Microsoft.OData.Edm.IEdmTypeReference edmType)
{
// Support for Entity types AND Complex types
if (edmType.Definition.TypeKind == EdmTypeKind.Entity || edmType.Definition.TypeKind == EdmTypeKind.Complex)
return _entityTypeSerializer;
else
return base.GetEdmTypeSerializer(edmType);
}
}
Now we need to Inject this into your Container Builder.
the specifics of this will vary depending on your version of .Net, for many older projects this will be where you are mapping the ODataServiceRoute, this will usually be located in your startup.cs or WebApiConfig.cs
builder => builder
.AddService(ServiceLifetime.Singleton, sp => model)
// Injected our custom serializer to override the current ODataSerializerProvider
// .AddService<{Type of service to Override}>({service lifetime}, sp => {return your custom implementation})
.AddService<Microsoft.AspNet.OData.Formatter.Serialization.ODataSerializerProvider>(ServiceLifetime.Singleton, sp => new IngoreNullEntityPropertiesSerializerProvider(sp));
And there you have it, re-exeecute your query and you should get the following:
{
"odata.metadata":"http://localhost:28776/api/$metadata#Values",
"value":[
{
"Id":1,
"Name":"name1",
"OptionalField":"Value Present"
},
{
"Id":3,
"Name":"name2"
}
]
}
This is a very handy solution that can significantly reduce the data consumption on many data entry applications based on OData Services
NOTE: At this point in time, this technique must be used to override any of these default services: (as defined here OData.Net - Dependency Injection Support
Service Default Implementation Lifetime Prototype?
-------------------------- -------------------------- ---------- ---------
IJsonReaderFactory DefaultJsonReaderFactory Singleton N
IJsonWriterFactory DefaultJsonWriterFactory Singleton N
ODataMediaTypeResolver ODataMediaTypeResolver Singleton N
ODataMessageReaderSettings ODataMessageReaderSettings Scoped Y
ODataMessageWriterSettings ODataMessageWriterSettings Scoped Y
ODataPayloadValueConverter ODataPayloadValueConverter Singleton N
IEdmModel EdmCoreModel.Instance Singleton N
ODataUriResolver ODataUriResolver Singleton N
UriPathParser UriPathParser Scoped N
ODataSimplifiedOptions ODataSimplifiedOptions Scoped Y
UPDATE: How to handle lists or complex types
Another common scenario is to exclude complex types from the output if all of their properties are null, especially now that we do not include the null properties. We can override the WriteObjectInline method in the IngoreNullEntityPropertiesSerializer for this:
public override void WriteObjectInline(object graph, IEdmTypeReference expectedType, Microsoft.OData.ODataWriter writer, ODataSerializerContext writeContext)
{
if (graph != null)
{
// special case, nullable Complex Types, just skip them if there is no value to write
if (expectedType.IsComplex() && graph.GetType().GetProperty("Instance")?.GetValue(graph) == null
&& (bool?)graph.GetType().GetProperty("UseInstanceForProperties")?.GetValue(graph) == true)
{
// skip properties that are null, especially if they are wrapped in generic types or explicitly requested by an expander
}
else
{
base.WriteObjectInline(graph, expectedType, writer, writeContext);
}
}
}
Q: And if we need to omit null list properties as well?
If you wanted to use the same logic to exclude all lists, if they are null, then you could remove the expectedType.IsComplex() clause:
// special case, nullable Complex Types, just skip them if there is no value to write
if (graph.GetType().GetProperty("Instance")?.GetValue(graph) == null
&& (bool?)graph.GetType().GetProperty("UseInstanceForProperties")?.GetValue(graph) == true)
{
// skip properties that are null, especially if they are wrapped in generic types or explicitly requested by an expander
}
I don't recommend this for lists that are navigation properties, navigation properties will only be included in the output if they are explicitly requested in an $expand clause, or by other convention-based logic you may have that does the same thing. An empty or null array in the output might be significant for some client side logic as a confirmation that the requested property data was loaded but that there is no data to return.
I know it does not look anyway logical, but simply adding DefaultODataSerializerProvider and DefaultODataDeserializerProvider to the list of Formatters did the trick for me:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//...
var odataFormatters = System.Web.OData.Formatter.ODataMediaTypeFormatters.Create(
System.Web.OData.Formatter.Serialization.DefaultODataSerializerProvider.Instance,
System.Web.OData.Formatter.Deserialization.DefaultODataDeserializerProvider.Instance);
config.Formatters.AddRange(odataFormatters);
UPDATE
Since the global formatters modification didn't work correctly for me, I chose a different way.
First I stepped away from the ODataController and inherited my controller from ApiController using a custom ODataFormatting attribute:
[ODataRouting]
[CustomODataFormatting]
public class MyController : ApiController
{
...
}
public class CustomODataFormattingAttribute : ODataFormattingAttribute
{
public override IList<System.Web.OData.Formatter.ODataMediaTypeFormatter> CreateODataFormatters()
{
return System.Web.OData.Formatter.ODataMediaTypeFormatters.Create(
new CustomODataSerializerProvider(),
new System.Web.OData.Formatter.Deserialization.DefaultODataDeserializerProvider());
}
}
The formatting attribute replaces the DefaultODataSerializerProvider with a modified one:
public class CustomODataSerializerProvider : DefaultODataSerializerProvider
{
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if(edmType.Definition.TypeKind == EdmTypeKind.Entity)
return new CustomODataEntityTypeSerializer(this);
else
return base.GetEdmTypeSerializer(edmType);
}
}
And the last, the custom serializer filters structural properties with null values:
public class CustomODataEntityTypeSerializer : System.Web.OData.Formatter.Serialization.ODataEntityTypeSerializer
{
public CustomODataEntityTypeSerializer(ODataSerializerProvider provider)
: base(provider) { }
public override ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, EntityInstanceContext entityInstanceContext)
{
var property = base.CreateStructuralProperty(structuralProperty, entityInstanceContext);
return property.Value != null ? property : null;
}
}
This doesn't seem to me like the best possible solution, but this is the one I found.
All the method are same I made changes to webapiconfig
var odataFormatters = ODataMediaTypeFormatters.Create(new CustomODataSerializerProvider(), new DefaultODataDeserializerProvider());
config.Formatters.InsertRange(0, odataFormatters);
This help me to solve the result

How can I tell the Data Annotations validator to also validate complex child properties?

Can I automatically validate complex child objects when validating a parent object and include the results in the populated ICollection<ValidationResult>?
If I run the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ConsoleApplication1
{
public class Person
{
[Required]
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
[Required]
public string Street { get; set; }
[Required]
public string City { get; set; }
[Required]
public string State { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person
{
Name = null,
Address = new Address
{
Street = "123 Any St",
City = "New York",
State = null
}
};
var validationContext = new ValidationContext(person, null, null);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(person, validationContext, validationResults);
Console.WriteLine(isValid);
validationResults.ForEach(r => Console.WriteLine(r.ErrorMessage));
Console.ReadKey(true);
}
}
}
I get the following output:
False
The Name field is required.
But I was expecting something similar to:
False
The Name field is required.
The State field is required.
I offered a bounty for a better child object validation solution but didn't get any takers, ideally
validating child objects to an arbitrary depth
handling multiple errors per object
correctly identifying the validation errors on the child object fields.
I'm still surprised the framework doesn't support this.
Issue - Model Binder Order
This is, unfortunately, the standard behavior of Validator.TryValidateObject which
does not recursively validate the property values of the object
As pointed out in Jeff Handley's article on Validating Object and Properties with the Validator, by default, the validator will validate in order:
Property-Level Attributes
Object-Level Attributes
Model-Level implementation IValidatableObject
The problem is, at each step of the way...
If any validators are invalid, Validator.ValidateObject will abort validation and return the failure(s)
Issue - Model Binder Fields
Another possible issue is that the model binder will only run validation on objects that it has decided to bind. For example, if you don't provide inputs for fields within complex types on your model, the model binder won't need to check those properties at all because it hasn't called the constructor on those objects. According to Brad Wilson's great article on Input Validation vs. Model Validation in ASP.NET MVC:
The reason we don't "dive" into the Address object recursively is that there was nothing in the form that bound any values inside of Address.
Solution - Validate Object at the same time as Properties
One way to solve this problem is to convert object-level validations to property level validation by adding a custom validation attribute to the property that will return with the validation result of the object itself.
Josh Carroll's article on Recursive Validation Using DataAnnotations provides an implementation of one such strategy (originally in this SO question). If we want to validate a complex type (like Address), we can add a custom ValidateObject attribute to the property, so it is evaluated on the first step
public class Person {
[Required]
public String Name { get; set; }
[Required, ValidateObject]
public Address Address { get; set; }
}
You'll need to add the following ValidateObjectAttribute implementation:
public class ValidateObjectAttribute: ValidationAttribute {
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
var results = new List<ValidationResult>();
var context = new ValidationContext(value, null, null);
Validator.TryValidateObject(value, context, results, true);
if (results.Count != 0) {
var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
}
public class CompositeValidationResult: ValidationResult {
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results {
get {
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage) {}
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) {}
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) {}
public void AddResult(ValidationResult validationResult) {
_results.Add(validationResult);
}
}
Solution - Validate Model at the Same time as Properties
For objects that implement IValidatableObject, when we check the ModelState, we can also check to see if the model itself is valid before returning the list of errors. We can add any errors we want by calling ModelState.AddModelError(field, error). As specified in How to force MVC to Validate IValidatableObject, we can do it like this:
[HttpPost]
public ActionResult Create(Model model) {
if (!ModelState.IsValid) {
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
return View(post);
}
}
Also, if you want a more elegant solution, you can write the code once by providing your own custom model binder implementation in Application_Start() with ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());. There are good implementations here and here
I also ran into this, and found this thread. Here's a first pass:
namespace Foo
{
using System.ComponentModel.DataAnnotations;
using System.Linq;
/// <summary>
/// Attribute class used to validate child properties.
/// </summary>
/// <remarks>
/// See: http://stackoverflow.com/questions/2493800/how-can-i-tell-the-data-annotations-validator-to-also-validate-complex-child-pro
/// Apparently the Data Annotations validator does not validate complex child properties.
/// To do so, slap this attribute on a your property (probably a nested view model)
/// whose type has validation attributes on its properties.
/// This will validate until a nested <see cref="System.ComponentModel.DataAnnotations.ValidationAttribute" />
/// fails. The failed validation result will be returned. In other words, it will fail one at a time.
/// </remarks>
public class HasNestedValidationAttribute : ValidationAttribute
{
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult"/> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = true;
var result = ValidationResult.Success;
var nestedValidationProperties = value.GetType().GetProperties()
.Where(p => IsDefined(p, typeof(ValidationAttribute)))
.OrderBy(p => p.Name);//Not the best order, but at least known and repeatable.
foreach (var property in nestedValidationProperties)
{
var validators = GetCustomAttributes(property, typeof(ValidationAttribute)) as ValidationAttribute[];
if (validators == null || validators.Length == 0) continue;
foreach (var validator in validators)
{
var propertyValue = property.GetValue(value, null);
result = validator.GetValidationResult(propertyValue, new ValidationContext(value, null, null));
if (result == ValidationResult.Success) continue;
isValid = false;
break;
}
if (!isValid)
{
break;
}
}
return result;
}
}
}
You will need to make your own validator attribute (eg, [CompositeField]) that validates the child properties.

Categories

Resources