【问题标题】:ASP.NET Core Cannot Read Request BodyASP.NET Core 无法读取请求正文
【发布时间】:2016-03-18 11:01:30
【问题描述】:

几周以来,我一直在研究 ASP.NET Core。我试图根据这个博客实现一些目标: Microservices

我的project.json如下:

{
  "version": "1.0.0-*",
  "compilationOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {

    "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
    "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
    "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-*",
    "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
    "EntityFramework.Core": "7.0.0-rc1-final",
    "EntityFramework.Commands": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer.Design": "7.0.0-rc1-final",
    "Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-rc1-final",
    "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-rc1-final",
    "System.Security.Cryptography.Algorithms": "4.0.0-beta-23516"

  },

  "commands": {
    "web": "Microsoft.AspNet.Server.Kestrel",
    "ef": "EntityFramework.Commands"
  },

  "frameworks": {

    "dnxcore50": {
      "dependencies": {


      }

    }
  },

  "exclude": [
    "wwwroot",
    "node_modules"
  ],
  "publishExclude": [
    "**.user",
    "**.vspscc"
  ]
}

Startup.cs中的ConfigureServices方法如下:

public void ConfigureServices(IServiceCollection services)
{
    //Registering Authorization Database
    AutorizationAccessRegisteration.RegisterComponents(services, Configuration);

    services.AddMvcCore()
        .AddJsonFormatters(a => a.ContractResolver = new CamelCasePropertyNamesContractResolver());

    //Add cors built in support.
    services.AddCors();

    services.AddMvcCore().AddApiExplorer();

    //Add MVC for supporting WebApi requests
    #region MVC Add

    services.AddMvc();

    services.AddMvc().AddMvcOptions(options =>
    {
        options.RespectBrowserAcceptHeader = true;

        // Input Formatters.
        options.InputFormatters.Clear();

        var jsonInputFormatter = new JsonInputFormatter()
        {
            SerializerSettings = new JsonSerializerSettings()
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
                ,
                DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
                NullValueHandling = NullValueHandling.Ignore
            }
        };


        options.InputFormatters.Add(jsonInputFormatter);

        //Output formater
        //as part of get/post request, set the header Accept = application/json or application/xml
        var jsonOutputFormatter = new JsonOutputFormatter();
        jsonOutputFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        jsonOutputFormatter.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore;
        options.OutputFormatters.Insert(0, jsonOutputFormatter);

        options.OutputFormatters.Insert(1, new XmlDataContractSerializerOutputFormatter());

    });

    #endregion
}

这是我在 Startup.cs 中的 Confiure 方法:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {

    }
    else if (env.IsStaging())
    {

    }
    else if (env.IsProduction())
    {

    }

    app.UseIISPlatformHandler();

    app.UseCors(builder =>
            builder.WithOrigins("*").AllowAnyHeader().AllowAnyMethod());

    //Middlewares addition to the pipelines:
    /// We add the middlewares in the following fashion:
    /// - Exception Handler
    /// - Logger
    /// - Authorization Handler
    /// There is a big reason of doing that.
    ///
    app.UseExceptionHandler();
    app.UseLoggerHandler();
    app.UseAuthorizationHandler();

    app.UseMvc();
}

一个AuthorizationController如下:

[Route("api/Authorization")]
public class AuthorizationController : Controller
{
 .
     [HttpPost]
     public void Post([FromBody]string value)
     {
     }
 .
}

Post 方法最初有[FromBody]string[] value。我通过使其成为简单的string 类型进一步简化了它。我在 Chrome 上使用 Advance Rest Client 发送HTTP request。当string[] 是我的类型时,正文中的值如下:

{

  ["value","sdjklgsdjlg"]

}

简化参数后,我尝试使用以下正文发送请求:

{"sdjklgsdjlg"}

也试过了:

{"value":"sdjklgsdjlg"}

我错过了什么吗?我之前读过,旧的 WebApi 过去使用 JSON 映射到复杂对象和普通参数的工作方式,它在 .NET Core 中以类似的方式工作。

另外我应该详细说明 breakpoint 在所有中间件和控制器上正常命中。但似乎没有一个中间件能够读取 Request 的流相关内容:

请告诉我哪里出了问题。非常感谢!

【问题讨论】:

  • 您的网络嗅探器对此有何看法?你可以粘贴http会话的捕获吗?另外,你能粘贴整个错误信息吗?
  • 什么意思?我正在使用 Advance Rest 客户端。如上所述,它显示了正确的请求标头和原始正文。
  • 你试过没有 [FromBody] 吗?我认为没有那个它会起作用
  • 我以前试过,现在又试了。但是您不认为如果该值没有显示在控制器中,那么这个[FromBody] 可能会出现问题。但事实并非如此。我发布了两张图片,这是我的第一个 ExceptionMiddleware,您可以看到请求的 Body 和 Form 变量中显示了哪些错误。
  • 你加了两次MVC,正常吗?并且 MvcCore 也被添加了两次。看起来很奇怪

标签: c# asp.net-core asp.net-core-mvc asp.net-core-1.0


【解决方案1】:

我来晚了,但想分享确切原因,以便任何其他用户可以获得准确的信息。

您无法在控制器处获取值,因为您将数据作为 JSON 对象发布:

{"value":"sdjklgsdjlg"} //See the curly braces represent an object.

为了解决这个问题,我们需要另一个对象来绑定这个数据。在控制器的操作中是这样的:

[HttpPost]
public void Post([FromBody]CustomViewModel data)
{
     ...
    //here you can get value as: data.Value
}

这里的 CustomViewModel 是一个类:

public CustomViewModel
{
    public string Value { get; set; }
}

如果您想根据当前的操作签名获取数据:

[HttpPost]
public void Post([FromBody]string value)
{
     ...
}

然后,您需要在请求正文中将数据作为 JSON 字符串传递:

"sdjklgsdjlg" //notice without any curly braces and property name

同样适用于字符串数组动作:

[HttpPost]
public void Post([FromBody]IEnumerable<string> values)
{
     ...
}

在请求正文中传递 JSON 字符串数组:

["item1", "item2"]

【讨论】:

  • 当我在正文中仅传递字符串时:“sdjklgsdjlg”然后我从 Web API 得到了这个异常。 “没有 MediaTypeFormatter 可用于从媒体类型为‘text/plain’的内容中读取‘String’类型的对象。”
【解决方案2】:

Mark Hughes 的回答在某些时候是正确的。如果您发布这种格式的 json,{ "": "sdjklgsdjlg" } modelbinder 应该能够将其绑定到简单的字符串模型,而无需包装模型。

【讨论】:

  • 我知道 DTO 的事情。正如马克休斯所说,这已经奏效了。你能告诉我为什么我无法阅读请求正文吗?在中间件中?
  • 哦,我不知道@tmg - 有趣!
【解决方案3】:

这对我有用:

[HttpPost]
public async Task<IActionResult> CreateApp([FromQuery]string userId)
{
    string appDefinition = await new StreamReader(Request.Body).ReadToEndAsync();
    var newAppJson = JObject.Parse(appDefinition);
...

【讨论】:

    【解决方案4】:

    你也可以这样做:

    [HttpPost]
    public System.Net.Http.HttpResponseMessage Post([FromBody]dynamic value)
    {
       //...
    }
    

    或用户[FromQuery] 并直接传递一个查询字符串值。

    【讨论】:

    • 您要发送什么正文?无论如何,我谦虚地建议您简化配置:您连续 4 次添加 MVC,我不知道当您多次执行时会发生什么。您还添加了两次 Cors。
    • 我尝试过各种身体。但不能使其任何工作。甚至不是最简单的,例如:{"value":"someValue"} 我想以原始(字节)形式读取它,然后将其转换为字符串。但是连接到流的属性都不可用。至于你的说法,我多次添加 MVC。那么事实并非如此。我们必须在ConfigureServices 方法中注册服务,然后在Configure 方法中注册use 它们。这正是我正在做的。
    • 如果您根本看不到任何内容,我对您的建议是简单一点。删除所有你不需要的东西,没有身份验证,没有 cors。简单明了。您也可以尝试使用 Visual Studio 模板创建一个演示应用,然后检查差异。
    • 我一定会这样做的。但它不像我看不到任何内容。属性: context.Request.ContentLength 设置正确(这意味着它具有不同的值,具体取决于我发送的有效负载)。我在有效负载中的值也确实映射到控制器中的变量。但我无法读取中间件中的请求流。
    • 对不起,我现在知道了。不能两次读取Request流(web api先读取);为此,请尝试先重置位置,例如 HttpContext.Current.Request.InputStream.Position=0。检查这个:stackoverflow.com/questions/21971467/…
    【解决方案5】:

    [FromBody] 使用已注册的格式化程序将提交数据的整个正文解码为应用它的单个参数 - 默认情况下,唯一注册的格式化程序接受 JSON。

    在 JSON 中,没有直接表示字符串的有效方法 - {"sdjklgsdjlg"} 不是有效的 JSON,{"value":"sdjklgsdjlg"} 是,但不会反序列化为简单的字符串参数。 编辑:查看@tmg 的答案,这可以使用语法{ "": "sdjklgsdjlg" }

    因此,您需要某种特定的模型类来表示您尝试从正文中获取的输入,例如:

    public class AuthRequest {
        public string Value { get; set; }
    }
    

    那么你应该可以成功做到:

    [Route("api/Authorization")]
    public class AuthorizationController : Controller
    {
        [HttpPost]
        public void Post([FromBody]AuthRequest authReq)
        {
            // authReq.Value should have your value in
        }
    }
    

    现在,如果您在此发布 { "Value": "some value" },它应该会如您所愿。

    【讨论】:

    • 在旧的 WebApi 2 控制器中,我一直在使用接受字符串 FromQuery,但它在这里也不起作用。好吧,您的 DTO 技巧确实允许我从请求中获取值以映射到我的方法。但我仍然无法阅读 Request.Body 和其他涉及上下文的内容。
    • @FreshDev 您仍然可以将 [FromQuery] 用于查询参数 - 只是不能用于正文绑定 - 如果您请求 api/Authorization?value=horse 然后使用 [FromQuery]string value 它应该可以正确解析。
    • 感谢@mark休斯,我为此苦苦挣扎了一段时间,很好的解释
    【解决方案6】:

    您应该使用所有依赖项的 RC2 版本。
    https://github.com/aspnet/KestrelHttpServer/issues/915 我发现System.Threading.Tasks.Extensions 的版本默认是4.0.0。所以你应该在 project.json 文件中明确指定这个包的版本:

    "System.Threading.Tasks.Extensions": "4.0.0-rc2-24027"
    

    【讨论】:

    • 在我写这篇文章的时候,我已经升级到 RTM,但还是一样。
    猜你喜欢
    • 1970-01-01
    • 2020-05-16
    • 2020-11-10
    • 2016-03-15
    • 1970-01-01
    • 1970-01-01
    • 2021-03-13
    • 2021-12-04
    • 2020-05-04
    相关资源
    最近更新 更多