From bd2be5f34093d0b3d89d24c932f8638b9d863947 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Mon, 6 Nov 2023 18:57:33 -0600 Subject: [PATCH 01/14] v8.0 init --- ...spNetCore.Identity.AzureTable.Model.csproj | 12 ++-- ...mino.AspNetCore.Identity.AzureTable.csproj | 17 ++---- .../Helpers/BaseKeyHelper.cs | 2 +- .../ElCamino.Azure.Data.Tables.csproj | 4 +- ...ino.Identity.AzureTable.DataUtility.csproj | 15 +++-- ...tCore.Identity.AzureTable.Templates.csproj | 4 +- .../StarterWebMvc-CSharp/samplemvccore.csproj | 2 +- .../samplerazorpagescore.csproj | 2 +- .../BaseUserStoreTests.cs | 2 +- ...spNetCore.Identity.AzureTable.Tests.csproj | 19 +++--- .../TableExtensions/TableClientTests.cs | 58 ++++++++++--------- .../UserStoreSHA256Tests.cs | 4 +- .../UserStoreTests.cs | 4 +- 13 files changed, 68 insertions(+), 77 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj index 9ecb3c9..e462201 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj @@ -5,7 +5,7 @@ Copyright © 2023 David Melendez, MIT License Azure Table Storage Provider for ASP.NET Identity Core Models David Melendez - netstandard2.0;net6.0;net7.0 + netstandard2.0;net8.0 11.0 ElCamino.AspNetCore.Identity.AzureTable.Model ../../tools/Key.snk @@ -22,7 +22,7 @@ git https://github.com/dlmelendez/identityazuretable.git True - 7.1 + 8.0 enable https://dlmelendez.github.io/identityazuretable @@ -35,12 +35,8 @@ - - - - - - + + diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj index f5bcc25..127a7b5 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj @@ -5,7 +5,7 @@ Copyright © 2023 David Melendez, MIT License Azure Table Storage Provider for ASP.NET Identity Core David Melendez - netstandard2.0;net6.0;net7.0 + netstandard2.0;net8.0 11.0 ElCamino.AspNetCore.Identity.AzureTable ../../tools/Key.snk @@ -21,7 +21,7 @@ git https://github.com/dlmelendez/identityazuretable.git True - 7.1 + 8.0 https://dlmelendez.github.io/identityazuretable @@ -37,23 +37,14 @@ - - - - - - - + - - - diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs index ff58a86..cde98d9 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs @@ -143,7 +143,7 @@ public virtual string GenerateRowKeyIdentityUserLogin(string? loginProvider, str return string.Format(FormatterIdentityUserLogin, hash); } - public double KeyVersion => 7.1; + public double KeyVersion => 8.0; public abstract string? ConvertKeyToHash(string? input); diff --git a/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj b/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj index 46c3e5e..e1768e6 100644 --- a/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj +++ b/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj @@ -5,7 +5,7 @@ Copyright © 2023 David Melendez, MIT License Azure Table Storage Extensions David Melendez - netstandard2.0;net6.0;net7.0 + netstandard2.0;net8.0 11.0 ElCamino.Azure.Data.Tables ../../tools/Key.snk @@ -20,7 +20,7 @@ git https://github.com/dlmelendez/identityazuretable.git True - 7.1 + 8.0 https://dlmelendez.github.io/identityazuretable diff --git a/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj b/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj index 4fa1d4c..6daa088 100644 --- a/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj +++ b/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 11.0 ElCamino.Identity.AzureTable.DataUtility Exe @@ -11,7 +11,7 @@ false false false - 7.1 + 8.0 enable @@ -43,12 +43,11 @@ - - - - - - + + + + + diff --git a/templates/ElCamino.AspNetCore.Identity.AzureTable.Templates.csproj b/templates/ElCamino.AspNetCore.Identity.AzureTable.Templates.csproj index 45721a1..ca610e9 100644 --- a/templates/ElCamino.AspNetCore.Identity.AzureTable.Templates.csproj +++ b/templates/ElCamino.AspNetCore.Identity.AzureTable.Templates.csproj @@ -3,7 +3,7 @@ Web Project Templates for Azure Table Storage Provider for ASP.NET Identity Core David Melendez - net7.0 + net8.0 ElCamino.AspNetCore.Identity.AzureTable.Templates ElCamino.AspNetCore.Identity.AzureTable.Templates ASP.NET Core Web Template Pack for ElCamino Identity Azure Table Storage @@ -19,7 +19,7 @@ git https://github.com/dlmelendez/identityazuretable.git True - 7.1 + 8.0 https://dlmelendez.github.io/identityazuretable MIT true diff --git a/templates/templates/StarterWebMvc-CSharp/samplemvccore.csproj b/templates/templates/StarterWebMvc-CSharp/samplemvccore.csproj index 5af6002..0cfd9dc 100644 --- a/templates/templates/StarterWebMvc-CSharp/samplemvccore.csproj +++ b/templates/templates/StarterWebMvc-CSharp/samplemvccore.csproj @@ -8,7 +8,7 @@ - + diff --git a/templates/templates/StarterWebRazorPages-CSharp/samplerazorpagescore.csproj b/templates/templates/StarterWebRazorPages-CSharp/samplerazorpagescore.csproj index 970ccd4..092a0ae 100644 --- a/templates/templates/StarterWebRazorPages-CSharp/samplerazorpagescore.csproj +++ b/templates/templates/StarterWebRazorPages-CSharp/samplerazorpagescore.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/BaseUserStoreTests.cs b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/BaseUserStoreTests.cs index 3e7d823..99c94ee 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/BaseUserStoreTests.cs +++ b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/BaseUserStoreTests.cs @@ -246,7 +246,7 @@ public virtual async Task GetUsersByRole() Assert.Equal(userCount, users.Count); users = await manager.GetUsersInRoleAsync(Guid.NewGuid().ToString()).ConfigureAwait(false); - Assert.Equal(0, users.Count); + Assert.Empty(users); await Assert.ThrowsAsync(() => manager.GetUsersInRoleAsync(string.Empty)).ConfigureAwait(false); } diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj index 696157f..ba65066 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj +++ b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj @@ -3,11 +3,11 @@ Testing David Melendez - net6.0;net7.0 + net8.0 11.0 ElCamino.AspNetCore.Identity.AzureTable.Tests false - 7.1 + 8.0 disable Full @@ -25,13 +25,14 @@ - - - - - - - + + + + + + + + diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs index d8eb6b1..7a802f6 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs +++ b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs @@ -28,7 +28,7 @@ public TableClientTests(TableFixture tableFixture, ITestOutputHelper output) private async Task SetupTableAsync() { //Setup Create table - await _tableClient.CreateIfNotExistsAsync().ConfigureAwait(false); + await _tableClient.CreateIfNotExistsAsync(); _output.WriteLine("Table created {0}", _tableName); } @@ -37,7 +37,7 @@ private async Task SetupTableAsync() public async Task AddUpdateGetEntityWithHeaderValues() { //Create Table - await SetupTableAsync().ConfigureAwait(false); + await SetupTableAsync(); //Setup Entity string key = "a-" + Guid.NewGuid().ToString("N"); _output.WriteLine("PartitionKey {0}", key); @@ -47,7 +47,7 @@ public async Task AddUpdateGetEntityWithHeaderValues() Assert.Equal(default, entity.Timestamp); //Execute Upsert - var addedEntity = await _tableClient.AddEntityWithHeaderValuesAsync(entity).ConfigureAwait(false); + var addedEntity = await _tableClient.AddEntityWithHeaderValuesAsync(entity); //Assert Assert.NotEqual(default, addedEntity.ETag); @@ -55,12 +55,14 @@ public async Task AddUpdateGetEntityWithHeaderValues() //Modify update string propertyName = "newProperty"; - TableEntity updateEntity = new TableEntity(key, key); - updateEntity.Add(propertyName, propertyName); + TableEntity updateEntity = new TableEntity(key, key) + { + { propertyName, propertyName } + }; - await Task.Delay(1000).ConfigureAwait(false); //wait 1 second for timestamp + await Task.Delay(1000); //wait 1 second for timestamp - var updatedEntity = await _tableClient.UpdateEntityWithHeaderValuesAsync(updateEntity, addedEntity.ETag).ConfigureAwait(false); + var updatedEntity = await _tableClient.UpdateEntityWithHeaderValuesAsync(updateEntity, addedEntity.ETag); //Assert Assert.NotEqual(default, updatedEntity.ETag); Assert.NotEqual(default, updatedEntity.Timestamp); @@ -70,7 +72,7 @@ public async Task AddUpdateGetEntityWithHeaderValues() //Assert.NotEqual(addedEntity.Timestamp, updatedEntity.Timestamp); //Get and check new property - var getEntity = await _tableClient.GetEntityOrDefaultAsync(updateEntity.PartitionKey, updatedEntity.RowKey).ConfigureAwait(false); + var getEntity = await _tableClient.GetEntityOrDefaultAsync(updateEntity.PartitionKey, updatedEntity.RowKey); Assert.Equal(propertyName, getEntity.GetString(propertyName)); } @@ -79,7 +81,7 @@ public async Task AddUpdateGetEntityWithHeaderValues() public async Task UpsertGetEntityWithHeaderValues() { //Create Table - await SetupTableAsync().ConfigureAwait(false); + await SetupTableAsync(); //Setup Entity string key = "a-" + Guid.NewGuid().ToString("N"); _output.WriteLine("PartitionKey {0}", key); @@ -89,7 +91,7 @@ public async Task UpsertGetEntityWithHeaderValues() Assert.Equal(default, entity.Timestamp); //Execute Upsert - var addedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(entity).ConfigureAwait(false); + var addedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(entity); //Assert Assert.NotEqual(default, addedEntity.ETag); @@ -97,12 +99,14 @@ public async Task UpsertGetEntityWithHeaderValues() //Modify update string propertyName = "newProperty"; - TableEntity updateEntity = new TableEntity(key, key); - updateEntity.Add(propertyName, propertyName); + TableEntity updateEntity = new TableEntity(key, key) + { + { propertyName, propertyName } + }; - await Task.Delay(1000).ConfigureAwait(false); //wait 1 second for timestamp + await Task.Delay(1000); //wait 1 second for timestamp - var updatedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(updateEntity).ConfigureAwait(false); + var updatedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(updateEntity); //Assert Assert.NotEqual(default, updatedEntity.ETag); Assert.NotEqual(default, updatedEntity.Timestamp); @@ -112,7 +116,7 @@ public async Task UpsertGetEntityWithHeaderValues() //Assert.NotEqual(addedEntity.Timestamp, updatedEntity.Timestamp); //Get and check new property - var getEntity = await _tableClient.GetEntityOrDefaultAsync(updateEntity.PartitionKey, updatedEntity.RowKey).ConfigureAwait(false); + var getEntity = await _tableClient.GetEntityOrDefaultAsync(updateEntity.PartitionKey, updatedEntity.RowKey); Assert.Equal(propertyName, getEntity.GetString(propertyName)); } @@ -121,13 +125,13 @@ public async Task UpsertGetEntityWithHeaderValues() public async Task ExecuteTableQueryTakeCount() { //Create Table - await SetupTableAsync().ConfigureAwait(false); + await SetupTableAsync(); //Setup Entity string partitionKey = "b-" + Guid.NewGuid().ToString("N"); _output.WriteLine("PartitionKey {0}", partitionKey); string filterByPartitionKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, partitionKey); - int count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync().ConfigureAwait(false); + int count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync(); const int maxTestEntities = 1001; _output.WriteLine("Entities found {0}", count); @@ -141,8 +145,8 @@ public async Task ExecuteTableQueryTakeCount() TableEntity entity = new TableEntity(partitionKey, rowKey); batch.UpsertEntity(entity, TableUpdateMode.Replace); } - await batch.SubmitBatchAsync().ConfigureAwait(false); - count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync().ConfigureAwait(false); + await batch.SubmitBatchAsync(); + count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync(); _output.WriteLine("Entities found after batch create {0}", count); } Assert.True(count >= maxTestEntities); @@ -151,37 +155,37 @@ public async Task ExecuteTableQueryTakeCount() TableQuery tq = new TableQuery(); tq.FilterString = filterByPartitionKey; tq.TakeCount = 1; - var take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync().ConfigureAwait(false); + var take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); Assert.Equal(tq.TakeCount.Value, take.Count); _output.WriteLine($"Expected:{tq.TakeCount.Value} Actual:{take.Count}"); //Execute Query Take 0 tq.TakeCount = 0; - take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync().ConfigureAwait(false); + take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); Assert.Equal(tq.TakeCount.Value, take.Count); _output.WriteLine($"Expected:{tq.TakeCount.Value} Actual:{take.Count}"); //Execute Query Take null tq.TakeCount = null; - take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync().ConfigureAwait(false); + take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); Assert.Equal(count, take.Count); _output.WriteLine($"Expected:{count} Actual:{take.Count}"); //Execute Query Take 100 tq.TakeCount = 100; - take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync().ConfigureAwait(false); + take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); Assert.Equal(tq.TakeCount.Value, take.Count); _output.WriteLine($"Expected:{tq.TakeCount.Value} Actual:{take.Count}"); //Execute Query Take 1000 tq.TakeCount = 1000; - take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync().ConfigureAwait(false); + take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); Assert.Equal(tq.TakeCount.Value, take.Count); _output.WriteLine($"Expected:{tq.TakeCount.Value} Actual:{take.Count}"); //Execute Query Take 1001 tq.TakeCount = 1001; - take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync().ConfigureAwait(false); + take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); Assert.Equal(tq.TakeCount.Value, take.Count); _output.WriteLine($"Expected:{tq.TakeCount.Value} Actual:{take.Count}"); @@ -189,8 +193,8 @@ public async Task ExecuteTableQueryTakeCount() { batch.DeleteEntity(te.PartitionKey, te.RowKey, te.ETag); } - await batch.SubmitBatchAsync().ConfigureAwait(false); - count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync().ConfigureAwait(false); + await batch.SubmitBatchAsync(); + count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync(); _output.WriteLine("Entities found after batch delete {0}", count); Assert.Equal(0, count); diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreSHA256Tests.cs b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreSHA256Tests.cs index 91d642b..e216d01 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreSHA256Tests.cs +++ b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreSHA256Tests.cs @@ -288,14 +288,14 @@ public async Task UserIdNotChangedIfImmutableIdSetUp() var userStore = GetImmutableUserIdStore(); var user = GenTestUser(); - await userStore.CreateAsync(user).ConfigureAwait(false); + await userStore.CreateAsync(user); var idBefore = user.Id; var pkBefore = user.PartitionKey; var rkBefore = user.RowKey; user.UserName += "changed"; - await userStore.UpdateAsync(user).ConfigureAwait(false); + await userStore.UpdateAsync(user); Assert.Equal(idBefore, user.Id); Assert.Equal(pkBefore, user.PartitionKey); diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreTests.cs b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreTests.cs index 7952ece..e4ad2c7 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreTests.cs +++ b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/UserStoreTests.cs @@ -287,14 +287,14 @@ public async Task UserIdNotChangedIfImmutableIdSetUp() var userStore = GetImmutableUserIdStore(); var user = GenTestUser(); - await userStore.CreateAsync(user).ConfigureAwait(false); + await userStore.CreateAsync(user); var idBefore = user.Id; var pkBefore = user.PartitionKey; var rkBefore = user.RowKey; user.UserName += "changed"; - await userStore.UpdateAsync(user).ConfigureAwait(false); + await userStore.UpdateAsync(user); Assert.Equal(idBefore, user.Id); Assert.Equal(pkBefore, user.PartitionKey); From e2b03bd94471cb5966b64578565d07265a79f428 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Mon, 6 Nov 2023 19:34:10 -0600 Subject: [PATCH 02/14] .net6 support --- ...o.AspNetCore.Identity.AzureTable.Model.csproj | 8 ++++++-- ...lCamino.AspNetCore.Identity.AzureTable.csproj | 10 ++++++++-- .../RoleStore.cs | 4 ++++ .../UserOnlyStore.cs | 16 ++++++++++++++++ .../UserStore.cs | 6 ++++++ .../ElCamino.Azure.Data.Tables.csproj | 2 +- 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj index e462201..ba7d0a9 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj @@ -5,7 +5,7 @@ Copyright © 2023 David Melendez, MIT License Azure Table Storage Provider for ASP.NET Identity Core Models David Melendez - netstandard2.0;net8.0 + netstandard2.0;net6.0;net8.0 11.0 ElCamino.AspNetCore.Identity.AzureTable.Model ../../tools/Key.snk @@ -35,10 +35,14 @@ - + + + + + diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj index 127a7b5..1b345c7 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj @@ -5,7 +5,7 @@ Copyright © 2023 David Melendez, MIT License Azure Table Storage Provider for ASP.NET Identity Core David Melendez - netstandard2.0;net8.0 + netstandard2.0;net6.0;net8.0 11.0 ElCamino.AspNetCore.Identity.AzureTable ../../tools/Key.snk @@ -37,12 +37,18 @@ - + + + + + + + diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs index 9a28cee..f445194 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs @@ -109,7 +109,9 @@ protected virtual void Dispose(bool disposing) } } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. public override async Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -118,7 +120,9 @@ protected virtual void Dispose(bool disposing) roleId.ToString(), cancellationToken: cancellationToken).ConfigureAwait(false); } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. public override async Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs index efaf651..10fb774 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs @@ -226,7 +226,9 @@ public override async Task DeleteAsync(TUser user, CancellationT } } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. protected override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -244,7 +246,9 @@ public override async Task DeleteAsync(TUser user, CancellationT return default; } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. protected override async Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -271,7 +275,9 @@ public override async Task DeleteAsync(TUser user, CancellationT /// /// /// +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. public override Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -282,7 +288,9 @@ public override async Task DeleteAsync(TUser user, CancellationT return GetUserFromIndexQueryAsync(GetUserIdByIndexQuery(partitionKey, rowKey), cancellationToken); } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. public override Task FindByEmailAsync(string plainEmail, CancellationToken cancellationToken = default) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -299,7 +307,9 @@ public async Task> FindAllByEmailAsync(string plainEmail, Can return users.Where(user => _keyHelper.GenerateRowKeyUserEmail(plainEmail) == _keyHelper.GenerateRowKeyUserEmail(user.Email)); } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. protected override Task FindUserAsync(TKey userId, CancellationToken cancellationToken) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -331,14 +341,18 @@ protected string GetUserIdsByIndexQuery(string partitionKey) return TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, partitionKey); } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return GetUserAsync(_keyHelper.GenerateRowKeyUserId(userId), cancellationToken); } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. public override async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -1077,7 +1091,9 @@ public override Task GetUserIdAsync(TUser user, CancellationToken cancel return Task.FromResult(user.Id is not null ? user.Id.ToString()??string.Empty : string.Empty); } +#pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. protected override async Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) +#pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { return await _userTable.GetEntityOrDefaultAsync(_keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)), _keyHelper.GenerateRowKeyIdentityUserToken(loginProvider, name), cancellationToken: cancellationToken).ConfigureAwait(false); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs index e99d83f..b5af051 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs @@ -14,7 +14,9 @@ namespace ElCamino.AspNetCore.Identity.AzureTable { public class UserStore : UserStore +#pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. , IUserStore +#pragma warning restore CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. where TUser : Model.IdentityUser, new() where TContext : IdentityCloudContext { @@ -28,7 +30,9 @@ public UserStore(TContext context, Model.IKeyHelper keyHelper) : base(context, k /// /// public class UserStore : UserStore +#pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. , IUserStore +#pragma warning restore CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. where TUser : Model.IdentityUser, new() where TRole : Model.IdentityRole, new() where TContext : IdentityCloudContext @@ -38,7 +42,9 @@ public UserStore(TContext context, Model.IKeyHelper keyHelper) : base(context, k public class UserStore : UserOnlyStore +#pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. , IUserRoleStore +#pragma warning restore CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. , IDisposable where TUser : Model.IdentityUser, new() where TRole : Model.IdentityRole, new() diff --git a/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj b/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj index e1768e6..5d7977b 100644 --- a/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj +++ b/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj @@ -5,7 +5,7 @@ Copyright © 2023 David Melendez, MIT License Azure Table Storage Extensions David Melendez - netstandard2.0;net8.0 + netstandard2.0;net6.0;net8.0 11.0 ElCamino.Azure.Data.Tables ../../tools/Key.snk From c80dd1908c78d1082328663dfd4c4aae2dc5146d Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 7 Nov 2023 10:52:45 -0600 Subject: [PATCH 03/14] ElCamino.Azure.Data.Tables.Tests project --- ElCamino.AspNetCore.Identity.AzureTable.sln | 10 +++- .../ElCamino.Azure.Data.Tables.Tests.csproj | 46 ++++++++++++++++++ .../TableClientTests.cs | 47 +++++++++---------- .../TableFixture.cs | 10 +--- .../config.json | 20 ++++++++ 5 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj rename tests/{ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions => ElCamino.Azure.Data.Tables.Tests}/TableClientTests.cs (82%) rename tests/{ElCamino.AspNetCore.Identity.AzureTable.Tests/Fixtures => ElCamino.Azure.Data.Tables.Tests}/TableFixture.cs (77%) create mode 100644 tests/ElCamino.Azure.Data.Tables.Tests/config.json diff --git a/ElCamino.AspNetCore.Identity.AzureTable.sln b/ElCamino.AspNetCore.Identity.AzureTable.sln index 8953596..31752a8 100644 --- a/ElCamino.AspNetCore.Identity.AzureTable.sln +++ b/ElCamino.AspNetCore.Identity.AzureTable.sln @@ -21,7 +21,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElCamino.AspNetCore.Identit EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElCamino.AspNetCore.Identity.AzureTable.Model", "src\ElCamino.AspNetCore.Identity.AzureTable.Model\ElCamino.AspNetCore.Identity.AzureTable.Model.csproj", "{C90FB0E3-5BE2-41E7-9DA3-9083C7700F91}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElCamino.Azure.Data.Tables", "src\ElCamino.Azure.Data.Tables\ElCamino.Azure.Data.Tables.csproj", "{DAF57676-8534-4A62-BC7B-317753F2A275}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElCamino.Azure.Data.Tables", "src\ElCamino.Azure.Data.Tables\ElCamino.Azure.Data.Tables.csproj", "{DAF57676-8534-4A62-BC7B-317753F2A275}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElCamino.Azure.Data.Tables.Tests", "tests\ElCamino.Azure.Data.Tables.Tests\ElCamino.Azure.Data.Tables.Tests.csproj", "{7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -66,6 +68,12 @@ Global {DAF57676-8534-4A62-BC7B-317753F2A275}.Release|Any CPU.Build.0 = Release|Any CPU {DAF57676-8534-4A62-BC7B-317753F2A275}.Signed|Any CPU.ActiveCfg = Debug|Any CPU {DAF57676-8534-4A62-BC7B-317753F2A275}.Signed|Any CPU.Build.0 = Debug|Any CPU + {7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}.Release|Any CPU.Build.0 = Release|Any CPU + {7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}.Signed|Any CPU.ActiveCfg = Debug|Any CPU + {7A71EC75-781D-42AB-BDF0-C2B6D4EF2B10}.Signed|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj b/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj new file mode 100644 index 0000000..e25a28c --- /dev/null +++ b/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj @@ -0,0 +1,46 @@ + + + + Testing + David Melendez + net6.0;net8.0 + 11.0 + ElCamino.Azure.Data.Tables.Tests + false + 8.0 + disable + Full + + + + + + PreserveNewest + PreserveNewest + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs b/tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs similarity index 82% rename from tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs rename to tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs index 7a802f6..a920d11 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/TableExtensions/TableClientTests.cs +++ b/tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs @@ -2,11 +2,10 @@ using System; using System.Threading.Tasks; using Azure.Data.Tables; -using ElCamino.Web.Identity.AzureTable.Tests.Fixtures; using Xunit; using Xunit.Abstractions; -namespace ElCamino.AspNetCore.Identity.AzureTable.Tests.TableExtensions +namespace ElCamino.Azure.Data.Tables.Tests { public class TableClientTests : IClassFixture { @@ -39,30 +38,30 @@ public async Task AddUpdateGetEntityWithHeaderValues() //Create Table await SetupTableAsync(); //Setup Entity - string key = "a-" + Guid.NewGuid().ToString("N"); + var key = "a-" + Guid.NewGuid().ToString("N"); _output.WriteLine("PartitionKey {0}", key); _output.WriteLine("RowKey {0}", key); - TableEntity entity = new TableEntity(key, key); + var entity = new TableEntity(key, key); Assert.Equal(default, entity.ETag); Assert.Equal(default, entity.Timestamp); //Execute Upsert - var addedEntity = await _tableClient.AddEntityWithHeaderValuesAsync(entity); + var addedEntity = await _tableClient.AddEntityWithHeaderValuesAsync(entity); //Assert Assert.NotEqual(default, addedEntity.ETag); Assert.NotEqual(default, addedEntity.Timestamp); //Modify update - string propertyName = "newProperty"; - TableEntity updateEntity = new TableEntity(key, key) + var propertyName = "newProperty"; + var updateEntity = new TableEntity(key, key) { { propertyName, propertyName } }; await Task.Delay(1000); //wait 1 second for timestamp - var updatedEntity = await _tableClient.UpdateEntityWithHeaderValuesAsync(updateEntity, addedEntity.ETag); + var updatedEntity = await _tableClient.UpdateEntityWithHeaderValuesAsync(updateEntity, addedEntity.ETag); //Assert Assert.NotEqual(default, updatedEntity.ETag); Assert.NotEqual(default, updatedEntity.Timestamp); @@ -83,30 +82,30 @@ public async Task UpsertGetEntityWithHeaderValues() //Create Table await SetupTableAsync(); //Setup Entity - string key = "a-" + Guid.NewGuid().ToString("N"); + var key = "a-" + Guid.NewGuid().ToString("N"); _output.WriteLine("PartitionKey {0}", key); _output.WriteLine("RowKey {0}", key); - TableEntity entity = new TableEntity(key, key); + var entity = new TableEntity(key, key); Assert.Equal(default, entity.ETag); Assert.Equal(default, entity.Timestamp); //Execute Upsert - var addedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(entity); + var addedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(entity); //Assert Assert.NotEqual(default, addedEntity.ETag); Assert.NotEqual(default, addedEntity.Timestamp); //Modify update - string propertyName = "newProperty"; - TableEntity updateEntity = new TableEntity(key, key) + var propertyName = "newProperty"; + var updateEntity = new TableEntity(key, key) { { propertyName, propertyName } }; await Task.Delay(1000); //wait 1 second for timestamp - var updatedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(updateEntity); + var updatedEntity = await _tableClient.UpsertEntityWithHeaderValuesAsync(updateEntity); //Assert Assert.NotEqual(default, updatedEntity.ETag); Assert.NotEqual(default, updatedEntity.Timestamp); @@ -127,22 +126,22 @@ public async Task ExecuteTableQueryTakeCount() //Create Table await SetupTableAsync(); //Setup Entity - string partitionKey = "b-" + Guid.NewGuid().ToString("N"); + var partitionKey = "b-" + Guid.NewGuid().ToString("N"); _output.WriteLine("PartitionKey {0}", partitionKey); - string filterByPartitionKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, partitionKey); - int count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync(); + var filterByPartitionKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, partitionKey); + var count = await _tableClient.QueryAsync(filter: filterByPartitionKey).CountAsync(); const int maxTestEntities = 1001; _output.WriteLine("Entities found {0}", count); - BatchOperationHelper batch = new BatchOperationHelper(_tableClient); + var batch = new BatchOperationHelper(_tableClient); if (count < maxTestEntities) - { - for (int i = 0; i < (maxTestEntities - count); i++) + { + for (var i = 0; i < maxTestEntities - count; i++) { - string rowKey = "b-" + Guid.NewGuid().ToString("N"); - TableEntity entity = new TableEntity(partitionKey, rowKey); + var rowKey = "b-" + Guid.NewGuid().ToString("N"); + var entity = new TableEntity(partitionKey, rowKey); batch.UpsertEntity(entity, TableUpdateMode.Replace); } await batch.SubmitBatchAsync(); @@ -152,7 +151,7 @@ public async Task ExecuteTableQueryTakeCount() Assert.True(count >= maxTestEntities); //Execute Query Take 1 - TableQuery tq = new TableQuery(); + var tq = new TableQuery(); tq.FilterString = filterByPartitionKey; tq.TakeCount = 1; var take = await _tableClient.ExecuteQueryAsync(tq).ToListAsync(); @@ -189,7 +188,7 @@ public async Task ExecuteTableQueryTakeCount() Assert.Equal(tq.TakeCount.Value, take.Count); _output.WriteLine($"Expected:{tq.TakeCount.Value} Actual:{take.Count}"); - foreach (TableEntity te in take) + foreach (var te in take) { batch.DeleteEntity(te.PartitionKey, te.RowKey, te.ETag); } diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/Fixtures/TableFixture.cs b/tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs similarity index 77% rename from tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/Fixtures/TableFixture.cs rename to tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs index 76020be..3bad6bb 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/Fixtures/TableFixture.cs +++ b/tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs @@ -2,17 +2,11 @@ using System; using Azure.Data.Tables; -using ElCamino.AspNetCore.Identity.AzureTable; -using ElCamino.AspNetCore.Identity.AzureTable.Model; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using IdentityRole = ElCamino.AspNetCore.Identity.AzureTable.Model.IdentityRole; -using IdentityUser = ElCamino.AspNetCore.Identity.AzureTable.Model.IdentityUser; -using Model = ElCamino.AspNetCore.Identity.AzureTable.Model; -namespace ElCamino.Web.Identity.AzureTable.Tests.Fixtures + +namespace ElCamino.Azure.Data.Tables.Tests { public class TableFixture : IDisposable { diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/config.json b/tests/ElCamino.Azure.Data.Tables.Tests/config.json new file mode 100644 index 0000000..63fab48 --- /dev/null +++ b/tests/ElCamino.Azure.Data.Tables.Tests/config.json @@ -0,0 +1,20 @@ +{ + "AppSettings": { + "SiteTitle": "SampleMvc2" + }, + "Data": { + "DefaultConnection": { + "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-SampleMvc2-444042c2-53c3-4c2b-ae65-5c753b0d2e4a;Trusted_Connection=True;MultipleActiveResultSets=true" + } + }, + "IdentityAzureTable": { + "identityConfiguration": { + "tablePrefix": "av62", + "indexTableName": "indexes", + "userTableName": "users", + "roleTableName": "roles", + "storageConnectionString": "UseDevelopmentStorage=true;", + "locationMode": "PrimaryOnly" + } + } +} From 1d71c063d6545abafebad378e730c13ba101c2ae Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 7 Nov 2023 10:58:53 -0600 Subject: [PATCH 04/14] test config cleanup --- .../TableFixture.cs | 2 +- .../config.json | 19 ++----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs b/tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs index 3bad6bb..315fe6b 100644 --- a/tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs +++ b/tests/ElCamino.Azure.Data.Tables.Tests/TableFixture.cs @@ -23,7 +23,7 @@ public TableFixture() _configuration = configuration.Build(); - _tableServiceClient = new TableServiceClient(_configuration["IdentityAzureTable:identityConfiguration:storageConnectionString"]); + _tableServiceClient = new TableServiceClient(_configuration["ElCamino:storageConnectionString"]); } protected virtual void Dispose(bool disposing) diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/config.json b/tests/ElCamino.Azure.Data.Tables.Tests/config.json index 63fab48..f4a93a3 100644 --- a/tests/ElCamino.Azure.Data.Tables.Tests/config.json +++ b/tests/ElCamino.Azure.Data.Tables.Tests/config.json @@ -1,20 +1,5 @@ { - "AppSettings": { - "SiteTitle": "SampleMvc2" - }, - "Data": { - "DefaultConnection": { - "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-SampleMvc2-444042c2-53c3-4c2b-ae65-5c753b0d2e4a;Trusted_Connection=True;MultipleActiveResultSets=true" - } - }, - "IdentityAzureTable": { - "identityConfiguration": { - "tablePrefix": "av62", - "indexTableName": "indexes", - "userTableName": "users", - "roleTableName": "roles", - "storageConnectionString": "UseDevelopmentStorage=true;", - "locationMode": "PrimaryOnly" - } + "ElCamino": { + "storageConnectionString": "UseDevelopmentStorage=true;" } } From a5572af3adb47b2007c7666507326a6851fa6e92 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 7 Nov 2023 11:48:10 -0600 Subject: [PATCH 05/14] null pattern matching --- .../Helpers/DefaultKeyHelper.cs | 4 +- .../Helpers/SHA256KeyHelper.cs | 4 +- .../IdentityAzureTableBuilderExtensions.cs | 4 +- .../UserOnlyStore.cs | 50 +++++++++---------- .../UserStore.cs | 18 +++---- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs index 4621c69..6bfc93b 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs @@ -12,7 +12,7 @@ public class DefaultKeyHelper : BaseKeyHelper public sealed override string? ConvertKeyToHash(string? input) { - if (input != null) + if (input is not null) { byte[] data = SHA1.HashData(Encoding.Unicode.GetBytes(input)); return FormatHashedData(data); @@ -22,7 +22,7 @@ public class DefaultKeyHelper : BaseKeyHelper #else public sealed override string? ConvertKeyToHash(string? input) { - if (input != null) + if (input is not null) { using SHA1 sha = SHA1.Create(); return GetHash(sha, input, Encoding.Unicode, 40); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs index 56dc109..4eedc64 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs @@ -15,7 +15,7 @@ public class SHA256KeyHelper : BaseKeyHelper #if NET6_0_OR_GREATER public sealed override string? ConvertKeyToHash(string? input) { - if (input != null) + if (input is not null) { byte[] data = SHA256.HashData(Encoding.UTF8.GetBytes(input)); return FormatHashedData(data); @@ -27,7 +27,7 @@ public class SHA256KeyHelper : BaseKeyHelper public sealed override string? ConvertKeyToHash(string? input) { - if (input != null) + if (input is not null) { using SHA256 sha = SHA256.Create(); return GetHash(sha, input, Encoding.UTF8, 64); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs index 241b745..8e264a7 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs @@ -47,14 +47,14 @@ public static IdentityBuilder AddAzureTableStores(this IdentityBuilder Type contextType = typeof(TContext); builder.Services.AddSingleton(contextType, contextType); - Type userStoreType = builder.RoleType != null ? typeof(UserStore<,,>).MakeGenericType(builder.UserType, builder.RoleType, contextType) + Type userStoreType = builder.RoleType is not null ? typeof(UserStore<,,>).MakeGenericType(builder.UserType, builder.RoleType, contextType) : typeof(UserOnlyStore<,>).MakeGenericType(builder.UserType, contextType); builder.Services.AddScoped( typeof(IUserStore<>).MakeGenericType(builder.UserType), userStoreType); - if (builder.RoleType != null) + if (builder.RoleType is not null) { Type roleStoreType = typeof(RoleStore<,>).MakeGenericType(builder.RoleType, contextType); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs index 10fb774..24a18cb 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs @@ -113,8 +113,8 @@ public override async Task AddClaimsAsync(TUser user, IEnumerable claims, public virtual async Task AddClaimAsync(TUser user, Claim claim) { ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (claim == null) throw new ArgumentNullException(nameof(claim)); + if (user is null) throw new ArgumentNullException(nameof(user)); + if (claim is null) throw new ArgumentNullException(nameof(claim)); List tasks = new List(2) { @@ -129,8 +129,8 @@ public override Task AddLoginAsync(TUser user, UserLoginInfo login, Cancellation { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (login == null) throw new ArgumentNullException(nameof(login)); + if (user is null) throw new ArgumentNullException(nameof(user)); + if (login is null) throw new ArgumentNullException(nameof(login)); TUserLogin item = CreateUserLogin(user, login); @@ -181,7 +181,7 @@ public override async Task DeleteAsync(TUser user, CancellationT { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); List tasks = new List(50); string userPartitionKey = _keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)); @@ -359,7 +359,7 @@ protected string GetUserIdsByIndexQuery(string partitionKey) TUser? user = await GetUserFromIndexQueryAsync(FindByUserNameIndexQuery(normalizedUserName), cancellationToken).ConfigureAwait(false); //Make sure the index lookup matches the user record. if (user != default(TUser) - && user.NormalizedUserName != null + && user.NormalizedUserName is not null && user.NormalizedUserName.Equals(normalizedUserName, StringComparison.OrdinalIgnoreCase)) { return user; @@ -377,7 +377,7 @@ public override async Task> GetClaimsAsync(TUser user, Cancellation { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); List rClaims = new List(); string partitionFilter = @@ -403,7 +403,7 @@ public override async Task> GetLoginsAsync(TUser user, Canc { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); List rLogins = new List(); @@ -476,7 +476,7 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< { string temp = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, tempUserId); - if (setFilterByUserId != null) + if (setFilterByUserId is not null) { temp = setFilterByUserId(tempUserId); } @@ -513,7 +513,7 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< { var userAgg = MapUserAggregate(s.Key, s); bool addUser = true; - if (whereClaim != null) + if (whereClaim is not null) { if (!userAgg.Claims.Any(whereClaim)) { @@ -701,14 +701,14 @@ async Task getUsers(IEnumerable ids, CancellationToken ct) public virtual async Task RemoveClaimAsync(TUser user, Claim claim) { ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (claim == null) throw new ArgumentNullException(nameof(claim)); + if (user is null) throw new ArgumentNullException(nameof(user)); + if (claim is null) throw new ArgumentNullException(nameof(claim)); // Claim ctor doesn't allow Claim.Value to be null. Need to allow string.empty. TUserClaim? local = await GetUserClaimAsync(user, claim).ConfigureAwait(false); - if (local != null) + if (local is not null) { TUserClaim deleteUserClaim = CreateUserClaim(user, claim); IdentityUserIndex deleteUserClaimIndex = CreateClaimIndex(_keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)), claim.Type, claim.Value); @@ -728,9 +728,9 @@ public override async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newC { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (claim == null) throw new ArgumentNullException(nameof(claim)); - if (newClaim == null) throw new ArgumentNullException(nameof(newClaim)); + if (user is null) throw new ArgumentNullException(nameof(user)); + if (claim is null) throw new ArgumentNullException(nameof(claim)); + if (newClaim is null) throw new ArgumentNullException(nameof(newClaim)); // Claim ctor doesn't allow Claim.Value to be null. Need to allow string.empty. BatchOperationHelper bHelper = new BatchOperationHelper(_userTable); @@ -738,7 +738,7 @@ public override async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newC TUserClaim? local = await GetUserClaimAsync(user, claim).ConfigureAwait(false); List tasks = new List(3); string userPartitionKey = _keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)); - if (local != null) + if (local is not null) { TUserClaim deleteClaim = CreateUserClaim(user, claim); bHelper.DeleteEntity(deleteClaim.PartitionKey!, deleteClaim.RowKey!, TableConstants.ETagWildcard); @@ -759,8 +759,8 @@ public override async Task RemoveClaimsAsync(TUser user, IEnumerable clai { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (claims == null) throw new ArgumentNullException(nameof(claims)); + if (user is null) throw new ArgumentNullException(nameof(user)); + if (claims is null) throw new ArgumentNullException(nameof(claims)); // Claim ctor doesn't allow Claim.Value to be null. Need to allow string.empty. List tasks = new List(); @@ -772,7 +772,7 @@ public override async Task RemoveClaimsAsync(TUser user, IEnumerable clai Claim? local = (from uc in userClaims where uc.Type == claim.Type && uc.Value == claim.Value select uc).FirstOrDefault(); - if (local != null) + if (local is not null) { var deleteUserClaim = CreateUserClaim(user, local); bHelper.DeleteEntity(deleteUserClaim.PartitionKey!, deleteUserClaim.RowKey!, TableConstants.ETagWildcard); @@ -808,11 +808,11 @@ public override async Task RemoveLoginAsync(TUser user, string loginProvider, st { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); string userPartitionKey = _keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)); TUserLogin? item = await FindUserLoginAsync(userPartitionKey, loginProvider, providerKey).ConfigureAwait(false); - if (item != null) + if (item is not null) { Model.IdentityUserIndex index = CreateLoginIndex(userPartitionKey, item.LoginProvider, item.ProviderKey); await Task.WhenAll(_indexTable.DeleteEntityAsync(index.PartitionKey, index.RowKey, TableConstants.ETagWildcard, cancellationToken), @@ -901,7 +901,7 @@ protected async Task DeleteUserNameIndexAsync(string? userId, string? userName) { string userPartitionKey = _keyHelper.GenerateRowKeyUserId(userId); var result = await _indexTable.GetEntityOrDefaultAsync(_keyHelper.GenerateRowKeyUserName(userName), userPartitionKey).ConfigureAwait(false); - if (result != null) + if (result is not null) { _ = await _indexTable.DeleteEntityAsync(result.PartitionKey, result.RowKey, TableConstants.ETagWildcard).ConfigureAwait(false); } @@ -933,7 +933,7 @@ public override async Task UpdateAsync(TUser user, CancellationT { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); string userPartitionKey = _keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)); List tasks = new List(3) { @@ -1048,7 +1048,7 @@ public override async Task> GetUsersForClaimAsync(Claim claim, Canc cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (claim == null) + if (claim is null) { throw new ArgumentNullException(nameof(claim)); } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs index b5af051..3b60350 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs @@ -77,7 +77,7 @@ public virtual async Task AddToRoleAsync(TUser user, string? roleName, Cancellat { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); if (string.IsNullOrWhiteSpace(roleName)) { throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, nameof(roleName)); @@ -237,7 +237,7 @@ public virtual async Task IsInRoleAsync(TUser user, string? roleName, Canc { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); if (string.IsNullOrWhiteSpace(roleName)) { throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, nameof(roleName)); @@ -271,7 +271,7 @@ public virtual async Task RemoveFromRoleAsync(TUser user, string? roleName, Canc { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, nameof(roleName)); @@ -280,7 +280,7 @@ public virtual async Task RemoveFromRoleAsync(TUser user, string? roleName, Canc { var item = await _userTable.GetEntityOrDefaultAsync(userPartitionKey, _keyHelper.GenerateRowKeyIdentityRole(roleName), cancellationToken: cancellationToken).ConfigureAwait(false); - if (item != null) + if (item is not null) { var deleteRoleIndex = CreateRoleIndex(userPartitionKey, roleName); await Task.WhenAll( @@ -324,7 +324,7 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< { string temp = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, tempUserId); - if (setFilterByUserId != null) + if (setFilterByUserId is not null) { temp = setFilterByUserId(tempUserId); } @@ -362,14 +362,14 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< { var userAgg = MapUserAggregate(s.Key, s); bool addUser = true; - if (whereClaim != null) + if (whereClaim is not null) { if (!userAgg.Claims.Any(whereClaim)) { addUser = false; } } - if (whereRole != null) + if (whereRole is not null) { if (!userAgg.Roles.Any(whereRole)) { @@ -409,7 +409,7 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< var vUser = userResults.Where(u => u.RowKey.Equals(userId) && u.PartitionKey.Equals(userId)).SingleOrDefault(); - if (vUser != null) + if (vUser is not null) { //User user = vUser.MapTableEntity(); @@ -451,7 +451,7 @@ public override async Task DeleteAsync(TUser user, CancellationT { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); + if (user is null) throw new ArgumentNullException(nameof(user)); List tasks = new List(50); string userPartitionKey = _keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)); From f7db45fa999763421822550a886f6a74a8e2a35d Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 7 Nov 2023 18:56:17 -0600 Subject: [PATCH 06/14] basetest class --- .../BaseTest.cs | 25 +++++++++++++++++++ .../TableClientTests.cs | 22 +++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 tests/ElCamino.Azure.Data.Tables.Tests/BaseTest.cs diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/BaseTest.cs b/tests/ElCamino.Azure.Data.Tables.Tests/BaseTest.cs new file mode 100644 index 0000000..fc5a32f --- /dev/null +++ b/tests/ElCamino.Azure.Data.Tables.Tests/BaseTest.cs @@ -0,0 +1,25 @@ +// MIT License Copyright 2020 (c) David Melendez. All rights reserved. See License.txt in the project root for license information. +using Azure.Data.Tables; +using Xunit; +using Xunit.Abstractions; + +namespace ElCamino.Azure.Data.Tables.Tests +{ + public class BaseTest : IClassFixture + { + protected readonly ITestOutputHelper _output; + protected readonly TableFixture _tableFixture; + protected readonly TableServiceClient _tableServiceClient; + protected const string TableName = "aatabletests"; + protected readonly TableClient _tableClient; + + public BaseTest(TableFixture tableFixture, ITestOutputHelper output) + { + _output = output; + _tableFixture = tableFixture; + _tableServiceClient = _tableFixture.TableService; + _tableClient = _tableServiceClient.GetTableClient(TableName); + } + + } +} diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs b/tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs index a920d11..7aae4fb 100644 --- a/tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs +++ b/tests/ElCamino.Azure.Data.Tables.Tests/TableClientTests.cs @@ -7,30 +7,20 @@ namespace ElCamino.Azure.Data.Tables.Tests { - public class TableClientTests : IClassFixture + public class TableClientTests : BaseTest { - private readonly ITestOutputHelper _output; - private readonly TableFixture _tableFixture; - private readonly TableServiceClient _tableServiceClient; - private readonly string _tableName = "aatabletests"; - private readonly TableClient _tableClient; - - public TableClientTests(TableFixture tableFixture, ITestOutputHelper output) - { - _output = output; - _tableFixture = tableFixture; - _tableServiceClient = _tableFixture.TableService; - _tableClient = _tableServiceClient.GetTableClient(_tableName); - } + public TableClientTests(TableFixture tableFixture, ITestOutputHelper output) : + base(tableFixture, output) + { } private async Task SetupTableAsync() { //Setup Create table await _tableClient.CreateIfNotExistsAsync(); - _output.WriteLine("Table created {0}", _tableName); + _output.WriteLine("Table created {0}", TableName); - } + } [Fact] public async Task AddUpdateGetEntityWithHeaderValues() From 978b47c98ba5323ccab59dab7aba5cc7de8adc03 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 7 Nov 2023 20:21:32 -0600 Subject: [PATCH 07/14] TableQuery Null Tests --- src/ElCamino.Azure.Data.Tables/TableQuery.cs | 4 +- .../TableQueryTests.cs | 142 ++++++++++++++++++ 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 tests/ElCamino.Azure.Data.Tables.Tests/TableQueryTests.cs diff --git a/src/ElCamino.Azure.Data.Tables/TableQuery.cs b/src/ElCamino.Azure.Data.Tables/TableQuery.cs index 092c6cf..08d0063 100644 --- a/src/ElCamino.Azure.Data.Tables/TableQuery.cs +++ b/src/ElCamino.Azure.Data.Tables/TableQuery.cs @@ -63,7 +63,7 @@ public static string GenerateFilterConditionForBool(string propertyName, string /// Generates a property filter condition string for a null boolean value. /// /// A string containing the name of the property to compare. - /// A string containing the comparison operator to use. or + /// A string containing the comparison operator to use. Is Null or Not Null /// A string containing the formatted filter condition. /// public static string GenerateFilterConditionForBoolNull(string propertyName, string operation) @@ -176,7 +176,7 @@ public static string GenerateFilterConditionForGuid(string propertyName, string /// Generates a property filter condition string for a null string value. /// /// A string containing the name of the property to compare. - /// A string containing the comparison operator to use. or + /// A string containing the comparison operator to use. Is Null or Not Null /// A string containing the formatted filter condition. /// public static string GenerateFilterConditionForStringNull(string propertyName, string operation) diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/TableQueryTests.cs b/tests/ElCamino.Azure.Data.Tables.Tests/TableQueryTests.cs new file mode 100644 index 0000000..64a0d22 --- /dev/null +++ b/tests/ElCamino.Azure.Data.Tables.Tests/TableQueryTests.cs @@ -0,0 +1,142 @@ +// MIT License Copyright 2020 (c) David Melendez. All rights reserved. See License.txt in the project root for license information. +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Azure.Data.Tables; +using Xunit; +using Xunit.Abstractions; + +namespace ElCamino.Azure.Data.Tables.Tests +{ + public class TableQueryTests : BaseTest + { + + public TableQueryTests(TableFixture tableFixture, ITestOutputHelper output) : + base(tableFixture, output) + { } + + private async Task SetupTableAsync() + { + //Setup Create table + await _tableClient.CreateIfNotExistsAsync(); + _output.WriteLine("Table created {0}", TableName); + + } + + [Fact] + public async Task QueryNullPropertyString() + { + string propertyName = "newProperty"; + + //Create Table + await SetupTableAsync(); + //Setup Entity + var key = "a-" + Guid.NewGuid().ToString("N"); + _output.WriteLine("PartitionKey {0}", key); + _output.WriteLine("RowKey {0}", key); + var entity = new TableEntity(key, key); + Assert.Equal(default, entity.ETag); + Assert.Equal(default, entity.Timestamp); + + //Execute Add + var addedEntity = await _tableClient.AddEntityWithHeaderValuesAsync(entity); + + //Execute isNull Query + string filterByPartitionKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, addedEntity.PartitionKey); + string filterByRowKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.RowKey), QueryComparisons.Equal, addedEntity.PartitionKey); + string filterByNullProperty = TableQuery.GenerateFilterConditionForStringNull(propertyName, QueryComparisons.Equal); + string filterByNotNullProperty = TableQuery.GenerateFilterConditionForStringNull(propertyName, QueryComparisons.NotEqual); + + string filterNull = TableQuery.CombineFilters( + TableQuery.CombineFilters(filterByPartitionKey, TableOperators.And, filterByRowKey), + TableOperators.And, + filterByNullProperty); + string filterNotNull = TableQuery.CombineFilters( + TableQuery.CombineFilters(filterByPartitionKey, TableOperators.And, filterByRowKey), + TableOperators.And, + filterByNotNullProperty); + + _output.WriteLine($"{nameof(filterNull)}:{filterNull}"); + _output.WriteLine($"{nameof(filterNotNull)}:{filterNotNull}"); + + //Assert + Assert.Equal(1, await _tableClient.QueryAsync(filter: filterNull).CountAsync()); + Assert.Equal(0, await _tableClient.QueryAsync(filter: filterNotNull).CountAsync()); + + //Modify update + var updateEntity = new TableEntity(key, key) + { + { propertyName, propertyName } + }; + + await Task.Delay(1000); //wait 1 second for timestamp + + _ = await _tableClient.UpdateEntityWithHeaderValuesAsync(updateEntity, addedEntity.ETag); + + //Assert + Assert.Equal(0, await _tableClient.QueryAsync(filter: filterNull).CountAsync()); + Assert.Equal(1, await _tableClient.QueryAsync(filter: filterNotNull).CountAsync()); + + + } + + [Fact] + public async Task QueryNullPropertyBool() + { + string propertyName = "newProperty"; + + //Create Table + await SetupTableAsync(); + //Setup Entity + var key = "a-" + Guid.NewGuid().ToString("N"); + _output.WriteLine("PartitionKey {0}", key); + _output.WriteLine("RowKey {0}", key); + var entity = new TableEntity(key, key); + Assert.Equal(default, entity.ETag); + Assert.Equal(default, entity.Timestamp); + + //Execute Add + var addedEntity = await _tableClient.AddEntityWithHeaderValuesAsync(entity); + + //Execute isNull Query + string filterByPartitionKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, addedEntity.PartitionKey); + string filterByRowKey = TableQuery.GenerateFilterCondition(nameof(TableEntity.RowKey), QueryComparisons.Equal, addedEntity.PartitionKey); + string filterByNullProperty = TableQuery.GenerateFilterConditionForBoolNull(propertyName, QueryComparisons.Equal); + string filterByNotNullProperty = TableQuery.GenerateFilterConditionForBoolNull(propertyName, QueryComparisons.NotEqual); + + string filterNull = TableQuery.CombineFilters( + TableQuery.CombineFilters(filterByPartitionKey, TableOperators.And, filterByRowKey), + TableOperators.And, + filterByNullProperty); + string filterNotNull = TableQuery.CombineFilters( + TableQuery.CombineFilters(filterByPartitionKey, TableOperators.And, filterByRowKey), + TableOperators.And, + filterByNotNullProperty); + + _output.WriteLine($"{nameof(filterNull)}:{filterNull}"); + _output.WriteLine($"{nameof(filterNotNull)}:{filterNotNull}"); + + //Assert + Assert.Equal(1, await _tableClient.QueryAsync(filter: filterNull).CountAsync()); + Assert.Equal(0, await _tableClient.QueryAsync(filter: filterNotNull).CountAsync()); + + //Modify update + var updateEntity = new TableEntity(key, key) + { + { propertyName, true } + }; + + await Task.Delay(1000); //wait 1 second for timestamp + + _ = await _tableClient.UpdateEntityWithHeaderValuesAsync(updateEntity, addedEntity.ETag); + + //Assert + Assert.Equal(0, await _tableClient.QueryAsync(filter: filterNull).CountAsync()); + Assert.Equal(1, await _tableClient.QueryAsync(filter: filterNotNull).CountAsync()); + + + } + + + } +} From 2308c68b8d0e26214eb36f24828e88d653764c66 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 7 Nov 2023 20:34:23 -0600 Subject: [PATCH 08/14] fix IDE0059 int parse --- src/ElCamino.Azure.Data.Tables/TableQuery.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ElCamino.Azure.Data.Tables/TableQuery.cs b/src/ElCamino.Azure.Data.Tables/TableQuery.cs index 08d0063..099cc8a 100644 --- a/src/ElCamino.Azure.Data.Tables/TableQuery.cs +++ b/src/ElCamino.Azure.Data.Tables/TableQuery.cs @@ -212,9 +212,7 @@ private static string GenerateFilterCondition(string propertyName, string operat } else if (edmType == EdmType.Double) { -#pragma warning disable IDE0059 // Unnecessary assignment of a value - bool isInteger = int.TryParse(givenValue, out int parsedInt); -#pragma warning restore IDE0059 // Unnecessary assignment of a value + bool isInteger = int.TryParse(givenValue, out _); valueOperand = isInteger ? string.Format(CultureInfo.InvariantCulture, "{0}.0", givenValue) : givenValue; } else if (edmType == EdmType.Int64) From e55d2c74d33afa38f16cbeb0353ceeb51ba55a03 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Thu, 9 Nov 2023 15:58:16 -0600 Subject: [PATCH 09/14] tighten up cloud context ctor and members --- .../IdentityCloudContext.cs | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityCloudContext.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityCloudContext.cs index e862864..e12f5c4 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityCloudContext.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityCloudContext.cs @@ -5,50 +5,64 @@ namespace ElCamino.AspNetCore.Identity.AzureTable { + /// + /// Identity table storage access + /// public class IdentityCloudContext { - protected TableServiceClient _client; - protected IdentityConfiguration _config; - protected TableClient _roleTable; - protected TableClient _indexTable; - protected TableClient _userTable; + private readonly TableServiceClient _client; + private readonly TableClient _roleTable; + private readonly TableClient _indexTable; + private readonly TableClient _userTable; -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + /// + /// Uses to configure identity table storage access + /// + /// Accepts public IdentityCloudContext(IdentityConfiguration config) -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(config, nameof(config)); +#else if (config is null) { throw new ArgumentNullException(nameof(config)); } - Initialize(config); - } - - protected virtual void Initialize(IdentityConfiguration config) - { - _config = config; - _client = new TableServiceClient(_config.StorageConnectionString); +#endif - _indexTable = _client.GetTableClient(FormatTableNameWithPrefix(!string.IsNullOrWhiteSpace(_config?.IndexTableName) ? _config!.IndexTableName! : TableConstants.TableNames.IndexTable)); - _roleTable = _client.GetTableClient(FormatTableNameWithPrefix(!string.IsNullOrWhiteSpace(_config?.RoleTableName) ? _config!.RoleTableName! : TableConstants.TableNames.RolesTable)); - _userTable = _client.GetTableClient(FormatTableNameWithPrefix(!string.IsNullOrWhiteSpace(_config?.UserTableName) ? _config!.UserTableName! : TableConstants.TableNames.UsersTable)); + _client = new TableServiceClient(config.StorageConnectionString); + _indexTable = _client.GetTableClient(FormatTableNameWithPrefix(config!.TablePrefix, !string.IsNullOrWhiteSpace(config!.IndexTableName) ? config!.IndexTableName! : TableConstants.TableNames.IndexTable)); + _roleTable = _client.GetTableClient(FormatTableNameWithPrefix(config!.TablePrefix, !string.IsNullOrWhiteSpace(config!.RoleTableName) ? config!.RoleTableName! : TableConstants.TableNames.RolesTable)); + _userTable = _client.GetTableClient(FormatTableNameWithPrefix(config!.TablePrefix, !string.IsNullOrWhiteSpace(config!.UserTableName) ? config!.UserTableName! : TableConstants.TableNames.UsersTable)); } - private string FormatTableNameWithPrefix(string baseTableName) + private static string FormatTableNameWithPrefix(string? tablePrefix, string baseTableName) { - if (!string.IsNullOrWhiteSpace(_config?.TablePrefix)) + if (!string.IsNullOrWhiteSpace(tablePrefix)) { - return string.Format("{0}{1}", _config!.TablePrefix, baseTableName); + return string.Format("{0}{1}", tablePrefix!, baseTableName); } return baseTableName; } + /// + /// Access Role table information + /// public TableClient RoleTable => _roleTable; + /// + /// Access User table information + /// public TableClient UserTable => _userTable; + /// + /// Access Index table information + /// public TableClient IndexTable => _indexTable; + /// + /// Table Service access + /// public TableServiceClient Client => _client; } } From 920342cf8248eff5ac818f827897a16df6d156a3 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Thu, 9 Nov 2023 17:12:35 -0600 Subject: [PATCH 10/14] comments for data tables extensions --- .../BatchOperationHelper.cs | 46 +++++++++++++++++++ src/ElCamino.Azure.Data.Tables/EdmType.cs | 34 +++++++++++++- .../ElCamino.Azure.Data.Tables.csproj | 1 + .../EntityMapExtensions.cs | 11 ++++- .../IAsyncEnumerableExtensions.cs | 41 +++++++++++++++++ .../TableClientExtensions.cs | 6 +++ .../TableOperators.cs | 4 ++ src/ElCamino.Azure.Data.Tables/TableQuery.cs | 13 +++++- 8 files changed, 151 insertions(+), 5 deletions(-) diff --git a/src/ElCamino.Azure.Data.Tables/BatchOperationHelper.cs b/src/ElCamino.Azure.Data.Tables/BatchOperationHelper.cs index ed9c5b2..9b04eb9 100644 --- a/src/ElCamino.Azure.Data.Tables/BatchOperationHelper.cs +++ b/src/ElCamino.Azure.Data.Tables/BatchOperationHelper.cs @@ -10,17 +10,29 @@ namespace Azure.Data.Tables /// public class BatchOperationHelper { + /// + /// Max entitie transactions by partition key, this is a table storage limit. + /// public const int MaxEntitiesPerBatch = 100; private readonly Dictionary> _batches = new(); private readonly TableClient _table; + /// + /// BatchOperationHelper constructor + /// + /// Table to target batch(es) of entity transactions public BatchOperationHelper(TableClient table) { _table = table; } + /// + /// Add entities with + /// + /// class, new() + /// Entities to submit for public virtual void AddEntities(IEnumerable entities) where T : class, ITableEntity, new() { foreach (T entity in entities) @@ -28,15 +40,33 @@ public BatchOperationHelper(TableClient table) AddEntity(entity); } } + + /// + /// Add entity with + /// + /// class, new() + /// Entity to submit for public virtual void AddEntity(T entity) where T : class, ITableEntity, new() { GetCurrent(entity.PartitionKey).Add(new TableTransactionAction(TableTransactionActionType.Add, entity)); } + + /// + /// Delete entity with by partitionKey + /// + /// PartitionKey of entity to delete + /// RowKey of entity to delete + /// of entity to delete public virtual void DeleteEntity(string partitionKey, string rowKey, ETag ifMatch = default) { GetCurrent(partitionKey).Add(new TableTransactionAction(TableTransactionActionType.Delete, new TableEntity(partitionKey, rowKey), ifMatch)); } + /// + /// Submits all transactions + /// + /// + /// of the calls public virtual async Task> SubmitBatchAsync(CancellationToken cancellationToken = default) { ConcurrentBag bag = new ConcurrentBag(); @@ -67,16 +97,32 @@ public virtual async Task> SubmitBatchAsync(CancellationTo return bag; } + /// + /// Update entities with or + /// + /// class, new() + /// Entity to submit for update + /// of entity to update + /// default, otherwise public virtual void UpdateEntity(T entity, ETag ifMatch, TableUpdateMode mode = TableUpdateMode.Merge) where T : class, ITableEntity, new() { GetCurrent(entity.PartitionKey).Add(new TableTransactionAction(mode == TableUpdateMode.Merge ? TableTransactionActionType.UpdateMerge : TableTransactionActionType.UpdateReplace, entity, ifMatch)); } + /// + /// Upsert entities with or + /// + /// class, new() + /// Entity to submit for upsert + /// default, otherwise public virtual void UpsertEntity(T entity, TableUpdateMode mode = TableUpdateMode.Merge) where T : class, ITableEntity, new() { GetCurrent(entity.PartitionKey).Add(new TableTransactionAction(mode == TableUpdateMode.Merge ? TableTransactionActionType.UpsertMerge : TableTransactionActionType.UpsertReplace, entity)); } + /// + /// Clears the non-submitted transaction dictionary, called after SubmitBatchAsync() by default + /// public void Clear() { _batches.Clear(); diff --git a/src/ElCamino.Azure.Data.Tables/EdmType.cs b/src/ElCamino.Azure.Data.Tables/EdmType.cs index f496238..4ca6596 100644 --- a/src/ElCamino.Azure.Data.Tables/EdmType.cs +++ b/src/ElCamino.Azure.Data.Tables/EdmType.cs @@ -23,14 +23,44 @@ namespace Azure.Data.Tables /// public enum EdmType { + /// + /// Represents fixed- or variable-length character data. + /// + String, + + /// + /// Represents fixed- or variable-length binary data. + /// Binary, + + /// + /// Represents the mathematical concept of binary-valued logic. + /// Boolean, + + /// + /// Represents date and time. + /// DateTime, + + /// + /// Represents a floating point number with 15 digits precision that can represent values with approximate range of +/- 2.23e -308 through +/- 1.79e +308. + /// Double, + + /// + /// Represents a 16-byte (128-bit) unique identifier value. + /// Guid, + + /// + /// Represents a signed 32-bit integer value. + /// Int32, - Int64, - String + /// + /// Represents a signed 64-bit integer value. + /// + Int64, } } diff --git a/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj b/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj index 5d7977b..c1242f8 100644 --- a/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj +++ b/src/ElCamino.Azure.Data.Tables/ElCamino.Azure.Data.Tables.csproj @@ -31,6 +31,7 @@ en-US enable enable + true diff --git a/src/ElCamino.Azure.Data.Tables/EntityMapExtensions.cs b/src/ElCamino.Azure.Data.Tables/EntityMapExtensions.cs index 713d17b..59208ff 100644 --- a/src/ElCamino.Azure.Data.Tables/EntityMapExtensions.cs +++ b/src/ElCamino.Azure.Data.Tables/EntityMapExtensions.cs @@ -6,6 +6,9 @@ namespace Azure.Data.Tables { + /// + /// Extensions for mapping to a class that implements + /// public static class EntityMapExtensions { private static readonly ConcurrentDictionary TypeProperties = new(); @@ -14,7 +17,7 @@ public static class EntityMapExtensions /// Threadsafe caching PropertyInfo[] because the check for the IgnoreDataMemberAttribute slows property lookup /// /// Type that implements ITableEntity and new() - /// + /// array private static PropertyInfo[] GetProperties(Type type) { if (type.FullName is not null) @@ -29,6 +32,12 @@ private static PropertyInfo[] GetProperties(Type type) .ToArray(); } + /// + /// Maps to a class that implements + /// + /// class, new() + /// + /// Returns T class, new() public static T MapTableEntity(this TableEntity dte) where T : ITableEntity, new() { T t = new(); diff --git a/src/ElCamino.Azure.Data.Tables/IAsyncEnumerableExtensions.cs b/src/ElCamino.Azure.Data.Tables/IAsyncEnumerableExtensions.cs index ecf4cec..0d62997 100644 --- a/src/ElCamino.Azure.Data.Tables/IAsyncEnumerableExtensions.cs +++ b/src/ElCamino.Azure.Data.Tables/IAsyncEnumerableExtensions.cs @@ -1,9 +1,21 @@ // MIT License Copyright 2020 (c) David Melendez. All rights reserved. See License.txt in the project root for license information. +using System.Collections.Generic; + namespace Azure.Data.Tables { + /// + /// Extensions for + /// public static class IAsyncEnumerableExtensions { + /// + /// FirstOrDefaultAsync{T} + /// + /// Generic type + /// + /// Optional, default + /// First in the enumerator or the default value public static async Task FirstOrDefaultAsync( this IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken = default) @@ -16,6 +28,13 @@ public static class IAsyncEnumerableExtensions return default; } + /// + /// ToListAsync{T} + /// + /// Generic type + /// + /// Optional, default + /// A List public static async Task> ToListAsync( this IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken = default) @@ -29,6 +48,14 @@ public static async Task> ToListAsync( return list; } + /// + /// ForEachAsync{T} + /// + /// Generic type + /// + /// Action for element T + /// Optional, default + /// A public static async Task ForEachAsync( this IAsyncEnumerable asyncEnumerable, Action action, @@ -41,6 +68,13 @@ public static async Task ForEachAsync( } } + /// + /// AnyAsync{T} + /// + /// Generic type + /// + /// Optional, default + /// A result if it exist in the enumerator public static async Task AnyAsync( this IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken = default) @@ -49,6 +83,13 @@ public static async Task AnyAsync( return await enumerator.MoveNextAsync().ConfigureAwait(false); } + /// + /// CountAsync{T} + /// + /// Generic type + /// + /// Optional, default + /// A count of the enumerator public static async Task CountAsync( this IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken = default) diff --git a/src/ElCamino.Azure.Data.Tables/TableClientExtensions.cs b/src/ElCamino.Azure.Data.Tables/TableClientExtensions.cs index 5e33693..96e2032 100644 --- a/src/ElCamino.Azure.Data.Tables/TableClientExtensions.cs +++ b/src/ElCamino.Azure.Data.Tables/TableClientExtensions.cs @@ -5,8 +5,14 @@ namespace Azure.Data.Tables { + /// + /// Extensions for the + /// public static class TableClientExtensions { + /// + /// Default Max Table Results Per Page + /// public const int MaxEntitiesPerPage = 1000; /// diff --git a/src/ElCamino.Azure.Data.Tables/TableOperators.cs b/src/ElCamino.Azure.Data.Tables/TableOperators.cs index 34d52f4..4b7cc7b 100644 --- a/src/ElCamino.Azure.Data.Tables/TableOperators.cs +++ b/src/ElCamino.Azure.Data.Tables/TableOperators.cs @@ -22,6 +22,10 @@ namespace Azure.Data.Tables /// public static class TableOperators { + /// + /// Represents the And operator. + /// + public const string And = "and"; /// diff --git a/src/ElCamino.Azure.Data.Tables/TableQuery.cs b/src/ElCamino.Azure.Data.Tables/TableQuery.cs index 099cc8a..f66bdb1 100644 --- a/src/ElCamino.Azure.Data.Tables/TableQuery.cs +++ b/src/ElCamino.Azure.Data.Tables/TableQuery.cs @@ -25,13 +25,22 @@ namespace Azure.Data.Tables /// public class TableQuery { - public const string OdataTrue = "true"; - public const string OdataFalse = "false"; + private const string OdataTrue = "true"; + private const string OdataFalse = "false"; + /// + /// Max take count for a given query + /// public int? TakeCount { get; set; } + /// + /// Defines Odata query string + /// public string? FilterString { get; set; } + /// + /// If defined, only returns the given column names in the query. null value returns all columns. + /// public List? SelectColumns { get; set; } = null; /// From 51cd5afe639c23bfc2b3c2a037f2a2c66dc9864e Mon Sep 17 00:00:00 2001 From: David Melendez Date: Fri, 10 Nov 2023 15:02:29 -0600 Subject: [PATCH 11/14] KeyHelper comments --- .../IKeyHelper.cs | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IKeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IKeyHelper.cs index bb6e763..0882d20 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IKeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IKeyHelper.cs @@ -2,62 +2,231 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// + /// Manages all table storage keys + /// public interface IKeyHelper { + /// + /// Key prefix for IdentityUserClaim + /// string PreFixIdentityUserClaim { get; } + + /// + /// Key prefix for IdentityUserClaimUpperBound + /// string PreFixIdentityUserClaimUpperBound { get; } + + /// + /// Key prefix for IdentityUserRole + /// string PreFixIdentityUserRole { get; } + + /// + /// Key prefix for IdentityUserRoleUpperBound + /// string PreFixIdentityUserRoleUpperBound { get; } + + /// + /// Key prefix for IdentityUserLogin + /// string PreFixIdentityUserLogin { get; } + + /// + /// Key prefix for IdentityUserLoginUpperBound + /// string PreFixIdentityUserLoginUpperBound { get; } + + /// + /// Key prefix for IdentityUserEmail + /// string PreFixIdentityUserEmail { get; } + + /// + /// Key prefix for IdentityUserToken + /// string PreFixIdentityUserToken { get; } + + /// + /// Key prefix for IdentityUserId + /// string PreFixIdentityUserId { get; } + + /// + /// Key prefix for IdentityUserIdUpperBound + /// string PreFixIdentityUserIdUpperBound { get; } + + /// + /// Key prefix for IdentityUserName + /// string PreFixIdentityUserName { get; } + /// + /// Key Formatter for IdentityUserClaim + /// string FormatterIdentityUserClaim { get; } + + /// + /// Key Formatter for IdentityUserRole + /// string FormatterIdentityUserRole { get; } + + /// + /// Key Formatter for IdentityUserLogin + /// string FormatterIdentityUserLogin { get; } + + /// + /// Key Formatter for IdentityUserEmail + /// string FormatterIdentityUserEmail { get; } + + /// + /// Key Formatter for IdentityUserToken + /// string FormatterIdentityUserToken { get; } + + /// + /// Key Formatter for IdentityUserId + /// string FormatterIdentityUserId { get; } + + /// + /// Key Formatter for IdentityUserName + /// string FormatterIdentityUserName { get; } + /// + /// Key prefix for IdentityRole + /// string PreFixIdentityRole { get; } + + /// + /// Key prefix for IdentityRoleUpperBound + /// string PreFixIdentityRoleUpperBound { get; } + + /// + /// Key prefix for IdentityRoleClaim + /// string PreFixIdentityRoleClaim { get; } + + /// + /// Key Formatter for IdentityRole + /// string FormatterIdentityRole { get; } + + /// + /// Key Formatter for IdentityRoleClaim + /// string FormatterIdentityRoleClaim { get; } + /// + /// Generate key for PartitionKeyIndexByLogin + /// + /// + /// + /// string GeneratePartitionKeyIndexByLogin(string plainLoginProvider, string plainProviderKey); + /// + /// Generate key for RowKeyUserEmail + /// + /// + /// string GenerateRowKeyUserEmail(string? plainEmail); + /// + /// Generate key for UserId + /// + /// string GenerateUserId(); + /// + /// Generate key for RowKeyUserName + /// + /// + /// string GenerateRowKeyUserName(string? plainUserName); + /// + /// Generate key for PartitionKeyUserName + /// + /// + /// string GeneratePartitionKeyUserName(string? plainUserName); + /// + /// Generate key for RowKeyUserId + /// + /// + /// string GenerateRowKeyUserId(string? plainUserId); + /// + /// Generate key for RowKeyIdentityUserRole + /// + /// + /// string GenerateRowKeyIdentityUserRole(string? plainRoleName); + /// + /// Generate key for RowKeyIdentityRole + /// + /// + /// string GenerateRowKeyIdentityRole(string? plainRoleName); + /// + /// Generate key for PartitionKeyIdentityRole + /// + /// + /// string GeneratePartitionKeyIdentityRole(string? plainRoleName); + /// + /// Generate key for RowKeyIdentityUserClaim + /// + /// + /// + /// string GenerateRowKeyIdentityUserClaim(string? claimType, string? claimValue); + /// + /// Generate key for RowKeyIdentityRoleClaim + /// + /// + /// + /// string GenerateRowKeyIdentityRoleClaim(string? claimType, string? claimValue); + /// + /// Generate key for RowKeyIdentityUserToken + /// + /// + /// + /// string GenerateRowKeyIdentityUserToken(string? loginProvider, string? tokenName); + /// + /// Generate key for RowKeyIdentityUserLogin + /// + /// + /// + /// string GenerateRowKeyIdentityUserLogin(string? loginProvider, string? providerKey); + /// + /// Parse PartitionKey From RowKey for IdentityRole + /// + /// + /// string ParsePartitionKeyIdentityRoleFromRowKey(string rowKey); + /// + /// Key Version + /// double KeyVersion { get; } } } From 2ef01ae4471fcca4d55229702059b770caec4c9f Mon Sep 17 00:00:00 2001 From: David Melendez Date: Fri, 10 Nov 2023 18:17:40 -0600 Subject: [PATCH 12/14] more comments --- ...mino.AspNetCore.Identity.AzureTable.csproj | 1 + .../Helpers/BaseKeyHelper.cs | 49 ++++++ .../Helpers/DefaultKeyHelper.cs | 6 +- .../Helpers/SHA256KeyHelper.cs | 5 +- .../IdentityAzureTableBuilderExtensions.cs | 3 + .../RoleStore.cs | 29 +++- .../TableConstants.cs | 112 ++++++++++++ .../UserOnlyStore.cs | 162 +++++++++++++++++- .../UserStore.cs | 39 +++++ 9 files changed, 394 insertions(+), 12 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj index 1b345c7..c73f1a4 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj @@ -31,6 +31,7 @@ README.md en-US enable + true diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs index cde98d9..0c0ad49 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/BaseKeyHelper.cs @@ -9,54 +9,79 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Helpers { + /// public abstract class BaseKeyHelper : IKeyHelper { + /// public virtual string PreFixIdentityUserClaim => TableConstants.RowKeyConstants.PreFixIdentityUserClaim; + /// public virtual string PreFixIdentityUserClaimUpperBound => TableConstants.RowKeyConstants.PreFixIdentityUserClaimUpperBound; + /// public virtual string PreFixIdentityUserRole => TableConstants.RowKeyConstants.PreFixIdentityUserRole; + /// public virtual string PreFixIdentityUserRoleUpperBound => TableConstants.RowKeyConstants.PreFixIdentityUserRoleUpperBound; + /// public virtual string PreFixIdentityUserLogin => TableConstants.RowKeyConstants.PreFixIdentityUserLogin; + /// public virtual string PreFixIdentityUserLoginUpperBound => TableConstants.RowKeyConstants.PreFixIdentityUserLoginUpperBound; + /// public virtual string PreFixIdentityUserEmail => TableConstants.RowKeyConstants.PreFixIdentityUserEmail; + /// public virtual string PreFixIdentityUserToken => TableConstants.RowKeyConstants.PreFixIdentityUserToken; + /// public virtual string PreFixIdentityUserId => TableConstants.RowKeyConstants.PreFixIdentityUserId; + /// public virtual string PreFixIdentityUserIdUpperBound => TableConstants.RowKeyConstants.PreFixIdentityUserIdUpperBound; + /// public virtual string PreFixIdentityUserName => TableConstants.RowKeyConstants.PreFixIdentityUserName; + /// public virtual string FormatterIdentityUserClaim => TableConstants.RowKeyConstants.FormatterIdentityUserClaim; + /// public virtual string FormatterIdentityUserRole => TableConstants.RowKeyConstants.FormatterIdentityUserRole; + /// public virtual string FormatterIdentityUserLogin => TableConstants.RowKeyConstants.FormatterIdentityUserLogin; + /// public virtual string FormatterIdentityUserEmail => TableConstants.RowKeyConstants.FormatterIdentityUserEmail; + /// public virtual string FormatterIdentityUserToken => TableConstants.RowKeyConstants.FormatterIdentityUserToken; + /// public virtual string FormatterIdentityUserId => TableConstants.RowKeyConstants.FormatterIdentityUserId; + /// public virtual string FormatterIdentityUserName => TableConstants.RowKeyConstants.FormatterIdentityUserName; + /// public virtual string PreFixIdentityRole => TableConstants.RowKeyConstants.PreFixIdentityRole; + /// public virtual string PreFixIdentityRoleUpperBound => TableConstants.RowKeyConstants.PreFixIdentityRoleUpperBound; + /// public virtual string PreFixIdentityRoleClaim => TableConstants.RowKeyConstants.PreFixIdentityRoleClaim; + /// public virtual string FormatterIdentityRole => TableConstants.RowKeyConstants.FormatterIdentityRole; + /// public virtual string FormatterIdentityRoleClaim => TableConstants.RowKeyConstants.FormatterIdentityRoleClaim; + /// public virtual string GeneratePartitionKeyIndexByLogin(string plainLoginProvider, string plainProviderKey) { string strTemp = string.Format("{0}_{1}", plainLoginProvider?.ToUpper(), plainProviderKey?.ToUpper()); @@ -64,52 +89,61 @@ public virtual string GeneratePartitionKeyIndexByLogin(string plainLoginProvider return string.Format(FormatterIdentityUserLogin, hash); } + /// public virtual string GenerateRowKeyUserEmail(string? plainEmail) { string? hash = ConvertKeyToHash(plainEmail?.ToUpper()); return string.Format(FormatterIdentityUserEmail, hash); } + /// public virtual string GenerateUserId() { return Guid.NewGuid().ToString("N"); } + /// public virtual string GenerateRowKeyUserId(string? plainUserId) { string? hash = ConvertKeyToHash(plainUserId?.ToUpper()); return string.Format(FormatterIdentityUserId, hash); } + /// public virtual string GenerateRowKeyUserName(string? plainUserName) { return GeneratePartitionKeyUserName(plainUserName); } + /// public virtual string GeneratePartitionKeyUserName(string? plainUserName) { string? hash = ConvertKeyToHash(plainUserName?.ToUpper()); return string.Format(FormatterIdentityUserName, hash); } + /// public virtual string GenerateRowKeyIdentityUserRole(string? plainRoleName) { string? hash = ConvertKeyToHash(plainRoleName?.ToUpper()); return string.Format(FormatterIdentityUserRole, hash); } + /// public virtual string GenerateRowKeyIdentityRole(string? plainRoleName) { string? hash = ConvertKeyToHash(plainRoleName?.ToUpper()); return string.Format(FormatterIdentityRole, hash); } + /// public virtual string GeneratePartitionKeyIdentityRole(string? plainRoleName) { string? hash = ConvertKeyToHash(plainRoleName?.ToUpper()); return hash?.Substring(startIndex: 0, length: 1)??string.Empty; } + /// public virtual string GenerateRowKeyIdentityUserClaim(string? claimType, string? claimValue) { string strTemp = string.Format("{0}_{1}", claimType?.ToUpper(), claimValue?.ToUpper()); @@ -117,6 +151,7 @@ public virtual string GenerateRowKeyIdentityUserClaim(string? claimType, string? return string.Format(FormatterIdentityUserClaim, hash); } + /// public virtual string GenerateRowKeyIdentityRoleClaim(string? claimType, string? claimValue) { string strTemp = string.Format("{0}_{1}", claimType?.ToUpper(), claimValue?.ToUpper()); @@ -124,6 +159,7 @@ public virtual string GenerateRowKeyIdentityRoleClaim(string? claimType, string? return string.Format(FormatterIdentityRoleClaim, hash); } + /// public virtual string GenerateRowKeyIdentityUserToken(string? loginProvider, string? name) { string strTemp = string.Format("{0}_{1}", loginProvider?.ToUpper(), name?.ToUpper()); @@ -131,11 +167,13 @@ public virtual string GenerateRowKeyIdentityUserToken(string? loginProvider, str return string.Format(FormatterIdentityUserToken, hash); } + /// public virtual string ParsePartitionKeyIdentityRoleFromRowKey(string rowKey) { return rowKey.Substring(PreFixIdentityRole.Length, 1); } + /// public virtual string GenerateRowKeyIdentityUserLogin(string? loginProvider, string? providerKey) { string strTemp = string.Format("{0}_{1}", loginProvider?.ToUpper(), providerKey?.ToUpper()); @@ -143,8 +181,14 @@ public virtual string GenerateRowKeyIdentityUserLogin(string? loginProvider, str return string.Format(FormatterIdentityUserLogin, hash); } + /// public double KeyVersion => 8.0; + /// + /// Convert Key Data to a hex hash string + /// + /// Plain text input + /// Returns a hex string public abstract string? ConvertKeyToHash(string? input); /// @@ -178,6 +222,11 @@ protected virtual string GetHash(HashAlgorithm shaHash, string input, Encoding e } #if NET6_0_OR_GREATER + /// + /// Format byte array to hex string + /// + /// byte array to format + /// protected static string FormatHashedData(byte[] hashedData) { // Convert the input string to a byte array and compute the hash. diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs index 6bfc93b..84c5b23 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/DefaultKeyHelper.cs @@ -6,10 +6,13 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Helpers { + /// + /// Default Key Helpers users SHA1 + /// public class DefaultKeyHelper : BaseKeyHelper { #if NET6_0_OR_GREATER - + /// public sealed override string? ConvertKeyToHash(string? input) { if (input is not null) @@ -20,6 +23,7 @@ public class DefaultKeyHelper : BaseKeyHelper return null; } #else + /// public sealed override string? ConvertKeyToHash(string? input) { if (input is not null) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs index 4eedc64..d150a77 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/Helpers/SHA256KeyHelper.cs @@ -13,6 +13,7 @@ public class SHA256KeyHelper : BaseKeyHelper { #if NET6_0_OR_GREATER + /// public sealed override string? ConvertKeyToHash(string? input) { if (input is not null) @@ -24,7 +25,7 @@ public class SHA256KeyHelper : BaseKeyHelper } #else - + /// public sealed override string? ConvertKeyToHash(string? input) { if (input is not null) @@ -35,7 +36,7 @@ public class SHA256KeyHelper : BaseKeyHelper return null; } #endif - + /// public override string GenerateRowKeyUserId(string? plainUserId) { return string.Format(FormatterIdentityUserId, plainUserId); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs index 8e264a7..491727b 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/IdentityAzureTableBuilderExtensions.cs @@ -8,6 +8,9 @@ namespace Microsoft.Extensions.DependencyInjection { + /// + /// IdentityBuilder extensions for di + /// public static class IdentityAzureTableBuilderExtensions { /// diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs index f445194..ac08865 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/RoleStore.cs @@ -12,29 +12,35 @@ namespace ElCamino.AspNetCore.Identity.AzureTable { + /// public class RoleStore : RoleStore where TRole : Model.IdentityRole, new() { + /// public RoleStore(IdentityCloudContext context, IKeyHelper keyHelper) : base(context, keyHelper) { } } + /// public class RoleStore : RoleStore where TRole : Model.IdentityRole, new() where TContext : IdentityCloudContext { + /// public RoleStore(TContext context, IKeyHelper keyHelper) : base(context, keyHelper) { } //Fixing code analysis issue CA1063 + /// protected override void Dispose(bool disposing) { base.Dispose(disposing); } } + /// public class RoleStore : RoleStoreBase where TRole : Model.IdentityRole, new() @@ -47,9 +53,13 @@ public class RoleStore : private readonly TableClient _roleTable; private readonly TContext _context; private readonly IdentityErrorDescriber _errorDescriber = new(); + /// + /// Key Helper + /// protected readonly IKeyHelper _keyHelper; private readonly string FilterString; + /// public RoleStore(TContext context, IKeyHelper keyHelper) : base(new IdentityErrorDescriber()) { _context = context ?? throw new ArgumentNullException(nameof(context)); @@ -63,8 +73,13 @@ public RoleStore(TContext context, IKeyHelper keyHelper) : base(new IdentityErro } + /// + /// Create table is not exists + /// + /// Task public Task CreateTableIfNotExistsAsync() => Context.RoleTable.CreateIfNotExistsAsync(); + /// public override async Task CreateAsync(TRole role, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -81,6 +96,7 @@ public override async Task CreateAsync(TRole role, CancellationT return IdentityResult.Success; } + /// public override async Task DeleteAsync(TRole role, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -95,12 +111,14 @@ public override async Task DeleteAsync(TRole role, CancellationT return IdentityResult.Success; } + /// public new void Dispose() { base.Dispose(); Dispose(true); } + /// protected virtual void Dispose(bool disposing) { if (!_disposed && disposing) @@ -110,6 +128,7 @@ protected virtual void Dispose(bool disposing) } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// public override async Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -121,6 +140,7 @@ protected virtual void Dispose(bool disposing) } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// public override async Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -131,7 +151,7 @@ protected virtual void Dispose(bool disposing) _keyHelper.GenerateRowKeyIdentityRole(roleName), cancellationToken: cancellationToken).ConfigureAwait(false); } - + /// public override async Task UpdateAsync(TRole role, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -172,7 +192,7 @@ public override async Task UpdateAsync(TRole role, CancellationT return IdentityResult.Failed(_errorDescriber.InvalidRoleName(role.Name)); } - + /// public override async Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -200,6 +220,7 @@ public override async Task> GetClaimsAsync(TRole role, Cancellation .ToList() as IList; } + /// public override async Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -227,6 +248,7 @@ public override async Task AddClaimAsync(TRole role, Claim claim, CancellationTo _ = await _roleTable.AddEntityAsync(item, cancellationToken).ConfigureAwait(false); } + /// public override async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -259,6 +281,9 @@ public override async Task RemoveClaimAsync(TRole role, Claim claim, Cancellatio _ = await _roleTable.DeleteEntityAsync(item.PartitionKey, item.RowKey, TableConstants.ETagWildcard, cancellationToken).ConfigureAwait(false); } + /// + /// Table Storage context access + /// public TContext Context => _context; /// diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/TableConstants.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/TableConstants.cs index 010d55f..9612f22 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/TableConstants.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/TableConstants.cs @@ -4,43 +4,155 @@ namespace ElCamino.AspNetCore.Identity.AzureTable { + /// + /// Default table constants + /// public static class TableConstants { + /// + /// ETag.All + /// public static readonly ETag ETagWildcard = ETag.All; + /// + /// Default Table Names + /// public static class TableNames { + /// + /// Defaut Roles Table Name + /// public const string RolesTable = "AspNetRoles"; + + /// + /// Defaut Users Table Name + /// public const string UsersTable = "AspNetUsers"; + + /// + /// Defaut Index Table Name + /// public const string IndexTable = "AspNetIndex"; } + /// + /// Default Key Constants + /// public static class RowKeyConstants { + /// + /// Default Key PreFix for IdentityUserClaim + /// public const string PreFixIdentityUserClaim = "C_"; + + /// + /// Default Key PreFix for IdentityUserClaimUpperBound + /// public const string PreFixIdentityUserClaimUpperBound = "D_"; + + /// + /// Default Key PreFix for IdentityUserRole + /// public const string PreFixIdentityUserRole = "R_"; + + /// + /// Default Key PreFix for IdentityUserRoleUpperBound + /// public const string PreFixIdentityUserRoleUpperBound = "S_"; + + /// + /// Default Key PreFix for IdentityUserLogin + /// public const string PreFixIdentityUserLogin = "L_"; + + /// + /// Default Key PreFix for IdentityUserLoginUpperBound + /// public const string PreFixIdentityUserLoginUpperBound = "M_"; + + /// + /// Default Key PreFix for IdentityUserEmail + /// public const string PreFixIdentityUserEmail = "E_"; + + /// + /// Default Key PreFix for IdentityUserToken + /// public const string PreFixIdentityUserToken = "T_"; + + /// + /// Default Key PreFix for IdentityUserId + /// public const string PreFixIdentityUserId = "U_"; + + /// + /// Default Key PreFix for IdentityUserIdUpperBound + /// public const string PreFixIdentityUserIdUpperBound = "V_"; + + /// + /// Default Key PreFix for IdentityUserName + /// public const string PreFixIdentityUserName = "N_"; + /// + /// Default Key Formatter for IdentityUserClaim + /// public const string FormatterIdentityUserClaim = PreFixIdentityUserClaim + "{0}"; + + /// + /// Default Key Formatter for IdentityUserRole + /// public const string FormatterIdentityUserRole = PreFixIdentityUserRole + "{0}"; + + /// + /// Default Key Formatter for IdentityUserLogin + /// public const string FormatterIdentityUserLogin = PreFixIdentityUserLogin + "{0}"; + + /// + /// Default Key Formatter for IdentityUserEmail + /// public const string FormatterIdentityUserEmail = PreFixIdentityUserEmail + "{0}"; + + /// + /// Default Key Formatter for IdentityUserToken + /// public const string FormatterIdentityUserToken = PreFixIdentityUserToken + "{0}"; + + /// + /// Default Key Formatter for IdentityUserId + /// public const string FormatterIdentityUserId = PreFixIdentityUserId + "{0}"; + + /// + /// Default Key Formatter for IdentityUserName + /// public const string FormatterIdentityUserName = PreFixIdentityUserName + "{0}"; + /// + /// Default Key PreFix for IdentityRole + /// public const string PreFixIdentityRole = "R_"; + + /// + /// Default Key PreFix for IdentityRoleUpperBound + /// public const string PreFixIdentityRoleUpperBound = "S_"; + + /// + /// Default Key PreFix for IdentityRoleClaim + /// public const string PreFixIdentityRoleClaim = "C_"; + + /// + /// Default Key Formatter for IdentityRole + /// public const string FormatterIdentityRole = PreFixIdentityRole + "{0}"; + + /// + /// Default Key Formatter for IdentityRoleClaim + /// public const string FormatterIdentityRoleClaim = PreFixIdentityRoleClaim + "{0}"; } } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs index 24a18cb..b1e4bdb 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/UserOnlyStore.cs @@ -15,21 +15,26 @@ namespace ElCamino.AspNetCore.Identity.AzureTable { + /// public class UserOnlyStore : UserOnlyStore where TUser : Model.IdentityUser, new() { + /// public UserOnlyStore(IdentityCloudContext context, IKeyHelper keyHelper) : base(context, keyHelper) { } } + /// public class UserOnlyStore : UserOnlyStore where TUser : Model.IdentityUser, new() where TContext : IdentityCloudContext { + /// public UserOnlyStore(TContext context, IKeyHelper keyHelper) : base(context, keyHelper) { } } + /// public class UserOnlyStore : UserStoreBase , IDisposable @@ -40,16 +45,27 @@ public class UserOnlyStore, new() where TContext : IdentityCloudContext { - protected bool _disposed; - + /// + /// User Table + /// protected readonly TableClient _userTable; + + /// + /// Index Table + /// protected readonly TableClient _indexTable; + + /// + /// Current Key Helper + /// protected readonly IKeyHelper _keyHelper; + private readonly TContext _context; private readonly string FilterString; private static readonly List IndexUserIdSelectColumns = new() { nameof(IdentityUserIndex.Id) }; + /// public UserOnlyStore(TContext context, IKeyHelper keyHelper) : base(new IdentityErrorDescriber()) { _context = context ?? throw new ArgumentNullException(nameof(context)); @@ -79,6 +95,10 @@ public override IQueryable Users } } + /// + /// Create tables for users and indexes + /// + /// public virtual Task CreateTablesIfNotExistsAsync() { Task[] tasks = new Task[] @@ -89,6 +109,7 @@ public virtual Task CreateTablesIfNotExistsAsync() return Task.WhenAll(tasks); } + /// public override async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -110,6 +131,7 @@ public override async Task AddClaimsAsync(TUser user, IEnumerable claims, await Task.WhenAll(tasks).ConfigureAwait(false); } + /// public virtual async Task AddClaimAsync(TUser user, Claim claim) { ThrowIfDisposed(); @@ -125,6 +147,7 @@ public virtual async Task AddClaimAsync(TUser user, Claim claim) await Task.WhenAll(tasks).ConfigureAwait(false); } + /// public override Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -140,6 +163,7 @@ public override Task AddLoginAsync(TUser user, UserLoginInfo login, Cancellation , _indexTable.UpsertEntityAsync(index, mode: TableUpdateMode.Replace, cancellationToken: cancellationToken)); } + /// public override async Task CreateAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -177,6 +201,7 @@ public override async Task CreateAsync(TUser user, CancellationT } } + /// public override async Task DeleteAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -227,6 +252,7 @@ public override async Task DeleteAsync(TUser user, CancellationT } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// protected override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -235,6 +261,7 @@ public override async Task DeleteAsync(TUser user, CancellationT return FindUserLoginAsync(ConvertIdToString(userId), loginProvider, providerKey); } + /// protected async Task FindUserLoginAsync(string? userId, string loginProvider, string providerKey) { if (userId is not null) @@ -247,6 +274,7 @@ public override async Task DeleteAsync(TUser user, CancellationT } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// protected override async Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -289,6 +317,7 @@ public override async Task DeleteAsync(TUser user, CancellationT } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// public override Task FindByEmailAsync(string plainEmail, CancellationToken cancellationToken = default) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -297,8 +326,7 @@ public override async Task DeleteAsync(TUser user, CancellationT return GetUserFromIndexQueryAsync(FindByEmailIndexQuery(plainEmail), cancellationToken); } - - + /// public async Task> FindAllByEmailAsync(string plainEmail, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -308,6 +336,7 @@ public async Task> FindAllByEmailAsync(string plainEmail, Can } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// protected override Task FindUserAsync(TKey userId, CancellationToken cancellationToken) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -316,18 +345,44 @@ public async Task> FindAllByEmailAsync(string plainEmail, Can return GetUserAsync(ConvertIdToString(userId), cancellationToken); } + /// + /// GetUserByRoleIndexQuery + /// + /// + /// Odata filter query protected string GetUserByRoleIndexQuery(string plainRoleName) => GetUserIdsByIndexQuery(_keyHelper.GenerateRowKeyIdentityUserRole(plainRoleName)); + /// + /// GetUserByClaimIndexQuery + /// + /// + /// Odata filter query protected string GetUserByClaimIndexQuery(Claim claim) => GetUserIdsByIndexQuery(_keyHelper.GenerateRowKeyIdentityUserClaim(claim.Type, claim.Value)); + /// + /// FindByEmailIndexQuery + /// + /// + /// Odata filter query protected string FindByEmailIndexQuery(string plainEmail) => GetUserIdsByIndexQuery(_keyHelper.GenerateRowKeyUserEmail(plainEmail)); + /// + /// FindByUserNameIndexQuery + /// + /// + /// Odata filter query protected string FindByUserNameIndexQuery(string userName) => GetUserIdsByIndexQuery(_keyHelper.GenerateRowKeyUserName(userName)); + /// + /// GetUserIdByIndexQuery + /// + /// + /// + /// Odata filter query protected string GetUserIdByIndexQuery(string partitionkey, string rowkey) { return TableQuery.CombineFilters( @@ -336,12 +391,18 @@ protected string GetUserIdByIndexQuery(string partitionkey, string rowkey) TableQuery.GenerateFilterCondition(nameof(TableEntity.RowKey), QueryComparisons.Equal, rowkey)); } + /// + /// GetUserIdsByIndexQuery + /// + /// + /// Odata filter query protected string GetUserIdsByIndexQuery(string partitionKey) { return TableQuery.GenerateFilterCondition(nameof(TableEntity.PartitionKey), QueryComparisons.Equal, partitionKey); } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -351,6 +412,7 @@ protected string GetUserIdsByIndexQuery(string partitionKey) } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// public override async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -367,12 +429,14 @@ protected string GetUserIdsByIndexQuery(string partitionKey) return default; } + /// protected async Task GetUserClaimAsync(TUser user, Claim claim) { return await _userTable.GetEntityOrDefaultAsync(_keyHelper.GenerateRowKeyUserId(ConvertIdToString(user.Id)), _keyHelper.GenerateRowKeyIdentityUserClaim(claim.Type, claim.Value)).ConfigureAwait(false); } + /// public override async Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -399,6 +463,7 @@ public override async Task> GetClaimsAsync(TUser user, Cancellation return rClaims; } + /// public override async Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -422,6 +487,7 @@ public override async Task> GetLoginsAsync(TUser user, Canc return rLogins; } + /// protected virtual async Task GetUserAsync(string? userId, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -437,6 +503,7 @@ public override async Task> GetLoginsAsync(TUser user, Canc /// Retrieves User rows by partitionkey UserId /// /// Must be formatted as UserId PartitionKey + /// /// protected IAsyncEnumerable GetUserAggregateQueryAsync(string userIdPartitionKey, CancellationToken cancellationToken = default) { @@ -447,6 +514,14 @@ protected IAsyncEnumerable GetUserAggregateQueryAsync(string userId return _userTable.QueryAsync(filter: filterString, cancellationToken: cancellationToken); } + /// + /// Used for complex queries across userids with a filter + /// + /// + /// + /// + /// + /// protected async Task> GetUserAggregateQueryAsync(IEnumerable userIds, Func? setFilterByUserId = null, Func? whereClaim = null, @@ -536,6 +611,12 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< return bag; } + /// + /// Gets by userids + /// + /// + /// + /// by userids protected virtual async Task> GetUserQueryAsync(IEnumerable userIds, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -597,6 +678,12 @@ protected virtual async Task> GetUserQueryAsync(IEnumerable + /// Maps table entities from a query result to strongly typed identity entities + /// + /// + /// + /// Identity Entities protected (TUser? User, IEnumerable Claims, IEnumerable Logins, @@ -643,6 +730,12 @@ protected virtual async Task> GetUserQueryAsync(IEnumerable + /// Executes an index query and then gets a TUser by userId + /// + /// Odata index filter query + /// + /// Nullable{TUser} protected virtual async Task GetUserFromIndexQueryAsync(string indexQuery, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -657,6 +750,13 @@ protected virtual async Task> GetUserQueryAsync(IEnumerable + /// Executes an index query and then gets a IEnumerable{TUser} by userIds + /// + /// Odata index filter query + /// + /// + /// protected async Task> GetUsersByIndexQueryAsync(string indexQuery, Func, CancellationToken, Task>> getUserFunc, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -698,6 +798,7 @@ async Task getUsers(IEnumerable ids, CancellationToken ct) return lUsers.SelectMany(u => u); } + /// public virtual async Task RemoveClaimAsync(TUser user, Claim claim) { ThrowIfDisposed(); @@ -724,6 +825,7 @@ public virtual async Task RemoveClaimAsync(TUser user, Claim claim) } + /// public override async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -755,6 +857,7 @@ public override async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newC await Task.WhenAll(tasks).ConfigureAwait(false); } + /// public override async Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -784,6 +887,7 @@ public override async Task RemoveClaimsAsync(TUser user, IEnumerable clai await Task.WhenAll(tasks).ConfigureAwait(false); } + /// protected override TUserClaim CreateUserClaim(TUser user, Claim claim) { TUserClaim uc = base.CreateUserClaim(user, claim); @@ -794,6 +898,7 @@ protected override TUserClaim CreateUserClaim(TUser user, Claim claim) return uc; } + /// protected override TUserLogin CreateUserLogin(TUser user, UserLoginInfo login) { TUserLogin ul = base.CreateUserLogin(user, login); @@ -804,6 +909,7 @@ protected override TUserLogin CreateUserLogin(TUser user, UserLoginInfo login) return ul; } + /// public override async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -820,6 +926,7 @@ await Task.WhenAll(_indexTable.DeleteEntityAsync(index.PartitionKey, index.RowKe } } + /// public override async Task SetUserNameAsync(TUser user, string? userName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -835,6 +942,7 @@ public override async Task SetUserNameAsync(TUser user, string? userName, Cancel user.UserName = userName; } + /// public override async Task SetEmailAsync(TUser user, string? email, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -877,7 +985,12 @@ public override async Task SetTokenAsync(TUser user, string loginProvider, strin await AddUserTokenAsync(token); } - //Fixes deletes for non-unique emails for users. + /// + /// Fixes deletes for non-unique emails for users. + /// + /// + /// + /// protected async Task DeleteEmailIndexAsync(string userId, string plainEmail) { string filterString = TableQuery.CombineFilters( @@ -894,6 +1007,12 @@ protected async Task DeleteEmailIndexAsync(string userId, string plainEmail) } } + /// + /// Deletes UserName Index + /// + /// + /// + /// protected async Task DeleteUserNameIndexAsync(string? userId, string? userName) { if (!string.IsNullOrWhiteSpace(userName) && @@ -909,7 +1028,7 @@ protected async Task DeleteUserNameIndexAsync(string? userId, string? userName) } /// - /// + /// Deletes all user table information by userId /// /// UserId in PartitionKey format /// @@ -928,7 +1047,7 @@ protected Task DeleteAllUserRows(string userId, IEnumerable userRow return deleteBatchHelper.SubmitBatchAsync(); } - + /// public override async Task UpdateAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -961,8 +1080,18 @@ public override async Task UpdateAsync(TUser user, CancellationT } + /// + /// Table Storage Context access + /// public TContext Context => _context; + /// + /// Generates new IdentityUserIndex for a user claim - suitable for a crud operation + /// + /// + /// + /// + /// protected Model.IdentityUserIndex CreateClaimIndex(string userPartitionKey, string? claimType, string? claimValue) { return new Model.IdentityUserIndex() @@ -1030,6 +1159,13 @@ protected Model.IdentityUserIndex CreateUserNameIndex(string userPartitionKey, s }; } + /// + /// Creates an IdentityUserIndex for Login suitable for a crud operation + /// + /// + /// + /// + /// protected Model.IdentityUserIndex CreateLoginIndex(string userPartitionKey, string loginProvider, string providerKey) { return new Model.IdentityUserIndex() @@ -1043,6 +1179,7 @@ protected Model.IdentityUserIndex CreateLoginIndex(string userPartitionKey, stri } + /// public override async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -1092,6 +1229,7 @@ public override Task GetUserIdAsync(TUser user, CancellationToken cancel } #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. + /// protected override async Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. { @@ -1099,6 +1237,14 @@ public override Task GetUserIdAsync(TUser user, CancellationToken cancel _keyHelper.GenerateRowKeyIdentityUserToken(loginProvider, name), cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Creates an IdentityUserIndex for UserToken suitable for a crud operation + /// + /// + /// + /// + /// + /// protected override TUserToken CreateUserToken(TUser user, string loginProvider, string name, string? value) { TUserToken item = base.CreateUserToken(user, loginProvider, name, value); @@ -1109,11 +1255,13 @@ protected override TUserToken CreateUserToken(TUser user, string loginProvider, return item; } + /// protected override Task AddUserTokenAsync(TUserToken token) { return _userTable.UpsertEntityAsync(token, TableUpdateMode.Replace); } + /// protected override Task RemoveUserTokenAsync(TUserToken token) { return _userTable.DeleteEntityAsync(token.PartitionKey, token.RowKey, TableConstants.ETagWildcard); diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs b/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs index 3b60350..cc29f1f 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/UserStore.cs @@ -13,6 +13,7 @@ namespace ElCamino.AspNetCore.Identity.AzureTable { + /// public class UserStore : UserStore #pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. , IUserStore @@ -20,6 +21,7 @@ public class UserStore : UserStore, new() where TContext : IdentityCloudContext { + /// public UserStore(TContext context, Model.IKeyHelper keyHelper) : base(context, keyHelper) { } } /// @@ -37,9 +39,11 @@ public class UserStore : UserStore, new() where TContext : IdentityCloudContext { + /// public UserStore(TContext context, Model.IKeyHelper keyHelper) : base(context, keyHelper) { } } + /// public class UserStore : UserOnlyStore #pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. @@ -55,13 +59,21 @@ public class UserStore, new() where TContext : IdentityCloudContext { + /// + /// Access to the Role Table + /// protected TableClient _roleTable; + /// public UserStore(TContext context, Model.IKeyHelper keyHelper) : base(context, keyHelper) { _roleTable = context.RoleTable; } + /// + /// Create user and role tables + /// + /// public override Task CreateTablesIfNotExistsAsync() { Task[] tasks = @@ -73,6 +85,7 @@ public override Task CreateTablesIfNotExistsAsync() return Task.WhenAll(tasks); } + /// public virtual async Task AddToRoleAsync(TUser user, string? roleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -106,6 +119,7 @@ public virtual async Task AddToRoleAsync(TUser user, string? roleName, Cancellat await Task.WhenAll(tasks).ConfigureAwait(false); } + /// public virtual async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -181,6 +195,11 @@ public virtual async Task> GetRolesAsync(TUser user, CancellationT return bag.ToList(); } + /// + /// Builds Odata role query + /// + /// + /// public string BuildRoleQuery(string normalizedRoleName) { string rowFilter = @@ -195,6 +214,7 @@ public string BuildRoleQuery(string normalizedRoleName) rowFilter); } + /// public virtual async Task> GetUsersInRoleAsync(string? roleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -233,6 +253,7 @@ string getTableQueryFilterByUserId(string? userId) return new List(); } + /// public virtual async Task IsInRoleAsync(TUser user, string? roleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -262,11 +283,13 @@ public virtual async Task IsInRoleAsync(TUser user, string? roleName, Canc return tasks.All(t => t.Result); } + /// public Task RoleExistsAsync(string roleName, CancellationToken cancellationToken = default) { return _roleTable.QueryAsync(filter: BuildRoleQuery(roleName), maxPerPage: 1, select: new List() { nameof(Model.IdentityRole.Name) }, cancellationToken: cancellationToken).AnyAsync(cancellationToken); } + /// public virtual async Task RemoveFromRoleAsync(TUser user, string? roleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -296,6 +319,15 @@ await Task.WhenAll( } } + /// + /// Executes a user aggregate query, that can filter by userid, roles and/or claims + /// + /// + /// + /// + /// + /// + /// protected async Task> GetUserAggregateQueryAsync(IEnumerable userIds, Func? setFilterByUserId = null, Func? whereRole = null, @@ -392,6 +424,12 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< return bag; } + /// + /// Maps table entities to strongly typed identity entities by userid + /// + /// + /// + /// protected new (TUser? User, IEnumerable Roles, IEnumerable Claims, @@ -447,6 +485,7 @@ protected async Task> GetUserAggregateQueryAsync(IEnumerable< return (user, roles, claims, logins, tokens); } + /// public override async Task DeleteAsync(TUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); From eec83fd3be4682031fb6ae3068132726ccb83c09 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Fri, 10 Nov 2023 19:34:13 -0600 Subject: [PATCH 13/14] more comments --- ...spNetCore.Identity.AzureTable.Model.csproj | 1 + .../IGenerateKeys.cs | 15 ++++++++++++++ .../IdentityConfiguration.cs | 18 +++++++++++++++++ .../IdentityRole.cs | 15 ++++++++++++++ .../IdentityRoleClaim.cs | 12 +++++++++++ .../IdentityUser.cs | 20 ++++++++++++++++--- .../IdentityUserClaim.cs | 12 ++++++++++- .../IdentityUserIndex.cs | 14 +++++++++++++ .../IdentityUserLogin.cs | 12 +++++++++++ .../IdentityUserRole.cs | 16 +++++++++++++++ .../IdentityUserToken.cs | 13 ++++++++++++ 11 files changed, 144 insertions(+), 4 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj index ba7d0a9..7058131 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj @@ -30,6 +30,7 @@ MIT true snupkg + true diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IGenerateKeys.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IGenerateKeys.cs index 35bc5d2..523f846 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IGenerateKeys.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IGenerateKeys.cs @@ -2,12 +2,27 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// + /// Generates keys suitable for table storage + /// public interface IGenerateKeys { + /// + /// Accept the keyhelper to generate keys for an entity + /// + /// void GenerateKeys(IKeyHelper keyHelper); + /// + /// Returns the rowkey for the entity without setting it + /// + /// + /// string PeekRowKey(IKeyHelper keyHelper); + /// + /// Key Version for the entity + /// double KeyVersion { get; set; } } } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityConfiguration.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityConfiguration.cs index 41c90a8..05873d6 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityConfiguration.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityConfiguration.cs @@ -3,16 +3,34 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// + /// Table Storage Configuration + /// public class IdentityConfiguration { + /// + /// Optional field, prefixes all given table names + /// public string? TablePrefix { get; set; } + /// + /// Storage connection string + /// public string? StorageConnectionString { get; set; } + /// + /// Optional, default value is AspNetIndex + /// public string? IndexTableName { get; set; } + /// + /// Optional, default value is AspNetUsers + /// public string? UserTableName { get; set; } + /// + /// Optional, default value is AspNetRoles + /// public string? RoleTableName { get; set; } } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRole.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRole.cs index 111ee3c..b8901bd 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRole.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRole.cs @@ -7,8 +7,10 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityRole : IdentityRole, IGenerateKeys { + /// public IdentityRole() : base() { } /// @@ -31,14 +33,17 @@ public string PeekRowKey(IKeyHelper keyHelper) return keyHelper.GenerateRowKeyIdentityRole(Name); } + /// public double KeyVersion { get; set; } + /// public IdentityRole(string roleName) : this() { base.Name = roleName; } + /// [IgnoreDataMember] public override string Id { @@ -53,19 +58,29 @@ public override string Id } } + /// public class IdentityRole : Microsoft.AspNetCore.Identity.IdentityRole, ITableEntity where TKey : IEquatable where TUserRole : IdentityUserRole { + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; + /// public IdentityRole() : base() { } + /// [IgnoreDataMember] public override TKey Id { diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRoleClaim.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRoleClaim.cs index d0d5955..d7db840 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRoleClaim.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityRoleClaim.cs @@ -9,8 +9,10 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityRoleClaim : IdentityRoleClaim, IGenerateKeys { + /// public IdentityRoleClaim() { } /// @@ -32,8 +34,10 @@ public string PeekRowKey(IKeyHelper keyHelper) return keyHelper.GenerateRowKeyIdentityRoleClaim(ClaimType, ClaimValue); } + /// public double KeyVersion { get; set; } + /// [IgnoreDataMember] public override string RoleId { @@ -48,12 +52,20 @@ public override string RoleId } } + /// public class IdentityRoleClaim : Microsoft.AspNetCore.Identity.IdentityRoleClaim, ITableEntity where TKey : IEquatable { + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUser.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUser.cs index a7ee316..fc01577 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUser.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUser.cs @@ -9,10 +9,13 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityUser : IdentityUser, IGenerateKeys { + /// public IdentityUser() : base() { } + /// public IdentityUser(string userName) : this() { @@ -44,10 +47,11 @@ public string PeekRowKey(IKeyHelper keyHelper) return keyHelper.GenerateRowKeyUserId(Id); } + /// public double KeyVersion { get; set; } - + /// public override string? UserName { get => base.UserName; @@ -61,14 +65,18 @@ public override string? UserName } } + /// public class IdentityUser : Microsoft.AspNetCore.Identity.IdentityUser, ITableEntity where TKey : IEquatable { + /// public IdentityUser() { } - + /// + /// Stores the LockoutEnd + /// public virtual DateTime? LockoutEndDateUtc { get; set; } /// @@ -99,10 +107,16 @@ public override DateTimeOffset? LockoutEnd } } - + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserClaim.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserClaim.cs index 45bafcf..6dcad77 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserClaim.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserClaim.cs @@ -5,8 +5,10 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityUserClaim : IdentityUserClaim, IGenerateKeys { + /// public IdentityUserClaim() { } /// @@ -28,18 +30,26 @@ public string PeekRowKey(IKeyHelper keyHelper) return keyHelper.GenerateRowKeyIdentityUserClaim(ClaimType, ClaimValue); } + /// public double KeyVersion { get; set; } - } + /// public class IdentityUserClaim : Microsoft.AspNetCore.Identity.IdentityUserClaim, ITableEntity where TKey : IEquatable { + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserIndex.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserIndex.cs index caeb530..7fb53a9 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserIndex.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserIndex.cs @@ -6,6 +6,9 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// + /// Index Entity + /// public class IdentityUserIndex : ITableEntity { /// @@ -13,10 +16,21 @@ public class IdentityUserIndex : ITableEntity /// public string Id { get; set; } = string.Empty; + /// + /// Key Version + /// public double KeyVersion { get; set; } + + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; } } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserLogin.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserLogin.cs index d693074..47bdc47 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserLogin.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserLogin.cs @@ -6,8 +6,10 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityUserLogin : IdentityUserLogin, IGenerateKeys { + /// public IdentityUserLogin() { } /// @@ -21,6 +23,7 @@ public void GenerateKeys(IKeyHelper keyHelper) KeyVersion = keyHelper.KeyVersion; } + /// public double KeyVersion { get; set; } /// @@ -35,15 +38,24 @@ public string PeekRowKey(IKeyHelper keyHelper) } + /// public class IdentityUserLogin : Microsoft.AspNetCore.Identity.IdentityUserLogin , ITableEntity where TKey : IEquatable { + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; + /// public virtual string Id { get; set; } = string.Empty; } } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserRole.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserRole.cs index 624d80d..d3abf18 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserRole.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserRole.cs @@ -7,8 +7,10 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityUserRole : IdentityUserRole, IGenerateKeys { + /// public IdentityUserRole() { } /// @@ -31,8 +33,10 @@ public string PeekRowKey(IKeyHelper keyHelper) return keyHelper.GenerateRowKeyIdentityUserRole(RoleName); } + /// public double KeyVersion { get; set; } + /// public string Id { get @@ -47,18 +51,30 @@ public string Id } + /// public class IdentityUserRole : Microsoft.AspNetCore.Identity.IdentityUserRole , ITableEntity where TKey : IEquatable { + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; + /// [IgnoreDataMember] public override TKey RoleId { get => base.RoleId; set => base.RoleId = value; } + /// + /// Role Name + /// public string? RoleName { get; set; } } } diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserToken.cs b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserToken.cs index 183cf12..efa0852 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserToken.cs +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/IdentityUserToken.cs @@ -8,8 +8,10 @@ namespace ElCamino.AspNetCore.Identity.AzureTable.Model { + /// public class IdentityUserToken : IdentityUserToken, IGenerateKeys { + /// public IdentityUserToken() { } @@ -23,6 +25,7 @@ public void GenerateKeys(IKeyHelper keyHelper) KeyVersion = keyHelper.KeyVersion; } + /// public double KeyVersion { get; set; } /// @@ -36,18 +39,28 @@ public string PeekRowKey(IKeyHelper keyHelper) } + /// public class IdentityUserToken : Microsoft.AspNetCore.Identity.IdentityUserToken , ITableEntity where TKey : IEquatable { + /// public string PartitionKey { get; set; } = string.Empty; + + /// public string RowKey { get; set; } = string.Empty; + + /// public DateTimeOffset? Timestamp { get; set; } + + /// public ETag ETag { get; set; } = ETag.All; + /// [IgnoreDataMember] public override string Name { get => base.Name; set => base.Name = value; } + /// [IgnoreDataMember] public override string? Value { get => base.Value; set => base.Value = value; } From a04fa7152660919098d91fd4b20b2093a1abfb45 Mon Sep 17 00:00:00 2001 From: David Melendez Date: Tue, 14 Nov 2023 16:05:51 -0600 Subject: [PATCH 14/14] .net8 ga --- ...o.AspNetCore.Identity.AzureTable.Model.csproj | 2 +- ...lCamino.AspNetCore.Identity.AzureTable.csproj | 6 +++--- ...Camino.Identity.AzureTable.DataUtility.csproj | 10 +++++----- ...o.AspNetCore.Identity.AzureTable.Tests.csproj | 16 ++++++++-------- .../ElCamino.Azure.Data.Tables.Tests.csproj | 10 +++++----- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj index 7058131..0221150 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable.Model/ElCamino.AspNetCore.Identity.AzureTable.Model.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj index c73f1a4..4356408 100644 --- a/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj +++ b/src/ElCamino.AspNetCore.Identity.AzureTable/ElCamino.AspNetCore.Identity.AzureTable.csproj @@ -39,9 +39,9 @@ - - - + + + diff --git a/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj b/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj index 6daa088..d4b2ad9 100644 --- a/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj +++ b/src/ElCamino.Identity.AzureTable.DataUtility/ElCamino.Identity.AzureTable.DataUtility.csproj @@ -43,11 +43,11 @@ - - - - - + + + + + diff --git a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj index ba65066..3aadf41 100644 --- a/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj +++ b/tests/ElCamino.AspNetCore.Identity.AzureTable.Tests/ElCamino.AspNetCore.Identity.AzureTable.Tests.csproj @@ -25,14 +25,14 @@ - - - - - - - - + + + + + + + + diff --git a/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj b/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj index e25a28c..f0de689 100644 --- a/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj +++ b/tests/ElCamino.Azure.Data.Tables.Tests/ElCamino.Azure.Data.Tables.Tests.csproj @@ -21,11 +21,11 @@ - - - - - + + + + +