Posting models with NodaTime auto-props to WebAPI

2019-02-24 08:29发布

问题:

We are in the process of using NodaTime for time / date models in our project. The project is a web application, using WebAPI. When attempting to post a model containing LocalTime autoprops, we receive an InsufficientExecutionStackException.

In order to isolate our project from this issue, I created a new WebAPI project. Using NuGet, I added NodaTime and NodaTime.Serialization.JsonNet to the project. In Startup.cs, I used ConfigureForNodaTime as such:

GlobalConfiguration.Configure(x =>
{
    x.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
    x.Formatters.JsonFormatter.SerializerSettings.ConfigureForNodaTime(
    DateTimeZoneProviders.Tzdb);
});

In the (boilerplate) ValuesController.cs file, I hijacked Post as such:

// POST api/values
public void Post(TestClass input)
{
    var x = input;
}

public class TestClass
{
    public LocalTime Open { get; set; }
    public LocalTime Close { get; set; }
}

When posting this input model (via postman), rather than receiving local times, I receive the error at the end of this post.

If we rewrite TestClass as shown, no errors occur, and we get the correct local times:

public class TestClass
{
    public LocalTime;
    public LocalTime;
}

Is there something I can do to get the application to work with autoprops? While I can (and probably will) start replacing autoprops with fields, it would be nice to understand what's going on here.

Here's the entire error, with 800 arbitrary lines removed from the middle:

{
"message":"An error has occurred.",
"exceptionMessage":"Insufficient stack to continue executing the program safely. This can happen from having too many functions on the call stack or function on the stack using too much stack space.",
"exceptionType":"System.InsufficientExecutionStackException",
"stackTrace":"   at System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateElements(IEnumerable model, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
    at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container, IEnumerable`1 validators)
    at System.Web.Http.Validation.DefaultBodyModelValidator.Validate(Object model, Type type, ModelMetadataProvider metadataProvider, HttpActionContext actionContext, String keyPrefix)
    at System.Web.Http.ModelBinding.FormatterParameterBinding.<ExecuteBindingAsyncCore>d__0.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
    at System.Web.Http.Controllers.HttpActionBinding.<ExecuteBindingAsyncCore>d__0.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
    at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
    at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
    at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()"
}

回答1:

It appears to be a WebAPI issue, but I don't fully understand the root cause, nor do I particularly like this solution. However, based on the code in this answer, I came up with a workaround:

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

In your configuration lambda (or wherever you are accessing the global WebAPI configuration:

x.Services.Replace(typeof(IBodyModelValidator), new CustomBodyModelValidator());

Consider this a hack of a workaround until I (or someone else) can figure out a better solution. Thanks.

Update: On further research, it appears this has already been posted to the Noda Time issue tracker as issue #249, and a similar solution was proposed.