我有一个 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”类型的对象。
实际上,遗憾的是 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());
由于 Web API 没有用于处理文本/纯文本的开箱即用格式化程序,因此有一些选项:
将您的操作修改为没有参数...原因是有参数会触发请求正文反序列化。现在您可以通过执行
await Request.Content.ReadAsStringAsync()
来获取字符串编写一个自定义 MediaTypeFormatter 来处理“文本/纯文本”...在这种情况下编写实际上很简单,您可以将参数保留在操作上。
在 ASP.NET Core 2.0 中,您只需执行以下操作:-
using (var reader = new StreamReader(Request.Body))
{
string plainText= reader.ReadToEnd();
// Do something else
return Ok(plainText);
}
使用 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 流程。请参阅此处:
“此方法的实现不应在完成后关闭 readStream。当 HttpContent 实例被处置时,该流将独立关闭。”
要使用它,请在构建时注册
HttpConfiguration
:
protected HttpConfiguration CreateHttpConfiguration()
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
...
httpConfiguration.Formatters.Add(new PlainTextFormatter());
...
return httpConfiguration;
}
在某些情况下,让 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" ) );
这个聚会已经很晚了,解决方案非常简化。 我在控制器方法中成功使用了此代码:
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"
};
这不是一个正确的答案,而是一个快速但肮脏的解决方法来解锁开发......
事实证明,用引号分隔的字符串本身就是有效的 JSON。因此,如果您确定内容总是非常简单,则可以将其用双引号括起来,并将其命名为 application/json。
// TODO: Temporary, fix for production
HttpContent content = new StringContent($"\"{command}\"", UTF8Encoding.UTF8, "application/json");
自 .NET 5+ 起,格式化程序已分为
TextInputFormatter
和 TextOutputFormatter
。因此,支持纯文本作为正文内容的结果类略有变化:
public class PlainTextFormatter : TextInputFormatter
{
public PlainTextFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue(MediaTypeNames.Text.Plain));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.ASCII);
}
protected override bool CanReadType(Type type) => type == typeof(string);
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var reader = new StreamReader(context.HttpContext.Request.Body);
var plainText = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(plainText);
}
}
在
Startup.cs
或Program.cs
注册:
services.AddControllers(options => options.InputFormatters.Add(new PlainTextFormatter()))
并确保通过
Content-Type: text/plain
发送您的请求。