How to include members of base entity in Swagger API Documentation? - c#

I have something like these:
BaseDTO
public record BaseDTO
{
public virtual int Id { get; protected init; }
public DateTime Timestamp { get; protected init; }
}
NotificationDTO
public record NotificationDTO(string Code, string Name, string Description, NotificationType Type) : BaseDTO;
NotificationsController
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<IActionResult> Post([FromBody] NotificationDTO notificationDTO)
{
await _notificationsService.AddNotification(notificationDTO);
return Ok();
}
When I open SwaggerURL only members of NotificationsDTO can be set in "Try it out" section, members of BaseDTO are missing. Even if I add them by hand like setting the Id to 1 in Swagger, if I put a breakpoint in controller, Id it's 0, so Swagger doesn't modify it.
How can I see the base entity members in Swagger?
Edit:
It seems the object binding is not done properly, I have tried to send this in the request body with Postman:
{
"type": 0,
"id": 1,
"timestamp": "2022-05-13T09:22:18.429Z",
"code": "string",
"name": "string",
"description": "string"
}
And in the controller I've got this:
{
"NotificationDTO"{
Id = 0,
"Timestamp = 1/1/0001 12":"00":00 AM,
"Code = string",
"Name = string",
"Description = string",
"Type = Email"
}
}
If I change the access modifiers of the properties of base entity from public virtual int Id { get; protected init; } to public virtual int Id { get; init; } then the objects are binded properly and I can see the members of the base entity in Swagger.

Related

Which type should be a free json field in a DTO in C# dotnet core?

I receive a free form JSON field, which I do not need to touch. My service simply saves it in a DB (the other fields are important, but this one has nothing to do with the logic. The client can put anything in it).
Example:
Body = {
title: myTitle,
stuff: {can be any json}
}
How should I map this to a DTO class?
Example:
public class MyDto(){
public string Title { get; set; }
public ?????? Stuff { get; set; }
I tried using "string", but it does not work (the whole body is received as null, as it does not understand that it's the correct DTO type).
My current idea is to go with the "dynamic" type, would that be a good idea?
I'm looking for best practices.
You could use dynamic and the JSON.NET library:
https://weblog.west-wind.com/posts/2012/aug/30/using-jsonnet-for-dynamic-json-parsing
You should use a System.Json.JsonElement to represent a generic Json node as exemplified bellow.
public class HomeController : ControllerBase
{
public class InputDTO
{
public string Title { get; set; }
public System.Text.Json.JsonElement Stuff { get; set; }
}
[HttpPost]
[Route("")]
public void Post([FromBody] InputDTO data)
{
var rawSuffJson = data.Stuff.ToString();
}
}
The HTTP request body can be like this:
{
"title": "My Title",
"stuff": {
"str": "String",
"int32": 1,
"list": [ 1,2,3 ]
}
}

The returned data is partially missing in ASP.NET Web API GET method

I have some trouble with my ASP.NET Web API application with returned data.
I have 2 models, Province-to-District as One-to-Many relationships. I use Eager Loading following a tutorial from Microsoft Website
Here is my Province.cs model
[Table("province")]
public class Province
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("displayName")]
public string DisplayName { get; set; }
[Column("code")]
[StringLength(10)]
public string Code { get; set; }
// FKs
public virtual ICollection<District> Districts { get; set; }
}
My District.cs model
[Table("district")]
public class District
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("displayName")]
public string DisplayName { get; set; }
[Column("prefix")]
public string Prefix { get; set; }
[Column("provinceId")]
public int ProvinceId { get; set; }
// FKs
public virtual Province Province { get; set; }
}
At my LocationController.cs
public async Task<ActionResult<IEnumerable<Province>>> GetProvince([FromQuery] Province _province)
{
// Eager Loading
return await _context.province.Include(p => p.Districts).Where(p => p.Id == _province.Id).ToListAsync();
}
When I call https://localhost:44346/api/province?id=1 or with any id existed in my database. Both Postman and all my browsers (MS Edge, Chrome...) return only one part of a JSONArray. Look likes this:
[
{
"id": 1,
"displayName": "Hồ Chí Minh",
"code": "SG",
"districts": [
{
"id": 1,
"displayName": "Bình Chánh",
"prefix": "Huyện",
"provinceId": 1
That's it. Only 11 lines of data. It doesn't even match the JSON format.
My expected return must be:
[
{
"id": 1,
"displayName": "Hồ Chí Minh",
"code": "SG",
"districts": [
{
"id": 1,
"displayName": "Bình Chánh",
"prefix": "Huyện",
"provinceId": 1
},
{
...
},...
},...
]
Or at least, the right format of JSON.
My previous 2 APIs get all data of each table work fine.
What I'm missing here? Thanks.
You should create a model and return it instead of your entities.
This is happening because of self reference loop.
If you are using Newtonsoft.Json you can ignore it in startup.cs
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
})

Need to inject values from parent object into child object when serializing

I'm setting up an API which dynamically advertises services as part of its response, letting the consumer know what else they can do with the object. I can't work out how to inject the parent's objects type and ID into the service URI.
I'm using attributes and custom JSON.NET IValueProviders to create the URIs (which is working fine for other dynamic examples), but in this case I need a property from outside the object.
Classes looks similar to:
public class Person
{
public Guid ID { get; set; }
public string Type { get; set; }
public string Name { get; set; }
[ObjectUri]
public string Uri { get; set; }
...
public IEnumerable<Service> Services { get; set; }
}
public class Service
{
public string ServiceName { get; set; }
[ServiceUri]
public string ServiceUri { get; set; }
}
I'm using a custom contract resolver to add the custom IValueProvider (ObjectUriValueProvider) which renders the object URI (which is obviously just using information from the same object).
However, because the contract resolver is operating at the level of the abstract model, there's no way to pass concrete values pertaining to the parent object to ServiceUriValueProvider.
Ideal output:
{
"id": "ae330dc8-0217-4c58-b64b-44a1c7ba4f46",
"type": "Person",
"name": "Fred",
"uri": "https://server/api/person/ae330dc8-0217-4c58-b64b-44a1c7ba4f46"
...
"services": [
{
"serviceName": "ListNotes",
"serviceUri": "https://server/api/notes/person/ae330dc8-0217-4c58-b64b-44a1c7ba4f46"
},
{
"serviceName": "ListContactDetails",
"serviceUri": "https://server/api/contacts/person/ae330dc8-0217-4c58-b64b-44a1c7ba4f46"
}
]
}
The actual results at the moment don't include the type and id being correctly populated into
{
"services": [
{
"serviceName": "ListNotes",
"serviceUri": "https://server/api/notes/{type}/{id}"
},{
"serviceName": "ListContactDetails",
"serviceUri": "https://server/api/contacts/{type}/{id}"
}
]
}

Correct way to pass object reference into .NET Core Web API

I'm just starting out with .NET Web API programming, and I have a question for seasoned .NET developers - what is the "correct" way to pass an object reference into a Create endpoint?
I have the following models:
public class Task
{
public int ID { get; set; }
public string Title { get; set; }
public virtual User User { get; set; }
}
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And my Controller - endpoint to create a Task:
[HttpPost]
public async Task<IActionResult> PostTask([FromBody] Models.Task task)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Task.Add(task);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTask", new { id = task.ID }, task);
}
By default, this has some interesting behavior. It expects an entire User model to be passed into the POST request (see below), and will actually create the user when passed:
{
"id": 0,
"title": "string",
"user": {
"id": 0,
"firstName": "string",
"lastName": "string"
}
}
I understand technically why it would do this, but this is definitely not acceptable behavior in a real app - so my question is - what is the "correct/appropriate" way to pass a UserID and do validation on the model in .NET? Should I forego the use of "ModelState.IsValid" and do manual validation?
As a secondary question - I am using NSwag to generate Swagger docs from my API, and it's showing "id" as a parameter that needs to be passed into the POST method. Obviously, ID cannot be passed as it's generated in code, but is there a way to get Swagger to not show ID as being a passable property?
Then create a data transfer model that exposes only the data you want sent over the wire.
public class NewTaskDto {
[Required]
public string Title { get; set; }
[Required]
public int UserId { get; set; }
}
and map the data to your models on the server, along with what ever validation is required. For example checking that the UserId exists and is valid.
[HttpPost]
public async Task<IActionResult> PostTask([FromBody] NewTaskDto data) {
if(data != null) {
validateUserId(data.UserId, ModelState);
}
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
Models.Task task = MapDataToTask(data); //create Task and copy members
await saveTask(task);
return CreatedAtAction("GetTask", new { id = task.ID }, task);
}
That way the doc will only see the and report on the exposed members.
The attribute [Required] is mandatory and then you can check parameter.

C# - Newtonsoft : Generic class for Client Response

After hours of attempts and research, I am asking for your help.
I am calling a public API which returns the same structure except for the datas returned.
For examples, the REST calls which retrieve stations and districts return those two JSON answers :
Stations response :
"response" : {
"status": { "#attributes": {"code": "0", "message": "OK"} },
"data" : {
"station": [{
"number": "stationId",
"name": "stationName",
"address": "stationAddress",
"state": "1",
"latitude": "stationLat",
"longitude": "stationLong",
"slotsavailable": "10",
"bikesavailable": "20",
"pos": "0",
"district": "stationDistrict",
"lastupdate": "2016-03-28T11:47:08+02:00"
}, {...}, ...]}
}
Districts response :
"response" : {
"status": { "#attributes": {"code": "0", "message": "OK"} },
"data" : { "district": [{"id": "districtId", "name": "districtName"}, {...}, ...] }
}
I am using a .NET 4.5/C# solution with Newtonsoft.Json to execute the call.
I want to make the object, mapped to the client response, generic so the execution of the call will be made as follow :
var result = await client.Execute<Response<ApiResponseDistrict>>(request);
var result = await client.Execute<Response<ApiResponseStation>>(request);
My first attempt was to make a non generic call (create a full object by returned datas) which was a success.
My second attempt was to created a generic object so I made the following classes using the JsonProperty of the library Newtonsoft :
public class ApiResponse<T>
{
[JsonProperty("response")]
public Response<T> Response { get; set; }
}
public class Response<T>
{
[JsonProperty("status")]
public Status Status { get; set; }
[JsonProperty("data")]
public Data<T> Data { get; set; }
}
public class Data<T>
{
public T ResponseData { get; set; }
}
public class ApiResponseDistrict
{
[JsonProperty("district")]
public List<District> Districts { get; set; }
}
public class District
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
At this point, when I am executing the call the object Response is valorized and also its property Status with the value waited but the property Data is never valorized (null).
My third attempt was to continue on the second attempt but using the JsonObject of the Newtonsoft library which it's given (with the same result) :
[JsonObject("district")]
public class ApiResponseDistrict
{
public List<District> Districts { get; set; }
}
As I am new to Newtonsoft, I would like to know if it is possible to use generic classes, as I am trying to do, to mapped the object returned by the call or I have to create a complete object for each "data" returned ?
Thank you for your answer and explanations or clues for me to find the answer !
public class Response<T>
{
[JsonProperty("status")]
public Status Status { get; set; }
[JsonProperty("data")]
public Data<T> Data { get; set; }
}
public class Data<T>
{
public T ResponseData { get; set; }
}
This adds another layer between the data, so a response would look like this:
{
"Status": …,
"Data": {
"ResponseData": {
<The actual type T>
}
}
}
Instead, you want to remove that ResponseData level:
public class Response<T>
{
[JsonProperty("status")]
public Status Status { get; set; }
[JsonProperty("data")]
public T Data { get; set; }
}
So for example, for the JSON above, you would have a StationResponseData class:
public class StationResponseData
{
public List<Station> Stations
{ get; set; }
}
And then you would deserialize the JSON as Response<StationResponseData>. The Station class would then contain those properties for number, name, address, etc.

Categories

Resources