Swashbuckle custom JsonSerializer settings - json.net

Using SwashBuckle 5.6.0 with WebAPI 5.2.3 and NewtonSoft.Json 8.0.3 I am getting errors trying to load the Swagger web page.
The problem boils down to SwaggerUI not being able to handle null methods in the path part of the spec object.
A simple fix would be to add
var jsonFormatter = new JsonMediaTypeFormatter
{
SerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}
};
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
to Global.asax, but that would break the API to clients.
Now is there a way to change the JsonSerializerSettings specifically for Swagger, without changing the global settings?
Edit:
Add GlobalConfiguration.Configuration.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(new JsonMediaTypeFormatter())); to Global.asax and here is the JsonContentNegotiator class. This will trigger the issue
public class JsonContentNegotiator : IContentNegotiator
{
//http://www.strathweb.com/2013/06/supporting-only-json-in-asp-net-web-api-the-right-way/
private readonly JsonMediaTypeFormatter _jsonFormatter;
public JsonContentNegotiator(JsonMediaTypeFormatter formatter)
{
_jsonFormatter = formatter;
}
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
return new ContentNegotiationResult(_jsonFormatter, new MediaTypeHeaderValue("application/json"));
}
}
Edit 2:
Fixed it by removing the JsonContentNegotiator class and instead adding:
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());

Related

How create custom authorization attribute for checking a role and url path in Asp.Net Core?

I want to create a custom authorization attribute for checking the role and url path.
I've find the way for doing it in the Asp.Net Core using the Policy-Based-Authorization but I've tried implement it but I can't get the HttpContext with incomming url.
AuthorizationHandlerContext hasn't access to HttpContext probable.
How can I get current HttpContext with the url path? Is it possible to do that or with another way?
I've tried this code for creating the custom Policy:
public class RoleUrlValidationHandler : AuthorizationHandler<RoleUrlValidationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleUrlValidationRequirement requirement)
{
var path = //Here I need get current url path for example - /api/posts/4545411
var pathPart = path.Split('/');
var clientId = pathPart[3];
if (context.User.IsInRole(clientId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
I want to create following:
[Authorize(Policy="RoleUrlValidation")] //Get ClientId from Url and check User's roles
public class PostsController : Controller
{
public ActionResult Get()
{
}
}
The policy approach is the right one. Only bit you missed is, that you can use Dependency Injection in the Handlers.
public class RoleUrlValidationHandler : AuthorizationHandler<RoleUrlValidationRequirement>
{
private readonly IHttpContextAccessor contextAccessor;
public RoleUrlValidationHandler(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleUrlValidationRequirement requirement)
{
var httpContext = contextAccessor.HttpContext;
var path = httpContext.Request.Path;
var pathPart = path.Split('/');
var clientId = pathPart[3];
if (context.User.IsInRole(clientId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
You also may have to register the IHttpContextAccessor as its not registered by default.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Extra bits:
Consider using var routeData = httpContext.GetRouteData() instead of using path.Split('/') for reading values from it so you can easily read the parameter values from the route.
Try like this:
((DefaultHttpContext)context.Resource).Request.Path.Value

Setting OData result Page Size at runtime in WebAPI app

I am writing an ASP.NET Web API 2 web service using OdataControllers I have found out how to set page size using the PageSize Property of the EnableQueryAttribute. I want to allow the users of my web service to set the page size in the app.config and then have the application read this setting and set the page size. The problem is that using the attribute requires Page Size be set to a compile time constant.
Usage of attribute:
[EnableQuery(PageSize = 10)]
public IHttpActionResult GetProducts()
{
return repo.GetProducts();
}
One proposed solution I have seen is to construct the EnableQueryAttribute and set it on the HTTPConfiguration config object like this
int customSize = ReadPageSizeSettingFromConfigFile();
var attr = new EnableQueryAttribute { PageSize = customSize };
config.AddODataQueryFilter(attr);
but this doesn't actually work. The HttpConfiguration's Filter collection remains empty.
A comment on another post (buried in a list of comments) suggested removing all the EnableQuery attributes on the controllers but that has no effect either. Since the EnableQuery attribute replaced the older Queryable attribute I am wondering if this is a Microsoft problem.
This question has been asked and not answered before: How limit OData results in a WebAPI
All help is greatly appreciated.
You can use $top and $skip to achieve your goal, if client want pagesize is 10, and want the second page:
localhost/odata/Customers?$top=10&$skip=10
About dynamically set the pagesize:
public class MyEnableQueryAttribute : EnableQueryAttribute
{
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
int pagesize = xxx;
var result = queryOptions.ApplyTo(queryable, new ODataQuerySettings { PageSize = pagesize });
return result;
}
}
and put this attribute in your controller method.
You can set config for MaxTop in webApiConfig resgister method
public static class WebApiConfig{
public static void Register(HttpConfiguration config){
config.Select().Expand().Filter().OrderBy().MaxTop(100).count() // you can change max page size here
}
}
I was able to accomplish this by creating a new attribute (I called it ConfigurableEnableQueryAttribute) that extends the enable query attribute. In the constructor for that attribute, load your config file and set any settings you are interested in in the base. Personally I loop through all settings provided in an "OData" section of my appsettings, and if there are matching settings in the EnableQuery attribute, I cast them to the specified type and supply them, but you can only look for specific settings if you want.
My attribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ConfigurableEnableQueryAttribute : EnableQueryAttribute
{
public ConfigurableEnableQueryAttribute()
{
var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
var configuration = builder.Build();
var configProps = configuration.GetSection("OData").GetChildren();
var baseProps = typeof(EnableQueryAttribute).GetProperties();
foreach (var configProp in configProps)
{
var baseProp = baseProps.FirstOrDefault(x => x.Name.Equals(configProp.Key));
if (baseProp != null)
{
baseProp.SetValue(this, Convert.ChangeType(configProp.Value, baseProp.PropertyType));
}
}
}
}
and then in the controller
[HttpGet]
[ConfigurableEnableQuery]
public IQueryable<T> Get()
{
return _context.Set<T>().AsQueryable();
}

Override host of webapi odata links

I'm using WebAPI 2.2 and Microsoft.AspNet.OData 5.7.0 to create an OData service that supports paging.
When hosted in the production environment, the WebAPI lives on a server that is not exposed externally, hence the various links returned in the OData response such as the #odata.context and #odata.nextLink point to the internal IP address e.g. http://192.168.X.X/<AccountName>/api/... etc.
I've been able to modify the Request.ODataProperties().NextLink by implementing some logic in each and every ODataController method to replace the internal URL with an external URL like https://account-name.domain.com/api/..., but this is very inconvenient and it only fixes the NextLinks.
Is there some way to set an external host name at configuration time of the OData service? I've seen a property Request.ODataProperties().Path and wonder if it's possible to set a base path at the config.MapODataServiceRoute("odata", "odata", GetModel()); call, or in the GetModel() implementation using for instance the ODataConventionModelBuilder?
UPDATE: The best solution I've come up with so far, is to create a BaseODataController that overrides the Initialize method and checks whether the Request.RequestUri.Host.StartsWith("beginning-of-known-internal-IP-address") and then do a RequestUri rewrite like so:
var externalAddress = ConfigClient.Get().ExternalAddress; // e.g. https://account-name.domain.com
var account = ConfigClient.Get().Id; // e.g. AccountName
var uriToReplace = new Uri(new Uri("http://" + Request.RequestUri.Host), account);
string originalUri = Request.RequestUri.AbsoluteUri;
Request.RequestUri = new Uri(Request.RequestUri.AbsoluteUri.Replace(uriToReplace.AbsoluteUri, externalAddress));
string newUri = Request.RequestUri.AbsoluteUri;
this.GetLogger().Info($"Request URI was rewritten from {originalUri} to {newUri}");
This perfectly fixes the #odata.nextLink URLs for all controllers, but for some reason the #odata.context URLs still get the AccountName part (e.g. https://account-name.domain.com/AccountName/api/odata/$metadata#ControllerName) so they still don't work.
Rewriting the RequestUri is sufficient to affect #odata.nextLink values because the code that computes the next link depends on the RequestUri directly. The other #odata.xxx links are computed via a UrlHelper, which is somehow referencing the path from the original request URI. (Hence the AccountName you see in your #odata.context link. I've seen this behavior in my code, but I haven't been able to track down the source of the cached URI path.)
Rather than rewrite the RequestUri, we can solve the problem by creating a CustomUrlHelper class to rewrite OData links on the fly. The new GetNextPageLink method will handle #odata.nextLink rewrites, and the Link method override will handle all other rewrites.
public class CustomUrlHelper : System.Web.Http.Routing.UrlHelper
{
public CustomUrlHelper(HttpRequestMessage request) : base(request)
{ }
// Change these strings to suit your specific needs.
private static readonly string ODataRouteName = "ODataRoute"; // Must be the same as used in api config
private static readonly string TargetPrefix = "http://localhost:8080/somePathPrefix";
private static readonly int TargetPrefixLength = TargetPrefix.Length;
private static readonly string ReplacementPrefix = "http://www.contoso.com"; // Do not end with slash
// Helper method.
protected string ReplaceTargetPrefix(string link)
{
if (link.StartsWith(TargetPrefix))
{
if (link.Length == TargetPrefixLength)
{
link = ReplacementPrefix;
}
else if (link[TargetPrefixLength] == '/')
{
link = ReplacementPrefix + link.Substring(TargetPrefixLength);
}
}
return link;
}
public override string Link(string routeName, IDictionary<string, object> routeValues)
{
var link = base.Link(routeName, routeValues);
if (routeName == ODataRouteName)
{
link = this.ReplaceTargetPrefix(link);
}
return link;
}
public Uri GetNextPageLink(int pageSize)
{
return new Uri(this.ReplaceTargetPrefix(this.Request.GetNextPageLink(pageSize).ToString()));
}
}
Wire-up the CustomUrlHelper in the Initialize method of a base controller class.
public abstract class BaseODataController : ODataController
{
protected abstract int DefaultPageSize { get; }
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var helper = new CustomUrlHelper(controllerContext.Request);
controllerContext.RequestContext.Url = helper;
controllerContext.Request.ODataProperties().NextLink = helper.GetNextPageLink(this.DefaultPageSize);
}
Note in the above that the page size will be the same for all actions in a given controller class. You can work around this limitation by moving the assignment of ODataProperties().NextLink to the body of a specific action method as follows:
var helper = this.RequestContext.Url as CustomUrlHelper;
this.Request.ODataProperties().NextLink = helper.GetNextPageLink(otherPageSize);
The answer by lencharest is promising, but I found an improvement on his method. Rather than using the UrlHelper, I created a class derived from System.Net.Http.DelegatingHandler. This class is inserted (first) into the message handling pipeline and thus has a crack at altering the incoming HttpRequestMessage. It's an improvement over the above solution because in addition to altering the controller-specific URLs (as the UrlHelper does, e,g, https://data.contoso.com/odata/MyController), it also alters the url that appears as the xml:base in the OData service document (e.g., https://data.contoso.com/odata).
My particular application was to host an OData service behind a proxy server, and I wanted all the URLs presented by the server to be the externally-visible URLs, not the internally-visible ones. And, I didn't want to have to rely on annotations for this; I wanted it to be fully automatic.
The message handler looks like this:
public class BehindProxyMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var builder = new UriBuilder(request.RequestUri);
var visibleHost = builder.Host;
var visibleScheme = builder.Scheme;
var visiblePort = builder.Port;
if (request.Headers.Contains("X-Forwarded-Host"))
{
string[] forwardedHosts = request.Headers.GetValues("X-Forwarded-Host").First().Split(new char[] { ',' });
visibleHost = forwardedHosts[0].Trim();
}
if (request.Headers.Contains("X-Forwarded-Proto"))
{
visibleScheme = request.Headers.GetValues("X-Forwarded-Proto").First();
}
if (request.Headers.Contains("X-Forwarded-Port"))
{
try
{
visiblePort = int.Parse(request.Headers.GetValues("X-Forwarded-Port").First());
}
catch (Exception)
{ }
}
builder.Host = visibleHost;
builder.Scheme = visibleScheme;
builder.Port = visiblePort;
request.RequestUri = builder.Uri;
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
You wire the handler up in WebApiConfig.cs:
config.Routes.MapODataServiceRoute(
routeName: "odata",
routePrefix: "odata",
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: ODataRoutingConventions.CreateDefault()
);
config.MessageHandlers.Insert(0, new BehindProxyMessageHandler());
There is another solution, but it overrides url for the entire context.
What I'd like to suggest is:
Create owin middleware and override Host and Scheme properties inside
Register the middleware as the first one
Here is an example of middleware
public class RewriteUrlMiddleware : OwinMiddleware
{
public RewriteUrlMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
context.Request.Host = new HostString(Settings.Default.ProxyHost);
context.Request.Scheme = Settings.Default.ProxyScheme;
await Next.Invoke(context);
}
}
ProxyHost is the host you want to have. Example: test.com
ProxyScheme is the scheme you want: Example: https
Example of middleware registration
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(RewriteUrlMiddleware));
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
}
}
A couple of years later, using ASP.NET Core, I figured that the easiest way to apply it in my service was to just create a filter that masquerades the host name. (AppConfig is a custom configuration class that contains the host name, among other things.)
public class MasqueradeHostFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var appConfig = context.HttpContext.RequestServices.GetService<AppConfig>();
if (!string.IsNullOrEmpty(appConfig?.MasqueradeHost))
context.HttpContext.Request.Host = new HostString(appConfig.MasqueradeHost);
}
}
Apply the filter to the controller base class.
[MasqueradeHostFilter]
public class AppODataController : ODataController
{
}
The result is a nicely formatted output:
{ "#odata.context":"https://app.example.com/odata/$metadata" }
Just my two cents.
Using system.web.odata 6.0.0.0.
Setting the NextLink property too soon is problematic. Every reply will then have a nextLink in it. The last page should of course be free of such decorations.
http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793048 says:
URLs present in a payload (whether request or response) MAY be
represented as relative URLs.
One way that I hope will work is to override EnableQueryAttribute:
public class myEnableQueryAttribute : EnableQueryAttribute
{
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
var result = base.ApplyQuery(queryable, queryOptions);
var nextlink = queryOptions.Request.ODataProperties().NextLink;
if (nextlink != null)
queryOptions.Request.ODataProperties().NextLink = queryOptions.Request.RequestUri.MakeRelativeUri(nextlink);
return result;
}
}
ApplyQuery() is where the "overflow" is detected. It basically asks for pagesize+1 rows and will set NextLink if the result set contains more than pagesize rows.
At this point it is relatively easy to rewrite NextLink to a relative URL.
The downside is that every odata method must now be adorned with the new myEnableQuery attribute:
[myEnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<TElement> options)
{
...
}
and other URLs embedded elsewhere remains problematic. odata.context remains a problem. I want to avoid playing with the request URL, because I fail to see how that is maintainable over time.
Your question boils down to controlling the service root URI from within the service itself. My first thought was to look for a hook on the media type formatters used to serialize responses. ODataMediaTypeFormatter.MessageWriterSettings.PayloadBaseUri and ODataMediaTypeFormatter.MessageWriterSettings.ODataUri.ServiceRoot are both settable properties that suggest a solution. Unfortunately, ODataMediaTypeFormatter resets these properties on every call to WriteToStreamAsync.
The work-around is not obvious, but if you dig through the source code you'll eventually reach a call to IODataPathHandler.Link. A path handler is an OData extension point, so you can create a custom path handler that always returns an absolute URI which begins with the service root you desire.
public class CustomPathHandler : DefaultODataPathHandler
{
private const string ServiceRoot = "http://example.com/";
public override string Link(ODataPath path)
{
return ServiceRoot + base.Link(path);
}
}
And then register that path handler during service configuration.
// config is an instance of HttpConfiguration
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel(),
pathHandler: new CustomPathHandler(),
routingConventions: ODataRoutingConventions.CreateDefault()
);

Missing owin context

I'm trying to use the ResourceAuthorize attribute from Thinktecture.IdentityModel, but everything stops because there is no owin context.
I have a owin startup class which setups the authorization manager
[assembly: OwinStartup(typeof(My.WebApi.Startup))]
namespace My.WebApi
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
AuthConfig.Configure(app);
}
}
}
public class AuthConfig
{
public static void Configure(IAppBuilder app)
{
app.UseResourceAuthorization(new ResourceAuthorizationMiddlewareOptions
{
Manager = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IResourceAuthorizationManager)) as IResourceAuthorizationManager
});
}
}
and I know that it is detected and invoked. But later on, when hitting the following code from IdentityModel, I get a null pointer exception:
public static Task<bool> CheckAccessAsync(this HttpRequestMessage request, IEnumerable<Claim> actions, IEnumerable<Claim> resources)
{
var authorizationContext = new ResourceAuthorizationContext(
request.GetOwinContext().Authentication.User ?? Principal.Anonymous,
actions,
resources);
return request.CheckAccessAsync(authorizationContext);
}
I have stepped through and sees that it's caused by the GetOwinContext() returning null, since there is no MS_OwinContext or MS_OwinEnvironment property on the request.
What am I missing?
UPDATE:
I have found that i have an owin.environment property available, but it's part of the `HttpContextWrapper, not the request.
By searching around, I found some code inside of System.Web.Http.WebHost.HttpControllerHandler that looks like it should have converted the owin.environment to an MS_OwinEnvironment, but apparently, that code is never called in my case...
internal static readonly string OwinEnvironmentHttpContextKey = "owin.Environment";
internal static readonly string OwinEnvironmentKey = "MS_OwinEnvironment";
internal static HttpRequestMessage ConvertRequest(HttpContextBase httpContextBase, IHostBufferPolicySelector policySelector)
{
HttpRequestBase requestBase = httpContextBase.Request;
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethodHelper.GetHttpMethod(requestBase.HttpMethod), requestBase.Url);
bool bufferInput = policySelector == null || policySelector.UseBufferedInputStream((object) httpContextBase);
httpRequestMessage.Content = HttpControllerHandler.GetStreamContent(requestBase, bufferInput);
foreach (string str in (NameObjectCollectionBase) requestBase.Headers)
{
string[] values = requestBase.Headers.GetValues(str);
HttpControllerHandler.AddHeaderToHttpRequestMessage(httpRequestMessage, str, values);
}
HttpRequestMessageExtensions.SetHttpContext(httpRequestMessage, httpContextBase);
HttpRequestContext httpRequestContext = (HttpRequestContext) new WebHostHttpRequestContext(httpContextBase, requestBase, httpRequestMessage);
System.Net.Http.HttpRequestMessageExtensions.SetRequestContext(httpRequestMessage, httpRequestContext);
IDictionary items = httpContextBase.Items;
if (items != null && items.Contains((object) HttpControllerHandler.OwinEnvironmentHttpContextKey))
httpRequestMessage.Properties.Add(HttpControllerHandler.OwinEnvironmentKey, items[(object) HttpControllerHandler.OwinEnvironmentHttpContextKey]);
httpRequestMessage.Properties.Add(HttpPropertyKeys.RetrieveClientCertificateDelegateKey, (object) HttpControllerHandler._retrieveClientCertificate);
httpRequestMessage.Properties.Add(HttpPropertyKeys.IsLocalKey, (object) new Lazy<bool>((Func<bool>) (() => requestBase.IsLocal)));
httpRequestMessage.Properties.Add(HttpPropertyKeys.IncludeErrorDetailKey, (object) new Lazy<bool>((Func<bool>) (() => !httpContextBase.IsCustomErrorEnabled)));
return httpRequestMessage;
}
UPDATE 2:
Inside of mvc controllers, the context is available. But not in webapi controllers.
A team mate found a solution. He simply added the following line to the owin startup class:
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
Why this solves the issue is another mystery, though. But we are using wsFederation, so I guess it's needed some how. But what if we didn't use wsFed? Would we still need it to get a context? Who knows...

ASP.NET Web API Controller Specific Serializer

I've a self host Web API with 2 controllers:
For controller 1, I need default DataContractSerializer (I'm exposing EF 5 POCO)
For controller 2, I need XmlFormatter with parameter UseXmlSerializer set to true (I'm exposing an XmlDocument)
I've tried to set formatters during controller initialization, but the configuration seems to be global, affecting all controllers:
public class CustomConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings settings,
HttpControllerDescriptor descriptor)
{
settings.Formatters.XmlFormatter.UseXmlSerializer = true;
}
}
How can I solve this?
You were very much on the right track. But you need to initallise a new instance of the XmlMediaTypeFormatter in your config attributes otherwise you will affect the global reference.
As you know, you need to create 2 attributes based on the IControllerConfiguration interface.
public class Controller1ConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
var xmlFormater = new XmlMediaTypeFormatter {UseXmlSerializer = true};
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(xmlFormater);
}
}
public class Controller2ConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
var xmlFormater = new XmlMediaTypeFormatter();
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(xmlFormater);
}
}
Then decorate your controllers with the relevant attribute
[Controller1ConfigAttribute]
public class Controller1Controller : ApiController
{
[Controller2ConfigAttribute]
public class Controller2Controller : ApiController
{
Configuration:
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Insert(0, new CustomXmlMediaTypeFormatter());
The Custom formatter:
public class CustomXmlMediaTypeFormatter : XmlMediaTypeFormatter
{
public CustomXmlMediaTypeFormatter()
{
UseXmlSerializer = true;
}
}
This seems to work, ok not so elegant.
Removing default Xml Formatter does not work,
so I concluded that the framework is somehow still using it.
Mark Jones' answer has a big downside: By clearing all formatters it is not possible to request different ContentTypes and make use of the relevant formatter.
A better way to enable the XMLSerializer per Controller is to replace the default formatter.
public class UseXMLSerializerAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
// Find default XMLFormatter
var xmlFormatter = controllerSettings.Formatters.FirstOrDefault(c => c.SupportedMediaTypes.Any(x => x.MediaType == "application/xml"));
if (xmlFormatter != null)
{
// Remove default formatter
controllerSettings.Formatters.Remove(xmlFormatter);
}
// Add new XMLFormatter which uses XmlSerializer
controllerSettings.Formatters.Add(new XmlMediaTypeFormatter { UseXmlSerializer = true });
}
}
And use it like this:
[UseXMLSerializer]
public TestController : ApiController
{
//Actions
}
I think you could write a custom ActionFilterAttribute.
In OnActionExecuting, store away the original values in the HttpContext and then in OnActionExecuted, restore the original values.
the controllers actions themselves should not be concerned with how the data is serialized. yo should be able to request the data and any format necessary the operation to retrieve the data would be the same.
by default web api serialized to json objects. however if you set the content type of the request to xml is should return the same result, but formatted as xml instead of json.

Resources