I'm unable to globally configure my ASP.NET MVC 4 application (using Web API controllers) so that actions that return HttpResponseMessage with a Json string that includes values of type Newtonsoft.Json.Linq.JValue on dynamic properties.
I can produce required output by serializing the JSON string manually in each action, but ideally I'd like to be able to globally configure the correct behaviour on Application_Start.
To demonstrate my problem, consider the following class:
public class Content : IAccountEntity
{
public string StringProperty { get; set; }
public dynamic DynamicProperty { get; set; }
}
Given the following global serialization configuration:
protected void Application_Start()
{
...
ConfigureJsonFormat();
}
private static void ConfigureJsonFormat()
{
var formatters = GlobalConfiguration.Configuration.Formatters;
formatters.Remove(formatters.XmlFormatter);
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
json.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
And the following MVC Action code
public HttpResponseMessage Get(HttpRequestMessage request)
{
... // Other initialisation code
content.StringProperty = "Hello world";
content.DynamicProperty.Email = new Newtonsoft.Json.Linq.JValue("test#test.com");
return Request.CreateResponse(HttpStatusCode.OK, content);
}
I get the following Json response. As you can see, it is missing the value for the dynamic sub property 'email'.
[
{
"stringProperty": "Hello world",
"dynamicProperty": {
email:[]
}
}
]
I can resolve the problem on a per action basis by taking a more manual approach to creating the JSON string response as follows:
public HttpResponseMessage Get(HttpRequestMessage request)
{
... // Other initialisation code
content.StringProperty = "Hello world";
content.DynamicProperty.Email = new Newtonsoft.Json.Linq.JValue("test#test.com");
var jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
string jsonStr = JsonConvert.SerializeObject(content, Formatting.Indented, jsonSerializerSettings);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(jsonStr, Encoding.UTF8, "application/json")
};
}
Which produces the desired output:
[
{
"stringProperty": "Hello world",
"dynamicProperty": {
email: "test#test.com"
}
}
]
I would prefer however to be able to replicate the above behaviour using the global configuration.
Is there a way I can modify my method ConfigureJsonFormat so that the values for dynamics are included on the Json response without having to code for this on an action by action basis?
I'm hoping that I can set something on json.SerializerSettings, but have not been able to find the correct setting as yet.
Related
I am writing my first Azure FunctionApp, and I am looking to return a valid JSON Object from a deserialized class that was created from a Faker JSON object as shown below:
Class:
public class TestData
{
//based on faker API
public int userId { get; set; }
public int id { get; set; }
public string title { get; set; }
public bool completed { get; set; }
}
Function App Entry point and return code:
public static Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,ExecutionContext context, ILogger log)
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
var config = configBuilder.Build();
log.LogInformation("Config Loaded");
// for later use when i get past the current issue
var settings = new AppSettings();
config.Bind("AppSettings", settings);
//Setting this seems to be the route cause, but need to respond correctly?
req.HttpContext.Response.Headers.Add("Content-Type", "application/json");
var worker = new WorkerClass();
var results = worker.DoSomeWork();
return Task.FromResult(results);
//Also Tried:
//return Task.FromResult<IActionResult>(new OkObjectResult(results));
}
Test Worker Class for returned object data and JSON Serialization:
public class WorkerClass
{
public IActionResult DoSomeWork()
{
try
{
var testdata = new TestData
{
userId = 123,
id = 1,
title = "delectus aut autem",
completed = true
};
//formatted as Json correctly
var results = JsonConvert.SerializeObject(testdata, Formatting.Indented);
return new OkObjectResult(results);
}
catch (Exception dswEx)
{
return new BadRequestObjectResult($"\{ERROR: {dswEx.Message}\}");
}
}
}
I have tried several ways to try and reach a clean JSON output using the response object types, as it would be good to add logic based on ObjectResults further down the line, but I suspect I am missing the obvious.
If the return is set to: return Task.FromResult(results); the the response is an escaped JSON output:
"{\"userId\": 1,\"id\": 1,\"title\": \"delectus aut autem\",\"completed\": false}"
if the return is amended to be: return Task.FromResult<IActionResult>(new OkObjectResult(results)); then the response is escaped but encapsulated in a Object wrapper:
{
"value": "{\r\n \"userId\": 1,\"id\": 1,\"title\": \"delectus aut autem\",\"completed\": false}",
"formatters": [],
"contentTypes": [],
"declaredType": null,
"statusCode": 200
}
All I would like to achieve is to have the correct response header as application/json and a result set presented as follows:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Thanks in advance.
You want to return new JsonResult(data), where data is your managed structures (not serialized json data like you were doing for some odd reason).
You don't need to serialize an object to json, Net will do it for you.
return Ok(testdata);
I have a problem with integration test (made with WebApplicationFactory) that cannot parse back to JSON obtained response. I have seen some similar issues reported here but nothing found so far was helpful.
Generally, for the test purpose there is this very simple Program.cs:
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddControllers();
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
var app = builder.Build();
app.MapControllers();
app.MapGet("/test", () => new SomeView { Name = "SomeName", SomeEnum = SomeEnum.No });
app.Run();
public class SomeView
{
public string Name { get; set; }
public SomeEnum SomeEnum { get; set; }
}
public enum SomeEnum
{
Yes = 1,
No
}
And the following very simple test:
public class UnitTest
{
[Fact]
public async Task Test1()
{
var factory = new WebApplicationFactory<Program>();
var client = factory.CreateClient();
var response = await client.GetAsync("/test");
var result = await response.Content.ReadFromJsonAsync<SomeView>(new JsonSerializerOptions()
{
Converters = { new JsonStringEnumConverter() }
});
Assert.Equal(SomeEnum.No, result.SomeEnum);
}
}
The issue is - running that test is failed since:
Xunit.Sdk.EqualException
Assert.Equal() Failure
Expected: No
Actual: 0
The whole object have nulled properties. Without providing JsonSerializerOptions the application throws another exception that it is not possible to parse that SomeEnum. However, if I will try to get the response by:
var result = await response.Content.ReadAsStringAsync()
The result is correctly set to the:
{"name":"SomeName","someEnum":"No"}
Any suggestion what is done incorrectly here would be appreciated.
Names of properties in json are in different case compared to the properties of SomeView. Either fix it with JsonPropertyNameAttribute or provide naming policy:
var options = new JsonSerializerOptions()
{
Converters = { new JsonStringEnumConverter() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var result = await response.Content.ReadFromJsonAsync<SomeView>(options);
I'm using .Net Core 3.1 and I'm having trouble sending requests from a Blazor component. I want to send a request to a controller I have, and these requests systematically end up in 400 Bad request.
In my Startup.cs, I have
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
services.AddScoped<HttpClient>(s =>
{
var uriHelper = s.GetRequiredService<NavigationManager>();
return new HttpClient
{
BaseAddress = new Uri(uriHelper.BaseUri)
};
});
}
In my Blazor component, I have:
var json2 = Newtonsoft.Json.JsonConvert.SerializeObject(_Model);
var stringContent2 = new StringContent(json2, System.Text.Encoding.UTF8, "application/json");
var response2 = await Http.PostAsync("/[controllerName]/[Method]", stringContent2);
if (response2.IsSuccessStatusCode)
{
var resultContent = response2.Content.ReadAsStringAsync().Result;
return resultContent;
}
else
return "failed";
And here is my Controller Method prototype:
[HttpPost]
public IActionResult Method([FromBody] Model form)
{...}
Would you happen to see what's wrong with the code?
You are passing a StringContent object in your PostAsync method, but in your action you have your Model as a parameter.
You have two options :
To change your action parameter to a StringContent.
To parse the Json as your Model to pass it to the PostAsync method content parameter.
Regards,
these requests systematically end up in 400 Bad request.
Please check you provide correct request header(s) and well-formatted data while you make request from your Blazor app to backend service.
I did a test using following code snippet with simple testing data, which work well on my side. If possible, you can create a new component and test if the code snippet can work for you.
var _Model = new Model { Id = 1, Name = "fehan" };
var json2 = Newtonsoft.Json.JsonConvert.SerializeObject(_Model);
var stringContent2 = new StringContent(json2, System.Text.Encoding.UTF8, "application/json");
var response2 = await Http.PostAsync("Home/Method", stringContent2);
if (response2.IsSuccessStatusCode)
{
var resultContent = response2.Content.ReadAsStringAsync().Result;
}
Model class
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
Test Result
Besides, if you are making request to MVC controller action, please check if you enabled antiforgery validation on controller or action(s).
In a security run on our code base we are getting a high priority issue i.e. "Deserialization of Untrusted Data" We are using Newtonsoft JSON package for deserialization. Below is the code snippet used and I followed this stack overflow answer(Fixing the deserializing of untrusted data using C#) to solve this issue. It is still not resolved. Any pointers will be helpful.
var idstate = HttpContext.Current.Request.Form[Constants.State];
var jsonSerializerSettings = new JsonSerializerSettings();
LoginRedirection redirectionObject = JsonConvert.DeserializeObject<LoginRedirectionModel>(idstate, jsonSerializerSettings)?.ToLoginRedirection();
Models used for deserialization are below:-
public class LoginRedirection
{
public string stateUrl { get; set; }
public string cartSession { get; set; }
}
public class LoginRedirectionModel
{
public string stateUrl { get; set; }
public string cartSession { get; set; }
public LoginRedirection ToLoginRedirection()
{
return new LoginRedirection { stateUrl = stateUrl, cartSession = cartSession };
}
}
Security exception "OWASP Top 10 2017: A8-Insecure Deserialization" is coming for the below line
LoginRedirection redirectionObject = JsonConvert.DeserializeObject<LoginRedirectionModel>(idstate, jsonSerializerSettings)?.ToLoginRedirection();
JSON:-
{ "stateUrl"="<URL HERE>", "cartSession":"<GUID HERE>"}
Another aspect to problem is:-
When we consume an API using HttpClient and then trying to deserialize the response from API, we are getting the same security warning. Below is the code for consuming and deserializing the API.
public T Post<T, M>(M data, string url, bool ocpSubscriptionHeaderRequired = true)
{
T response = default(T);
try
{
string postBody = JsonConvert.SerializeObject(data);
using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(ApiRequestTimeOutInSeconds) })
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (ocpSubscriptionHeaderRequired)
{
client.DefaultRequestHeaders.Remove(Constants.ApiSubscriptionKey);
client.DefaultRequestHeaders.Add(Constants.ApiSubscriptionKey, GenericUtilities.GetConfigData(Constants.ApiSubscriptionKeyValue));
}
HttpResponseMessage result = Task.Run(() => client.PostAsync(url, new StringContent(postBody, Encoding.UTF8, "application/json"))).Result;
if (result.IsSuccessStatusCode)
{
string responseString = Task.Run(() => result.Content.ReadAsStringAsync()).Result;
response = JsonConvert.DeserializeObject<T>(responseString, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.None
});
}
}
}
catch (Exception ex)
{
_logger.WriteException(ex);
}
return response;
}
You can use the JsonConvert.PopulateObject(sourceJsonString, obj) instead of deserializing it using JsonConvert.DesrializeObject<>();.
Sounds like a false-positive to me.
JsonConvert.DeserializeObject with the wrong JsonSerializerSettings options could be used to construct some other .net type, which could execute something unexpected within its constructor. Or you could write your own serialiser, with some other exploitable bugs in it.
But if you can't use JsonConvert.DeserializeObject for a simple object with two string fields, then every .net app that handles json would already be broken.
Currently, my ApiControllers are returning XML as a response, but for a single method, I want to return JSON. i.e. I can't make a global change to force responses as JSON.
public class CarController : ApiController
{
[System.Web.Mvc.Route("api/Player/videos")]
public HttpResponseMessage GetVideoMappings()
{
var model = new MyCarModel();
return model;
}
}
I tried doing this, but can't seem to convert my model to a JSON string correctly:
var jsonString = Json(model).ToString();
var response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
return response;
If you can't make a global change to force responses as JSON,
then try:
[Route("api/Player/videos")]
public HttpResponseMessage GetVideoMappings()
{
var model = new MyCarModel();
return Request.CreateResponse(HttpStatusCode.OK,model,Configuration.Formatters.JsonFormatter);
}
OR
[Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Json(model);
}
If you want to change globally, then first go to YourProject/App_Start/WebApiConfig.cs and add:
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(
config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml"));
at the bottom of the Register method.
Then try:
[Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Ok(model);
}
The XML is returned instead JSON because the caller is requesting XML. The returned format can be forced to JSON using a filter that adds the header you need and lets MVC resolve the JSON.
public class AcceptHeaderJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
actionContext.Request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
So you can decorate the method you want to force a JSON response with this attribute and keep the same global JSON configuration and serialization as any other method.
Try this ApiController.Ok.
You just do return Ok(model) and change the return type to IHttpActionResult.
Example:
public class CarController : ApiController
{
[System.Web.Mvc.Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Ok(model);
}
}
For API controllers it is up to the caller to determine how the response is created. Unless you specifically add code to force only one type of response. Here is a simple example of an API method and what happens when called requesting XML, or JSON.
public class XmlEampleController : ApiController
{
[HttpPost]
[ActionName("MyOrderAction")]
public HttpResponseMessage MyOrder([FromBody]MyOder order)
{
if (order != null)
{
return Request.CreateResponse<MyOder>(HttpStatusCode.Created, order);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
[Serializable]
public partial class MyOder
{
private string dataField;
public string MyData
{
get
{
return this.dataField;
}
set
{
this.dataField = value;
}
}
}
}
Sample:
Maybe the issue is with WebApiConfig file.
At the end of the file add these 2 lines
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
It is in Project/App_Start/WebApiConfig.cs For asp.net MVC