Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 26 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Confluent.Kafka", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Confluent.Kafka.Tests", "tests\Aspire.Confluent.Kafka.Tests\Aspire.Confluent.Kafka.Tests.csproj", "{A8CB331A-1247-41D9-8118-538E5A2CC9DF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Playgrounds", "Playgrounds", "{DF8ADBC2-5B15-422F-A703-C60711D5552D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CosmosEndToEnd", "CosmosEndToEnd", "{DBEDDF76-1C33-4943-8CCB-337A7D48AFF5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CosmosEndToEnd.AppHost", "src\Playgrounds\CosmosEndToEnd\CosmosEndToEnd.AppHost\CosmosEndToEnd.AppHost.csproj", "{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CosmosEndToEnd.ServiceDefaults", "src\Playgrounds\CosmosEndToEnd\CosmosEndToEnd.ServiceDefaults\CosmosEndToEnd.ServiceDefaults.csproj", "{71A215C3-61E3-4E46-880C-286C794AD7B3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosEndToEnd.ApiService", "src\Playgrounds\CosmosEndToEnd\CosmosEndToEnd.ApiService\CosmosEndToEnd.ApiService.csproj", "{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -520,6 +530,18 @@ Global
{A8CB331A-1247-41D9-8118-538E5A2CC9DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8CB331A-1247-41D9-8118-538E5A2CC9DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8CB331A-1247-41D9-8118-538E5A2CC9DF}.Release|Any CPU.Build.0 = Release|Any CPU
{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Release|Any CPU.Build.0 = Release|Any CPU
{71A215C3-61E3-4E46-880C-286C794AD7B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71A215C3-61E3-4E46-880C-286C794AD7B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71A215C3-61E3-4E46-880C-286C794AD7B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71A215C3-61E3-4E46-880C-286C794AD7B3}.Release|Any CPU.Build.0 = Release|Any CPU
{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -610,6 +632,10 @@ Global
{F7D9FA54-1F64-4A36-961A-0087F8E88D07} = {8BAF2119-8370-4E9E-A887-D92506F8C727}
{174E0507-3BB0-4CDC-829E-9CA75DA66473} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{A8CB331A-1247-41D9-8118-538E5A2CC9DF} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{DBEDDF76-1C33-4943-8CCB-337A7D48AFF5} = {DF8ADBC2-5B15-422F-A703-C60711D5552D}
{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4} = {DBEDDF76-1C33-4943-8CCB-337A7D48AFF5}
{71A215C3-61E3-4E46-880C-286C794AD7B3} = {DBEDDF76-1C33-4943-8CCB-337A7D48AFF5}
{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64} = {DBEDDF76-1C33-4943-8CCB-337A7D48AFF5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ public AzureCosmosDBDatabaseResource(string name, AzureCosmosDBResource parent)
/// Gets the connection string to use for this database.
/// </summary>
/// <returns>The connection string to use for this database.</returns>
public string? GetConnectionString() => ConnectionString;
public string? GetConnectionString() => ConnectionString ?? $"{Parent.GetConnectionString()}Database={Name};"; // HACK: Will go away when we get rid of Azure Provisioner package.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly will go away? If we are going to get rid of the Azure Provisioner package, do we even need this hack now?

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data.Common;
using Aspire.Hosting.Azure.Cosmos;
using Aspire.Microsoft.Azure.Cosmos;
using Azure.Identity;
Expand Down Expand Up @@ -108,6 +109,19 @@ private static void AddAzureCosmosDB(
if (serviceKey is null)
{
builder.Services.AddSingleton(_ => ConfigureDb());

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line 104, please remove clientOptions.LimitToEndpoint = true; this should never be set in any general content, even in the case of the emulator, users might copy this and think this is ok.

I know this came from an official doc (I found the source) and I also corrected it there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remove this, it seems to stop the code working. It just hangs.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not hanging, it probably is facing an HttpRequestException and retrying. This flag turns off the retries. HttpRequestException is normally related to some connectivity issue with the endpoint being passed. Was it localhost?

var csBuilder = new DbConnectionStringBuilder();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a) why are we only doing this for the non-EF component?
b) why are we only doing this for the non-Keyed DI path?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is incomplete. I was just getting something working to facilitate conversation.

csBuilder.ConnectionString = settings.ConnectionString;

if (csBuilder.TryGetValue("Database", out var databaseName))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "Database" a valid connection string property? I only see these 2:

https://github.com/Azure/azure-cosmos-dotnet-v3/blob/431c6f224747e463bed0475083d5e0d7c1afaee2/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs#L52-L53

Would CosmosDB start throwing if it finds unrecognized properties in the connection string?

cc @Pilchie @sourabh1007

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it throws, it will ignore other properties...you can run a quick test to check it out.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not. Cosmos DB Connection String only has endpoint + Key. Users do not specify the DB on the connection string. Also, this would not work if the authentication method is AAD?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually going to propose that Cosmos start accepting a connection string with segments even for AAD auth. The reason is being able to encode extra options like this.

{
builder.Services.AddSingleton<Database>(sp =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels really weird to conditionally add a DI service based on the format used in the connection string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about this, the more I don't think we should be doing this in the Aspire component. If someone wants to make a separate NuGet package to do these kinds of convenience operations, that would be OK, but Aspire is more geared towards "cloud" concerns like telemetry, config, health checks, etc. Its goal isn't trying to make the underlying libraries easier to use. It registers the root concept in DI and then gets out of the user's way and let's them decide how to structure their app.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fair. Ideally Cosmos libraries would do a lot of this for us.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if instead is a keyedSingleton that takes the DB Name? That way the caller can obtain the Database instance for the name they need?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or a KeyedSingleton that takes dbName + containerName and return a Container from client.GetContainer().

{
var client = sp.GetRequiredService<CosmosClient>();
var database = client.CreateDatabaseIfNotExistsAsync(databaseName.ToString()).Result.Database;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var database = client.CreateDatabaseIfNotExistsAsync(databaseName.ToString()).Result.Database;
var database = client.GetDatabase(databaseName);

Doing a blocking metadata async call sounds not ideal? Also, what if the app interacts with multiple databases?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an app interacts with multiple databases, then the developer using Aspire would probably have wired up their app model a little differently and this code wouldn't kick in.

return database;
});
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should use the word "Playgrounds" and this shouldn't be under the src folder - which is for product code.

I'd rather call these "TestProjects", even if they are manual tests.


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

<ItemGroup>
<ProjectReference Include="..\..\..\Components\Aspire.Microsoft.Azure.Cosmos\Aspire.Microsoft.Azure.Cosmos.csproj" />
<ProjectReference Include="..\CosmosEndToEnd.ServiceDefaults\CosmosEndToEnd.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@CosmosEndToEnd.ApiService_HostAddress = http://localhost:5193

GET {{CosmosEndToEnd.ApiService_HostAddress}}/weatherforecast/
Accept: application/json

###
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;
using CosmosEndToEnd.ApiService;
using Microsoft.Azure.Cosmos;

var sessionId = Guid.NewGuid().ToString();

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.AddAzureCosmosDB("db",
settings =>
{
settings.IgnoreEmulatorCertificate = true;
},
clientOptions =>
{
// Default serializer for Cosmos V3 client is JSON.NET, this changes
// us to use S.T.J for this playground.
clientOptions.Serializer = new StjSerializer(new System.Text.Json.JsonSerializerOptions());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later follow-up: With next release of SDK there will be a new type for serialization to be implemented to handle LINQ queries.

});

builder.Services.AddKeyedSingleton<Container>("entries", (sp, _) =>
{
var db = sp.GetRequiredService<Database>();
var container = db.CreateContainerIfNotExistsAsync("entries", "/sessionId").Result.Container;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to avoid the blocking call?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is occurring in DI wire up which isn't async. It may be that we just don't do this database injection, and we leave all this to the developer, but my goal was to try and make it as seamless as possible. But perhaps this should be a Cosmos SDK responsibility as @eerhardt mentions above.

return container;
});

var app = builder.Build();

app.MapGet("/", async ([FromKeyedServices("entries")]Container container) =>
{
// Add an entry to the database on each request.
await container.CreateItemAsync(new Entry(Guid.NewGuid().ToString(), sessionId)).ConfigureAwait(false);

var entries = new List<Entry>();
var iterator = container.GetItemQueryIterator<Entry>(requestOptions: new QueryRequestOptions() { MaxItemCount = 5 });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ealsur - will this use query serialization? If so, it's not going to use the S.T.J serializer until Azure/azure-cosmos-dotnet-v3#4138 ships and your serializer is updated with the new API

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Queries work well with Custom Serializer. What does not work well is LINQ Queries

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call here is effectively a ReadFeed operation (a query without any query text)


var batchCount = 0;
while (iterator.HasMoreResults)
{
batchCount++;
var batch = await iterator.ReadNextAsync().ConfigureAwait(false);
foreach (var entry in batch)
{
entries.Add(entry);
}
}

return new
{
batchCount = batchCount,
totalEntries = entries.Count,
entries = entries
};
});

app.Run();

public class Entry(string id, string sessionId)
{
[JsonPropertyName("id")]
public string Id { get; set; } = id;

[JsonPropertyName("sessionId")]
public string SessionId { get; set; } = sessionId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5193",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using Azure.Core.Serialization;
using Microsoft.Azure.Cosmos;

namespace CosmosEndToEnd.ApiService;

public class StjSerializer: CosmosSerializer
{
private readonly JsonObjectSerializer _systemTextJsonSerializer;

public StjSerializer(JsonSerializerOptions jsonSerializerOptions)
{
this._systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
}

public override T FromStream<T>(Stream stream)
{
using (stream)
{
if (stream.CanSeek
&& stream.Length == 0)
{
return default!;
}

if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)stream;
}

return (T)this._systemTextJsonSerializer.Deserialize(stream, typeof(T), default)!;
}
}

public override Stream ToStream<T>(T input)
{
MemoryStream streamPayload = new MemoryStream();
this._systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default);
streamPayload.Position = 0;
return streamPayload;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" />
<ProjectReference Include="..\..\..\Aspire.Hosting\Aspire.Hosting.csproj" />
<ProjectReference Include="..\CosmosEndToEnd.ApiService\CosmosEndToEnd.ApiService.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<!-- NOTE: This line is only required because we are using P2P references, not NuGet. It will not exist in real apps. -->
<Import Project="../../../Aspire.Hosting/build/Aspire.Hosting.props" />

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />

<!-- NOTE: This line is only required because we are using P2P references, not NuGet. It will not exist in real apps. -->
<Import Project="../../../Aspire.Hosting/build/Aspire.Hosting.targets" />

</Project>
13 changes: 13 additions & 0 deletions src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

var builder = DistributedApplication.CreateBuilder(args);

var db = builder.AddAzureCosmosDB("cosmos")
.UseEmulator()
.AddDatabase("db");

builder.AddProject<Projects.CosmosEndToEnd_ApiService>("api")
.WithReference(db);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15129",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175"
}
},
"generate-manifest": {
"commandName": "Project",
"launchBrowser": true,
"dotnetRunMessages": true,
"commandLineArgs": "--publisher manifest --output-path aspire-manifest.json",
"applicationUrl": "http://localhost:15129",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />

<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />

<ProjectReference Include="..\..\..\Microsoft.Extensions.ServiceDiscovery.Dns\Microsoft.Extensions.ServiceDiscovery.Dns.csproj" />
<ProjectReference Include="..\..\..\Microsoft.Extensions.ServiceDiscovery\Microsoft.Extensions.ServiceDiscovery.csproj" />

</ItemGroup>

</Project>
Loading