【问题标题】:ASP.NET Core Deserializing From Body, w/ Default ValuesASP.NET Core 从正文反序列化,带默认值
【发布时间】:2018-11-09 03:04:49
【问题描述】:

我有以下控制器(缩写):

public class ServicesController : Controller
{
    private readonly IServiceRepository _repo;
    public ServicesController(IServiceRepository repo)
    {
        _repo = repo;
    }

    [HttpPost]
    public async Task<StatusCodeResult> CreateService([FromBody] JsonServiceEntity service)
    {
        return await _repo.CreateServiceAsync(new ServiceEntity(service));
    }
}

JsonServiceEntity 类看起来像这样(缩写):

public class JsonServiceEntity
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
}

Id 是一个 guid 形式的字符串。如果一个是从客户端传入的,我应该使用给定的 ID。如果客户端没有传入任何值,我应该生成一个新的 guid 并使用它。

但是,当客户端没有提供默认值时,我无法让它使用默认值。无论如何,它最终都是一个空字符串。我可以调试并观察它命中属性并为默认值生成一个新的 guid,它会被使用空字符串调用 setter 迅速覆盖。

我在Startup.cs 中的SerializerSettings 上尝试了NullValueHandlingDefaultValueHandling 的不同值,但这并没有帮助。如果我在Id 属性上设置[Required],我会收到一个ModelState 错误,让我知道尝试的值是null,而不是空字符串。

如何让它取默认值?

CONNECTServiceRegistry.dll!CONNECTServiceRegistry.Models.JsonServiceEntity.Id.set(string value) Line 45 C#
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.ExpressionValueProvider.SetValue(object target, object value) Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, object target)    Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, string id) Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue) Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)  Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(Newtonsoft.Json.JsonReader reader, System.Type objectType, bool checkAdditionalContent)  Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.DeserializeInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType)   Unknown
Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.Deserialize(Newtonsoft.Json.JsonReader reader, System.Type objectType)   Unknown
Microsoft.AspNetCore.Mvc.Formatters.Json.dll!Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatter.ReadRequestBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext context, System.Text.Encoding encoding) Line 303 C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter.ReadRequestBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext context) Line 57    C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Formatters.InputFormatter.ReadAsync(Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext context) Line 116  C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext bindingContext) Line 158   C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder modelBinder, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider valueProvider, Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor parameter, Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata metadata, object value) Line 239  C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.CreateBinderDelegate.__Bind|0(Microsoft.AspNetCore.Mvc.ControllerContext controllerContext, object controller, System.Collections.Generic.Dictionary<string, object> arguments) Line 77    C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.BindArgumentsAsync() Line 424   C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.State next, ref Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Scope scope, ref object state, ref bool isCompleted) Line 69   C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() Line 385   C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.State next, ref Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Scope scope, ref object state, ref bool isCompleted) Line 692  C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter() Line 793 C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.State next, ref Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Scope scope, ref object state, ref bool isCompleted) Line 407  C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync() Line 123    C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync() Line 81   C#
Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Internal.MvcAttributeRouteHandler.RouteAsync.AnonymousMethod__0(Microsoft.AspNetCore.Http.HttpContext c) Line 105    C#

【问题讨论】:

  • 你使用的是asp.net core 2.1吗?
  • 不是一个信息丰富的。我无法让 Visual Studio 在跟踪中显示对 Guid.NewGuid() 的调用。但是,我可以在默认值和设置器上放置一个断点,看到调用的是默认值,然后是设置器。调用 setter 时,Id 属性具有有效的 guid 值。无论如何,我已经添加了跟踪。
  • 是的,这是 asp.net core 2.1。
  • 您的客户端是否真的在 JSON 文本中传递了 Id: ""
  • 无论我如何尝试,都无法重现它。也许你应该看看客户端。似乎总是设置 Id。

标签: asp.net-core json.net deserialization asp.net-core-webapi


【解决方案1】:

假设客户端实际上传递了一个空字符串作为Id,您可以指示Json.Net 使用DefaultValue 属性将一个空字符串作为默认值处理:

public class JsonServiceEntity
{
    [DefaultValue("")]
    public string Id { get; set; } = Guid.NewGuid().ToString();
}

然后在 JSON 配置中,将其设置为 Ignore 默认值:

DefaultValueHandling = DefaultValueHandling.Ignore

Documentation

【讨论】:

  • 这非常适合我的情况,而且效果很好。谢谢!
  • 您还应该添加NullValueHandling = NullValueHandling.Ignore 来处理null 的情况。
【解决方案2】:

当您在自动实现的属性上指定默认值时,例如:

public string Id { get; set; } = Guid.NewGuid().ToString();

只有在根本没有设置值的情况下才会使用默认值。在这里,如果客户端没有设置 id,则根本不应该发送它,即:

如果这是您当前的 JSON:

{
    "id": "",
    "name": "foo"
}

那么客户端应该发送的是:

{
    "name": "foo"
}

然后,您将根据需要使用您的默认设置。如果您无法实现这一点,或者您只是想处理客户端错误发送空字符串的情况,那么您将需要自定义 getter 和 setter:

private string id;
public string Id
{
    get => id;
    set => id = string.IsNullOrWhiteSpace(value) ? Guid.NewGuid().ToString() : value;
}

【讨论】:

  • 传递 "id": null 将导致值为 null,而不是 NewGuid。所以,它必须被省略。
  • 每次访问Id都会生成一个新的guid。
  • 是的,如果您将对 Guid.NewGuid 的调用移到 setter 中,则效果会很好(或更改 getter 中 id 的值)。但是,我更喜欢所选的解决方案,因为它非常适合我的用例并且更加优雅。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
相关资源
最近更新 更多