Posting a collection of subclasses - c#

I have a requirement for users to edit a list of quotes for a lead, the quotes can be different types such as:
QuoteForProductTypeA
QuoteForProductTypeB
All quote types share a common base class, such as QuoteBase.
I have my quotes displaying fine on the front end, and appear to post back the correct data too.
However, on the server it doesn't obviously doesn't know which subclass to use, so just uses the base class.
I think i need some kind of custom model binder for WebApi to check for a hidden field such as ModelType which contains the type of the object in the collection, the model binder then creates a new object of this type and binds the properties from my posted values to this object.
However, i am stuck at this point with very little documentation / blogs on how to do this.
I have checked the source code for WebApi to see if i can extend a default model binder, but any defaults are sealed classes.
I can only implement IModelBinder by the looks of it, i can create the correct model type by looking for a value called ModelType, but then i'm not sure how to fill the rest of the values in my subclasses, if there was a default model binder i was inheriting from i would just call the base classes bind method.

If your post collection comes from request body, it won't go through model binder. Web API will use formatter to deserialize the content.
If you just want to support json, it's quite easy. Just add following code to your web api config:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
The setting will let json.net to save type name in the payload if the runtime type is different with the declare type. When you post it back, json.net will deserialize the payload to the type you specified in the payload.
A sample payload looks like:
{"$type":"MvcApplication2.Models.Car, MvcApplication2","SeatCount":10,"WheelCount":4,"Model":0,"Brand":null}]

Related

Customize parameter in NSwag/ApiExplorer

I'm using NSwag to generate a Swagger document for my ASP.NET 6 API. My application uses strongly typed ids which are simple records with a value property.
public record Id(Guid Value);
Such ids are used as parameters in controller methods. Example:
public class MyController
{
[HttpPost]
public void Test(Id id) {}
}
Execution wise this works fine. I have a custom model binder alongside with a factory that automatically handles binding from GUID strings. Same goes for serialization where a custom json converter handles this. To ensure that NSwag properly generates a string property I have a type mapper that maps Id properties as strings in the model.
The only remaining issue is that NSwag generates strange input parameters for my controller actions. It ends up looking like this:
I was able to dig into the code of NSwag and find the corresponding code for generating parameters. The OperationParameterProcessor uses the data generated by the api explorer provided by ASP.NET. The api explorer seems to interpret this property wrong and therefore generates two parameters instead of just a single one.
I was unable to find any resources on how to customize the behavior of the api explorer so you can customize models. How do I convince ASP.NET that my Id object is just a plain string instead of a complex object?
It seems that adding a pseudo type converter did the trick. I decorated my Id class with a custom type converter
internal class IdentityObjectTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
sourceType == typeof(string);
}
While this converter itself does not handle the value conversion it does provide ASP.NET with enough metadata so that the Api Explorer generated the proper definition. I would prefer a type converter over a model binder, however type converters are not flexible enough when it comes to polymorphism.
In the end the type converter fakes the type while the model binder does the actual binding. It's not ideal but probably the best solution as long as type converters are missing proper metadata when converting a value.

How can I write a controller method in MVC 5 when the incoming notifications have different JSON structures?

I'm attempting to write a webhook receiving service in MVC 5 to receive notifications from the GoToConnect webapi.
The webhook notifications that will be coming from their server have a standard wrapping structure.
{
"source": "messaging",
"type": "message",
"content": {object}
}
However, the content value can be one of multiple types of notifications that all have different structures which can be identified by the value that comes in the "type" field. My issue is finding a way of doing the model binding when the incoming json structure can be completely different.
If I can get access to the raw json file, I can use a switch statement to manually deserialize the incoming content based on the type value but I've had difficulty finding an easy way to do that. Is there already a way to control the model binding so that I can choose the object that gets created and then lump them together using inheritance or is there an easy way to get the raw json as a parameter to a controller method?
So, with some more research I found an answer to my problem.
First, I created classes for each of the notification content types with an empty marker interface and also a wrapping class containing the Type and an property with that Interface type.
Then, I built a custom model binder by extending the DefaultModelBinder class. In the BindModel method, I used the Type value of the incoming notification to instantiate the specific Notification Content object that I wanted. I stored that object in the Interface type property and then returned it.
Afterwards, I could access that data by casting the Interface Type property to the specific type I wanted in the controller method.

Seperate DTO for post and put request & nested json as post model

I have two questions about RESTful api.
1. Can we have separate DTO for GET, POST and PUT request? Is it good practice or can I make one abstract class with common properties and then inherit from it?
2. Is sending nested json in POST request a good practice?
As did in following article:
https://code-maze.com/net-core-web-development-part6/
Yes, and more to the point, you should. A DTO is a representation of a particular group of data in a particular scenario. That data can and will be different between different request types. Any time there's different data being transferred, there should be a different DTO to represent it.
You can utilize inheritance if you like and it makes sense. However, be aware that due to the way modelbinding works, you will still need to use your concrete, derived classes as params, not some base class. The modelbinder will instantiate the class specified by the param (so it can't be abstract), bind any request values represented on that class, and discard the rest. So if you bind to BaseClass, all you will have is BaseClass, not DerivedClass, even if the request body was a representation of DerivedClass. If you then attempted to downcast to DerivedClass, all the properties specific to DerivedClass would be null/default.
It is neither good nor bad practice. JSON is an object representation format. If your object has nested objects, then your JSON would have nested JSON.
Can we have separate DTO for GET, POST and PUT request?
DTOs are not created based on the request type, instead DTOs are created for entities. For example you have a Customer entity, for the Customer entity you will create a Customer DTO and the same Customer DTO can be used for GET, POST and PUT requests of Customer entity.
Can I make one abstract class with common properties and then inherit from it?
Yes you can use inheritance while creating your DTOs but I would say thats over complication for no added value and keep in mind that inheritance, if not used properly can increase coupling in system.
Is sending nested json in POST request a good practice?
As Chris mentioned in his answer, if your object has a nested object then your JSON will have nested JSON.

.net CORE view model property of type object does not bind

I have an MVC web app that I am porting from .net framework to .net core. Part of the app has a view model with a number of properties, one of which is of type "object".
In .net framework when I post this view model data the controller receives it with no problem. However, in .net CORE all the properties contain their expected values except the one that is object.
If I change the type to string most work fine (but it means if the value is ever an int (for example) it will be the string representation of an int).
Does anybody know how I can get values for view model properties that are object types to work or if this just isn't supported in Core?
I really dont feel like this would work.
Model binding uses reflection to map request data, route data and query strings into the parameter of your action method.
Request data is essentially all string data and so the defaultmodelbinder is able to map the request into any simple value type such as string, integer or bool by checking to see if the string can be converted into an int or bool without failure.
Complex types, like a Customer object, which contain simple value types (like string and int) can then also be mapped using this same methodology. An example of a complex type like this is shown here:
public class Customer
{
public string Name {get;set}
public bool IsLongTermCustomer {get;set;}
}
Because the defaultmodelbinder uses reflection, I really dont see how it would know how to map to a property of type "object" which could be anything.
An object could be a Customer, an int ,a string etc and so i feel like this would fail no matter what version of MVC you use becaue i dont see how the defaultmodelbinder would know if the data can be ported into the object data type. Here is a detailed article on model binding specifically in MVC core and how it works.
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.2
As an extra check, I would also look to see if you are running the mvc default model binder in your older application and that someone is not using a custom model binder which may be allowing this binding to occur in your legacy application.

Deserialising an implementation of an interface

Similar to JSON.NET deserialize to object with Type parameter, I need to deserialise some JSON, which has been previously serialised:
Serialisation code:
...
Data = new ImplementationofICommandData{ Id = Id }
DataType = typeof(ImplementationofICommandData).AssemblyQualifiedName,
...
Deserialisation code:
...
(ICommandData)JsonConvert.DeserializeObject(dto.Data, Type.GetType(dto.DataType))
...
In this case, dto.DataType contains the AssemblyFullName.
Initially, the projects that contained the serialisation and deserialisation shared a project with DTOs in it, so the deserialisation was able to use the same AssemblyQualifiedName for the type.
Now, I have a new project that is serialising data to the database. I have a DTO in the original shared project that has the exact same structure as the DTO in the new project, but due to being in a different assembly, the AssemblyQualified name that gets serialised does not correspond to a type about which the deserialising code knows.
Ideally, I won't have to make the serialisation and deserialisation code share the same DTO project, so is it possible to get the type by something less than the AssemblyQualifiedName? Or is there a better approach?
As it stands, I've added a default namespace/assembly to search for a bare class name. This could be expanded to allow for a non-hack method of registration of a list of namespaces/assemblies in which to search for the DTO class, all wrapped up in some kind of resolver class.

Categories