更新: 我已经将一个小的测试项目上传到了github:链接
我正在使用 .Net Core 2 创建一个小型 Web 服务,并希望让客户端能够指定他们是否需要响应中的导航信息。Web API 应该只支持 xml 和 json,但如果客户端可以使用 接受:application/xml+hateoas 或 接受:application/json+hateoas 在他们的要求中。
我尝试像这样设置我的 AddMvc 方法:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json", MediaTypeHeaderValue.Parse("application/json"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml+hateoas", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json+hateoas", MediaTypeHeaderValue.Parse("application/json"));
})
.AddJsonOptions(options => {
// Force Camel Case to JSON
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters()
;
我在我的控制器方法中使用 accept 标头来区分正常的 xml/json 响应和类似 hateoas 的响应,如下所示:
[HttpGet]
[Route("GetAllSomething")]
public async Task<IActionResult> GetAllSomething([FromHeader(Name = "Accept")]string accept)
{
...
bool generateLinks = !string.IsNullOrWhiteSpace(accept) && accept.ToLower().EndsWith("hateoas");
...
if (generateLinks)
{
AddNavigationLink(Url.Link("GetSomethingById", new { Something.Id }), "self", "GET");
}
...
}
因此,简而言之,我不想创建自定义格式化程序,因为唯一的"自定义"方法是在我的响应中包含或排除导航链接,但响应本身应该是基于 Accept 标头值的 xml 或 json。
我的模型类看起来像这样(其中主要是字符串和基本值):
[DataContract]
public class SomethingResponse
{
[DataMember]
public int Id { get; private set; }
从 Fiddler 调用我的服务时,我得到了不同接受值的以下结果:
- 接受:应用程序/json -> 状态代码 200,仅包含请求的数据。
- 接受:application/json+hateoas -> 状态代码 406(不可接受)。
- 接受:应用程序/xml -> 状态代码 504。[提琴手]ReadResponse() 失败:服务器未返回此请求的完整响应。服务器返回 468 字节。
- 接受:application/xml+hateoas -> 状态代码 406(不可接受)。
有人可以告诉我哪个设置是错误的吗?
格式到媒体类型的映射(SetMediaTypeMappingForFormat
调用)无法按预期工作。此映射不使用请求中的Accept
标头。它从路由数据或 URL 查询字符串中名为format
的参数读取请求的格式。还应使用FormatFilter
属性标记控制器或操作。有几篇关于基于FormatFilter
属性的响应格式的好文章,请查看此处和此处。
要修复当前的格式映射,应执行以下操作:
-
重命名格式,使其不包含加号。特殊的
+
字符在传入URL时会给您带来麻烦。最好用-
替换它:options.FormatterMappings.SetMediaTypeMappingForFormat( "xml-hateoas", MediaTypeHeaderValue.Parse("application/xml")); options.FormatterMappings.SetMediaTypeMappingForFormat( "json-hateoas", MediaTypeHeaderValue.Parse("application/json"));
-
将
format
参数添加到路由:[Route("GetAllSomething/{format}")]
无法 从标头中提取用于格式映射
Accept
格式,因此您将在 URL 中传递它。由于您需要知道控制器中逻辑的格式,因此您可以从路由到操作参数映射上述format
以避免标头Accept
重复:public async Task<IActionResult> GetAllSomething(string format)
现在,您无需在标头中传递所需的格式
Accept
因为格式将从请求 URL 映射。使用
FormatFilter
属性标记控制器或操作。最后的行动:
[HttpGet] [Route("GetAllSomething/{format}")] [FormatFilter] public async Task<IActionResult> GetAllSomething(string format) { bool generateLinks = !string.IsNullOrWhiteSpace(format) && format.ToLower().EndsWith("hateoas"); // ... return await Task.FromResult(Ok(new SomeModel { SomeProperty = "Test" })); }
现在,如果您请求 URL/GetAllSomething/xml-hateoas
(即使缺少Accept
标头),FormatFilter
会将format
xml-hateoas
的值映射到application/xml
,并且 XML 格式化程序将用于响应。请求的格式也可以在GetAllSomething
操作的参数format
访问。
GitHub 上具有格式化程序映射的示例项目
除了格式化程序映射之外,还可以通过将新的受支持媒体类型添加到现有媒体类型格式化程序来实现目标。支持的媒体类型存储在OutputFormatter.SupportedMediaTypes
集合中,并填充在具体输出格式化程序的构造函数中,例如XmlSerializerOutputFormatter
.您可以自己创建格式化程序实例(而不是使用AddXmlSerializerFormatters
扩展调用),并将所需的媒体类型添加到SupportedMediaTypes
集合中。要调整默认添加的 JSON 格式化程序,只需在options.OutputFormatters
中找到它的实例:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.InputFormatters.Add(new XmlSerializerInputFormatter());
var xmlOutputFormatter = new XmlSerializerOutputFormatter();
xmlOutputFormatter.SupportedMediaTypes.Add("application/xml+hateoas");
options.OutputFormatters.Add(xmlOutputFormatter);
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
jsonOutputFormatter?.SupportedMediaTypes.Add("application/json+hateoas");
})
.AddJsonOptions(options => {
// Force Camel Case to JSON
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlDataContractSerializerFormatters();
}
在这种情况下GetAllSomething
应该与您原始问题中的相同。您还应该在标头Accept
传递所需的格式,例如Accept: application/xml+hateoas
.
GitHub 上具有自定义媒体类型的示例项目