Swashbuckle how to show an example dictionary instead of additionalProp? - c#

I have an API and I want swashbuckle to autogenerate all the swagger documentation for me.
I have an endpoint that returns a class with a dictionary property but the swagger generated example contains "additionalProp1, additionalProp2" etc instead of example values. Is there a way to instead use example values specified in the SimpleClass class?
The class with the example for swagger (that doesn't work).
public class SimpleClass
{
/// <example>"{"age":31,"height":234}"</example>
public Dictionary<string, int> DictionaryProperty { get; set; }
/// <example>The cow jumped over the moon</example>
public string someProperty { get; set; }
}
The controller
[HttpGet]
[Route("/testexample")]
[ProducesResponseType(typeof(SimpleClass), StatusCodes.Status200OK)]
public async Task<IActionResult> TestExample()
{
return Ok();
}
The result in swagger:

Take out the quotes inside the XML example value:
/// <example>{"age":31,"height":234}</example>

Related

ODataController, if the model is incorrect, it gets null on post

If there is any wrong property (For example if I send the payload data, Person_ instead of Person), model fully gets as null (Post([FromBody] Request data))
public class Person
{
public Guid Id { get; set; }
public string? Firstname { get; set; }
public string? Lastname { get; set; }
}
public class Request
{
public Guid Id { get; set; }
public Guid? Personid { get; set; }
public virtual Person? Person { get; set; }
}
public IActionResult Post([FromBody] Request data)
{
...
}
curl --location --request POST 'https://localhost:7124/v2/request?$expand=Person($select=Id,Firstname,Lastname)/Request&#odata.context=%27https://localhost:7124/v2/$metadata' \
--header 'Content-Type: application/json' \
--data-raw '{
"Id": "a436677a-fa4b-465e-8e70-211a1a3de8e9",
"Personid": "be9b53ad-4dfb-4db5-b269-32669f7c4e2d",
"Person_" : {
"Firstname": "JOHN",
"Lastname": "SMITH",
}
}'
I need to get the model even though some properties not correct according to model schema.
What could be the reason for it being null?
One of the main issues is that the type argument forms a strong contract that the OData subsystem tries to enforce. If the Deserializer cannot match the expected type fully, then it returns null, not a partially constructed object, or an empty object if none of the properties matched.
What you are expecting was a lazy implementation that we often took for granted in previous versions of OData and JSON.Net, but the OData Entity Serializer doesn't work this way any more.
When the argument is null, the ModelState should provide detailed information on the reason for the failure.
OData has support for allowing additional members, it is called Open-Type Support. Similar to the catch all solutions in other deserialization methods, we designate a dictionary to route all un-mapped properties so that you can inspect them after deserialization. This was a good walkthrough in .Net FX but basically we add the property:
public class Request
{
public Guid Id { get; set; }
public Guid? Personid { get; set; }
public virtual Person? Person { get; set; }
public IDictionary<string, object> DynamicProperties { get; set; }
}
Then in your model builder you need to declare the type as open:
builder.Entity<Request>("request").EntityType.IsOpen();
This is alone is still going to be hard to use though because your additional member is a complex type, so the type cannot be easily resolved automatically.
You could implement your own deserializer, but that is a lot more universal to all of your controllers and endpoints, you should take a little bit more care because it really opens a back door and cancels out a lot of functionality if you don't do it right. In your example the _person is omitted entirely, which might not be your intention.
Other solutions are a bit more permanent and messy, like adding additional properties to your model to capture the input and re-assign it internally. The best advice however is to respond to the client with an adequate error message so that they update the call.
There is another way that we can also cheat by using the JToken type, instead of the expected concrete type. This will universally ingest the payload from the request, then we can use good old JSON.Net to resolve the object:
/// <summary>
/// Inserts a new item into this collection
/// </summary>
/// <param name="item">The item to insert</param>
/// <returns>CreatedODataResult</returns>
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Format | AllowedQueryOptions.Select)]
public virtual async Task<IActionResult> Post([FromBody] Newtonsoft.Json.Linq.JToken x)
//public virtual async Task<IActionResult> Post(TEntity item)
{
TEntity item = x.ToObject<TEntity>();
... insert custom logic to resolve the badly formed properties
// Tell the client that the request is invalid, if it is still invalid.
if (!ModelState.IsValid)
return BadRequest(ModelState);
//EvalSecurityPolicy(item, Operation.Insert);
await ApplyPost(item);
//UpdateRelationships(item, Operation.Insert);
await _db.SaveChangesAsync();
return Created(item);
}
/// <summary>
/// Inheriting classes can override this method to apply custom fields or other properties from dynamic members in the posted item, base class will apply TenantId only if it has not already been applied
/// </summary>
/// <remarks>This process is called after the usual validation overriding just this process means you do not have to replicate the existing internal logic for the afore mentioned tasks.</remarks>
/// <param name="item">The new item that has been uploaded</param>
/// <returns>Promise to add the item to the underlying table store</returns>
public virtual Task ApplyPost(TEntity item)
{
GetEntitySet().Add(item);
return Task.CompletedTask;
}
This is a base class implementation of ODataController Inheriting controller classes only override ApplyPost if needed. I've commented out some more advanced logic routines to give you other hints on how you might use this pattern.
Is a good practice? I'm undecided but it works and will allow your API to be resilient to schema changes that the client hasn't yet been updated to support, you can also inspect and handle the invalid ModelState in your controller before you return to the caller, or can easily add your own custom mapping logic if needed.
The problem is the declaration of Person in the class Request, It should be public Person Person_ { get; set; }.
You can declare it as public virtual Person? Person_ { get; set; } also if you don't want to change the declaration.
The only catch here is the suffix underscore before Person.
If you don't want to change the declaration then you can use JsonProperty
[JsonProperty("Person_")]
public virtual Person? Person { get; set; }
I have found a solution. I have used a custom ODataResourceDeserializer to handle the exception of doesn't exist properties and, included a try/catch block in the ApplyNestedProperty method's content. So the web service cannot throw an exception for not exists properties while deserialization process.
public class CustomResourceDeserializer : ODataResourceDeserializer
{
public CustomResourceDeserializer(IODataDeserializerProvider deserializerProvider) : base(deserializerProvider)
{
}
public override void ApplyNestedProperty(object resource, ODataNestedResourceInfoWrapper resourceInfoWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)
{
try
{
base.ApplyNestedProperty(resource, resourceInfoWrapper, structuredType, readContext);
}
catch (System.Exception)
{
}
}
}

API with Dynamic input param example

Team.
I am working on creating dynamic API for my application my API looks like below,
[HttpPost]
[Route("publish")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Publish(PublishEventDto publishEventDto)
{
await _eventDomain.PublishEventAsync(publishEventDto);
return Ok();
}
and dto structure will be
public class PublishEventDto
{
public string EventName { get; set; }
public dynamic Payload { get; set; }
}
now when I am trying to call my API it is showing double brace in input param I am unable to create object from it.
You could use an ExpandoObject or the object type as an alternative to dynamic.
It's work as a Dictionary

How can I enhance ModelBinder in ASP.NET Core (update property values for a model class)

I would like to enhance final result that ModelBinder returns.
For example:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
public string Title {get;set;}
}
In API method I expect that all string properties in MyModel which has MyUpperCaseAttribute is in upper case.
For example:
[HttpPost("AddRecord")]
public async Task<ActionResult<int>> AddRecord(MyModel model)
{
model.Title should be upper case, even if send from client in lower case.
}
My idea was to override default ModelBinder and enumerate through all properties and check if property is string and has MyUpperCaseAttribute and correct property value to upper case. I check documentation, but doesn't examples doesn't fill right, since they completely redesign what is returned. I would like to just modify result properties.
What would be the best option to achieve desired behaviour?
Important: (edited):
It would be nice if directive attributes could be stacked:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
[RemoveSpacesAttribute]
public string Title {get;set;}
}
Edited:
It looks similar to this, but if not other, this is ASP.NET Core, and on link is just ASP.NET. Method, properties, interfaces... are not the same.
I should say, that it would be nice if JSON case rule would work:
public class MyModel
{
public int Order {get;set;}
public string Title {get;set;}
}
should work if {order: 1, title: "test"} (notice lowercase) is send from JavaScript.
This might not be the 'best' option, but I would just use .ToUpper() extension method instead of a custom attribute filter.
public class MyModel
{
private string _title;
public int Order {get;set;}
public string Title { get => _title.ToUpper(); set => _title = value.ToUpper(); }
}
There's a big red herring here, and that's the fact that it appears that this is the sort of thing that could and should be accomplished via model binding. Unfortunately, that's not the case in ASP.Net Core Web API: because the incoming data is JSON, it is in fact handled by input formatters, not model binders. Therefore, in order to achieve the desired effect, you need to create your own custom input formatter that replaces the standard JsonInputFormatter.
First the attribute:
[AttributeUsage(AttributeTargets.Property)]
public class ToUppercaseAttribute : Attribute
{
}
Then we decorate our model class with it:
public class MyModel
{
public int Order { get; set; }
[ToUppercase]
public string Title { get; set; }
}
Now create our custom input formatter that checks for that attribute and transforms the output if necessary. In this case, it simply wraps and delegates to JsonInputFormatter to do the heavy lifting as normal, then modifies the result if it finds our ToUppercaseAttribute attribute on any string property:
public class ToUppercaseJsonInputFormatter : TextInputFormatter
{
private readonly JsonInputFormatter _jsonInputFormatter;
public ToUppercaseJsonInputFormatter(JsonInputFormatter jsonInputFormatter)
{
_jsonInputFormatter = jsonInputFormatter;
foreach (var supportedEncoding in _jsonInputFormatter.SupportedEncodings)
SupportedEncodings.Add(supportedEncoding);
foreach (var supportedMediaType in _jsonInputFormatter.SupportedMediaTypes)
SupportedMediaTypes.Add(supportedMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var result = _jsonInputFormatter.ReadRequestBodyAsync(context, encoding);
foreach (var property in context.ModelType.GetProperties().Where(p => p.PropertyType.IsAssignableFrom(typeof(string))
&& p.CustomAttributes.Any(a => a.AttributeType.IsAssignableFrom(typeof(ToUppercaseAttribute)))))
{
var value = (string)property.GetValue(result.Result.Model);
property.SetValue(result.Result.Model, value.ToUpper());
}
return result;
}
}
Next we create an extension method that makes it simple to substitute the default JsonInputFormatter with our custom formatter:
public static class MvcOptionsExtensions
{
public static void UseToUppercaseJsonInputFormatter(this MvcOptions opts)
{
if (opts.InputFormatters.FirstOrDefault(f => f is JsonInputFormatter && !(f is JsonPatchInputFormatter)) is JsonInputFormatter jsonInputFormatter)
{
var jsonInputFormatterIndex = opts.InputFormatters.IndexOf(jsonInputFormatter);
opts.InputFormatters[jsonInputFormatterIndex] = new ToUppercaseJsonInputFormatter(jsonInputFormatter);
}
}
}
And finally, call that method to effect the replacement in Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(options => options.UseToUppercaseJsonInputFormatter());
}
}
Et voilĂ !
You can do this thing inside your MyUpperCaseAttribute as follows:
public class MyUpperCaseAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
validationContext.ObjectType
.GetProperty(validationContext.MemberName)
.SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
}
return null;
}
}
Property value will be converted to UpperCase during Model Binding. I have checked it in my side and it works perfectly.

Raw POST data deserialization using C#/.net (bind to complex model) like in modelbinder of MVC/webApi

I would like to have any ready generic solution to convert string with POST data like :
"id=123&eventName=eventName&site[domain][id]=123"
to my complex object
public class ComplObject {
public int id {get;set;}
public string eventName {get;set;}
public siteClass site{get;set;}
}
public class siteClass {
public domainClass domain {get;set;}
}
public class domainClass {
public int id {get;set;}
}
Access to asp.net MVC reference is allowed.
Looks,like i need standalone formdata binder, but i cannot find any work library/code to handle it.
You need to implement your custom http parameter binding by overriding the HttpParameterBinding class. Then create a custom attribute to use it on your web API.
Example with a parameter read from json content :
CustomAttribute:
/// <summary>
/// Define an attribute to define a parameter to be read from json content
/// </summary>
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public class FromJsonAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new JsonParameterBinding(parameter);
}
}
ParameterBinding :
/// <summary>
/// Defines a binding for a parameter to be read from the json content
/// </summary>
public class JsonParameterBinding : HttpParameterBinding
{
...Here your deserialization logic
}
WepAPi
[Route("Save")]
[HttpPost]
public HttpResponseMessage Save([FromJson] string name,[FromJson] int age,[FromJson] DateTime birthday)
{
...
}

Calling the controller method depending on the incoming parameters

I want to implement a certain functionality, but I do not know where to start. I will describe what I have.
Backend
public enum SourceType { Database, Folder }
public class DatabaseSource
{
public string ServerName { get; set; }
public string DatabaseName { get; set; }
}
public class FolderSource
{
public string FolderName { get; set; }
}
public class TestController : ApiController
{
[HttpPost]
[Route("source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}
Frontend
export enum SourceType {
Database,
Folder
}
export class DatabaseSource {
public ServerName: string;
public DatabaseName: string;
}
export class FolderSource {
public FolderName: string;
}
var source = new DatabaseSource();
source.ServerName = "serverName";
source.DatabaseName = "dbName";
var obj = {
sourceType: SourceType.Database,
source: source
};
Now imagine that I will send obj to the server. I want that specific controller method to be called depending on the enum. How can I do this?
P.S. The example is greatly simplified.
Your implementation is inconsistent for what you've specified in code.
On the front-end you are describing an object which has a sourceType field and a source object property, while on the backend you're overloading the ApiController method and mapping different REST object resources to a single HTTP method and endpoint (which I believe will not work).
There is no magic way for the ApiController to use your enum property to differentiate between the object types automatically.
A simpler (and better) implementation would be to have separate ApiController classes for your Database and Folder source object POST calls. This follows the principle of REST API design where you are essentially mapping basic CRUD operations to the HTTP methods with object types.
If your intention is to perform an operation based on these parameter objects, then clarify the intention via the API routing for the endpoint as below:
public class TestController : ApiController
{
[HttpPost]
[Route("ETLLoad/Database/source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("ETLLoad/Folder/source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}

Categories