"dynamic" keyword and JSON data - c#

An action method in my ASP.NET MVC2 application returns a JsonResult object and in my unit test I would like to check that the returned JSON object indeed contains the expected values.
I tried this:
1. dynamic json = ((JsonResult)myActionResult).Data;
2. Assert.AreEqual(JsonMessagesHelper.ErrorLevel.ERROR.ToString(), json.ErrorLevel);
But I get a RuntimeBinderException "'object' does not contain a definition for 'ErrorLevel'".
However, when I place a breakpoint on line 2 and inspect the json dynamic variable (see picture below), it obviously does contain the ErrorLevel string and it has the expected value, so if the runtime binder wasn't playing funny the test would pass.
What am I not getting? What am I doing wrong and how can I fix this? How can I make the assertion pass?

You don't really need dynamic. Here's an example. Suppose you had the following action which you would like to unit test:
public ActionResult Index()
{
return Json(new { Id = 5, Foo = "bar" });
}
and the corresponding test:
// act
var actual = subjectUnderTest.Index();
// assert
var data = new RouteValueDictionary(actual.Data);
Assert.AreEqual(5, data["Id"]);
Assert.AreEqual("bar", data["Foo"]);
Also you might find the following blog post useful.

The Data property of the JsonResult is of type Object this means, although you have a dynamic declaration, the type that is set is still Object. The other issue is that you are using an anonymous type as the Data and then trying to access that as a declared instance outside of its applicable scope. Use #Darin's technique for accessing the property values using a RouteValueDictionary.

Related

FromQueryAttribute enum parameters throw validation_error invalid value, but parameters with FromBodyAttribute do not?

In ASP.NET Core, if an action accepts directly an enum type, and that enum for example has defined 1 to something, if we pass a value different than 1, we'll get a validation error.
This is good! But it doesn't work for when the enum is inside a complex object type, when they are built from the body of a request ([FromBody] attribute).
Why is this happening? I know that anything coming from the body, is being handled by JSON Converters. Why can't they handle this for us, when the Query binder (what/where is it?) does it for us?
Example Enum:
public Enum Example
{
One = 1
}
Example Action:
public object ExampleAction(Enum hello)
{
return Ok();
}
If you hit the action with a HTTP request, passing in the hello parameter in a query string with a value different than 1, you will get a validation error.
Now, if you annotate the hello parameter with the [FromBody] attribute and make a new request (passing in this time the data via the body instead of the query string), that behavior is lost.

Json to C# Deserialize - Property name is an int - how to fix?

Okay so I'm trying to get data from an API, and it works. This is the data I get:
json data
Yet the only problem is, when I try to access the "7" and "10" property in order to get "rankPoints", I get a bug:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'The call is ambiguous between the following methods or properties: 'System.Console.WriteLine(string, params object[])' and 'System.Console.WriteLine(char[])''
This is the code that works:
var x = oMycustomclassname["rankedSeasons"];
This is the code that does not work:
var x = oMycustomclassname["rankedSeasons.7.10.rankPoints"];
I'm guessing it cant access the property because it is an int? But I cant change the data, since it is from an API.
Thank you,
MV
every sub item is another json object which can be accessed like a dictionary
var x = (int)oMycustomclassname["rankedSeasons"]["7"]["10"]["rankPoints"];

Web API resolves parameters with mismatched types as missing

I am using ASP.NET Core to host a Web API. I have the following action defined in my Orders controller:
[HttpGet("list")]
public List<Order> List([FromQuery] int index = 0, [FromQuery] int length = 100)
{
return GetOrders(index, length);
}
The following returns a single order:
https://localhost:5000/api/v1/Orders/list?index=0&length=1
The following returns 100 orders:
https://localhost:5000/api/v1/Orders/list?index=0&length=a
It appears that for a request, any parameters that cannot be converted to the declared type will be treated as missing, thus resulting in the use of the default value as per the example above, or the value type default value, or null for reference types.
In this case it is preferable to have the request fail when executed with mismatched parameter types rather than have it proceed with default/null values.
Is there a way to modify this behaviour?
This link is a comprehensive description of how model binding works in web api:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Based on it, I'd say you'll need a custom model binder that throws an exception when it cannot convert the value.

How to update variable columns based on json object from body? (C# web api, LINQ to SQL)

So right now I can update a record using this code:
//POST: api/Cicmpies/testname
[HttpPost("{cmp_name}")]
public async Task<IActionResult> UpdateCicmpy([FromRoute] string cmp_name, [FromBody] Cicmpy cicmpy)
{
var cicmpyUpdated = await
(from c in _context.Cicmpy
where c.CmpName.Equals(cmp_name)
select c).SingleOrDefaultAsync();
cicmpyUpdated.CmpCode = cicmpy.CmpCode;
await _context.SaveChangesAsync();
return Ok(cicmpy);
}
To achieve this I send a json object using Postman like this:
{
"cmpCode": "testcodeupdated333"
}
To the following URL:
http://localhost:54488/api/Cicmpies/testname
This works since I know I'll only have to update "CmpCode" so I can do:
cicmpyUpdated.CmpCode = cicmpy.CmpCode;
What if I don't know what values will have to be updated? So sometimes the json object can contain 1 key-value pair (CmpCode), but sometimes it can contain 4, sometimes all 20, etc... How can I ensure "cicmpyUpdated" will always set all the values from howmany key-value pairs you entered in your json object?
you can do 3 things:
1.- use default values. Give some default values and then update everything
2.- set a maximum of n pairs per object and then send several objects.
3.- keep checking: example: while (more pairs){do whatever}
Generally, the proper way to handle this is to establish a “ViewModel” class for your input parameter on your action method. One option is to give your viewmodel the same properties of your entity and make those viewmodel properties nullable so that if the input doesn’t provide a value you can add a check in your controller to avoid setting the model property if the input viewmodel property is null. Alternatively you could add a custom model binder to set additional properties on your viewmodel that tell you which fields were present in the Json data.

Asserting JsonResult Containing Anonymous Type

I was trying to unit test a method in one of my Controllers returning a JsonResult. To my surprise the following code didn't work:
[HttpPost]
public JsonResult Test() {
return Json(new {Id = 123});
}
This is how I test it (also note that the test code resides in another assembly):
// Act
dynamic jsonResult = testController.Test().Data;
// Assert
Assert.AreEqual(123, jsonResult.Id);
The Assert throws an exception:
'object' does not contain a definition for 'Id'
I've since resolved it by using the following:
[HttpPost]
public JsonResult Test() {
dynamic data = new ExpandoObject();
data.Id = 123;
return Json(data);
}
I'm trying to understand why isn't the first one working ? It also seems to be working with basically anything BUT an anonymous type.
To be clear, the specific problem you are encountering is that C# dynamic does not work with non-public members. This is by design, presumably to discourage that sort of thing. Since as LukLed stated, anonymous types are public only within the same assembly (or to be more precise, anonymous types are simply marked internal, not public), you are running into this barrier.
Probably the cleanest solution would be for you to use InternalsVisibleTo. It allows you to name another assembly that can access its non-public members. Using it for tests is one of the primary reasons for its existance. In your example, you would place in your primary project's AssemblyInfo.cs the following line:
[assembly: InternalsVisibleTo("AssemblyNameOfYourTestProject")]
Once you do that, the error will go away (I just tried it myself).
Alternatively, you could have just used brute force reflection:
Assert.AreEqual(123, jsonResult.GetType().GetProperty("Id").GetValue(jsonResult, null));
Having read the responses here and then looking further afield I found a 2009 msdn blog post with a different approach again. But.. in the comments was a very simple and very elegant solution by Kieran ... to use .ToString().
In your original case:
[HttpPost]
public JsonResult Test()
{
return Json(new {Id = 123});
}
You could test by doing:
var jsonResult = controller.Test();
Assert.AreEqual("{Id = 123}", jsonResult.Data.ToString());
I much prefer this solution as it:
avoids changing the original code (InternalsVisibleTo, ExpandoObject),
avoids using MvcContrib and RhinoMocks (no issue with either of these but why add just to be able to test JsonResult?), and,
avoids using Reflection (adds complexity to the tests).
Anonymous types are internal, so you can't expose them to another library, the one with tests. If you placed testing code in the same library as controller, it will work.

Categories