Asp.net mvc 4 如果返回的对象具有IList,则ASP.NET Web API无法返回XML<;T>;所有物
我正在使用ASP.NETWebAPI的ApiController将业务逻辑公开为Web服务。我正在测试XML和JSON,因为我们对两者都有需求,我一直在使用Fiddler进行测试。我已经把范围缩小到这样:由于某种原因,拥有Asp.net mvc 4 如果返回的对象具有IList,则ASP.NET Web API无法返回XML<;T>;所有物,asp.net-mvc-4,asp.net-web-api,Asp.net Mvc 4,Asp.net Web Api,我正在使用ASP.NETWebAPI的ApiController将业务逻辑公开为Web服务。我正在测试XML和JSON,因为我们对两者都有需求,我一直在使用Fiddler进行测试。我已经把范围缩小到这样:由于某种原因,拥有IList属性会强制使用JSON,但是将属性更改为List可以使用JSON或XML。不幸的是,我需要这些来使用IList,那么我如何用IList属性的对象生成XML呢 如果我使用以下HTML标题获取http://localhost:4946/Api/MyBizLog/GetDo
IList
属性会强制使用JSON,但是将属性更改为List
可以使用JSON或XML。不幸的是,我需要这些来使用IList
,那么我如何用IList
属性的对象生成XML呢
如果我使用以下HTML标题获取http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo
:
Authorization: basic ***************
Accept: application/xml
Host: localhost:4946
如果抛出异常,我将返回JSON。如果我将Content-Type
更改为Content-Type:application/xml
,则在引发异常时会得到xml。但是,如果没有抛出异常,我总是得到JSON
我调用的方法有一个签名,如公共虚拟MyDomainObject GetDomainObject(字符串id)
我如何让它在成功或失败时返回我要求的内容类型
我有以下WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "AlternativeApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { }
);
config.Formatters.XmlFormatter.UseXmlSerializer = true;
}
}
更多信息
我根据@Darren Miller的建议安装了WebAPI跟踪,我得到以下信息:
iisexpress.exe Information: 0 : Message='Action returned 'DomainObjects.MyDomainObject'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
iisexpress.exe Information: 0 : Operation=ApiControllerActionInvoker.InvokeActionAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=TransactionalApiFilterAttribute.ActionExecuted, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=MyBizLogController.ExecuteAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Response, Status=200 (OK), Method=GET, Url=http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
iisexpress.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
iisexpress.exe Information: 0 : Operation=MyBizLogController.Dispose
我在操作的第一行上放置了一个断点。然后我把提琴手的礼物寄了出去。当执行在断点处停止时,输出显示以下内容:
iisexpress.exe Information: 0 : Request, Method=GET, Url=http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo, Message='http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo'
iisexpress.exe Information: 0 : Message='MyBizLog', Operation=DefaultHttpControllerSelector.SelectController
iisexpress.exe Information: 0 : Message='WebApp.Api.MyBizLogController', Operation=DefaultHttpControllerActivator.Create
iisexpress.exe Information: 0 : Message='WebApp.Api.MyBizLogController', Operation=HttpControllerDescriptor.CreateController
iisexpress.exe Information: 0 : Message='Selected action 'GetDomainObject(String id)'', Operation=ApiControllerActionSelector.SelectAction
iisexpress.exe Information: 0 : Message='Parameter 'id' bound to the value 'foo'', Operation=ModelBinderParameterBinding.ExecuteBindingAsync
iisexpress.exe Information: 0 : Message='Model state is valid. Values: id=foo', Operation=HttpActionBinding.ExecuteBindingAsync
iisexpress.exe Information: 0 : Operation=TransactionalApiFilterAttribute.ActionExecuting
然后我让执行继续,我得到以下结果:
iisexpress.exe Information: 0 : Message='Action returned 'DomainObjects.MyDomainObject'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
iisexpress.exe Information: 0 : Operation=ApiControllerActionInvoker.InvokeActionAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=TransactionalApiFilterAttribute.ActionExecuted, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=MyBizLogController.ExecuteAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Response, Status=200 (OK), Method=GET, Url=http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
iisexpress.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
iisexpress.exe Information: 0 : Operation=MyBizLogController.Dispose
我确实有一个ActionFilterAttribute
,它读取基本身份验证并告诉业务逻辑层当前用户是谁,但是跳过它不会改变结果
更多信息
所以我把范围缩小到了IList和List。如果我定义作品,我就会得到XML。如果我#define不起作用
,我就会得到JSON。这实际上是实际运行的代码
public class Bar
{
}
public class Foo
{
#if WORKS
public virtual List<Bar> Bars { get; set; }
#elif DOESNT_WORK
public virtual IList<Bar> Bars { get; set; }
#endif
}
[HttpPost]
[HttpGet]
public Foo Test()
{
return new Foo();
}
公共类栏
{
}
公开课Foo
{
#如果有效
公共虚拟列表栏{get;set;}
#艾利夫不工作
公共虚拟IList条{get;set;}
#恩迪夫
}
[HttpPost]
[HttpGet]
公开食物测试()
{
返回新的Foo();
}
这是因为您使用了错误的标题<代码>内容类型用于描述正在传输的有效负载。对于GET,没有有效负载,因此不需要内容类型或内容长度。您应该设置Accept
标题,以指示您对将返回的媒体类型的偏好。这是因为您使用了错误的标题<代码>内容类型用于描述正在传输的有效负载。对于GET,没有有效负载,因此不需要内容类型或内容长度。您应该设置Accept
标题,以指示您对将返回的媒体类型的偏好。@Darrel Miller给出了答案:
据我所知,XmlSerializer无法处理接口。任何一个
将属性更改为List,在您的服务器上实现IXmlSerializable
类,或使用DataContractSerializer。或者更好的是,不要试图
通过连接返回域对象
@达雷尔·米勒给出了答案: 据我所知,XmlSerializer无法处理接口。任何一个 将属性更改为List,在您的服务器上实现IXmlSerializable 类,或使用DataContractSerializer。或者更好的是,不要试图 通过连接返回域对象
正如@Patrick提到的,@Darrel的回答是正确的。我并不是在这里提出不同的答案,这只是一个整体解决方案,以防其他人在这里绊倒: 控制器:
[HttpPost]
[Route("myRoute")]
[ResponseType(typeof(MyCustomModel))]
/* Note: If your response type is of type IEnumerable, i.e. IEnumerable<MyCustomModel>, then Example in Swagger will look like this:
<?xml version="1.0"?>
<Inline Model>
<AttributeIdProperty>string</AttributeIdProperty>
<PropertyForElement>string</PropertyForElement>
</Inline Model>
The real output will be correct representation, however:
<ArrayOfMyCustomModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyCustomModel AttributeIdProperty="Value for this attribute property">
<PropertyForElement>Value for this element property</PropertyForElement>
</MyCustomModel>
</ArrayOfContentMetadata>
*/
public virtual IHttpActionResult MyMethod([FromBody]MyCustomModel myCustomModel)
{
if (myCustomModel== null) throw new Exception("Invalid input", HttpStatusCode.BadRequest);
return Ok(_myBusiness.MyMethod(myCustomModel);
}
Web API配置:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Filters.Add(new GlobalExceptionFilter());
//below line is what's most important for this xml serialization
config.Formatters.XmlFormatter.UseXmlSerializer = true;
}
}
招摇过市配置:
public class SwaggerConfig
{
public static void Register()
{
var swaggerHeader = new SwaggerHeader();
///...
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
swaggerHeader.Apply(c);
});
摆动式收割台:
public class SwaggerHeader : IOperationFilter
{
public string Description { get; set; }
public string Key { get; set; }
public string Name { get; set; }
public void Apply(SwaggerDocsConfig c)
{
c.ApiKey(Key).Name(Name).Description(Description).In("header");
c.OperationFilter(() => this);
}
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (!operation.produces.Contains("application/xml")) operation.produces.Add("application/xml");
if (!operation.produces.Contains("text/xml")) operation.produces.Add("text/xml");
}
}
正如@Patrick提到的,@Darrel的回答是正确的。我并不是在这里提出不同的答案,这只是一个整体解决方案,以防其他人在这里绊倒: 控制器:
[HttpPost]
[Route("myRoute")]
[ResponseType(typeof(MyCustomModel))]
/* Note: If your response type is of type IEnumerable, i.e. IEnumerable<MyCustomModel>, then Example in Swagger will look like this:
<?xml version="1.0"?>
<Inline Model>
<AttributeIdProperty>string</AttributeIdProperty>
<PropertyForElement>string</PropertyForElement>
</Inline Model>
The real output will be correct representation, however:
<ArrayOfMyCustomModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyCustomModel AttributeIdProperty="Value for this attribute property">
<PropertyForElement>Value for this element property</PropertyForElement>
</MyCustomModel>
</ArrayOfContentMetadata>
*/
public virtual IHttpActionResult MyMethod([FromBody]MyCustomModel myCustomModel)
{
if (myCustomModel== null) throw new Exception("Invalid input", HttpStatusCode.BadRequest);
return Ok(_myBusiness.MyMethod(myCustomModel);
}
Web API配置:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Filters.Add(new GlobalExceptionFilter());
//below line is what's most important for this xml serialization
config.Formatters.XmlFormatter.UseXmlSerializer = true;
}
}
招摇过市配置:
public class SwaggerConfig
{
public static void Register()
{
var swaggerHeader = new SwaggerHeader();
///...
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
swaggerHeader.Apply(c);
});
摆动式收割台:
public class SwaggerHeader : IOperationFilter
{
public string Description { get; set; }
public string Key { get; set; }
public string Name { get; set; }
public void Apply(SwaggerDocsConfig c)
{
c.ApiKey(Key).Name(Name).Description(Description).In("header");
c.OperationFilter(() => this);
}
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (!operation.produces.Contains("application/xml")) operation.produces.Add("application/xml");
if (!operation.produces.Contains("text/xml")) operation.produces.Add("text/xml");
}
}
Darrel——应该使用Accept,这是对的。不幸的是,使用
Accept:application/xml
会产生相同的结果。我修改了这个问题以反映您的更正,不过我认为这个问题仍然存在。@Don01001100您的域对象中很可能存在一些问题,使XML序列化程序无法呈现它。安装/启用WebAPI跟踪,这会告诉你为什么它不工作。我没有看到任何明显的问题。你能看一下吗?我将输出添加到我的问题中。@Don01001100 MyDomainObject中是否有循环?也就是说,它指向一个指向它的物体?我相信XmlSerializer会因为这个而窒息。@Don01001100据我所知,XmlSerializer无法处理接口。将属性更改为List,在类上实现IXmlSerializable,或者使用DataContractSerializer。或者更好的是,不要试图通过wire.Darrel返回域对象——应该使用Accept,这是正确的。不幸的是,使用Accept:application/xml
会产生相同的结果。我修改了这个问题以反映您的更正,不过我认为这个问题仍然存在。@Don01001100您的域对象中很可能存在一些问题,使XML序列化程序无法呈现它。安装/启用WebAPI跟踪,这会告诉你为什么它不工作。我没有看到任何明显的问题。你能看一下吗?我将输出添加到我的问题中。@Don01001100 MyDomainObject中是否有循环?也就是说,它指向一个指向它的物体?我相信XmlSerializer会因为这个而窒息。@Don01001100据我所知,XmlSerializer无法处理接口。要么改变你的公关