I have the following controller action
[HttpPut("{id}")]
public void Put(int id, [Bind("BirthDate")][FromBody]ExpandoObject value)
{
_dataContext.Update(value);
}
I am trying to limit the allowed properties, and I assumed this would work, however, it doesn't and value receives all the values sent and not just BirthDate - I don't want to use a view model because I want a dynamic object to be sent. FYI, this doesn't work with a normal model object either.
this is the json I am sending in request the body
{
"FirstName": "Lionell",
"LastName": "Messi",
"BirthDate": "2009-04-06T11:54:29.047",
"PhoneNumber": "1234567890",
"EmailAddress": "blabla#gmail.com",
"SchoolId": 1,
"IsIndependent": true
}
After reading through the source code, when posting a json in the request body, the BodyModelBinder is used. Unfortunately this model binder doesn't seem to apply the PropertyFilters provided by instances of the IPropertyBindingPredicateProvider like the [Bind] attribute.
It just uses the JsonInputFormatter to deserialize the json into the model type:
var formatters = bindingContext.OperationBindingContext.InputFormatters;
var formatter = formatters.FirstOrDefault(f => f.CanRead(formatterContext));
...
try
{
var previousCount = bindingContext.ModelState.ErrorCount;
var result = await formatter.ReadAsync(formatterContext);
var model = result.Model;
This seems like a bug to me...
In the meantime, you could try to evaluate some options you have available. As stated in the docs we know the formatter uses Json.Net:
The JsonInputFormatter is the default formatter and it is based off of
Json.NET.
So essentially you would need to follow an approach to exclude the properties that works with Json.Net, like using the [JsonIgnore] attribute. (Or just use some DTO object that doesn't contains those properties :))
Of course in your case is harder because you have a dynamic object and not a strongly typed object, which means:
There is no type for you to apply the attributes
Json.net will be unaware of any attributes you set in the controller action.
You could explore updating the JsonSerializerSettings, for example trying to use a contract resolver to dynamically exclude properties. You might possible need some global configuration that links routes and parameters to be ignored, so you can decide when to ignore a property or not based on the current request.
All of this would be much easier if it was fixed by Microsoft so it works out of the box!
Related
I have a SpecFlow test definition set up where I want to assert that the response have a specific collection of fields in its response. For example I have this particular expected response from the API:
{
isActive: false,
lastProcessed: "2020-11-03T19:03:16.537"
}
What I want to verify is that the response contains those two fields, not necessarily the value those fields contain. I tried the following method:
Assert.NotNull(response.Model.isActive);
Assert.NotNull(response.Model.lastProcessed);
But I'm getting an error using this method:
Do not use Assert.NotNull() on value type 'bool'
How else can I make sure the response structure is as expected other than using "NotNull()"?
The Solution:
Following the accepted answer, I serialized the model returned from the API call into JSON and parsed it into a JObject. Then I used the ContainsKey() method to assert it.
JObject jObject = JObject.Parse(JsonConvert.SerializeObject(response.Model));
Assert.True(jObject.ContainsKey("isActive"));
I don't know what packages you use for sending requests and deserialization but if you could get the response content as a raw json string you could then use Newtonsoft.JSON to parse the response into a JObject with JObject.Parse(responseContent). JObject has a method called ContainsKey(propertyName) which determines whether there is a field of a specified name in the object. You could assert if it returns true for the desired property names.
Edit
Regarding Greg's answer below. The original error is in fact caused by bool not being a nullable type and making it nullable in the model would fix the error. However, this solution is not ideal. In some cases null can be a valid value returned by the API and this would generate false negatives. e.g. if we recieved:
{
isActive: null,
lastProcessed: "2020-11-03T19:03:16.537"
}
then Assert.NotNull(response.Model.isActive) would yield a negative test result even though the field is present in the json, and that's what we wanted to check.
So theoretically if we are 100% sure that null will never be returned by the API itself, then we could do it that way, but it won't be a universal method. Also not very descriptive of what we are trying to achieve ;)
Since the isActive property is a bool you'll need to assert that it is false. If instead you want a true or false value, and then something to represent that it is missing, use a nullable boolean instead in your DTO:
public class YourDTO
{
public bool? isActive { get; set; }
...
}
Then you can assert isActive is null, true or false.
Alternative: If you cannot update the original data transfer object, then this might be a good use case for writing your own code to call the web service and map the JSON response to your own DTO used just for your tests.
This could be a large amount of work, however. The advantage is your test code is truly decoupled from the code it tests. I've done this with applications that use a database as well. It is extra work, but it allows your tests to use whatever data structure makes sense for the test.
I'm introducing Elasticsearch into a C# API project. I'd like to leverage existing API models as search documents, many of which allow for adding custom data points. These are implemented using the JObject type from Json.NET. For example:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public JObject ExtraProps { get; set; }
}
This allows users to send JSON request bodies like this, which works great:
{
"Id": 123,
"Name": "Thing",
"ExtraProps": {
"Color": "red",
"Size": "large"
}
}
However, if I use this as a document type in NEST, those extra properties are losing their values somehow, serializing as:
{
"Id": 123,
"Name": "Thing",
"ExtraProps": {
"Color": [],
"Size": []
}
}
Adding a [Nest.Object] attribute to ExtraProps didn't change the behavior. As I understand it, NEST uses Json.NET internally, so I wouldn't expect it to have problems with Json.NET types. Is there a relatively simple fix for this?
Here are some options I'm weighing:
Use custom serialization. I started down this path, it got to feeling way more complicated than it should be, and I never did get it working.
Map JObjects to Dictionary<string, object>s. I have verified this works, but if there are nested objects (which there could be), I'll need to enhance it with recursion. And, ideally, I'd like this to work with the more general JToken type. This is the option I'm leaning toward, but again, it feels more complicated than it should be.
Use the "Low Level" client or even raw HTTP calls. Admittedly I haven't explored this, but if it's really simpler/cleaner than the alternatives, I'm open to it.
Report this as a bug. I'll probably do this regardless. I just have a hunch this should work with JObject or any JToken out of the box, unless there is some reason that this is intended behavior.
This is expected behaviour with NEST 6.x.
NEST uses Json.NET for serialization. In NEST 6.x however, this dependency was internalized within the NEST assembly by
IL-merging all Json.NET types into the NEST assembly
renamespacing the types within Newtonsoft.Json to Nest.Json
marking all types internal
There's a blog post with further details explaining the motivations behind this change.
When it comes to handling Json.NET types such as Newtonsoft.Json.Linq.JObject, Json.NET has special handling for these types for serialization/deserialization. With NEST 6.x, the internalized Json.NET does not know how to specially handle Newtonsoft.Json.Linq.JObject because all types within the internalized Json.NET have been renamespaced to the Nest.Json namespace.
To support Json.NET types, a serializer that uses Json.NET to serialize your documents needs to be hooked up. The NEST.JsonNetSerializer nuget package was created to help with this. Simply add a reference to NEST.JsonNetSerializer to your project, then hook up the serializer as follows
// choose the appropriate IConnectionPool for your use case
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings =
new ConnectionSettings(pool, JsonNetSerializer.Default);
var client = new ElasticClient(connectionSettings);
With this is place, documents with JObject properties will be serialized as expected.
I need to enable Objects TypeHandling for the JsonSerializer that is used for bulk index query. However, when I change the Serializer settings for NEST, the bulk query is being serialized wrong as a whole.
The serializer I used:
public class SearchJsonNetSerializer : JsonNetSerializer
{
public SearchJsonNetSerializer(IConnectionSettingsValues settings)
: base(settings)
{
}
protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings)
{
settings.Formatting = Formatting.None;
settings.TypeNameHandling = TypeNameHandling.Objects;
}
}
The output I got:
{"index":{"$type":"Nest.BulkIndexOperation`1[[TestProject.TestDTO, TestProject]], Nest","_type":"testdto","_id":"146949756709543936"}}
{"$type":"TestProject.TestDTO, TestProject","Id":146949756709543936,"Title":"test","TitleRaw":"test"}
The second line is correct, however, NEST used the serializer settings to serialize the initial line in a way that totally destroys the request.
Is there a way to apply the changed serializing only to the actual object? If not, is there a way to send a raw, prepared json string as a request for a bulk query? I've seen that functionality in older version, but in the current one - 2.0, I just can't find a way to do that...
This is related to https://github.com/elastic/elasticsearch-net/issues/1155
Sadly you can not do the following in JSON.NET
[JsonObject(TypeNameHandling = TypeNameHandling.Objects)]
public class MyPoco {}
That would solve the issue at hand, only enabling that typenamehandling for your specific types. Sadly it can only be specified on properties. Would make a great feature request there.
You have two options either writing a custom serializer for your type, or preserialize them and send them using the lowlevel client, but then you need to add the metadata items manually as well.
var client = new ElasticClient().LowLevel.Bulk<BulkResponse>("index", "type", new[]
{
"",
});
NEST does provide several ways to get true covariant search results without having to index $type:
https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/covariant-search-results.html
This is self-hosted RESTful MVC4 Web API, the only route is api/{controller}/{state}. When I send an HTTP-POST that has a body, the state argument comes in null. If I remove the body the state variable is present.
The way I thought it worked for HTTP-POST was that the url parameters get mapped then the body gets serialized into the extra parameter, which in this case is data parameter. The content is just string data which I had to write a custom MediaTypeFormatter (which I thought was odd it couldn't handle a regular string).
Here is my controller signature
public class MyController : ApiController
{
public void Post(string state, string data)
{
}
}
Has anyone seen this behavior before or can explain to me why having a body present is affecting my url parameter?
One Solution:
I tried changing data parameter into a complex type (Just a class with a public property) and sending the content as text/xml instead of text/plain and it worked as expected. The state parameter wasn't null and I had my strongly typed object with the data. I suppose MVC wants to have something to deserialize like XML or JSON for the http-request body...
More Research:
I've had the chance to run some more tests. If the body of a post is XML/JSON it will first try to map the properties of the body-object to the method parameters like so. If still has unmapped properties then it will match the remaining properties to the properties of any strongly-typed objects in the method parameters
PostMethod(string p1, string p2, myClass obj) // if myClass has a p3 property it will be mapped from the xml body.
{
}
// xml in body of http-post
<Xml>
</p1>
</p2>
</p3>
</Xml>
If all the parameters were not mapped, then it will attempt to map the url parameters. To relate it directly to my initial problem. The best and easiest solution I see at this time is to send text/xml like this.
PostMethod(string state, string data)
{
}
<data>put data here</data>
Urlencoded key/value pairs also work very well.
var r = client.PostAsync(url, new StringContent("data=Something", Encoding.UTF8, "application/x-www-form-urlencoded"));
My best guess is that the key/value nature of JSON and XML, FormEncoded help it to map to parameters so that is why it doesn't like plain strings.
This sure gave me a headache and I find the MVC4 documentation to be rather scarce (its still in beta), but I hope this can help someone else who may have the same problem.
Is there a way in MVC3 to set what properties the Json function outputs?
ie. properties on my model have an attribute that tells the Json function not to output them.
It looks like the ScriptIgnoreAttribute will do what you want. Just decorate whatever property you don't want serialized with it.
Use anonymous method for that:
so instead of
return Json(it);
do
return Json(new {
it.Name,
CreatedAt = it.CreatedAt.ToString("D")
// And so on...
});
this way you explicitly publish (map) set of attributes to the web which ensures that only allowed properties can be accessed from JSON.
If you don't want to Repeat Yourself, you can use JSON.NET serializer with which you can customise how objects are serialised. (So you can create custom HideAttribute and take that into account).
With JSON.NET you will also need to write Controller.Json method replacement (SmartJson or so). But it should not be an issue I suppose.