Send JSON with list to Controller - asp.net-core

I am trying to send following JSON to my controller:
[
{
"collection": "col1",
"uuid": [
"11:22:33:44:55:66",
"11:22:33:44:55:66"
]
},
{
"collection": "test"
}
]
Every object "collection" contains a list of strings symbolized by the uuids.
My model looks like this:
public class DummyDeviceApiModel {
[Display(Name = "UUID")]
[StringLength(36)]
public List<string> uuid {get; set;}
[Display(Name = "Collection")]
public string collection {get; set;}
}
and my controller function like this:
public async Task<ActionResult<DummyDeviceModel>> PostCreateDummyDevice(List<DummyDeviceApiModel> ddpm)
What works is when I just send the collection part, but the UUID with its list makes problem:
System.InvalidCastException: Unable to cast object of type
'System.Collections.Generic.List`1[System.String]' to type
'System.String'
.
Any idea what I am doing wrong? The issue seems to be the "second" list in the model.
Thanks
Stephan

[StringLength(36)] is a validation attribute for type string not arrays of string or List<string>. The exception is probably happening behind the scenes as it tries to validate on your property since it is of type List<string>. If you want to do what I think you do, create a custom validation on your list to make sure you only have strings in your list which are less than or equal to a length of 36, then you need to either implement IValidatableObject on your model (class) or create a custom Validation Attribute for validation by creating a class which inherits from ValidationAttribute. You can read more about how to implement this interface and/or create a custom attribute at MSDN: (https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1). As a test to see if it is indeed the StringLength attribute which is throwing the exception, comment out that particular Validation Attribute in your code and see if it runs without error.

Related

ASP.Net Model Binder - Ignore Not Mapped Properties

I am working with ASP.Net Core 5, Entity Framework Core 6, and using ODATA controllers with an EDM model. The model I am working with has several properties, including a [NotMapped] property. I have a React SPA that gets an entity from the controller, including the [NotMapped] property. When I post this entity back to my controller from the react app. The controller uses [FromBody] attribute to deserialize the JSON into a complex .NET object. However, the model fails to bind because of the [NotMapped]` property. If I remove this not mapped property in the React app on the client side before posting to the controller, the model binder works fine. However, this is not an acceptable solution, since this means my code will break anytime I add a not mapped property to my models without updating the React client to deal with this. The React client has no understanding of which properties are mapped or not mapped. Is there a way to get the model binder to just ignore properties that do not exist in the model, instead of having the model fail to bind completely?
Here is my model:
public class Person
{
public Person() { }
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string FullName => FirstName + " " + LastName;
}
And here is my controller Get action:
[ODataRoute("{id}")]
public Person Get([FromODataUri] int id)
{ return db.Persons.Find(id); }
The controller successfully gets the Person, including the [NotMapped] FullName property from the model and returns the model to the React client.
When I update the model on the React client and want to update Person, I call the Put method of this controller:
[ODataRoute("{id}")]
public async Task<IActionResult> Put([FromODataUri] int id, [FromBody] Person person)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
if (id != person.Id) return BadRequest();
db.Entry(person).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(person);
}
However, the Person object is null in the controller because it does not recognize the [NotMapped] FullName property of Person. I presume this, because if I remove this property from the JSON before POST'ing the data to the controller the model binder properly populate the Person object.
Is there a simple way to tell the model binder to simple ignore extra properties that do not exist as mapped properties of the model it is trying to bind, or do I have to write a complete custom model binder to do this?
Edit
Here is an example JSON payload that results in the model not binding, and the Person entity in my controller is null:
{
"#odata.context": "https://localhost:44304/rest/$metadata#Persons/$entity",
"Id": 1,
"FirstName": "Jimmy",
"LastName": "Johnson",
"FullName": "Jimmy Johnson",
}
Note the FullName property is [NotMapped]. When included in the JSON payload in my controller, it will not deserserialize. The person object is Null. Also notice there #odata.context does not interfere with the deserialization, even though it is not in the model. I assume the Odata inputformatter knows to expect that and ignores it.
If I send the same payload as above, but omit the FullName property, the JSON deserializes and the person object is populated.
I would like to figure out how to make it so that FullName or any [NotMapped] property is ignored, rather than causing the deserialization to fail. I am hoping for a solution that doesn't make me have a custom inputformatter for each entity type, and one that does not require me to make changes for every new [NotMapped] property I add to any of my models. It seems like there should just be an option in System.Text.Json options that tells the deserializer to ignore properties that don't exists for the model, just like #Odata.context which is ignored. However, I have not seen such an option.

How to store a binary tree as a property of an entity framework object (code first)

To preface, I am using Entity Framework 6 with a code first approach.
I created a simple binary tree node class for storing a series of 'rules'. I want to store the tree as an object in the database but still be able to access it directly as a property of the object it belongs to.
The problem is I encounter this error when trying to create the tree as a property of the object:
A circular ComplexType hierarchy was detected. Self-referencing ComplexTypes are not supported.
Here is the definition for the table I want to store the tree in:
<Table("Logic", Schema:="dbo")> _
Public Class Logic
<Key>
Public Property ID As Integer
Public Property Expression As String
Public Property Tree As LanguageTest5.Survey.BinaryExpressionNode
End Class
And this is the definition of the Tree:
<Serializable, ComplexType>
Public Class BinaryExpressionNode
Public Property Value As Object
Public Property LeftNode As BinaryExpressionNode
Public Property RightNode As BinaryExpressionNode
Public Sub New(Val As Object)
Value = Val
End Sub
End Class
Is there another way I can have Tree as an accessible property of Logic without having to manually serialize/deserialize it?
UPDATE: Since I wasn't able to avoid serialization, I decided to just store the tree in the database as a string using postfix notation:
"1 2 or 3 4 or and"
From there it is pretty simple to build up the tree.
The accepted answer works nicely, provided you are familiar with serialization. In my particular case the serialization was too complex and I didn't want to take the time to sort it out.
I did put the suggestion to the test with some simpler objects and it worked without issue.
One approach is to make two properties on the Logic class as follows :
1. Store the tree as serialized string in a database property in order to avoid the storage problems - store the whole tree as one object in the database.
2. Make non-database property through which you access the deserialized tree and its nodes.
I suppose that you are going to traverse the tree in code after it is extracted from the database.
UPDATE
Here is an example of a non database property. This is a real example. Do not count in that this is an abstract class. It is the same if it was not. The [NotMapped] attribute gets the job done. Here is a complete reference to Entity Framework Data Annotations where you can find the attribute. :
public abstract class Document : Entity
{
[Required]
[StringLength(50)]
public virtual string Egn { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Guid { get; set; }
public virtual string Subject { get; set; }
[NotMapped]
public abstract EntityDataAccessKeyType EntityDataAccessKeyType { get; }
[NotMapped]
public abstract int EntityDataAccessKeyId { get; }
}
In the getter of the non database property you can implement the deserialization and return the object rather then a string. You can implement the serialization in the setter. This way you will "abstract away" the process of serialization-deserialization inside the non database property and you will continue to work with objects.
Do not fear of performance hit unless your trees are "humongous". You can always make a unit test in which you can test the count "barrier" after which the object gets too heavy.
For the serialization-deserialization you can use Json.NET.

RESTEasy mapping parameter with '-' in their name

A simple question (I hope so...) for RESTEasy experts.
I receive a form posted via POST which contains attributes with '-' in their names :
Example :
return-code=12
I want to map all the content of this POST into a Pojo :
public class MyFormInfo {
public String attr1="";
public String return_code=""; // don't work because return-code is not mapped in return_code
...
The method declaration is the following :
#POST
#Path("/return-cic-payment")
public String receiveForm(MyFormInfo form) throws Exception {
log.info("Return-code is : {}", form.return_code);
}
I don't to map attributes one by one in the parameters lists because the form contains a large number of fields.
Because I can't have an attribute named "return-code" in my POJO, I wonder how to do toget this parameter's value.
A custom mapping can be a solution, but I don't know how to achieve that.
Other idea I try without success, to receive a Map of attribute.
Thanks for your help.
Try this: http://docs.jboss.org/resteasy/docs/1.0.0.GA/userguide/html_single/#_Form
class MyFormInfo{
#FormParam("return-code")
private String returnCode;
//etc.
}

WebAPI only binds first parameter

Dead simple question, possibly not so simple answer. Posting JSON.
public void Post(Model1 model1, Model2 model2)
{}
model1 is populated but not model2 (null).
public void Post(Model2 model2, Model1 model1)
{}
Now, model2 is populated but not model1 (null).
Why?
Edit
The reason for two parameters? Model2 used to be referenced from Model1, but that didn't work. That's when I split them up.
Edit
Right. Thanks marcind for the answer to the question above. Now for the reason the original setup didn't work. I'm not the forms universe anymore. I post Json. If you have child objects in your model then post child objects in your json.
Given
class ProductEditModel {
public string Name {get; set;}
}
class UserEditModel {
public string User {get; set;}
public ProductEditModel Product {get; set;}
}
the following
{"user": "philip", "product.name": "barbie"}
is not going to work. You'd even get an error if you in js try and setup the sematic equivalent
{user: "philip", product.name: "barbie"}
Neither of the following work either, I don't know why they would:
{"user": "philip", "productname": "barbie"}
{"user": "philip", "product_name": "barbie"}
What does work and which should be obvious holding my profession is
{"user": "philip", "product": {"name": "barbie"}}
Please kick me.
Beware! The following will not work given corresponding edit to the model above.
{"user": "philip", "ProductEditModel": {"name": "barbie"}}
Not sure which version you are using, but the general rule we have settled on is that when binding complex types Web API considers the entire body of the request to represent a single entity and thus a single action parameter. In your case if you want to bind multiple Models you could introduce a custom binding object or alternatively you could bind to a Model[] or some other collection type.

help me understand the method Validator.TryValidateObject()

this is the method definition:
public static bool TryValidateObject(
Object instance,
ValidationContext validationContext,
ICollection<ValidationResult> validationResults,
bool validateAllProperties
)
what i am confused is the validateAllProperties parameter, I understand when it is true-validate all properties.
What about when it is false, not validate all properties, but which property will be validated?
See here for a good answer:
http://connect.microsoft.com/VisualStudio/feedback/details/605635/missleading-parametername-validateallproperties-in-validator-try-validate-componentemodel-dataannotations
It seems that when validateAllProperties is set to false that only the RequiredAttribute is validated.
When the property is false the Validator should validate each of the properties on the object that have a ValidationAttribute applied to them. This can include any of these attributes: CustomValidationAttribute, DataTypeAttribute, RangeAttribute, RegularExpressionAttribute, RequiredAttribute, and StringLengthAttribute, along with any other attributes that derive from ValidationAttribute.
See the MSDN library on the TryValidateObject method for more information.
In the following example, Foo should be validated, while Bar should not.
public class Example
{
[Required(ErrorMessage = "Foo is a required property.")]
public object Foo { get; set; }
public object Bar { get; set; }
}
I also don't fully understand it but after struggling with Unit Testing custom validators written by me I noticed one interresting thing.
When I launched my tests without this parameter (so by default it was false), my custom validators were omitted! if I set it to true, they were taken into account in my tests and now I can happily continue TDD. Hope this helps you a bit.
Arjen is right, only the Required attribute is validated when the validateAllProperties parameter is false.
I wrote a post about OData validation using DataAnnotations and I found the same issue.
http://blog.jorgef.net/2011/01/odata-dataannotations.html