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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Both repositories use raw SQL via Dapper. They differ in naming conventions:
| Columns | PascalCase, via `nameof(DataProtectionKeyEntity.X)` | snake_case literals |
| Unique index on friendly name | Filtered NONCLUSTERED (`WHERE ... IS NOT NULL`) | `CREATE UNIQUE INDEX IF NOT EXISTS` (NULLs allowed) |

- The PostgreSQL provider sets the global `Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true` so snake_case columns map to `DataProtectionKeyEntity`. This affects the whole host process (documented in the README).
- The PostgreSQL `SELECT` aliases each snake_case column to its `DataProtectionKeyEntity` property name (`AS "{nameof(...)}"`). Never set the process-wide `Dapper.DefaultTypeMap.MatchNamesWithUnderscores`: it changes column mapping for every Dapper query in the host application.
- `Delete()` requires `DataProtectionKeyEntity` instances (as returned by `GetAll()`) and deletes by `Id`, row by row in order, in one transaction.
- Schema and table names are interpolated directly into SQL strings, not parameterized or validated.
- Defaults are in each provider's `Constants.cs`. Keep the table DDL in sync with the "Database schema" section of `README.md`.
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ app.Services.UseDapperDataProtection();
app.Run();
```

> **Note:** the PostgreSQL provider enables Dapper's snake_case column mapping globally
> (`Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true`). Keep this in mind if the same
> application uses Dapper elsewhere.

## Configuration

Both providers accept an optional `Action<DapperDataProtectionConfig>` to override the defaults:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Dapper;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
Expand Down Expand Up @@ -40,8 +39,6 @@ public static IDataProtectionBuilder PersistKeysWithDapperInPostgreSQL(this IDat

var npgsqlDataSource = new NpgsqlDataSourceBuilder(connectionString).Build();

DefaultTypeMap.MatchNamesWithUnderscores = true;

builder.Services.AddSingleton(npgsqlDataSource);

builder.Services.AddScoped<IDbDataProtectionStorage, PostgreSQLDataProtectionRepository>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public class PostgreSQLDataProtectionRepository : IDbDataProtectionStorage
private string GetAllQuery() =>
$"""
SELECT
id,
insert_date ,
friendly_name,
xml
id AS "{nameof(DataProtectionKeyEntity.Id)}",
insert_date AS "{nameof(DataProtectionKeyEntity.InsertDate)}",
friendly_name AS "{nameof(DataProtectionKeyEntity.FriendlyName)}",
xml AS "{nameof(DataProtectionKeyEntity.Xml)}"
FROM {_config.SchemaName}.{_config.TableName};
""";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Threading.Tasks;
using AspNetCore.DataProtection.CustomStorage.Dapper;
using AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL;
using Dapper;
using DotNet.Testcontainers.Builders;
using Microsoft.Extensions.Options;
using Npgsql;
Expand Down Expand Up @@ -46,7 +45,6 @@ public async ValueTask InitializeAsync()
}
};
NpgsqlDataSource = npgsqlDataSourceBuilder.Build();
DefaultTypeMap.MatchNamesWithUnderscores = true;
SeedDatabase();
await using var dbConnection = await NpgsqlDataSource.OpenConnectionAsync();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq;
using AspNetCore.DataProtection.CustomStorage.Dapper.PostgreSQL;
using Dapper;
using FluentAssertions;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -25,4 +26,15 @@ public void PersistKeysWithDapperInPostgreSQL_PreservesIncludeErrorDetail(string
new NpgsqlConnectionStringBuilder(dataSource.ConnectionString).IncludeErrorDetail
.Should().Be(expected);
}

[Fact]
public void PersistKeysWithDapperInPostgreSQL_DoesNotEnableMatchNamesWithUnderscores()
{
DefaultTypeMap.MatchNamesWithUnderscores = false;
var services = new ServiceCollection();

services.AddDataProtection().PersistKeysWithDapperInPostgreSQL("Host=localhost;Database=test");

DefaultTypeMap.MatchNamesWithUnderscores.Should().BeFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ public async Task GetAllAsync_KeysInserted_ReturnsEntitiesWithIdAndInsertDate()
.And.OnlyContain(x => x.Id > 0 && x.InsertDate != default);
entities.Select(x => x.Id).Should().OnlyHaveUniqueItems();
}
[Fact]
public async Task GetAllAsync_MatchNamesWithUnderscoresDisabled_MapsAllColumns()
{
DefaultTypeMap.MatchNamesWithUnderscores = false;
await _fixture.ResetDatabaseAsync();
var key = new DataProtectionKey { FriendlyName = "MappingFriendlyName", Xml = "MappingXml" };
_sut.Insert(key);

var entity = (await _sut.GetAllAsync()).Should().ContainSingle().Subject;

entity.Id.Should().BePositive();
entity.InsertDate.Should().NotBe(default);
entity.FriendlyName.Should().Be(key.FriendlyName);
entity.Xml.Should().Be(key.Xml);
}

[Fact]
public void InitializeDb_CustomSchemaAndTable_PersistsKeysInConfiguredTable()
Expand Down
Loading