Customize parameter in NSwag/ApiExplorer - c#

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.

Related

Is there an model validation attribute to check parameter types without using custom code?

I have a webapi where I want to validate if the used parameter is of type int. I know of the attribute DataTypesAttribute but it doesn't support the simple types. I do not want to create a custom attribute for this.
Take a look at here, as mentioned when you desalinize from a json string to object the value type must be int, if not the desalinization will fail. model validation as it's name suggests is mostly for domain driven and business related validations not type specific.

AutoMapper - Determine Source Type from Destination Type

I have inherited a project that uses AutoMapper and the AutoMapper.Attributes NuGet package. In the ViewModels the [MapsFrom...] Attributes were placed on properties to identify the mapping from the source property. Recently we re-architected the application, separating the Data Models into a separate C# library (they had all been located within the Website project), unfortunately AutoMapper.Attributes has a known bug where this breaks the [MapsFrom...] and related Attributes (they are ignored causing the properties in the ViewModel to end up Null).
The AutoMapper.Attributes project has officially been abandoned and the owner no longer recommends its use meaning this bug will never be fixed, you can read about it here: https://github.com/schneidenbach/AutoMapper.Attributes/issues/26
That being said, we have a block of code that takes in a ViewModel Type, inspected the Custom Attributes on that type looking for the [MapsFrom...], and uses this to determine the Source Type for that ViewModel. We don't know the Source Type at runtime and need to determine, from the AutoMapper Mappings, what the Source Type of the object is (i.e. the Entity Framework DbSet that is set as the Source, via the CreateMap(...). This has proven elusive to solve, after much research i'm turning to you guys for help on this.
What I Have:
Here is what the method currently looks like, obviously this will no longer work as we have had to remove the [MapsFrom....] attributes:
internal static Type GetSourceType(Type viewModelType)
{
if (!(viewModelType.GetCustomAttributes(typeof(MapsFromAttribute), true).FirstOrDefault() is MapsFromAttribute mapsFromAttribute))
{
throw new Exception("The view model class named " + $"{viewModelType.Name} is not decorated with MapsFrom attribute");
}
var destinationType = mapsFromAttribute.SourceType;
return destinationType;
}
What I need:
I need to modify the method above so that when a ViewModel Type is passed into the method I can extract the source Type thats mapped to that ViewModel. Lets see an example, if we had the Mapping below:
CreateMap<User, UserViewModel>();
When a UserViewModel is passed into the method I need to determine that its source Type is User (which just so happens to also be an EntityFramework DbSet but that doesn't really matter in terms of this discussion).
The FindTypeMapFor method (shown below) won't help because you have to provide it with the Source Entity, which is exactly what I'm trying to discover.
TypeMap typeMap = AutoMapper.Mapper.Instance.ConfigurationProvider.FindTypeMapFor(TDto, TEntity>();
PLEASE NOTE: We are using AutoMapper version 8.0, we have all of our CreateMap(...) statements in Profile classes, we are NOT using Attributes anymore.

.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.

Posting a collection of subclasses

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}]

Dynamic form validation - emulation of RegularExpressionAttribute in CustomMetadataProvider

My task is to perform validation on form which fields are constructed dynamically(upon database query). I would like to use data annotations. While model is dynamic I can't decorate properties with annotations, but I can use custom metadata provider for example inheriting from DataAnnotationsModelMetadataProvider.
Simply in global.asax at Application_start I supplied my own MetaDataProvider:
ModelMetadataProviders.Current = new MetadataProvider.CustomModelMetadataProvider();
I made a little hack, cause ModelMetadataProviders.Current is per application, my problem needed serving different metadata in each request, but it was not so hard.
This work fine for emulating IsRequired attribute, because metadata provider uses System.Web.Mvc.ModelMetadata and there is IsRequired property , but there is no property such as RegularExpression or anything similar.
So I run debugger and looked at ModelMetadata returned by original DataAnnotationsModelMetadataProvider for property with RegularExpression attribute, and I hadn't found regular expression there anyway.
I would love to get some hints on that.
I figured answer (by inspecting MVC 3 source code) which is as follows:
create custom ModelValidatorProvider for example inheriting from DataAnnotationsModelValidatorProvider
override GetValidators method
add yout custom provider to ModelValidatorProviders.Providers collection
GetValidators method returns IEnumerable<ModelValidator> so its enough to return RegularExpressionAttributeAdapter which inhertis from ModelValidator.

Categories