Skip to content

Commit f680cc4

Browse files
CosmosDB Repository Support (#292)
* Add Cosmos DB integration and repository implementation Introduced new projects and configurations to support Cosmos DB integration in a .NET web application. This includes: - Adding new project references and updating solution configurations. - Setting up necessary services, controllers, models, and configuration files. - Implementing a Cosmos DB repository with options, extensions, and tests. - Adding license information to several files. - Creating a test project to ensure repository functionality and correctness. * Refactor solution and add new Bicep deployment files - Removed `Sample.Datasync.Server.SingleContainer` project from `Datasync.Toolkit.sln`. - Added `CommunityToolkit.Datasync.Server.CosmosDb.Test` to the solution. - Created new solution file `Datasync.Server.CosmosDb.SingleContainer.sln` with relevant projects. - Updated `main.bicep` to include licensing and resource deployment parameters. - Added `main.parameters.json` for Bicep deployment parameters. - Created `resources.bicep` to define CosmosDB and App Service resources. - Updated `Sample.Datasync.Server.SingleContainer.csproj` with necessary project references. * Add Cosmos DB parameters and repository tests - Updated `main.bicep` to include new parameters for `accountName`, `databaseName`, and `containerName`, modifying existing parameters to use these values. - Adjusted composite indexes in `resources.bicep` for better data organization. - Enhanced `PackedKeyRepository_Tests.cs` with setup code and new test cases for error handling of malformed IDs. - Introduced `PackedKeyOptions.cs` to manage ID generation and partition key handling for Cosmos DB entities. * Update partitionKey in Bicep resource definition Modified the `partitionKey` property in the Bicep file, changing the path from `'/myPartitionKey'` to `'/entity'`. This change reflects an updated partitioning strategy for the database container. * Refactor variable declarations in Program.cs Updated the declarations for `builder` and `app` to use explicit types (`WebApplicationBuilder` and `WebApplication`, respectively) instead of `var`. This change improves code clarity and readability. * Update Bicep and Azure YAML configurations Renamed connection string resource to 'appsettings' and updated its properties. Added HTTP logging settings for the app service. Enhanced `azure.yaml` with schema reference, project metadata, and defined a deployment workflow. Configured backend service with project details and hosting environment.
1 parent 630d0db commit f680cc4

31 files changed

+1542
-0
lines changed

Datasync.Toolkit.sln

+14
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{75F7
6464
EndProject
6565
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Datasync.Server", "samples\datasync-server\src\Sample.Datasync.Server\Sample.Datasync.Server.csproj", "{A9967817-2A2C-4C6D-A133-967A6062E9B3}"
6666
EndProject
67+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.CosmosDb", "src\CommunityToolkit.Datasync.Server.CosmosDb\CommunityToolkit.Datasync.Server.CosmosDb.csproj", "{D9356867-0A30-4B17-BD4C-0F7EF70984C6}"
68+
EndProject
6769
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.MongoDB", "src\CommunityToolkit.Datasync.Server.MongoDB\CommunityToolkit.Datasync.Server.MongoDB.csproj", "{DC20ACF9-12E9-41D9-B672-CB5FD85548E9}"
6870
EndProject
6971
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.MongoDB.Test", "tests\CommunityToolkit.Datasync.Server.MongoDB.Test\CommunityToolkit.Datasync.Server.MongoDB.Test.csproj", "{4FC45D20-0BA9-484B-9040-641687659AF6}"
@@ -72,6 +74,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.S
7274
EndProject
7375
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.OpenApi", "src\CommunityToolkit.Datasync.Server.OpenApi\CommunityToolkit.Datasync.Server.OpenApi.csproj", "{505421EC-2598-4114-B2EC-997959B89E74}"
7476
EndProject
77+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.CosmosDb.Test", "tests\CommunityToolkit.Datasync.Server.CosmosDb.Test\CommunityToolkit.Datasync.Server.CosmosDb.Test.csproj", "{FC15CF34-2C1A-4B15-85CC-A99EA25C47D2}"
78+
EndProject
7579
Global
7680
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7781
Debug|Any CPU = Debug|Any CPU
@@ -162,6 +166,10 @@ Global
162166
{A9967817-2A2C-4C6D-A133-967A6062E9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
163167
{A9967817-2A2C-4C6D-A133-967A6062E9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
164168
{A9967817-2A2C-4C6D-A133-967A6062E9B3}.Release|Any CPU.Build.0 = Release|Any CPU
169+
{D9356867-0A30-4B17-BD4C-0F7EF70984C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
170+
{D9356867-0A30-4B17-BD4C-0F7EF70984C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
171+
{D9356867-0A30-4B17-BD4C-0F7EF70984C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
172+
{D9356867-0A30-4B17-BD4C-0F7EF70984C6}.Release|Any CPU.Build.0 = Release|Any CPU
165173
{DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
166174
{DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
167175
{DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -178,6 +186,10 @@ Global
178186
{505421EC-2598-4114-B2EC-997959B89E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
179187
{505421EC-2598-4114-B2EC-997959B89E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
180188
{505421EC-2598-4114-B2EC-997959B89E74}.Release|Any CPU.Build.0 = Release|Any CPU
189+
{FC15CF34-2C1A-4B15-85CC-A99EA25C47D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
190+
{FC15CF34-2C1A-4B15-85CC-A99EA25C47D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
191+
{FC15CF34-2C1A-4B15-85CC-A99EA25C47D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
192+
{FC15CF34-2C1A-4B15-85CC-A99EA25C47D2}.Release|Any CPU.Build.0 = Release|Any CPU
181193
EndGlobalSection
182194
GlobalSection(SolutionProperties) = preSolution
183195
HideSolutionNode = FALSE
@@ -204,8 +216,10 @@ Global
204216
{D3B72031-D4BD-44D3-973C-2752AB1570F6} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
205217
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
206218
{A9967817-2A2C-4C6D-A133-967A6062E9B3} = {75F709FD-8CC2-4558-A802-FE57086167C2}
219+
{D9356867-0A30-4B17-BD4C-0F7EF70984C6} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
207220
{DC20ACF9-12E9-41D9-B672-CB5FD85548E9} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
208221
{4FC45D20-0BA9-484B-9040-641687659AF6} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
222+
{FC15CF34-2C1A-4B15-85CC-A99EA25C47D2} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
209223
{99E727A3-8EB3-437E-AAC8-3596E8B9B2AE} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
210224
{505421EC-2598-4114-B2EC-997959B89E74} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
211225
EndGlobalSection

Directory.Packages.props

+3
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@
1212
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
1313
<PackageVersion Include="Microsoft.AspNetCore.OData" Version="9.1.3" />
1414
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
15+
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.46.1" />
1516
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
1617
<PackageVersion Include="Microsoft.EntityFrameworkCore.Cosmos" Version="9.0.1" />
1718
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.1" />
1819
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.1" />
1920
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
2021
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.1" />
2122
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.1" />
23+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" />
2224
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.1" />
2325
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
2426
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
2527
<PackageVersion Include="Microsoft.OData.Core" Version="8.2.3" />
2628
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
2729
<PackageVersion Include="Microsoft.Spatial" Version="8.2.3" />
30+
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
2831
<PackageVersion Include="MongoDB.Driver" Version="3.1.0" />
2932
<PackageVersion Include="NSubstitute" Version="5.3.0" />
3033
<PackageVersion Include="NSwag.AspNetCore" Version="14.2.0" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.12.35728.132
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Datasync.Server.SingleContainer", "src\Sample.Datasync.Server.SingleContainer.csproj", "{5C9AB15D-EBE4-4F60-939C-B24E73F4174E}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Projects", "Projects", "{2410E728-3A10-4E61-BCE0-64031E9ABC1C}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.CosmosDb", "..\..\src\CommunityToolkit.Datasync.Server.CosmosDb\CommunityToolkit.Datasync.Server.CosmosDb.csproj", "{2CA5DC15-69DE-47EA-9FF3-595E6D6D9FB5}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.Swashbuckle", "..\..\src\CommunityToolkit.Datasync.Server.Swashbuckle\CommunityToolkit.Datasync.Server.Swashbuckle.csproj", "{98195FB2-DABA-48F6-8D52-50CF920B4F72}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Release|Any CPU = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{5C9AB15D-EBE4-4F60-939C-B24E73F4174E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{5C9AB15D-EBE4-4F60-939C-B24E73F4174E}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{5C9AB15D-EBE4-4F60-939C-B24E73F4174E}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{5C9AB15D-EBE4-4F60-939C-B24E73F4174E}.Release|Any CPU.Build.0 = Release|Any CPU
24+
{2CA5DC15-69DE-47EA-9FF3-595E6D6D9FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{2CA5DC15-69DE-47EA-9FF3-595E6D6D9FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{2CA5DC15-69DE-47EA-9FF3-595E6D6D9FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{2CA5DC15-69DE-47EA-9FF3-595E6D6D9FB5}.Release|Any CPU.Build.0 = Release|Any CPU
28+
{98195FB2-DABA-48F6-8D52-50CF920B4F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29+
{98195FB2-DABA-48F6-8D52-50CF920B4F72}.Debug|Any CPU.Build.0 = Debug|Any CPU
30+
{98195FB2-DABA-48F6-8D52-50CF920B4F72}.Release|Any CPU.ActiveCfg = Release|Any CPU
31+
{98195FB2-DABA-48F6-8D52-50CF920B4F72}.Release|Any CPU.Build.0 = Release|Any CPU
32+
EndGlobalSection
33+
GlobalSection(SolutionProperties) = preSolution
34+
HideSolutionNode = FALSE
35+
EndGlobalSection
36+
GlobalSection(NestedProjects) = preSolution
37+
{2CA5DC15-69DE-47EA-9FF3-595E6D6D9FB5} = {2410E728-3A10-4E61-BCE0-64031E9ABC1C}
38+
{98195FB2-DABA-48F6-8D52-50CF920B4F72} = {2410E728-3A10-4E61-BCE0-64031E9ABC1C}
39+
EndGlobalSection
40+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
2+
3+
name: community-toolkit-datasync-server
4+
metadata:
5+
6+
7+
workflows:
8+
up:
9+
steps:
10+
- azd: provision
11+
- azd: deploy --all
12+
13+
services:
14+
backend:
15+
project: ./src
16+
language: csharp
17+
host: appservice
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
targetScope = 'subscription'
6+
7+
@minLength(1)
8+
@maxLength(64)
9+
@description('Name of the the environment which is used to generate a short unique hash used in all resources.')
10+
param environmentName string
11+
12+
@minLength(1)
13+
@description('Primary location for all resources')
14+
param location string
15+
16+
@description('Optional - the name of the App Service to create. If not provided, a unique name will be generated.')
17+
param appServiceName string = ''
18+
19+
@description('Optional - the name of the App Service Plan to create. If not provided, a unique name will be generated.')
20+
param appServicePlanName string = ''
21+
22+
@description('Optional - the name of the Resource Group to create. If not provided, a unique name will be generated.')
23+
param resourceGroupName string = ''
24+
25+
@description('Optional - the name for the CosmosDB account. If not provided, a unique name will be generated.')
26+
param accountName string = ''
27+
28+
@description('Optional - the name for the database. default is TodoDb')
29+
param databaseName string = 'TodoDb'
30+
31+
@description('Optional - the name for the container. default is TodoContainer.')
32+
param containerName string = 'TodoContainer'
33+
34+
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
35+
var tags = { 'azd-env-name': environmentName }
36+
37+
resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
38+
name: !empty(resourceGroupName) ? resourceGroupName : 'rg-${environmentName}'
39+
location: location
40+
tags: tags
41+
}
42+
43+
module resources './resources.bicep' = {
44+
name: 'resources'
45+
scope: resourceGroup
46+
params: {
47+
location: location
48+
tags: tags
49+
appServiceName: !empty(appServiceName) ? appServiceName : 'app-${resourceToken}'
50+
appServicePlanName: !empty(appServicePlanName) ? appServicePlanName : 'asp-${resourceToken}'
51+
accountName: !empty(accountName) ? accountName : 'cdb-${resourceToken}'
52+
databaseName: databaseName
53+
containerName: containerName
54+
}
55+
}
56+
57+
output SERVICE_ENDPOINT string = resources.outputs.SERVICE_ENDPOINT
58+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
3+
"contentVersion": "1.0.0.0",
4+
"parameters": {
5+
"environmentName": {
6+
"value": "${AZURE_ENV_NAME}"
7+
},
8+
"location": {
9+
"value": "${AZURE_LOCATION}"
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
targetScope = 'resourceGroup'
2+
3+
@minLength(1)
4+
@description('Primary location for all resources')
5+
param location string
6+
7+
@description('The name of the App Service to create.')
8+
param appServiceName string
9+
10+
@description('The name of the App Service Plan to create.')
11+
param appServicePlanName string
12+
13+
@description('The list of tags to apply to all resources.')
14+
param tags object = {}
15+
16+
@description('The name for the CosmosDB account')
17+
param accountName string
18+
19+
@description('The name for the database')
20+
param databaseName string
21+
22+
@description('The name for the container')
23+
param containerName string
24+
25+
resource account 'Microsoft.DocumentDB/databaseAccounts@2022-05-15' = {
26+
name: toLower(accountName)
27+
kind: 'GlobalDocumentDB'
28+
location: location
29+
properties: {
30+
consistencyPolicy: {
31+
defaultConsistencyLevel: 'Session'}
32+
locations: [
33+
{
34+
locationName: location
35+
}
36+
]
37+
databaseAccountOfferType: 'Standard'
38+
}
39+
}
40+
41+
resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = {
42+
parent: account
43+
name: databaseName
44+
properties: {
45+
resource: {
46+
id: databaseName
47+
}
48+
options: {
49+
throughput: 1000
50+
}
51+
}
52+
}
53+
54+
resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2022-05-15' = {
55+
parent: database
56+
name: containerName
57+
properties: {
58+
resource: {
59+
id: containerName
60+
partitionKey: {
61+
paths: [
62+
'/entity'
63+
]
64+
kind: 'Hash'
65+
}
66+
indexingPolicy: {
67+
indexingMode: 'consistent'
68+
includedPaths: [
69+
{
70+
path: '/*'
71+
}
72+
]
73+
excludedPaths: [
74+
{
75+
path: '/_etag/?'
76+
}
77+
]
78+
compositeIndexes: [
79+
[
80+
{
81+
path: '/updatedAt'
82+
order: 'ascending'
83+
}
84+
{
85+
path: '/id'
86+
order: 'ascending'
87+
}
88+
]
89+
]
90+
}
91+
}
92+
}
93+
}
94+
95+
resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
96+
name: appServicePlanName
97+
location: location
98+
tags: tags
99+
sku: {
100+
name: 'B1'
101+
capacity: 1
102+
}
103+
}
104+
105+
resource appService 'Microsoft.Web/sites@2022-09-01' = {
106+
name: appServiceName
107+
location: location
108+
tags: union(tags, {
109+
'azd-service-name': 'backend'
110+
'hidden-related:${appServicePlan.id}': 'empty'
111+
})
112+
properties: {
113+
httpsOnly: true
114+
serverFarmId: appServicePlan.id
115+
siteConfig: {
116+
ftpsState: 'Disabled'
117+
minTlsVersion: '1.2'
118+
}
119+
}
120+
121+
resource configLogs 'config' = {
122+
name: 'logs'
123+
properties: {
124+
applicationLogs: { fileSystem: { level: 'Verbose' } }
125+
detailedErrorMessages: { enabled: true }
126+
failedRequestsTracing: { enabled: true }
127+
httpLogs: { fileSystem: { retentionInMb: 35, retentionInDays: 3, enabled: true } }
128+
}
129+
}
130+
131+
resource connectionStrings 'config' = {
132+
name: 'appsettings'
133+
properties: {
134+
ConnectionStrings__DefaultConnection: account.listConnectionStrings().connectionStrings[0].connectionString
135+
}
136+
}
137+
}
138+
139+
output SERVICE_ENDPOINT string = 'https://${appService.properties.defaultHostName}'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.Server;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Sample.Datasync.Server.SingleContainer.Models;
8+
9+
namespace Sample.Datasync.Server.Controllers;
10+
11+
[Route("tables/[controller]")]
12+
public class TodoItemController : TableController<TodoItem>
13+
{
14+
public TodoItemController(IRepository<TodoItem> repository)
15+
: base(repository)
16+
{
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.Server;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Sample.Datasync.Server.SingleContainer.Models;
8+
9+
namespace Sample.Datasync.Server.Controllers;
10+
11+
[Route("tables/[controller]")]
12+
public class TodoListController : TableController<TodoList>
13+
{
14+
public TodoListController(IRepository<TodoList> repository) : base(repository)
15+
{
16+
Options = new TableControllerOptions { EnableSoftDelete = true };
17+
}
18+
}

0 commit comments

Comments
 (0)