Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASP.NET Core Identity support [Feature Request] #38

Open
mmarinchenko opened this issue Feb 1, 2019 · 18 comments
Open

ASP.NET Core Identity support [Feature Request] #38

mmarinchenko opened this issue Feb 1, 2019 · 18 comments
Labels
enhancement-XXL New feature request. Tshirt size: XXL

Comments

@mmarinchenko
Copy link

mmarinchenko commented Feb 1, 2019

To support custom ASP.NET Core Identity scenario with EFDesigner some manual work has to be done.

  1. Implement 7 custom entities which inherit types from Microsoft.AspNetCore.Identity namespace:
  • IdentityUser
  • IdentityRole
  • IdentityUserClaim<TKey>
  • IdentityUserRole<TKey>
  • IdentityRoleClaim<TKey>
  • IdentityUserLogin<TKey>
  • IdentityUserToken<TKey>

Note: TKey type parameter defaults to string type.

  1. Implement custom part of EFDesigner-generated DbContext class to define 7 respective DbSets and create model using partial OnModelCreatedImpl() method.

  2. Somehow inherit IdentityDbContext<TUser,TRole,TKey,TUserClaim,TUserRole,TUserLogin,TRoleClaim,TUserToken> type from Microsoft.AspNetCore.Identity.EntityFrameworkCore namespace instead of default DbContext from Microsoft.EntityFrameworkCore.

Note: IdentityDbContext<> in turn inherits DbContext.

First 2 points are manual work because EFDesigner lacks:

  • support for generated entity to inherit any type (not just other entity type defined on a diagram);
  • support for string type to be used as identity (actually not a strong requirement because Int64 may be used for TKey type parameter).

This is safe to implement. Not a big problem actually.

But 3rd point needs to copy EFCoreDesigner.ttinclude template to a project directory and remove the : Microsoft.EntityFrameworkCore.DbContext text from it. Then in file from point 2 add something like : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<MyUser, MyRole, string/*TKey*/, MyUserClaim, MyUserRole, MyUserLogin, MyRoleClaim, MyUserToken>.

This introduces a problem to EFDesigner extension updates management. So it would be great to implement string property in EFDesigner for setting custom base class for DbContext (like ConnectionString). Truth to be told it's the only crusial part of all this request :)

@msawczyn msawczyn added the enhancement New feature or request label Feb 3, 2019
@msawczyn
Copy link
Owner

msawczyn commented Feb 3, 2019

Intriguing. I'll look into it. Thanks for the suggestion.

As an aside, your comment above:

... EFDesigner lacks:

- support  for generated entity to inherit any type (not just other entity type defined on a diagram);

isn't completely accurate. Since generated entities are partial classes, you can declare a base class in a custom partial file as long, of course, as there isn't any other base class for that type in your model.

@mmarinchenko
Copy link
Author

Thanks, @msawczyn!

You're right, I was not completely accurate about entity inheritance. I guess I wrote too many words :) In fact I created this feature request mainly becuase of point 3.

As for the entity inheritance... This is difficult. Since the base type (e.g. IdentityUser) is not a part of a diagram the model for its properities will not be autogenerated. This requires some kind of import feature in EFDesigner. Inherited properties must be a part of a diagram and must be generated in data context class but at the same time they must be readonly on a diagram and must be excluded from entity class generation. Besides that the base class may be changed in future. So EFDesigner must check this and reimport base properties... This is difficult :)

@ensemblebd
Copy link

I really think this is going to need totally separate tt template generators.

Several critical factors:

  • [Pre-Generate]
    <TKey> variation (int, guid(string), etc)
  • [Design-Time]
    Restrict modification on base-class to prevent user[-/compilation]-error
    (must use override instead of virtual)
  • [Compilation-Time via Generator]
    OnModelCreationImpl must call IdentityDbContext's base.OnModelCreating base class impl, or re-implement the associations it defined for ef6.

The last item I think is the game changer. I'm not sure how to even approach this.

  • Should there be a "standard" .efmodel provisioned?
  • Or should the drag/drop processor be expanded to process DbContext classes and their ef6 relations?
  • And does this affect inheritance strategy (aka discriminator)?

Hmm. Am speaking aloud here. Welcome any thoughts.


I truly wish I had more time to work on this aspect. Pulling hair out as it is.
This extension is overly well deserving of community support on this. And imagine the possibilities with base-class inheritance + control in gui, and abstraction through generated partials adhering to above principles.
You'd simply drag n' drop, and expose a scoped interface in your services. How cool.

+1 vote on feature request from me.
If i can find time, I'll do what I can to contribute ideas/concept-work to help edge this along.

@msawczyn
Copy link
Owner

Thanks for the excellent input! Let's hit those up in order:

  • <TKey> changes: not a big deal

  • Design-time restrictions: not a huge deal. The designer could pre-populate identity base classes and lock them down from modification (see below).

  • Any code generation changes can do the Right Thing depending on what the base class is, checking whether it's one of the identity bases. Customization points, as always, are handled by generated partials.

  • I think you're right in that we'll need separate T4 templates for this, but that isn't such a bad thing. This is enough of a standardized compartment case that custom templates would be fitting.

  • I was thinking along the same lines regarding a pre-provisioned template. It would be a separate "Add New Item" choice, and would add a model with the necessary base classes, likely created based on reflection from the library classes so that, when they changed, a new model would have the correct properties pre-set for those classes.

  • I'm not entirely sure what you mean by "should the drag/drop processor be expanded to process DbContext classes and their ef6 relations". Entities are independent of the context, and sticking with the concept of POCO entities is a bit of a Prime Directive for the modeler.

  • Any changes in how inheritance is handled can be done by the T4, looking at the base class and generating appropriate code depending on whether it were user-created or one of the identity base classes, if indeed such a change is needed.

As always, I welcome any contributions you might have time for. I'm a bit swamped at work right now and don't have a lot of time to work in many (any?) hours for the designer, but that should change in a couple of handfuls of weeks. This is an intriguing problem!

Thanks again for the input.

ensemblebd referenced this issue in ensemblebd/EFDesigner Mar 5, 2019
@ensemblebd
Copy link

ensemblebd commented Mar 5, 2019

I was going to create a sample project, but ran out of time.
The committed TT templates work for Identity if you feed it a baseClass followed by subclass (drag/drop), where base-class is a basic "class model" of decompiled AspnetCore 2.1 Identity public properties with a Identity[Superclass]<TKey>, and subClass extends said "base".
ensemblebd/EFDesigner@d49079e
And here's the .efmodel vstemplate (bypass feeding requirement, throw into project):
ensemblebd/EFDesigner@1c8ee69
The commits are (I believe) unworthy of production, let alone a pull request.
But provides a sample changeset for intended goal. aka "works for me, wish you luck!"

If I can find time this week, I'll submit a git repo in this issue for a working proof of concept AspNetCore project.
I have it working for my project, but gotta get rid of all the company stuff to provide such a thing publicly.

  1. My custom TT makes some assumptions: (project override - aka bad for extension updates)
  • <TKey> is hardcoded both at template level and efmodel level (need an MEF prop to procure designer field)
  • DbSets were dropped for base-classes to prevent collision with Identity. I believe base-class reflection is required here (to know what is and isn't editable). I have hardcoded what is ignored.
  • Default class names are assumed and hardcoded into the template:
    MyNamespace.IdentityUser is derived from a base class of AspNetCore.IdentityUser<TKey> (for generator usage/purposes due to # 2 below), and is subclassed by ApplicationUser (the custom impl).
  • Default constructor for subclass must be marked public to be used in Type Parameters for Identity class registration
  1. Drag/Drop doesn't work with <TKey> , need MEF modification to support this. It tries to make use of it as a DbSet, in this case a.) no dbset is desired and b.) nor can it be textually procured given the raw text [< , > ] symbols.
  2. MEF Designer allows editing of base-class tables, which is not possible for derived AspNetCore.Identity base classes (user-error prevention). With my sample .efmodel, it's all about "hey you, don't touch that"
  3. The .efmodel attributes are important, so standardization of a .efmodel template for Identity may be necessary.

Additional /etc Not related to Identity but valuable...
5. Some tables have multiple primary keys, which need an Order specified. I used the custom attr prop on modelClass, however it required some hackery to prevent duplicate [Key] column. Perhaps an exposed prop for "Key Order" could be used, or an improvement to prevent Validation error on tables with no primary key.

  1. Owin (anyone use that? hmm) requires access to the DbContextOptions<TClass> constructor, so added a partial for that

@mmarinchenko
Copy link
Author

I performed some additional tests for ASP.NET Core scenario and realize that first 2 points of original request are not needed for default identity support :) The only thing that needs to be done is point 3.

P.S. I also mention this in issue msawczyn/EFDesigner#72.

@msawczyn
Copy link
Owner

Just wanted to let you know that I've started implementing this, both in EF6 and EFCore. If you'd like to follow the progress (and contribute!) the branch is called identity-context.

@prince272
Copy link

I've rewritten the Asp Core Identity for Mvc. Feel free to take a look at https://github.com/prince272/AspCoreIdentityMvc

Simply change the url for the area 'Identity' to 'IdentityMvc' and you'll be automatically directed to the Mvc version for Identity.

@msawczyn
Copy link
Owner

msawczyn commented Jun 4, 2019

Nice ... thanks! I'll dig into that. I've got the changes needed in the designer pretty much done, so the next step is the code generation. This will certainly help.

@burakdobur
Copy link

Hi @msawczyn , I'm also interested in this topic. Is there way to speed up things by helping you out in this issue/enhancement ?

@msawczyn
Copy link
Owner

msawczyn commented Sep 9, 2020

Absolutely! Always happy to have the help!

The goal, obviously, is to create a mechanism where the user can easily scaffold a model that will work with asp.net identity, then be able to modify it to their needs without breaking its compatability.

There's an older branch named identity-context that I had started some time ago; the approach was having a different model starting template. Feel free to see if that's valid or needs scrapped and redone.

Thanks for volunteering.

@msawczyn
Copy link
Owner

Hey all, just wanted to let you know that, even after being on the list for a year, this isn't being ignored ... it just keeps getting bumped down in priority. It's not a simple task to get this right so that it can be used as a general modeling aid (will definitely require a custom starting project item) and will need some basic infrastructure before it can be implemented.

The EFCore5 release is taking up all available hours to get solid. But I haven't forgotten!

@msawczyn msawczyn added enhancement-XXL New feature request. Tshirt size: XXL and removed enhancement New feature or request labels Feb 11, 2021
@msawczyn msawczyn transferred this issue from msawczyn/EFDesigner Aug 14, 2022
@burakdobur
Copy link

Greetings, I've been using your extension myself and with fellow developers. I hope changing the code base for 2022 made some room to give priority for this request. Using this great tool with built-in identity solution would makes us more than happy. Thanks again.

@Mattnificent
Copy link

I have been using Entity Framework Visual Editor for a few years now; it has been a great tool for quickly reasoning about my data models. However, I am accumulating tech debt in a few different projects now from lack of integration of this tool with Microsoft Identity. I first attempted to resolve this on my own here. I have since started up a few projects where I maintain 2 separate "user"/"person" tables with a 1-1 relationship. This has resulted in lots of extra files, classes, lines of code, SQL queries, and general obscurity that probably leads down paths away from best practices. Here is an example of a "best" solution I could come up with after way more time than I would like to have spent:

public partial class Person
{
    [NotMapped]
    public string Email => AspNetUser.Email;

    /// <summary>
    /// THIS MUST BE EXPLICITLY SET
    /// - it will not be auto-populated
    /// </summary>
    public IdentityUser AspNetUser { get; set; }

    public override string ToString()
    {
        return $"{this.UserName} [{this.Email}]"; //  unnecessary: ({this.Id}, {this.AspNetUserId})
    }
}

And then on the Razor index page:

public class IndexModel : PageModel
{
    private readonly MyModel _context;
    private readonly UserManager<IdentityUser> _userManager;
    private readonly ILogger<IndexModel> _logger;

    public IndexModel(MyModel context, UserManager<IdentityUser> userManager,
        ILogger<IndexModel> logger)
    {
        _context = context;
        _userManager = userManager;
        _logger = logger;
    }

    public List<Person> people { get;set; } = default!;

    public async Task OnGetAsync()
    {
        List<IdentityUser> allUsers = await _userManager.Users
            .ToListAsync();

        people = await _context.People
            .ToListAsync();

        foreach (var person in people)
        {
            person.AspNetUser = allUsers
                .Where(x => x.Id == person.AspNetUserId)
                .First();
        }
    }
}

Does anyone have any better workarounds, or solutions for this disconnection of EF Visual Editor from Microsoft's Identity model?

@Mattnificent
Copy link

And, the code I just posted above has broken another line of code where I was doing:

person.Email = User.FindFirstValue(ClaimTypes.Email);

in another Razor page. I will have to write even more logic to gracefully handle when I have the User ClaimsPrincipal, but not the IdentityUser - in all of my projects. I google search get IdentityUser from ClaimsPrincipal, and there are no results - this is a big red flag for me, and I am very concerned about the viability of my projects being designed with this tool now.

@mmarinchenko
Copy link
Author

/// <summary>
/// THIS MUST BE EXPLICITLY SET
/// - it will not be auto-populated
/// </summary>
public IdentityUser AspNetUser { get; set; }

@Mattnificent, the data context class generated from the EF model is partial. It has partial methods for customization purposes:

    partial void CustomInit(DbContextOptionsBuilder optionsBuilder);

    partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
    partial void OnModelCreatedImpl(ModelBuilder modelBuilder);

Therefore, in this particular case, you can manually add the <YourEFModelName>.custom.cs file with the following text:

public partial class <YourEFModelName>
{
    partial void OnModelCreatedImpl(ModelBuilder modelBuilder) =>
        modelBuilder.Entity<Person>()
            .HasOne<IdentityUser>(p => p.AspNetUser)
            .WithOne();
}

This adds the Person.AspNetUser property to the model as navigation property with a One-to-One association.

Do not forget to generate a new migration. And check the Context Base Class property of the EF model - it should be Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext.

Hope this helps!😉

@mmarinchenko
Copy link
Author

I google search get IdentityUser from ClaimsPrincipal, and there are no results

I guess you can use var aspNetUser = await _userManager.GetUserAsync(claimsPrincipal);

@Mattnificent
Copy link

Mattnificent commented Sep 30, 2023

@mmarinchenko Thank you!!! You are a saint, and a king! I had tried to use the ForeignKey("AspNetUserId") attribute on the custom AspNetUser property to get the context to understand the 1-1 relationship between those tables, but it gave me some strange constructor error, so I assumed it was not possible. The Fluent API approach did the trick. Even the database deployment didn't have to drop any data, and my new OnGetAsync method worked first try out of the box:

public async Task OnGetAsync()
{
    people = await _context.People
        .Include(x => x.AspNetUser)
        .ToListAsync();
}

If you ever need a favor, I'm your guy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement-XXL New feature request. Tshirt size: XXL
Projects
None yet
Development

No branches or pull requests

6 participants