I'm using ASP.NET Web API's ApiController to expose business logic as a Web service. I'm testing both XML and JSON, since we have demand for both, and I've been using Fiddler to test. I've narrowed it down to this: having an IList<T>
property forces JSON for some reason, but changing the property to List<T>
allows either JSON or XML. Unfortunately, I need these to use IList<T>
, so how can I make XML out of objects with IList<T>
properties?
If I use the following HTML headers to GET http://localhost:4946/Api/MyBizLog/GetDomainObject?id=foo
:
Authorization: basic ***************
Accept: application/xml
Host: localhost:4946
I get back JSON if an exception is thrown. If I change Content-Type
to Content-Type: application/xml
, I get XML if an exception is thrown. If an exception is not thrown, however, I always get JSON.
The method I'm calling has a signature like public virtual MyDomainObject GetDomainObject(String id)
.
How do I get it to return the content type I ask for on success as well as failure?
I have the following 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;
}
}
More Information
I installed WebAPI tracing per @Darren Miller's suggestion, and I get the following:
I put a breakpoint on the first line of the action. I then sent the GET from Fiddler. The output showed the following when execution stopped at the breakpoint:
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
I then let execution continue, and I got the following:
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
I do have an ActionFilterAttribute
that reads the basic authentication and tells the business logic layer who the current user is, but skipping that doesn't change the results.
Yet More Information
So I've narrowed it down to IList and List. If I #define WORKS
, I get XML. If I #define DOESNT_WORK
, I get JSON. This is actually code that actually runs.
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();
}
Just like @Patrick mentioned, @Darrel's answer is correct. I am not suggesting a different answer here, this is only a solution as whole, just in case anyone else stumbles here:
Controller:
Model:
Web API Config:
Swagger Config:
Swagger Header:
@Darrel Miller has the answer:
That's because you are using the wrong header.
Content-Type
is used to describe the payload you are transferring. In the case of a GET there is no payload, therefore there is no need for Content-Type or Content-Length. You should be setting theAccept
header to indicate your preference of the media type that will be returned.