-
Notifications
You must be signed in to change notification settings - Fork 87
Added Entity Framework Core backed IXmlRepository with tests and sample #303
Conversation
@dansward is there an issue associated with this PR that describes the scenario? If not, could you open a new issue at https://github.com/aspnet/Home/issues and describe the details? Thanks! |
@Eilon A related issue exists. Create SQL XmlRepository #2505, should I create one specifically for EF or will that one work? |
namespace Microsoft.AspNetCore.DataProtection | ||
{ | ||
/// <summary> | ||
/// Contains Redis-specific extension methods for modifying a <see cref="IDataProtectionBuilder"/>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change mention of Redis :)
/// <summary> | ||
/// Code first model used by <see cref="EntityFrameworkCoreXmlRepository"/>. | ||
/// </summary> | ||
public class Key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is never going to be typed in full by the user anywhere a more specific name would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DataProtectionKey? Should the DbContext class be renamed to match as well, DataProtectionKeyStore?
And adding @natemcmaster |
Fixed comment on extension method: replaced Redis with EntityFrameworkCore
Hey @dansward, thanks for taking an initial stab at this. We haven't yet nailed down a design for the API and expected usage of storing keys in a SQL database. We're going to take a look at dotnet/aspnetcore#2505 in our triage on Friday, and I'll get back to you then. |
…tion more natural. Updated EFCore sample and tests to use new extension method.
We discussed this in triage. First of all, thanks. We've been thinking of doing dotnet/aspnetcore#2505 for a while. This was a good nudge to start the conversation again. We're going to leave this PR open for now for two reasons.
Thanks, |
@natemcmaster Sounds good. I'll wait for the update to aspnet/Home#2505. |
Any way I can get migrations working with this? I tried to do:
|
Anyone wanna play in on this? @dansward |
Will put together an example or fix it if I can't as soon as I have a few minutes. But, I'd try putting migrations in your assembly first.
…________________________________
From: Endre Karlson <[email protected]>
Sent: Tuesday, June 26, 2018 5:50:11 PM
To: aspnet/DataProtection
Cc: dan.s.ward; Mention
Subject: Re: [aspnet/DataProtection] Added Entity Framework Core backed IXmlRepository with tests and sample (#303)
Anyone wanna play in on this? @dansward<https://github.com/dansward>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#303 (comment)>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AAKDpqvrcUCInqSLnRwbe5eE4ruMZGjhks5uAqyTgaJpZM4TDu7J>.
|
Hey @dansward, just wanted to let you know I haven't forgotten about this. Thanks for being patient. Our master branch is open for 3.0 work now. DataProtection is one of the dozens of projects I have to find time to support, so I appreciate your help. What are your thoughts on this comment? dotnet/aspnetcore#2505 (comment). I think @ekarlso's comment pointed out one of the things we should discuss more - how can we help users create/manage the database to back this repository? |
Just tossing out an idea. One way I thought we might implement is by providing an API like this: public static class EtityFrameworkDataProtectionExtensions
{
public static IDataProtectionBuilder PersistKeysToDbContext<TContext>(this IDataProtectionBuilder builder)
where TContext: DbContext, IDataProtectionKeysDbSet
{
/// implementation ...
return builder;
}
}
public interface IDataProtectionKeysDbSet
{
DbSet<DataProtectionKeys> DataProtectionKeys { get; }
}
public class DataProtectionKeys
{
public long Id { get; set; }
public string Content { get; set; }
} I would have to play with an implementation, but my thinking here is that this gives devs the option to use their existing DbContext type and just add a new table, but also gives them flexibility to keep keys in a separate DbContext if they wish. Also, we can create the DbContext instance through dependency injection, so devs only have to configure their connection strings in EF Core once |
[auto-updated: dependencies]
Updated test and sample projects
@natemcmaster, that worked really well. Let me know if this meets the requirements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@divega @bricelam can you also take a look at this? I want someone from EF to take a look, too. My memory is a bit rusty on some the EF Core workings, so want to make sure what I've said below is still accurate.
@dansward - this is a really good start. I like the API shape and I think this is headed in the right direction. My comments are mostly about implementation details now. Also, I've updated your PR to target release/2.2
since the master
branch now targets 3.0, which won't ship for a while.
Thanks!
Nate
/// <summary> | ||
/// The friendly name of the <see cref="DataProtectionKey"/>. | ||
/// </summary> | ||
[Key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current contract for IXmlRepository specifies that friendly name is optional. If specified, it should be unique, but it may be null. We should probably add a property instead, such as public int Id { get; set; }
.
@divega can you refresh my memory - do EF model conventions apply to internal
properties?
{ | ||
builder.Services.Configure<KeyManagementOptions>(options => | ||
{ | ||
options.XmlRepository = new EntityFrameworkCoreXmlRepository<TContext>(builder.Services.BuildServiceProvider().GetRequiredService<TContext>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this may cause problems with scope. Typically, DbContext is a scoped service, but this would required a DbContext to be a singleton.
|
||
/// <inheritdoc /> | ||
public IReadOnlyCollection<XElement> GetAllElements() | ||
=> _context.DataProtectionKeys.Select(key => XElement.Parse(key.Xml)).ToList().AsReadOnly(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be gentle and use TryParse instead. If the database has malformed xml, we should skip loading that key and log a warning.
Also, let's make this a notracking query. We don't need to make changes to the entities.
/// Creates a new instance of the <see cref="EntityFrameworkCoreXmlRepository{TContext}"/>. | ||
/// </summary> | ||
/// <param name="context">The context used to store instances of <see cref="DataProtectionKey"/></param> | ||
public EntityFrameworkCoreXmlRepository(TContext context) => _context = context ?? throw new ArgumentNullException(nameof(context)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of taking an instance of TContext
, we should take a service which can construct a new instance of the DbContext. In earlier versions of EFCore, there was an IDbContextFactory<TContext>
service you could use. I don't remember if that is still around in EF Core 2.0 (cc @divega).
Also, EntityFrameworkCoreXmlRepository is a singleton, but each time it invokes an interaction with the database, we should create a new instance of DbContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use IDbContextFactory
. It was only for design time. We renamed it in 2.0 to reflect that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When are keys retrieved? Once during app start? During a request?
If it's just once, just creating a scope here might be best.
{ | ||
_context.DataProtectionKeys.Add(newKey); | ||
} | ||
_context.SaveChanges(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a logging statement to this method before you call SaveChanges? It only needs to be LogLevel.Debug that says something like "Saving key {friendlyName} to {dbcontextname}".
@@ -0,0 +1,13 @@ | |||
using Microsoft.EntityFrameworkCore; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting nit: can you apply the standard file header to all .cs files?
.AddDbContext<DataProtectionKeyContext>() | ||
.AddDataProtection() | ||
.PersistKeysToDbContext<DataProtectionKeyContext>(); | ||
var serviceProvider = serviceCollection.BuildServiceProvider(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change this to .BuildServiceProvider(validateScopes: true)
? This typically helps us find issues in the CI services.
public interface IDataProtectionKeyContext | ||
{ | ||
/// <inheritdoc /> | ||
DbSet<DataProtectionKey> DataProtectionKeys { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requiring a DbSet<DataProtectionKey>
on the DbContext
seems a bit rigid. Could just use DbContext.Set<DataProtectionKey>()
instead.
/// </summary> | ||
public interface IDataProtectionKeyContext | ||
{ | ||
/// <inheritdoc /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inherit docs from where? 😉
Added exception handling and logging for malformed key xml Changed to NoTracking queries Added logging when saving new key Added standard file headers
@natemcmaster @bricelam I'm not sure how to fix the conflicts. I hate to waste your time but any help would be appreciated. |
Thanks for doing the initial legwork to get this going @dansward. I've pushed a commit to your branch to resolve the conflicts. I'm going to merge this in to a staging branch while I do a few more tweaks. Assuming I don't run into other issues, this will be in the 2.2 Preview 2 release. |
No description provided.