Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exclude a type from model validation (example DbGeography) to avoid InsufficientExecutionStackException

UPDATE: for the tl;dr version skip to the bottom


I have a pretty simple subclass of JsonConverter that I'm using with Web API:

public class DbGeographyJsonConverter : JsonConverter
{
    public override bool CanConvert(Type type)
    {
        return typeof(DbGeography).IsAssignableFrom(type);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = (string)reader.Value;

        if (value.StartsWith("POINT", StringComparison.OrdinalIgnoreCase))
        {
            return DbGeography.PointFromText(value, DbGeography.DefaultCoordinateSystemId);
        }
        else if (value.StartsWith("POLYGON", StringComparison.OrdinalIgnoreCase))
        {
            return DbGeography.FromText(value, DbGeography.DefaultCoordinateSystemId);
        }
        else //We don't want to support anything else right now.
        {
            throw new ArgumentException();
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, ((DbGeography)value).AsText());
    }
}

The problem is, after ReadJson returns the application never returns a bound object to the action method as it appears to be stuck in an infinite validation loop.

Here's the top of the call stack when I pause execution:

System.Web.Http.dll!System.Web.Http.Metadata.Providers.AssociatedMetadataProvider.GetMetadataForPropertiesImpl.AnonymousMethod__0() Line 40 C# System.Web.Http.dll!System.Web.Http.Metadata.ModelMetadata.Model.get() Line 85 C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(System.Web.Http.Metadata.ModelMetadata metadata, System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext, object container) Line 94 C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(System.Web.Http.Metadata.ModelMetadata metadata, System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext) Line 156 C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(System.Web.Http.Metadata.ModelMetadata metadata, System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext, object container) Line 130 C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateElements(System.Collections.IEnumerable model, System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext) Line 176 C#

After that, the DefaultBodyModelValidator.Validation* pattern of calls repeats over and over and over again. Everytime I pause execution, it appears to be at about the same depth, so it doesn't appear to be getting recursively deeper.

If I force the JsonConverter to return null, control returns to the API controller action method, I'm assuming because there's nothing to validate.

I don't have the brain juices left to figure this one out. What am I doing wrong?


UPDATE: With brain juices somewhat replenished, I've stepped through most of the code and it appears that when validating the model the DefaultBodyModelValidator is drilling way down into the SqlTypesAssembly and getting stuck in a loop reading attributes somewhere. I don't really care to find out exactly where because I don't want the DefaultBodyModelValidator drilling into DbGeography type instances to start with.

There's no reason for model validation to drill down into the DbGeography class. I need to figure out how to get the MediaTypeFormatterCollection.IsTypeExcludedFromValidation method to return true for typeof(DbGeography), which will cause the DefaultBodyModelValidator to perform shallow validation on any DbGeography instances. So now the question at hand is- how do I exclude a type from model validation? The ShouldValidateType method of DefaultBodyModelValidator is marked virtual, but is there not a simple way to add an excluded type at startup?

like image 409
joelmdev Avatar asked Nov 12 '13 23:11

joelmdev


3 Answers

Whether this issue is a bug or a limitation of Web API, I do not know, but here's my workaround:

First, we need to subclass the DefaultBodyModelValidator and override the ShouldValidateType method.

public class CustomBodyModelValidator : DefaultBodyModelValidator
{
    public override bool ShouldValidateType(Type type)
    {
        return type!= typeof(DbGeography) && base.ShouldValidateType(type);
    }
}

Now in global.asax's Application_Start method, add

GlobalConfiguration.Configuration.Services.Replace(typeof(IBodyModelValidator), new CustomBodyModelValidator());

and that's it. Shallow validation will now be performed on the DbGeography type instances and everything binds nicely.

like image 87
joelmdev Avatar answered Nov 17 '22 22:11

joelmdev


The answer by joelmdev lead me in the right direction, but with my WebApi configuration in MVC and WebApi 5.2.3 the new validator would not get called when placed in Global.asax.

The solution was to put it in my WebApiConfig.Register method with the other WebApi routes: config.Services.Replace(typeof(IBodyModelValidator), new CustomBodyModelValidator());

like image 45
Sherwin F Avatar answered Nov 17 '22 20:11

Sherwin F


Just had exactly the same issue but then with a custom type. After quite a lot of reasearch it turned out to be quite logical with the knowledge of this thread. The custom class had a public readonly property which returned another instance of the same class. The validator walks down all properties of the class (even if you do not do any validation at all) and gets the value. If your class return a new instance of the same class this happens again and again and... It looks like the StartPoint property in the Geography class has this very same problem. https://msdn.microsoft.com/en-us/library/system.data.spatial.dbgeography.startpoint(v=vs.110).aspx

like image 1
Nico Timmerman Avatar answered Nov 17 '22 21:11

Nico Timmerman