Specific JSON settings per controller on ASP.NET MVC 6 - c#

I need specific JSON settings per controller in my ASP.NET MVC 6 webApi.
I found this sample that works (I hope !) for MVC 5 :
Force CamelCase on ASP.NET WebAPI Per Controller
using System;
using System.Linq;
using System.Web.Http.Controllers;
using System.Net.Http.Formatting;
using Newtonsoft.Json.Serialization;
public class CamelCaseControllerConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
var formatter = controllerSettings.Formatters.OfType<JsonMediaTypeFormatter>().Single();
controllerSettings.Formatters.Remove(formatter);
formatter = new JsonMediaTypeFormatter
{
SerializerSettings = {ContractResolver = new CamelCasePropertyNamesContractResolver()}
};
controllerSettings.Formatters.Add(formatter);
}
}

This class works fine :
using System;
using System.Linq;
using Newtonsoft.Json.Serialization;
using Microsoft.AspNet.Mvc.Filters;
using Newtonsoft.Json;
using Microsoft.AspNet.Mvc.Formatters;
namespace Teedl.Web.Infrastructure
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class MobileControllerConfiguratorAttribute : Attribute, IResourceFilter
{
private readonly JsonSerializerSettings serializerSettings;
public MobileControllerConfiguratorAttribute()
{
serializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
Binder = new TypeNameSerializationBinder("Teedl.Model.Mobile.{0}, Teedl.Model.ClientMobile")
};
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
var mobileInputFormatter = new JsonInputFormatter(serializerSettings);
var inputFormatter = context.InputFormatters.FirstOrDefault(frmtr => frmtr is JsonInputFormatter);
if (inputFormatter != null)
{
context.InputFormatters.Remove(inputFormatter);
}
context.InputFormatters.Add(mobileInputFormatter);
var mobileOutputFormatter = new JsonOutputFormatter(serializerSettings);
var outputFormatter = context.OutputFormatters.FirstOrDefault(frmtr => frmtr is JsonOutputFormatter);
if (outputFormatter != null)
{
context.OutputFormatters.Remove(outputFormatter);
}
context.OutputFormatters.Add(mobileOutputFormatter);
}
}
}
Use :
[Route("api/mobile/businessrequest")]
[Authorize]
[MobileControllerConfigurator]
public class MobileBusinessRequestController : BaseController
{

You can use a return type of JsonResult on your controller action methods. In my case, I needed specific actions to return Pascal Case for certain legacy scenarios. The JsonResult object allows you to pass an optional parameter as the JsonSerializerSettings.
public JsonResult Get(string id)
{
var data = _service.getData(id);
return Json(data, new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver()
});
}
To have more consistent controller method signatures I ended up creating an extension method:
public static JsonResult ToPascalCase(this Controller controller, object model)
{
return controller.Json(model, new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver()
});
}
Now I can simply call the extension method inside my controller like below:
public IActionResult Get(string id)
{
var data = _service.getData(id);
return this.ToPascalCase(data);
}

Related

Trimming Request.Body before reaching action method, for model binding in XML

I have an Ethernet to 1-Wire interface that periodically sends out an HTTP post with sensors data. The data body is in XML, except it's not fully valid XML. I can not change the HTTP body because it's in an embedded software. The complete request body looks like this:
------------------------------3cbec9ce8f05
Content-Disposition: form-data; name="ServerData"; filename="details.xml"
Content-Type: text/plain
<?xml version="1.0" encoding="UTF-8"?>
<Devices-Detail-Response xmlns="http://www.example.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<PollCount>2739</PollCount>
<DevicesConnected>1</DevicesConnected>
<LoopTime>1.022</LoopTime>
<DevicesConnectedChannel1>0</DevicesConnectedChannel1>
<DevicesConnectedChannel2>0</DevicesConnectedChannel2>
<DevicesConnectedChannel3>1</DevicesConnectedChannel3>
<DataErrorsChannel1>0</DataErrorsChannel1>
<DataErrorsChannel2>0</DataErrorsChannel2>
<DataErrorsChannel3>0</DataErrorsChannel3>
<VoltageChannel1>4.91</VoltageChannel1>
<VoltageChannel2>4.92</VoltageChannel2>
<VoltageChannel3>4.92</VoltageChannel3>
<VoltagePower>5.16</VoltagePower>
<DeviceName>Unit 3 OW2</DeviceName>
<HostName>EDSOWSERVER2</HostName>
<MACAddress>00:00:00:00:00:00</MACAddress>
<DateTime>2018-12-12 16:44:48</DateTime>
<owd_DS18B20 Description="Programmable resolution thermometer">
<Name>DS18B20</Name>
<Family>28</Family>
<ROMId>F70000024D85E528</ROMId>
<Health>7</Health>
<Channel>3</Channel>
<RawData>C6004B467FFF0A102A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</RawData>
<PrimaryValue>12.3750 Deg C</PrimaryValue>
<Temperature Units="Centigrade">12.3750</Temperature>
<UserByte1 Writable="True">75</UserByte1>
<UserByte2 Writable="True">70</UserByte2>
<Resolution>12</Resolution>
<PowerSource>0</PowerSource>
</owd_DS18B20>
</Devices-Detail-Response>
------------------------------3cbec9ce8f05--
So i'm trying to remove the '--------...' and Content-Type, and the '-------..' at the end before it hits the action method.
Here's my controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Monitor.Models;
using System;
using System.IO;
namespace Monitor.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class SensorController : Controller
{
private readonly ILogger _log;
public SensorController(ILogger<SensorController> logger)
{
_log = logger;
}
[HttpPost]
[OwServer]
public IActionResult Post([FromBody] Ow_ServerModel model)
{
return Ok("Working");
}
}
public class OwServer : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.Headers["Content-Type"] = "application/xml";
using (StreamReader stream = new StreamReader(context.HttpContext.Request.Body))
{
string body = stream.ReadToEnd();
int start = body.IndexOf('<');
int last = body.LastIndexOf('>') + 1;
string parsedBody = body.Substring(start, (last - start));
// context.HttpContext.Request.Body =
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}
using System;
using System.Xml.Serialization;
namespace Monitor.Models
{
[Serializable]
[XmlRoot("Devices-Detail-Response", Namespace = "http://www.example.com")]
public class Ow_ServerModel
{
public int PollCount { get; set; }
}
}
The request body indicates that the embedded software is posting a multipart data. And the following content-disposition means that it's sending a file of details.xml:
------------------------------3cbec9ce8f05
Content-Disposition: form-data; name="ServerData"; filename="details.xml"
Content-Type: text/plain
So you don't need to manually remove the boundary of '------------------------------3cbec9ce8f05' and Content-Type=.... Simply use Request.Form.Files.
Also, as suggested by #ivan-valadares, you can use a Model binder to lift the heavy things . But it seems that he is treating all the request body as string and then construct an XDocument . A much more elegant way is use XmlSerializer to create a strongly-typed object. Also, IModelBinder Interface doesn't have a method of public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext). We should use BindModelAsync(ModelBindingContext bindingContext) instead.
So create a model binder as below :
public class EmbededServerDataBinder<T> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName ?? "ServerData";
XmlSerializer serializer = new XmlSerializer(typeof(T));
var data= bindingContext.HttpContext.Request.Form.Files[modelName];
if(data == null){
bindingContext.ModelState.AddModelError(modelName,"invalid error");
return Task.CompletedTask;
}
using(var stream = data.OpenReadStream()){
var o = serializer.Deserialize(stream);
bindingContext.Result = ModelBindingResult.Success(o);
}
return Task.CompletedTask;
}
}
Now you can use it in the Action method:
[HttpPost]
public IActionResult Post([ModelBinder(typeof(EmbededServerDataBinder<Ow_ServerModel>))] Ow_ServerModel ServerData)
{
return Ok("Working");
}
Note the name of ServerData matters. The model binder will seek this name within content-disposition.
I test it with your payload , and it works as expected for me :
You are trying to do that with a ActionFilter, i would recomend a custom Binder and the use of regex to extract de xml from the request.
Register the new custom xml binder in WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.Services.Insert(typeof(ModelBinderProvider), 0,
new SimpleModelBinderProvider(typeof(XDocument), new XmlCustomBinder()));
}
Create a custom binder that will get the content body and extract only the xml
public class XmlCustomBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
try
{
var parsedXml = actionContext.Request.Content.ReadAsStringAsync().Result;
Regex regex = new Regex(#"<\?xml.*>", RegexOptions.Singleline);
Match match = regex.Match(parsedXml);
if (!match.Success) return false;
parsedXml = match.Groups[0].Value;
TextReader textReader = new StringReader(parsedXml);
XDocument xDocument = XDocument.Load(textReader);
bindingContext.Model = xDocument;
return true;
}
catch(Exception ex)
{
bindingContext.ModelState.AddModelError("XmlCustomBinder", ex);
return false;
}
}
}
Controller code, get a XDocument (XML) value
[HttpPost]
[OwServer]
public IActionResult Post([ModelBinder(typeof(XmlCustomBinder))] XDocument xDocument)
{
return Ok("Working");
}
https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument?view=netframework-4.7.2

Return the result of View Component as a string in C#

I have a service which generates email messages and returns them a string. However, I have a view component which I usually call from my views and append the result with an already present string:
string result = #await Component.InvokeAsync("Widget", new { widgetZone = "stock_levels_summary_cart_price", additionalData = product.Id })
sb.AppendLine($"<td style=\"padding: 0.6em 0.4em;text-align: center;\">{result}</td>");
How can I call this view component in my service and pass the result into a string variable which I can use in my email message service?
I was able to implement this by creating a partial view for the view component.
In the partial view, I declared my view component:
#model int
#await Component.InvokeAsync("Widget", new { widgetZone = "stock_levels_summary_cart_price", additionalData = Model})
In my controller, I created an action as follows:
public virtual PartialViewResult OrderDetailsStocklevel(int productId)
{
return PartialView("~/Views/Shared/_OrderDetailsStocklevel.cshtml", productId);
}
Then I implemented a service to render the partial view content and return the result:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
namespace Nop.Services.Helpers
{
public interface IViewRenderHelper
{
string RenderToString(string viewName, object model, string viewPath);
}
public class ViewRenderHelper : IViewRenderHelper
{
private readonly IServiceProvider _serviceProvider;
public ViewRenderHelper(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public string RenderToString(string viewName, object model, string viewPath)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var engine = _serviceProvider.GetService(typeof(IRazorViewEngine)) as IRazorViewEngine;
var tempDataProvider = _serviceProvider.GetService(typeof(ITempDataProvider)) as ITempDataProvider;
if (engine == null)
{
throw new Exception("Can't find IRazorViewEngine");
}
var viewEngineResult = engine.FindView(actionContext, viewPath, false);
if (!viewEngineResult.Success)
{
throw new InvalidOperationException($"Couldn't find view '{viewName}'");
}
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
};
using (var output = new StringWriter())
{
var viewContext = new ViewContext(actionContext, viewEngineResult.View,
viewDictionary, new TempDataDictionary(actionContext.HttpContext, tempDataProvider),
output, new HtmlHelperOptions());
viewEngineResult.View.RenderAsync(viewContext).GetAwaiter().GetResult();
return output.ToString();
}
}
}
}
Then, to load the partial view that was created from a view component, I executed the method as follows:
var stockLevelLabel = _viewRenderService.RenderToString("stocklevel", orderItem.ProductId, "_OrderDetailsStocklevel");

Creating per-request controller/action based formatters in ASP.NET 5

I'm trying to implement HATEOAS in my ASP rest API, changing the ReferenceResolverProvider.
The problem is, that depending on which controller I use, I'd like to use different ReferenceResolvers, because I need to behave differently for each Controller.
Now I have universal options:
services.AddMvc()
.AddJsonOptions(option => option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver())
.AddJsonOptions(options => options.SerializerSettings.ReferenceResolverProvider = () => new RoomsReferenceResolver<Room>())
.AddJsonOptions(options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects);
And I want to have something like this:
services.AddMvc()
.AddJsonOptions(option => option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver())
.AddJsonOptions<RoomsController>(options => options.SerializerSettings.ReferenceResolverProvider = () => new RoomsReferenceResolver<Room>())
.AddJsonOptions(options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects);
You seem to be wanting to create a per-controller specific formatters. This can be achieved by using a filter called IResourceFilter. A quick example:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CamelCaseJsonFormatterResourceFilter : Attribute, IResourceFilter
{
private readonly JsonSerializerSettings serializerSettings;
public CamelCaseJsonFormatterResourceFilter()
{
// Since the contract resolver creates the json contract for the types it needs to deserialize/serialize,
// cache it as its expensive
serializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
// remove existing input formatter and add a new one
var camelcaseInputFormatter = new JsonInputFormatter(serializerSettings);
var inputFormatter = context.InputFormatters.FirstOrDefault(frmtr => frmtr is JsonInputFormatter);
if (inputFormatter != null)
{
context.InputFormatters.Remove(inputFormatter);
}
context.InputFormatters.Add(camelcaseInputFormatter);
// remove existing output formatter and add a new one
var camelcaseOutputFormatter = new JsonOutputFormatter(serializerSettings);
var outputFormatter = context.OutputFormatters.FirstOrDefault(frmtr => frmtr is JsonOutputFormatter);
if (outputFormatter != null)
{
context.OutputFormatters.Remove(outputFormatter);
}
context.OutputFormatters.Add(camelcaseOutputFormatter);
}
}
// Here I am using the filter to indicate that only the Index action should give back a camelCamse response
public class HomeController : Controller
{
[CamelCaseJsonFormatterResourceFilter]
public Person Index()
{
return new Person() { Id = 10, AddressInfo = "asdfsadfads" };
}
public Person Blah()
{
return new Person() { Id = 10, AddressInfo = "asdfsadfads" };
}
If you are curious about the filter execution order, following is an example of the sequence of them:
Inside TestAuthorizationFilter.OnAuthorization
Inside TestResourceFilter.OnResourceExecuting
Inside TestActionFilter.OnActionExecuting
Inside Home.Index
Inside TestActionFilter.OnActionExecuted
Inside TestResultFilter.OnResultExecuting
Inside TestResultFilter.OnResultExecuted
Inside TestResourceFilter.OnResourceExecuted
Interesting problem.
What about making the ReferenceResolver a facade:
class ControllerReferenceResolverFacade : IReferenceResolver
{
private IHttpContextAccessor _context;
public ControllerReferenceResolverFacade(IHttpContextAccessor context)
{
_context = context;
}
public void AddReference(object context, string reference, object value)
{
if ((string)_context.HttpContext.RequestServices.GetService<ActionContext>().RouteData.Values["Controller"] == "HomeController")
{
// pass off to HomeReferenceResolver
}
throw new NotImplementedException();
}
Then you should be able to do:
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ReferenceResolverProvider = () => {
return new ControllerReferenceResolverFacade(
services.BuildServiceProvider().GetService<IHttpContextAccessor>());
});
This might not be exactly what you need but it might help you get started?

How can i have a IServiceProvider available in ValidationContext parameter of IValidatableObject.Validate method

Controller calls IValidatableObject.Validate internally and passes a ValidationContext object as an argument. I want to use validationContext.GetService() method to get a service object and use it.
I can pass this service as a dependency to controller constructor using AutoFac(DI Injection dll). How do i make it availale to the ValidationContext object?
This stackoverflow question might contain the answer but i do not understand it fully : Asp.Net MVC3: Set custom IServiceProvider in ValidationContext so validators can resolve services
Here is the code:
Model : Employee
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ValidationContextDemo.Models
{
public class Employee : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
var EmployeeService = (Service.EmployeeService)validationContext.GetService(typeof(Service.EmployeeService));
if (!EmployeeService.IsValidDepartment(this.DepartmentId))
{
result.Add(new ValidationResult("This DepartmentId does not exists"));
}
return result;
}
}
}
Repository : EmployeeRepository
using System.Collections.Generic;
using System.Linq;
namespace ValidationContextDemo.Models
{
public class EmployeeRepository
{
public EmployeeRepository()
{
Employees = new List<Employee>() {
new Employee{Id=1, Name="Alpha", DepartmentId=1},
new Employee{Id=2, Name="Beta", DepartmentId=1},
new Employee{Id=3, Name="Gamma", DepartmentId=1}
};
}
public List<Employee> Employees { get; set; }
public void AddEmployee(Employee e)
{
Employees.Add(e);
}
public void UpdateEmployee(int id, Employee e)
{
Employee emp = Employees.Where(x => x.Id == id).FirstOrDefault();
emp.Name = e.Name;
emp.DepartmentId = e.DepartmentId;
}
public void DeleteEmployee(Employee e)
{
Employees.Remove(e);
}
}
}
Validation Source : Enum
namespace ValidationContextDemo.Enums
{
public enum Department
{
Engineering=1,
Sales=2,
Shipping=3,
HumanResources=4
}
}
Service : EmployeeService
using System;
using System.Linq;
namespace ValidationContextDemo.Service
{
public class EmployeeService
{
public bool IsValidDepartment(int departmentId)
{
return Enum.GetValues(typeof(Enums.Department)).Cast<Enums.Department>().Contains((Enums.Department)departmentId);
}
}
}
IServiceProvider : EmployeeServiceProvider
using System;
namespace ValidationContextDemo.Service
{
public class EmployeeServiceProvider: IServiceProvider
{
public object GetService(Type serviceType)
{
if (serviceType==typeof(EmployeeService))
{
return new EmployeeService();
}
return null;
}
}
}
Controller : EmployeeController
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using ValidationContextDemo.Models;
using ValidationContextDemo.Service;
namespace ValidationContextDemo.Controllers
{
public class EmployeeController : ApiController
{
EmployeeRepository _repository;
EmployeeServiceProvider _serviceProvider;
public EmployeeController()
{
_repository = new EmployeeRepository();
_serviceProvider = new EmployeeServiceProvider();
}
public IHttpActionResult Get()
{
return Json(_repository.Employees);
}
public HttpResponseMessage Post(Employee e)
{
ValidationContext vContext = new ValidationContext(e, _serviceProvider, null);
e.Validate(vContext);
_repository.AddEmployee(e);
return new HttpResponseMessage(HttpStatusCode.Created);
}
}
}
Notice the ValidationContext parameter in Validate method of Employee model. Before model binding, when the validation happens, the IServiceProvider part of ValidationContext is null.
So, after model binding, when the code reaches inside my Controller Action "Post", i create another ValidationContext with a _serviceProvider and call Validate again.
My question is , how can i have this _serviceProvider in my ValidationContext before model binding.
Please let me know if this is still not clear.
Note: I created this example for the sake of this question, i am not using Autofac as a DI container in this example.
For starters your above code example is pretty irrelevant to the problem you are facing because you can potentially hack around the issue by a simple null check in the Employee model as you are validating it explicitly:
ValidationContext vContext = new ValidationContext(e, _serviceProvider, null);
e.Validate(vContext);
I assume (ideally) you don't want to do that explicitly and unfortunately there is no easy way. You have to set up a fair amount of plumbing (i.e. model binders etc) to hook into the MVC/WebApi pipelines. This solution will most likely work for you if you have the time to follow it :).
PS: The solution is catered towards MVC but I don't see any reason why it can be tweaked for the API.

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