【问题标题】:how to post plain text to ASP.NET Web API endpoint?如何将纯文本发布到 ASP.NET Web API 端点?
【发布时间】:2026-02-14 17:05:01
【问题描述】:

我有一个 ASP.NET Web API 端点,其控制器操作定义如下:

[HttpPost]
public HttpResponseMessage Post([FromBody] object text)

如果我的帖子请求正文包含纯文本(即不应被解释为 json、xml 或任何其他特殊格式),那么我想我可以在我的请求中包含以下标头:

Content-Type: text/plain

但是,我收到错误:

No MediaTypeFormatter is available to read an object of type 'Object' from content with media type 'text/plain'.

如果我将控制器操作方法签名更改为:

[HttpPost]
public HttpResponseMessage Post([FromBody] string text)

我收到一条略有不同的错误消息:

没有 MediaTypeFormatter 可用于从媒体类型为“text/plain”的内容中读取“String”类型的对象。

【问题讨论】:

  • 你不希望你的“文本”参数是字符串类型吗?
  • 是的,我也想过这个想法,但没有多大帮助(请参阅我修改后的问题)
  • @BaltoStar 我知道你的问题已经 8 个月大了,但我遇到了同样的问题。请参阅下面的答案。
  • 嗨 BaltoStar,您能否将@gzou 的答案标记为答案?这将有助于社区将其视为答案。谢谢

标签: c# asp.net-web-api content-type


【解决方案1】:

实际上,Web API 没有用于纯文本的MediaTypeFormatter,这很可惜。这是我实施的一个。它也可以用于发布内容。

public class TextMediaTypeFormatter : MediaTypeFormatter
{
    public TextMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        var taskCompletionSource = new TaskCompletionSource<object>();
        try
        {
            var memoryStream = new MemoryStream();
            readStream.CopyTo(memoryStream);
            var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
            taskCompletionSource.SetResult(s);
        }
        catch (Exception e)
        {
            taskCompletionSource.SetException(e);
        }
        return taskCompletionSource.Task;
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, System.Net.TransportContext transportContext, System.Threading.CancellationToken cancellationToken)
    {
        var buff = System.Text.Encoding.UTF8.GetBytes(value.ToString());
        return writeStream.WriteAsync(buff, 0, buff.Length, cancellationToken);
    }

    public override bool CanReadType(Type type)
    {
        return type == typeof(string);
    }

    public override bool CanWriteType(Type type)
    {
        return type == typeof(string);
    }
}

您需要通过以下方式在您的 HttpConfig 中“注册”此格式化程序:

config.Formatters.Insert(0, new TextMediaTypeFormatter());

【讨论】:

  • 正是我需要的!好东西!
  • 太棒了!这是一个很棒的解决方案!
  • 我想知道为什么不将 ReadFromStreamAsync 方法简化为: return await new StreamReader(readStream).ReadToEndAsync();
  • 我的 MediaTypeFormatter 在WriteToStreamAsync 上没有取消令牌参数。当我删除该论点时有效。
【解决方案2】:

由于 Web API 没有用于处理文本/纯文本的开箱即用格式化程序,因此有一些选项:

  1. 将您的操作修改为没有参数...原因是有参数会触发请求正文反序列化。现在您可以通过await Request.Content.ReadAsStringAsync() 来明确读取请求内容以获取字符串

  2. 编写一个自定义 MediaTypeFormatter 来处理“text/plain”...在这种情况下编写实际上很简单,您可以将参数保留在操作上。

【讨论】:

  • 感谢基兰的回复。实际上,我的动作签名有额外的 [FromUri] 参数,我省略了这些参数以保持我的问题简单。所以我想这意味着我无法避免请求正文的隐式反序列化?在这种情况下,我需要编写一个自定义 MediaTypeFormatter 来处理'text/plain' ....
  • 请求反序列化只会发生在 normally 被认为是从正文中读取的参数...示例:显式 [FromBody] 修饰参数,隐式从正文中读取(例如:复杂类型)。 ..所以我上面的答案只适用于从正文中读取的那些......所以你应该能够像往常一样使用FromUri参数......但无论如何创建自定义格式化程序是最好的方法它非常适合 Web API 设计..
  • 提供这个格式化程序不危险吗? text/plain 是具有 post 方法的 HTML 表单的有效编码类型。请务必添加防伪令牌以防止跨站请求伪造。
【解决方案3】:

在 ASP.NET Core 2.0 中,您只需执行以下操作:-

using (var reader = new StreamReader(Request.Body))
{
      string plainText= reader.ReadToEnd();

      // Do something else

      return Ok(plainText);
}

【讨论】:

  • 简单而完美!
  • 在 2020 年帮助了我。找到这个答案应该没有那么难。谢谢:)
【解决方案4】:

使用使用 async/await 的 gwenzek 格式化程序的纯化版本:

public class PlainTextFormatter : MediaTypeFormatter
{
    public PlainTextFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
    }

    public override bool CanReadType(Type type) =>
        type == typeof(string);

    public override bool CanWriteType(Type type) =>
        type == typeof(string);

    public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        var streamReader = new StreamReader(readStream);
        return await streamReader.ReadToEndAsync();
    }

    public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
    {
        var streamReader = new StreamWriter(writeStream);
        await streamReader.WriteAsync((string) value);
    }
}

请注意,我有意不处置 StreamReader/StreamWriter,因为这将处置底层流并破坏 Web Api 流。见here

“此方法的实现不应在完成后关闭 readStream。当释放 HttpContent 实例时,该流将独立关闭。”

要使用它,请在构建时注册HttpConfiguration

protected HttpConfiguration CreateHttpConfiguration()
{
    HttpConfiguration httpConfiguration = new HttpConfiguration();
    ...
    httpConfiguration.Formatters.Add(new PlainTextFormatter());
    ...
    return httpConfiguration;
}

【讨论】:

  • 具体配置会破坏什么?我发现在处理异常时不处理它们会导致错误。
【解决方案5】:

在某些情况下,让 JsonMediaTypeFormatter 完成工作可能会更简单:

var formatter = GlobalConfiguration.Configuration.Formatters.Where(f=>f is System.Net.Http.Formatting.JsonMediaTypeFormatter).FirstOrDefault();
if (!formatter.SupportedMediaTypes.Any( mt => mt.MediaType == "text/plain" ))
    formatter.SupportedMediaTypes.Add( new MediaTypeHeaderValue( "text/plain" ) );

【讨论】:

  • 如果我这样做,然后像这样设置正文:resp.Content = new StringContent(outputString, Encoding.UTF8, "text\plain");,它是否会按原样输出字符串(即使它不是 JSON)和纯文本内容类型?
【解决方案6】:

这个聚会很晚了,解决方案非常简单。 我在控制器方法中成功使用了这段代码:

       public HttpResponseMessage FileHandler()
       {
        HttpResponseMessage response = new HttpResponseMessage();

        using (var reader = new StreamReader(System.Web.HttpContext.Current.Request.GetBufferedInputStream()))
        {
            string plainText = reader.ReadToEnd();
        } .....}

在客户端,这些是我使用的 Ajax 选项:

var ajaxOptions = {
url: 'api/fileupload/' + "?" + $.param({ "key": metaKey2, "File-Operation": "Remove", "removalFilePath": $removalFilePath, "Audit-Model": model, "Parent-Id": $parentId, "Audit-Id": $auditId }),
type: 'POST', processData: false, contentType: false, data: "BOB"
};

【讨论】:

    【解决方案7】:

    不是一个正确的答案,而是一个快速的解决方法来解除开发障碍......

    事实证明,引号分隔的字符串本身就是有效的 JSON。所以如果你确定内容总是很简单,你可以用双引号把它包起来,命名为application/json。

    // TODO: Temporary, fix for production
    HttpContent content = new StringContent($"\"{command}\"", UTF8Encoding.UTF8, "application/json");
    

    【讨论】: