How to check for custom attributes - c#

I am using a middleware to sanitize asp.net endpoints using HtmlSanitizer as explained here. But its not working for file uploads. So I am trying to use a custom attribute called XssSanitizeIgnore as explained in the discussions section.
I create XssSanitizeIgnore attribute inside My controller as follows,
namespace CarPortal.ReportingServiceApi.Controllers.APIControllers
{
[Route("api/[controller]")]
[ApiController]
public class AutoReportController : BaseController
{
private readonly IAutoReportService _autoReportService;
public AutoReportController(IAutoReportService autoReportService) : base()
{
_autoReportService = autoReportService;
}
[HttpPost]
[XssSanitizeIgnore]
[Route("ProcessUploadedFile")]
public async Task<string> ProcessUploadedFile([FromForm] object formData)
{
return await _autoReportService.ProcessUploadedFile((IFormFile)formData);
}
}
}
[System.AttributeUsage(System.AttributeTargets.All)]
public class XssSanitizeIgnore: Attribute
{
}
but I am not sure how can I use it inside the middleware
// enable buffering so that the request can be read by the model binders next
httpContext.Request.EnableBuffering();
// leaveOpen: true to leave the stream open after disposing, so it can be read by the model binders
using (var streamReader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, leaveOpen: true))
{
var raw = await streamReader.ReadToEndAsync();
var sanitiser = new HtmlSanitizer();
var sanitised = sanitiser.Sanitize(raw);
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(typeof(AutoReportController));
//ignore if XssSanitizeIgnore
if (raw != sanitised)
{
throw new BadRequestException("XSS injection detected from middleware.");
}
}
// rewind the stream for the next middleware
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
await _next.Invoke(httpContext);

You can use this snippet code to check if this endpoint has an attribute.
var endpoint = httpContext.GetEndpoint();
var myCustomAttribute = endpoint?.Metadata?.GetMetadata<MyCustomAttribute>();
if (myCustomAttribute is not null)
{
// ToDo: do someting
}
and if possible use this attribute more than once so you can use this instead.
var endpoint = httpContext.GetEndpoint();
var myCustomAttributes = endpoint?.Metadata?.GetOrderedMetadata<MyCustomAttribute>();
if (myCustomAttributes != null && myCustomAttributes.Any())
{
// ToDo: do someting
}

Related

How to change request body in IActionFilter in asp.net core

Currently I am sending encrypted request to asp.net core controller. The following is the request body structure:
{
"body": "pEbXVvl1ue95eGQywK/q80cGHjYk+2VNPYEgnRgU+vI="
}
Before that I had implemented the filter IActionFilter so in OnActionExecuting(ActionExecutingContext context) where I decrypt the above mention request body(body key)
After decryption, I got the decrypted request body like below:
{
"Urno":"URN123456"
}
After decryption, I want to pass decrypted request (mentioned above "Urno":"URN123456") body to controller
I had tried to convert the string to byte than pass it to Request.body but no success:
public void OnActionExecuting(ActionExecutingContext context)
{
var request = context.HttpContext.Request;
request.EnableRewind();
request.Body.Position = 0;
using (var reader = new StreamReader(request.Body))
{
var bodyString = reader.ReadToEnd(); //{ "body": "pEbXVvl1ue95eGQywK/q80cGHjYk+2VNPYEgnRgU+vI="}
if (bodyString != "")
{
var data = JObject.Parse(bodyString);
var key = data.GetValue("body");
var keybytes = Encoding.UTF8.GetBytes("808080808080808011");
var iv = Encoding.UTF8.GetBytes("8080808080808080111");
var encrypted = Convert.FromBase64String(key.ToString());
var decriptedFromJavascript = DecryptStringFromBytes(encrypted, keybytes, iv); //{ "Urno":"URN123456"}
byte[] bytes = Encoding.ASCII.GetBytes(decriptedFromJavascript);
request.Body = new MemoryStream(bytes); // here i am trying to change request body
}
}
}
// controller
[HttpPost("[action]")]
public async Task<string> GetInvestorUrno(Urnoinvestor InfoReq){
}
// class
public class Urnoinvestor
{
public string Urno{ get; set; }
}
I want to change request body and pass the decrypted request to controller
For your scenario, IActionFilter would not work. Check this image:
The Model binding is run before ActionFilter which means no matter what you changed to the body, it would not change the model. You need to try the filter before Model binding like Resource filter.
Change to Resource filter
public class RequestBodyFilter : IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
//throw new NotImplementedException();
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
var request = context.HttpContext.Request;
request.EnableRewind();
request.Body.Position = 0;
using (var reader = new StreamReader(request.Body))
{
var decriptedFromJavascript = "{ \"Urno\":\"URN123456\"}"; //{ "Urno":"URN123456"}
byte[] bytes = Encoding.ASCII.GetBytes(decriptedFromJavascript);
request.Body = new MemoryStream(bytes); // here i am trying to change request body
}
}
}
Since you are binding from body, you need to specify the action with [FromBody] as the suggestion from #Tony Abrams.
[HttpPost("[action]")]
[TypeFilter(typeof(RequestBodyFilter))]
public async Task<string> GetInvestorUrno([FromBody]Urnoinvestor InfoReq)
{
return InfoReq.Urno;
}
You don't have the parameter marked as from body, so it's not loading the data from the request body. Instead it's expecting it in the URI.
Try this instead:
[HttpPost("[action]")]
public async Task<string> GetInvestorUrno([FromBody]Urnoinvestor InfoReq){
}

Accept x-www-form-urlencoded in Asp .net core Web Api

I have a .Net Core(2.1) Web API that has to adapt to an existed .Net framework(4.6.2) system, and the existed system send a request that the Api accepts.
Here is the problem. In the .Net framework system, it calls the api like this:
var request = (HttpWebRequest)WebRequest.Create("http://xxx.xxx/CloudApi/RegionsList");
request.KeepAlive = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "*/*";
var data = new Person()
{
Name = "Alex",
Age = 40
};
byte[] dataBuffer;
using (MemoryStream ms = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, data);
dataBuffer = ms.GetBuffer();
}
request.ContentLength = dataBuffer.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(dataBuffer, 0, dataBuffer.Length);
requestStream.Close();
try
{
var response = (HttpWebResponse)request.GetResponse();
Console.WriteLine("OK");
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}
Here is the api controller code:
[Route("cloudapi")]
public class LegacyController : ControllerBase
{
[HttpPost]
[Route("regionslist")]
public dynamic RegionsList([FromBody]byte[] value)
{
return value.Length;
}
}
Person class:
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
According to this article: Accepting Raw Request Body Content in ASP.NET Core API Controllers
I have made a custom InputFormatter to deal with this case:
public class RawRequestBodyFormatter : IInputFormatter
{
public RawRequestBodyFormatter()
{
}
public bool CanRead(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException("argument is Null");
var contentType = context.HttpContext.Request.ContentType;
if (contentType == "application/x-www-form-urlencoded")
return true;
return false;
}
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = context.HttpContext.Request.ContentType;
if (contentType == "application/x-www-form-urlencoded")
{
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
var content = ms.ToArray();
return await InputFormatterResult.SuccessAsync(content);
}
}
}
return await InputFormatterResult.FailureAsync();
}
}
But I found that the data I send(the Person class instance) was not in request.Body but in request.Form, and I can't deserialize it Form.
Any help greatly appreciated.
Since you need to read the raw Request.Body, it's better to enable rewind feature.
InputFormatter is overkill for this scenario. InputFormatter cares about content negotiation. Typically, we use it in this way : if the client sends a payload of application/json, we shoud do A ; if the client sends a payload of application/xml, we should do B . But your client (legacy system) only sends x-www-form-urlencoded. Rather than creating InputFormatter, you could create a dead simple ModelBinder to deserialize the payload.
Hack: Your legacy .Net framework(4.6.2) system use BinaryFormatter to serialize the Person class, and your .NET Core website needs to deserialize it to an object of Person. Typically, this requires your .NET Core app and the Legacy .NET Framework system share the same Person assembly. But obviously the original Person targets .NET Framewrok 4.6.2, in other words, this assembly cannot be referenced by .NET Core. A walkaround is to create a type that shares the same name of Person, and create a SerializationBinder to bind a new type.
Suppose in your Person class of the Legacy system is :
namespace App.Xyz{
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
You should create a same class in your .NET Core WebSite:
namespace App.Xyz{
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Note the namespace should also keep the same.
How to in details.
Create a Filter that enables Rewind for Request.Body
public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context) { }
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.EnableRewind();
}
}
Now you can create a ModelBinder:
public class BinaryBytesModelBinder: IModelBinder
{
internal class LegacyAssemblySerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName) {
var typeToDeserialize = Assembly.GetEntryAssembly()
.GetType(typeName); // we use the same typename by convention
return typeToDeserialize;
}
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName?? "LegacyBinaryData";
var req = bindingContext.HttpContext.Request;
var raw= req.Body;
if(raw == null){
bindingContext.ModelState.AddModelError(modelName,"invalid request body stream");
return Task.CompletedTask;
}
var formatter= new BinaryFormatter();
formatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
formatter.Binder = new LegacyAssemblySerializationBinder();
var o = formatter.Deserialize(raw);
bindingContext.Result = ModelBindingResult.Success(o);
return Task.CompletedTask;
}
}
Finally, decorate your action method with the Filter, and use the model binder to retrieve the instance :
[Route("cloudapi")]
public class LegacyController : ControllerBase
{
[EnableRewindResourceFilter]
[HttpPost]
[Route("regionslist")]
public dynamic RegionsList([ModelBinder(typeof(BinaryBytesModelBinder))] Person person )
{
// now we gets the person here
}
}
a demo :
Alternative Approach : Use InputFormatter (not suggested)
Or if you do want to use InputFormatter, you should also enable rewind:
[Route("cloudapi")]
public class LegacyController : ControllerBase
{
[HttpPost]
[EnableRewindResourceFilter]
[Route("regionslist")]
public dynamic RegionsList([FromBody] byte[] bytes )
{
return new JsonResult(bytes);
}
}
and configure the services :
services.AddMvc(o => {
o.InputFormatters.Insert(0, new RawRequestBodyFormatter());
});
And also, you should deserialize the person object in the same way as we do in Model Binder.
But be careful the performance!
I know there is an already accepted response, but I came up with a way of parsing the request.Form data and rebuilding the content into an original request.Body format:
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = request.ContentType;
if (contentType.StartsWith("application/x-www-form-urlencoded")) // in case it ends with ";charset=UTF-8"
{
var content = string.Empty;
foreach (var key in request.Form.Keys)
{
if (request.Form.TryGetValue(key, out var value))
{
content += $"{key}={value}&";
}
}
content = content.TrimEnd('&');
return await InputFormatterResult.SuccessAsync(content);
}
return await InputFormatterResult.FailureAsync();
}

API File Upload using HTTP content not exposed in swagger

I am implementing a swagger interface into an existing web API. The current API controller exposes an async upload function which uses the Request.Content to transport an image asynchronously. The code that has been used is explained in this article.
My api controller:
[HttpPost]
[Route("foo/bar/upload")]
public async Task<HttpResponseMessage> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = await Request.Content.ReadAsMultipartAsync(new InMemoryMultipartFormDataStreamProvider());
NameValueCollection formData = provider.FormData;
HttpResponseMessage response;
//access files
IList<HttpContent> files = provider.Files;
if (files.Count > 0)
{
HttpContent file1 = files[0];
using (Stream input = await file1.ReadAsStreamAsync())
{
object responseObj = ExternalProcessInputStream(input)
response = Request.CreateResponse(HttpStatusCode.OK, responseObj);
}
}
else
{
response = Request.CreateResponse(HttpStatusCode.BadRequest);
}
return response;
}
This works dandy, but when i expose this through swagger i have a parameterless function, which returns an error when used.
My question is how can supply a proper value to test this method with?
You'll need to add a custom IOperationFilter to handle this.
Given you have a controller like so:
[ValidateMimeMultipartContentFilter]
[HttpPost, Route("softwarepackage")]
public Task<SoftwarePackageModel> UploadSingleFile()
{
var streamProvider = new MultipartFormDataStreamProvider(ServerUploadFolder);
var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<SoftwarePackageModel>(t =>
{
var firstFile = streamProvider.FileData.FirstOrDefault();
if (firstFile != null)
{
// Do something with firstFile.LocalFileName
}
return new SoftwarePackageModel
{
};
});
return task;
}
You then need to create an Swashbuckle.Swagger.IOperationFilter to add a file upload parameter to your function like:
public class FileOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.operationId.ToLower() == "softwarepackage_uploadsinglefile")
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>(1);
else
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = "File",
#in = "formData",
description = "Upload software package",
required = true,
type = "file"
});
operation.consumes.Add("application/form-data");
}
}
}
And in your Swagger config you'll need to register the filter:
config.EnableSwagger(c => {... c.OperationFilter<FileOperationFilter>(); ... });
To top this up, I also added a FilterAttribute to filter out Multipart content:
public class ValidateMimeMultipartContentFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
}
}

Custom authorization IActionResults in aspnet-5 mvc-6

In ASP.NET 4 MVC5, I had this class that allowed me to return custom responses for unauthenticated responses to JSON endpoints. Here it is.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (IsAjax(filterContext))
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
success = false,
error = "You must be signed in."
}
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
private bool IsAjax(AuthorizationContext filterContext)
{
return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() !=
null;
}
}
However, in MVC6, the new AuthorizeAttribute is no overrides for creating custom IActionResult results. How do I do this in MVC6?
A good point has been made by #blowdart in his comment about whether returning 401/403 should be the expected behaviour. In any case, I have tried a different approach for doing what the OP was asking, modifying the behavior of the default MVC authorization filters so that we return a json when user is unauthorized.
First thing I did was creating a new IAsyncAuthorizationFilter that will format the unauthorized result as a json for ajax request. It will basically:
Wrap an existing filter
Execute the wrapped filter
In case the user is unauthorized by the wrapped filter, return a json for ajax requests
This would be the CustomJsonAuthorizationFilter class:
public class CustomJsonAuthorizationFilter : IAsyncAuthorizationFilter
{
private AuthorizeFilter wrappedFilter;
public CustomJsonAuthorizationFilter(AuthorizeFilter wrappedFilter)
{
this.wrappedFilter = wrappedFilter;
}
public async Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
await this.wrappedFilter.OnAuthorizationAsync(context);
if(context.Result != null && IsAjaxRequest(context))
{
context.Result = new JsonResult(new
{
success = false,
error = "You must be signed in."
});
}
return;
}
//This could be an extension method of the HttpContext/HttpRequest
private bool IsAjaxRequest(Microsoft.AspNet.Mvc.Filters.AuthorizationContext filterContext)
{
return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
Then I have created an IApplicationModelProvider in order to wrap all existing AuthorizeFilter with the new custom filter. The AuthroizeFilter is added by AuthorizationApplicationModelProvider, but the new provider will be run after the default one since the order of the default provider is -990.
public class CustomFilterApplicationModelProvider : IApplicationModelProvider
{
public int Order
{
get { return 0; }
}
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
//Do nothing
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
this.ReplaceFilters(context.Result.Filters);
foreach(var controller in context.Result.Controllers)
{
this.ReplaceFilters(controller.Filters);
foreach (var action in controller.Actions)
{
this.ReplaceFilters(action.Filters);
}
}
}
private void ReplaceFilters(IList<IFilterMetadata> filters)
{
var authorizationFilters = filters.OfType<AuthorizeFilter>().ToList();
foreach (var filter in authorizationFilters)
{
filters.Remove(filter);
filters.Add(new CustomJsonAuthorizationFilter(filter));
}
}
}
Finally, update ConfigureServices in startup with the new application model provider:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, CustomFilterApplicationModelProvider>());
I finally figured it out after looking at the source.
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
Func<CookieRedirectContext, Task> _old;
public CustomCookieAuthenticationEvents()
{
_old = OnRedirectToLogin;
OnRedirectToLogin = OnCustomRedirectToLogin;
}
public Task OnCustomRedirectToLogin(CookieRedirectContext context)
{
var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
if (actionContext.ActionContext == null)
return _old(context);
if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute))
{
// this is an ajax request, return custom JSON telling user that they must be authenticated.
var serializerSettings = context
.HttpContext
.RequestServices
.GetRequiredService<IOptions<MvcJsonOptions>>()
.Value
.SerializerSettings;
context.Response.ContentType = "application/json";
using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8))
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.CloseOutput = false;
var jsonSerializer = JsonSerializer.Create(serializerSettings);
jsonSerializer.Serialize(jsonWriter, new
{
success = false,
error = "You must be signed in."
});
}
}
return Task.FromResult(0);
}
else
{
// this is a normal request to an endpoint that is secured.
// do what ASP.NET used to do.
return _old(context);
}
}
}
Then, use this event class as follows:
services.Configure<IdentityOptions>(options =>
{
options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents();
});
ASP.NET 5 sure made simple things harder to do. Granted though, I can now customize things at a more granular level without effecting other pieces. Also, the source code is amazingly easy to read/understand. I am pretty happy having the confidence that any issue I am having can easily be identified as a bug or resolved by looking at the source.
Cheers to the future!

Camel-Casing Issue with Web API Using JSON.Net

I would like to return camel-cased JSON data using Web API. I inherited a mess of a project that uses whatever casing the previous programmer felt like using at the moment (seriously! all caps, lowercase, pascal-casing & camel-casing - take your pick!), so I can't use the trick of putting this in the WebApiConfig.cs file because it will break the existing API calls:
// Enforce camel-casing for the JSON objects being returned from API calls.
config.Formatters.OfType<JsonMediaTypeFormatter>().First().SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
So I'm using a custom class that uses the JSON.Net serializer. Here is the code:
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class JsonNetApiController : ApiController
{
public string SerializeToJson(object objectToSerialize)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
if (objectToSerialize != null)
{
return JsonConvert.SerializeObject(objectToSerialize, Formatting.None, settings);
}
return string.Empty;
}
}
The problem is that the raw data returned looks like this:
"[{\"average\":54,\"group\":\"P\",\"id\":1,\"name\":\"Accounting\"}]"
As you can see, the backslashes mess things up. Here is how I'm calling using the custom class:
public class Test
{
public double Average { get; set; }
public string Group { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
public class SomeController : JsonNetApiController
{
public HttpResponseMessage Get()
var responseMessage = new List<Test>
{
new Test
{
Id = 1,
Name = "Accounting",
Average = 54,
Group = "P",
}
};
return Request.CreateResponse(HttpStatusCode.OK, SerializeToJson(responseMessage), JsonMediaTypeFormatter.DefaultMediaType);
}
What can I do differently to get rid of the backslashes? Is there an alternative way to enforcing camel-casing?
Thanks to all the references to other Stackoverflow pages, I'm going to post three solutions so anyone else having a similar issue can take their pick of the code. The first code example is one that I created after looking at what other people were doing. The last two are from other Stackoverflow users. I hope this helps someone else!
// Solution #1 - This is my solution. It updates the JsonMediaTypeFormatter whenever a response is sent to the API call.
// If you ever need to keep the controller methods untouched, this could be a solution for you.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Web.Http;
using Newtonsoft.Json.Serialization;
public class CamelCasedApiController : ApiController
{
public HttpResponseMessage CreateResponse(object responseMessageContent)
{
try
{
var httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, responseMessageContent, JsonMediaTypeFormatter.DefaultMediaType);
var objectContent = httpResponseMessage.Content as ObjectContent;
if (objectContent != null)
{
var jsonMediaTypeFormatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
};
httpResponseMessage.Content = new ObjectContent(objectContent.ObjectType, objectContent.Value, jsonMediaTypeFormatter);
}
return httpResponseMessage;
}
catch (Exception exception)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, exception.Message);
}
}
}
The second solution uses an attribute to decorate the API controller method.
// http://stackoverflow.com/questions/14528779/use-camel-case-serialization-only-for-specific-actions
// This code allows the controller method to be decorated to use camel-casing. If you can modify the controller methods, use this approach.
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Web.Http.Filters;
using Newtonsoft.Json.Serialization;
public class CamelCasedApiMethodAttribute : ActionFilterAttribute
{
private static JsonMediaTypeFormatter _camelCasingFormatter = new JsonMediaTypeFormatter();
static CamelCasedApiMethodAttribute()
{
_camelCasingFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
public override void OnActionExecuted(HttpActionExecutedContext httpActionExecutedContext)
{
var objectContent = httpActionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
if (objectContent.Formatter is JsonMediaTypeFormatter)
{
httpActionExecutedContext.Response.Content = new ObjectContent(objectContent.ObjectType, objectContent.Value, _camelCasingFormatter);
}
}
}
}
// Here is an example of how to use it.
[CamelCasedApiMethod]
public HttpResponseMessage Get()
{
...
}
The last solution uses an attribute to decorate the entire API controller.
// http://stackoverflow.com/questions/19956838/force-camalcase-on-asp-net-webapi-per-controller
// This code allows the entire controller to be decorated to use camel-casing. If you can modify the entire controller, use this approach.
using System;
using System.Linq;
using System.Net.Http.Formatting;
using System.Web.Http.Controllers;
using Newtonsoft.Json.Serialization;
public class CamelCasedApiControllerAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings httpControllerSettings, HttpControllerDescriptor httpControllerDescriptor)
{
var jsonMediaTypeFormatter = httpControllerSettings.Formatters.OfType<JsonMediaTypeFormatter>().Single();
httpControllerSettings.Formatters.Remove(jsonMediaTypeFormatter);
jsonMediaTypeFormatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
};
httpControllerSettings.Formatters.Add(jsonMediaTypeFormatter);
}
}
// Here is an example of how to use it.
[CamelCasedApiController]
public class SomeController : ApiController
{
...
}
If you want to set it globally you can just remove the current Json formatter from the HttpConfiguration and replace it with your own.
public static void Register(HttpConfiguration config)
{
config.Formatters.Remove(config.Formatters.JsonFormatter);
var serializer = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
var formatter = new JsonMediaTypeFormatter { Indent = true, SerializerSettings = serializer };
config.Formatters.Add(formatter);
}
Comment on https://stackoverflow.com/a/26506573/887092 works for some cases but not others
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
This way works in other cases
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
So, cover all bases with:
private void ConfigureWebApi(HttpConfiguration config)
{
//..
foreach (var jsonFormatter in config.Formatters.OfType<JsonMediaTypeFormatter>())
{
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
var singlejsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
singlejsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}

Categories

Resources