Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ public interface IPetService
{
Task<PetDto?> PostAsStringContentAsync();

Task<PetDto?> PostAsJsonAsync();
Task<PetDto?> PostWithPostAsJsonAsync();

Task<PetDto?> PostAsJsonContentAsync();

Task<PetDto?> PostAsSourceGeneratedJsonAsync();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<PropertyGroup Condition=" '$(RunConfiguration)' == 'https' " />
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Text.Json.Serialization;

namespace JsonObjectsWithHttpClient;

[JsonSerializable(typeof(PetDto))]
public partial class PetContext : JsonSerializerContext
{
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

namespace JsonObjectsWithHttpClient;

public class PetService : IPetService
public class PetService(HttpClient httpClient) : IPetService
{
private readonly HttpClient _httpClient;

public PetService(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<PetDto?> PostAsStringContentAsync()
{
var petData = CreatePet();
Expand All @@ -22,29 +14,43 @@ public PetService(HttpClient httpClient)

var request = new HttpRequestMessage(HttpMethod.Post, "pet");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent(pet, Encoding.UTF8);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = new StringContent(pet, Encoding.UTF8, "application/json");

var response = await _httpClient.SendAsync(request);
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();

var content = await response.Content.ReadAsStringAsync();
var createdPet = JsonSerializer.Deserialize<PetDto>(content);
return await response.Content.ReadFromJsonAsync<PetDto>();
}

return createdPet;
public async Task<PetDto?> PostWithPostAsJsonAsync()
{
var petData = CreatePet();

var response = await httpClient.PostAsJsonAsync("pet", petData);
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<PetDto>();
}
public async Task<PetDto?> PostAsJsonAsync()

public async Task<PetDto?> PostAsJsonContentAsync()
{
var petData = CreatePet();

var response = await _httpClient.PostAsJsonAsync("pet", petData);
using var content = JsonContent.Create(petData);
var response = await httpClient.PostAsync("pet", content);
response.EnsureSuccessStatusCode();

var content = await response.Content.ReadAsStringAsync();
var createdPet = JsonSerializer.Deserialize<PetDto>(content);
return await response.Content.ReadFromJsonAsync<PetDto>();
}

public async Task<PetDto?> PostAsSourceGeneratedJsonAsync()
{
var petData = CreatePet();

var response = await httpClient.PostAsJsonAsync("pet", petData, PetContext.Default.PetDto);
response.EnsureSuccessStatusCode();

return createdPet;
return await response.Content.ReadFromJsonAsync(PetContext.Default.PetDto);
}

private PetDto CreatePet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IPetService, PetService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOpenApi();
builder.Services.AddHttpClient<IPetService, PetService>(client =>
{
client.BaseAddress = new Uri("https://petstore.swagger.io/v2/");
Expand All @@ -14,14 +12,19 @@

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapOpenApi();
}

app.MapPost("/postAsStringContent", async (IPetService petService)
=> await petService.PostAsStringContentAsync());

app.MapPost("/postAsJson", async (IPetService petService)
=> await petService.PostAsJsonAsync());
=> await petService.PostWithPostAsJsonAsync());

app.MapPost("/postAsJsonContent", async (IPetService petService)
=> await petService.PostAsJsonContentAsync());

app.MapPost("/postAsSourceGeneratedJson", async (IPetService petService)
=> await petService.PostAsSourceGeneratedJsonAsync());

app.Run();
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"launchUrl": "openapi/v1.json",
"applicationUrl": "https://localhost:7072;http://localhost:5229",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
global using System.Net;
global using System.Net.Http.Json;
global using System.Text;
global using JsonObjectsWithHttpClient;
global using Moq;
global using Moq.Protected;
Expand Down
80 changes: 57 additions & 23 deletions dotnet-client-libraries/JsonObjectsWithHttpClient/Tests/PetTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,91 @@ public class PetTest
{
private const string _baseAddress = "https://mockdomain.mock";

[Fact]
public async void GivenPetObjectHasValues_WhenPostAsStringContentIsCalled_ThenPetResultIsReturned()
private static (HttpClient Client, List<string?> ContentTypes) CreateClient()
{
var contentTypes = new List<string?>();
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();

httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(r => r.RequestUri!.AbsoluteUri == $"{_baseAddress}/pet"), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
.Callback<HttpRequestMessage, CancellationToken>((request, _) => contentTypes.Add(request.Content?.Headers.ContentType?.ToString()))
.ReturnsAsync(() => new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{\n \"id\": 12,\n \"name\": \"German Shepherd\"\n}")
Content = new StringContent("{\n \"id\": 12,\n \"name\": \"German Shepherd\"\n}", Encoding.UTF8, "application/json")
});

var httpClientMock = new HttpClient(httpMessageHandlerMock.Object)
var httpClient = new HttpClient(httpMessageHandlerMock.Object)
{
BaseAddress = new Uri(_baseAddress)
};

var successResult = await new PetService(httpClientMock)
return (httpClient, contentTypes);
}

[Fact]
public async Task GivenPetObjectHasValues_WhenPostAsStringContentIsCalled_ThenPetResultIsReturned()
{
var (httpClient, contentTypes) = CreateClient();

var successResult = await new PetService(httpClient)
.PostAsStringContentAsync();

Assert.NotNull(successResult);
Assert.Equal(12, successResult!.Id);

Assert.Equal("German Shepherd", successResult!.Name);
Assert.Equal("application/json; charset=utf-8", Assert.Single(contentTypes));
}

[Fact]
public async void GivenPetObjectHasValues_WhenPostAsJsonIsCalled_ThenPetResultIsReturned()
public async Task GivenPetObjectHasValues_WhenPostWithPostAsJsonIsCalled_ThenPetResultIsReturned()
{
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
var (httpClient, contentTypes) = CreateClient();

httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(r => r.RequestUri!.AbsoluteUri == $"{_baseAddress}/pet"), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{\n \"id\": 12,\n \"name\": \"German Shepherd\"\n}")
});
var successResult = await new PetService(httpClient)
.PostWithPostAsJsonAsync();

var httpClientMock = new HttpClient(httpMessageHandlerMock.Object)
{
BaseAddress = new Uri(_baseAddress)
};

var successResult = await new PetService(httpClientMock)
.PostAsJsonAsync();
Assert.NotNull(successResult);
Assert.Equal(12, successResult!.Id);
Assert.Equal("German Shepherd", successResult!.Name);
Assert.Equal("application/json; charset=utf-8", Assert.Single(contentTypes));
}

[Fact]
public async Task GivenPetObjectHasValues_WhenPostAsJsonContentIsCalled_ThenPetResultIsReturned()
{
var (httpClient, contentTypes) = CreateClient();

var successResult = await new PetService(httpClient)
.PostAsJsonContentAsync();

Assert.NotNull(successResult);
Assert.Equal(12, successResult!.Id);
Assert.Equal("German Shepherd", successResult!.Name);
Assert.Equal("application/json; charset=utf-8", Assert.Single(contentTypes));
}

[Fact]
public async Task GivenPetObjectHasValues_WhenPostAsSourceGeneratedJsonIsCalled_ThenPetResultIsReturned()
{
var (httpClient, contentTypes) = CreateClient();

var successResult = await new PetService(httpClient)
.PostAsSourceGeneratedJsonAsync();

Assert.NotNull(successResult);
Assert.Equal(12, successResult!.Id);
Assert.Equal("German Shepherd", successResult!.Name);
Assert.Equal("application/json; charset=utf-8", Assert.Single(contentTypes));
}

[Fact]
public void GivenStringContent_WhenMediaTypeIsOmitted_ThenContentTypeIsTextPlain()
{
var withoutMediaType = new StringContent("{}", Encoding.UTF8);
var withMediaType = new StringContent("{}", Encoding.UTF8, "application/json");

Assert.Equal("text/plain; charset=utf-8", withoutMediaType.Headers.ContentType!.ToString());
Assert.Equal("application/json; charset=utf-8", withMediaType.Headers.ContentType!.ToString());
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand All @@ -10,17 +10,18 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="10.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.70" />
<!-- Moq stays at 4.18.4: versions from 4.20.0 on ship the SponsorLink analyzer. -->
<PackageReference Include="Moq" Version="4.18.4" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading