尝试调用控制器端点时,“JSON值无法转换为System.String”。

13

我一直在尝试创建一个简单的 API,我设法使Get正常工作,但每当我尝试使用PostPut时,它就无法正常工作。
我正在尝试将 JSON 发布/放置,并在我的控制器中将其作为字符串获取。
我正在使用 Postman 和 Insomnia 进行测试(我明确指出,我关闭了两者的 SSL 验证,因为我在本地运行)。
这是我的控制器:

[Route("backoffice/[controller]")]
[ApiController]
public class AddQuestionController : ControllerBase
{
    private IQuestionRepository _questionRepository;

    public AddQuestionController(IQuestionRepository questionRepository)
    {
        _questionRepository = questionRepository ?? throw new ArgumentNullException(nameof(questionRepository));

    }

    [ProducesResponseType((int)System.Net.HttpStatusCode.OK)]
    [HttpPost]
    public async Task<ActionResult> AddQuestion([FromBody] string question)
    {
        Question q = JsonConvert.DeserializeObject<Question>(question);
        await Task.Run(() => _questionRepository.InsertOne(q));
        return Ok();
    }
}

postman

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|a0b79872-4e41e975d19e251e.",
    "errors": {
        "$": [
            "The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1."
        ]
    }
}

所以我认为这是因为在Postman中使用的Json格式。但是当我尝试使用text格式时,发生了这种情况:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
    "title": "Unsupported Media Type",
    "status": 415,
    "traceId": "|a0b79873-4e41e975d19e251e."
}

每次都没有达到我的控制器的第一行,有人能告诉我这里出了什么问题吗?是我的控制器的问题吗?还是我的Postman使用方式有问题?

2个回答

25

模型绑定器无法将发送的数据映射/绑定到控制器参数

您的操作从请求正文中期望一个简单的字符串

public async Task<ActionResult> AddQuestion([FromBody] string question)

但是你发送了一个复杂的对象

{ "test" : "test" }

如果属性名称匹配,您可能已经得到了一个匹配项。

例如:

{ "question" : "test" }

由于模型绑定程序在匹配参数时会考虑属性名称。

如果您想接收原始字符串,则需要发送有效的原始 JSON 字符串。

"{ \"test\": \"test \"}"

这是正确转义的。

另一个选项是使用复杂对象作为参数。

class Question  {
    public string test { get; set; }
    //...other properties
}

符合预期数据的匹配

public async Task<ActionResult> AddQuestion([FromBody] Question question) {
    string value = question.test;

    //...
}

模型绑定器将绑定数据并将其传递给操作参数。

参考 ASP.NET Core中的模型绑定


谢谢您的模型绑定工作,您有任何想法为什么我不能只将它作为字符串检索?即使通过Postman传递字符串? - Platypus
嗨@Nkosi,我有一个类似的问题。我有以下API端点`` [HttpPost] public IActionResult CreateSkill([FromBody] Employee employee)。Employee对象类具有以下属性public int Id { get; set; } public string FirstName { get; set; } public string Surname { get; set; } public List<EmployeeSkillMiddleTable> EmployeeSkillMiddleTables { get; set; }其中EmployeeSkillMiddleTables`是多对多关系中间表的表示。...->继续下一个评论 - noruk
我遇到了以下错误:"errors": { "$.employeeSkillMiddleTables[0]": [ "The JSON value could not be converted to System.Collections.Generic.List1[Models.EmployeeSkillMiddleTable]. Path: $.employeeSkillMiddleTables[0] | LineNumber: 3 | BytePositionInLine: 35." ]` 请问如何将这些值传递给端点? - noruk
@noruk,在评论中很难理解您的问题。如果您发布一个实际的问题,它将有助于更好地理解实际问题,并允许其他人提供更多的帮助。 - Nkosi
先生,您真是个天才。谢谢。 - Shreekanth Gaanji
显示剩余4条评论

3

感谢 @Nkosi 发现了这个问题,并提供了这篇文章ASP.NET Core中的模型绑定技术

由于我花费了很多时间查看过时的API调用示例,所以在此提供我的代码作为参考(截至2020年9月):

在API项目中,我使用[BindProperty]属性来设置模型类的属性。

    // ASP.NET CORE API - C# model

    using Microsoft.AspNetCore.Mvc;
    using System;
    
    namespace MyTestAPI.Models
    {
        public partial class MyTest
        {
            [BindProperty]
            public int TestId { get; set; }
            [BindProperty]
            public string Message { get; set; }
            [BindProperty]
            public Guid? CreatedBy { get; set; }
            [BindProperty]
            public DateTime Timestamp { get; set; }
        }
    }

在API控制器中,由于MyTest模型类的属性特性,mytest类会自动进行反序列化处理。
// ASP.NET CORE API - C# controller

using Dapper;
using HangVue.API.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MyTestAPI.Models;

namespace HangVue.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {     

       [HttpPost]
       public void Post([FromBody] MyTest mytest)
       {

           var parameters = new DynamicParameters();
           parameters.Add("@pMessage", mytest.Message, System.Data.DbType.String, System.Data.ParameterDirection.Input);
           parameters.Add("@CreatedBy", mytest.CreatedBy.ToString(), System.Data.DbType.String, System.Data.ParameterDirection.Input);

           string sql = "[dbo].[uspTest]";

           using (var conn = new System.Data.SqlClient.SqlConnection(*** SQL_conn_string_goes_here ***))
           {
             var affectedRows = conn.Query(sql, parameters, commandType: System.Data.CommandType.StoredProcedure);
           }
       }
    }  
}

在客户端方面,我正在使用Xamarin.FormsRestSharp来调用我的API。 AccessToken 是必需的,因为我正在使用Azure AD B2C身份验证
// Xamarin.Forms - C# Client (iOS + Android)

using Microsoft.Identity.Client;
using RestSharp;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace YourApp.Services
{
    public static class TestServices
    {   

       public async static Task<string> NewTest(int iTestId, string sMessage, Guid? gCreatedBy, DateTime dTimestamp)
       {
          try
          {
             var mytest = new Models.MyTest 
             {
                TestId = iTestId,
                Message = sMessage,
                CreatedBy = gCreatedBy,
                Timestamp = dTimestamp
             };

             // Client --- API end-point example:  https://yourAPIname.azurewebsites.net/
             RestSharp.RestClient client = new RestClient(*** https://Your_API_base_end_point_goes_here ***);
            
             // Request 
             RestSharp.RestRequest request = new RestSharp.RestRequest("api/test", RestSharp.Method.POST, RestSharp.DataFormat.Json);
             request.AddParameter("Authorization", "Bearer " + *** Your_AccessToken_goes_here ***, RestSharp.ParameterType.HttpHeader);
             request.AddHeader("Content-Type","application/json; CHARSET=UTF-8");
             request.AddHeader("Accept", "application/json");
             request.AddJsonBody(mytest);

             // Invoke
             RestSharp.IRestResponse response = await client.ExecuteAsync(request);

             if (response.StatusCode == System.Net.HttpStatusCode.OK)
             {
                *** do something ***    
                return *** a string *** ;
             }
             else
             {
                *** do something ***
                return *** a string *** ;
             }
          }
          catch (Exception ex)
          {
             *** do something ***
          }
       }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接