Reputation: 67281
I'm diving into NET8, minimal API and clean architecture.
First of all: This works (Swagger UI and postman)
app.MapPost("/user/login", async (HttpContext httpContext, [FromBody] LoginDto loginDto, [FromServices] IConfiguration conf, [FromServices] UserDataBackupDbContext db, [FromServices] UserManager<IdentityUser> userManager) =>
{
try
{
{... shortened ...}
var response = new AuthResponseDto
{
Id = user.Id,
UserName = user.UserName ?? Constants.UnknownValue,
Token = accessToken
};
return TypedResults.Ok<AuthResponseDto>(response);
}
catch (Exception e)
{
return Results.BadRequest<string>(e.Message);
}
}).WithName("UserLogin")
.Produces<AuthResponseDto>(StatusCodes.Status200OK)
.Produces<string>(StatusCodes.Status400BadRequest)
.AllowAnonymous();
Via Swagger UI or Postman I get the object AuthResponseDto
back.
Now I fiddled with nswag client generation. The created method is this:
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <exception cref="UserDataBackupApiException">A server side error occurred.</exception>
public virtual async System.Threading.Tasks.Task<AuthResponseDto> UserLoginAsync(LoginDto loginDto, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
if (loginDto == null)
throw new System.ArgumentNullException("loginDto");
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(loginDto, _settings.Value);
var content_ = new System.Net.Http.ByteArrayContent(json_);
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
request_.Content = content_;
request_.Method = new System.Net.Http.HttpMethod("POST");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
var urlBuilder_ = new System.Text.StringBuilder();
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
// Operation Path: "user/login"
urlBuilder_.Append("user/login");
await PrepareRequestAsync(client_, request_, urlBuilder_, cancellationToken).ConfigureAwait(false);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
await PrepareRequestAsync(client_, request_, url_, cancellationToken).ConfigureAwait(false);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>>();
foreach (var item_ in response_.Headers)
headers_[item_.Key] = item_.Value;
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<AuthResponseDto>(response_, headers_, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new UserDataBackupApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 400)
{
var objectResponse_ = await ReadObjectResponseAsync<string>(response_, headers_, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new UserDataBackupApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new UserDataBackupApiException<string>("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new UserDataBackupApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
On run-time this will get a response with content length = 0.
Later, within the status_ == 200
block, it will read the object response and return a new instance (all default values, not what was sent from the API).
Questions:
ResponseHeadersRead
? If I manually set this to ResponseContentRead
the content length is >0 and I can retrieve the content as string. But the rest of the function will return the "new born" instance anyway.For completness: The generated function reading the object. In my case the else
part will try to read from the stream:
protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Threading.CancellationToken cancellationToken)
{
if (response == null || response.Content == null)
{
return new ObjectResponseResult<T>(default(T), string.Empty);
}
if (ReadResponseAsString)
{
var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var typedBody = System.Text.Json.JsonSerializer.Deserialize<T>(responseText, JsonSerializerSettings);
return new ObjectResponseResult<T>(typedBody, responseText);
}
catch (System.Text.Json.JsonException exception)
{
var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
throw new UserDataBackupApiException(message, (int)response.StatusCode, responseText, headers, exception);
}
}
else
{
try
{
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
var typedBody = await System.Text.Json.JsonSerializer.DeserializeAsync<T>(responseStream, JsonSerializerSettings, cancellationToken).ConfigureAwait(false);
return new ObjectResponseResult<T>(typedBody, string.Empty);
}
}
catch (System.Text.Json.JsonException exception)
{
var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
throw new UserDataBackupApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
}
}
}
If you need more context, just ask. TIA
Upvotes: 0
Views: 376
Reputation: 67281
This is what I found myself:
The solution for me was to change all property names in my DTOs in camelCase
instead of PascalCase
.
Reading the object came back empty because the embedded JSON had lower-case initial letters, while my DTO's properties had upper-case there.
Probably it's possible to find a way to configure this (JSON settings or Attributes to the properties), but it turned out to be nasty, especially when it comes to nested JSON objects.
However, I decided to walk the camelCase
route.
In this link there is a discussion about casing in JSON and special situations with minimal API.
I'm still open for an easy going with PascalCase
properties.
Hint: After waiting quite a while with no alternative answer, I'll accept my own answer. If there'll be any better solution I'd be happy to accept this.
Upvotes: 0