From d49079ea2244a958e38db92f1a709b4700186bf8 Mon Sep 17 00:00:00 2001 From: Barry Durand Date: Mon, 4 Mar 2019 22:24:50 -0600 Subject: [PATCH] Implementation of AspNetCore Identity. Works if you feed it proper classes (drag/drop). See issue #53 --- .../TextTemplates/EFCoreDesigner.ttinclude | 20 +++- .../TextTemplates/EFDesigner.ttinclude | 106 +++++++++++++++--- 2 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude b/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude index e88ccbec5..4552a41af 100644 --- a/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFCoreDesigner.ttinclude @@ -109,6 +109,9 @@ void WriteDbContextEFCore(ModelRoot modelRoot) Output("using System.Linq;"); Output("using System.ComponentModel.DataAnnotations.Schema;"); Output("using Microsoft.EntityFrameworkCore;"); + if (IsAspNetCoreIdentity()) { + Output("using Microsoft.AspNetCore.Identity.EntityFrameworkCore;"); + } NL(); BeginNamespace(modelRoot.Namespace); @@ -130,7 +133,9 @@ void WriteDbContextEFCore(ModelRoot modelRoot) Output("/// "); } - Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : Microsoft.EntityFrameworkCore.DbContext"); + String inherit_from = (IsAspNetCoreIdentity()?$"IdentityDbContext<{IdentityUserImpl},{IdentityRoleImpl},{IdentityTKey}>":"Microsoft.EntityFrameworkCore.DbContext"); + + Output($"{modelRoot.EntityContainerAccess.ToString().ToLower()} partial class {modelRoot.EntityContainerName} : {inherit_from}"); Output("{"); PluralizationService pluralizationService = ModelRoot.PluralizationService; @@ -162,6 +167,9 @@ void WriteDbContextEFCore(ModelRoot modelRoot) { string dbSetName; + // don't output dbsets for already-included sets via base class of Identity. + if (isIdentityModel(modelClass.Name) || isIdentityBase(modelClass.Name)) continue; + if (!string.IsNullOrEmpty(modelClass.DbSetName)) dbSetName = modelClass.DbSetName; else @@ -208,11 +216,15 @@ void WriteDbContextEFCore(ModelRoot modelRoot) Output("/// "); Output($"public {modelRoot.EntityContainerName}(DbContextOptions<{modelRoot.EntityContainerName}> options) : base(options)"); Output("{"); + Output("CustomCreate(options);"); Output("}"); NL(); Output("partial void CustomInit(DbContextOptionsBuilder optionsBuilder);"); NL(); + // added to support Owin context creation.. Cause err body loves Owen Wilson? :o + Output($"partial void CustomCreate(DbContextOptions<{modelRoot.EntityContainerName}> options);"); + NL(); Output("/// "); Output($"protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)"); @@ -267,6 +279,11 @@ void WriteDbContextEFCore(ModelRoot modelRoot) foreach (ModelClass modelClass in modelRoot.Classes.OrderBy(x => x.Name)) { + // don't output ef6 correlations for already-included items via base class of Identity. + if (isIdentityBase(modelClass.Name)) continue; + // also respect developer wishes to override generator and use custom correlations of their design. + if (shouldDisableModelImpl(modelClass.Name)) continue; + segments.Clear(); foreignKeyColumns.Clear(); NL(); @@ -305,6 +322,7 @@ void WriteDbContextEFCore(ModelRoot modelRoot) // indexed properties foreach (ModelAttribute indexed in modelClass.Attributes.Where(x => x.Indexed && !x.IsIdentity)) { + segments.Clear(); segments.Add($"modelBuilder.Entity<{modelClass.FullName}>().HasIndex(t => t.{indexed.Name})"); if (indexed.IndexedUnique) segments.Add("IsUnique()"); diff --git a/src/DslPackage/TextTemplates/EFDesigner.ttinclude b/src/DslPackage/TextTemplates/EFDesigner.ttinclude index b5819340c..6a2ef1807 100644 --- a/src/DslPackage/TextTemplates/EFDesigner.ttinclude +++ b/src/DslPackage/TextTemplates/EFDesigner.ttinclude @@ -11,6 +11,66 @@ // Copyright (c) 2017-2019 Michael Sawczyn // https://github.com/msawczyn/EFDesigner + +#region Todo_Expose_As_MEF_Attributes_In_Designer + +List IdentityBaseTypes = new List(){ + "IdentityRole", + "IdentityRoleClaim", + "IdentityUser", + "IdentityUserClaim", + "IdentityUserLogin", + "IdentityUserRole", + "IdentityUserToken" +}; +List IdentityBaseTypesWithoutPrimary = new List(){ + "IdentityUserLogin", + "IdentityUserRole", + "IdentityUserToken" +}; +List IdentitySubTypes = new List(){ + "ApplicationRole", + "ApplicationRoleClaim", + "ApplicationUser", + "ApplicationUserClaim", + "ApplicationUserLogin", + "ApplicationUserRole", + "ApplicationUserToken" +}; +List IdentitySubTypesDisableModelImpl = new List(){ + "ApplicationDepartment", + "ApplicationGroup", + "ApplicationGroupDepartment", + "ApplicationGroupRole", + "ApplicationUserGroup" +}; + +bool isIdentityBase(String name) { // todo: ModelClass.IsIdentity (exposed property) + return IdentityBaseTypes.Any(x=> x.EndsWith(name, StringComparison.OrdinalIgnoreCase)); +} +bool isIdentityBaseWithoutPK(String name) { // todo: ModelClass.NoPrimaryKey (exposed property) + return IdentityBaseTypesWithoutPrimary.Any(x=> x.EndsWith(name, StringComparison.OrdinalIgnoreCase)); +} +bool isIdentityModel(String name) { // todo: ModelClass.IsIdentitySubClass (exposed property) + return IdentitySubTypes.Any(x=> x.EndsWith(name, StringComparison.OrdinalIgnoreCase)); +} +bool shouldDisableModelImpl(String name) { // todo: ModelClass.DisableModelImpl (exposed property) + return IdentitySubTypesDisableModelImpl.Any(x=> x.EndsWith(name, StringComparison.OrdinalIgnoreCase)); +} +bool shouldForceDefaultConstructorToPublic(String name) { // todo: ModelClass.DefaultConstructorIsPublic (exposed property) + return IdentitySubTypesDisableModelImpl.Any(x=> x.EndsWith(name, StringComparison.OrdinalIgnoreCase)); +} +bool IsAspNetCoreIdentity() {return true;} // todo: ModelRoot.IsNetCoreIdentity (exposed property) + +String IdentityUserImpl = "ApplicationUser";// todo: ModelRoot.IdentityUserClass (exposed property) +String IdentityRoleImpl = "ApplicationRole";// todo: ModelRoot.IdentityRoleClass (exposed property) +String IdentityTKey = "int";// todo: ModelRoot.IdentityPreferredKeyType (exposed property) + +#endregion + + + + void NL() { WriteLine(string.Empty); @@ -251,6 +311,10 @@ void WriteEnum(ModelEnum modelEnum) void WriteClass(ModelClass modelClass) { + bool b_isIdentityBase=isIdentityBase(modelClass.Name); + bool b_isIdentityModel=isIdentityModel(modelClass.Name); + bool b_desiresPublicAccess = shouldForceDefaultConstructorToPublic(modelClass.Name); + Output("using System;"); Output("using System.Collections.Generic;"); Output("using System.Collections.ObjectModel;"); @@ -259,6 +323,9 @@ void WriteClass(ModelClass modelClass) Output("using System.ComponentModel.DataAnnotations.Schema;"); Output("using System.Linq;"); Output("using System.Runtime.CompilerServices;"); + if (b_isIdentityBase) { + Output("using Microsoft.AspNetCore.Identity;"); // required for drag/drop of Identity class derivations. + } List additionalUsings = GetAdditionalUsingStatementsEF6(modelClass.ModelRoot); if (additionalUsings.Any()) Output(string.Join("\n", additionalUsings)); @@ -296,12 +363,16 @@ void WriteClass(ModelClass modelClass) Output($"public {isAbstract}partial class {modelClass.Name}{baseClass}"); Output("{"); - WriteConstructor(modelClass); - WritePersistentProperties(modelClass); - WriteCalculatedProperties(modelClass); - WritePersistentNavigationProperties(modelClass, true); - WriteCalculatedNavigationProperties(modelClass); - WriteNotifyPropertyChanged(modelClass); + if (!b_isIdentityBase) { + WriteConstructor(modelClass, b_isIdentityModel, b_desiresPublicAccess); + } + WritePersistentProperties(modelClass, b_isIdentityBase); + if (!b_isIdentityBase) { + WriteCalculatedProperties(modelClass); + WritePersistentNavigationProperties(modelClass, true); + WriteCalculatedNavigationProperties(modelClass); + WriteNotifyPropertyChanged(modelClass); + } Output("}"); @@ -384,7 +455,7 @@ void WriteDefaultConstructorBody(ModelClass modelClass) Output("Init();"); } -void WriteConstructor(ModelClass modelClass) +void WriteConstructor(ModelClass modelClass, bool b_isIdentityModel, bool b_desiresPublicAccess) { Output("partial void Init();"); NL(); @@ -395,7 +466,9 @@ void WriteConstructor(ModelClass modelClass) bool hasRequiredParameters = GetRequiredParameters(modelClass, false).Any(); string visibility = hasRequiredParameters || modelClass.IsAbstract ? "protected" : "public"; - + // Error CS0310- must be a non-abstract type with a public parameterless constructor in order to use it as parameter in the generic type or method + if (b_isIdentityModel || b_desiresPublicAccess) visibility = "public"; + if (visibility == "public") { Output("/// "); @@ -562,7 +635,7 @@ string FullyQualified(ModelRoot modelRoot, string typeName) return modelEnum != null ? $"{modelEnum.FullName}.{parts.Last()}" : typeName; } -void WritePersistentProperties(ModelClass modelClass) +void WritePersistentProperties(ModelClass modelClass, bool b_isIdentityBase) { if (!modelClass.Attributes.Any(x => x.Persistent)) return; @@ -574,8 +647,11 @@ void WritePersistentProperties(ModelClass modelClass) List segments = new List(); + bool b_attr_hasCustomKeyOverride = modelClass.Attributes.Any(x => x.Persistent && x.CustomAttributes.Contains("Key,")); + foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.Persistent)) { + if (isIdentityBaseWithoutPK(modelClass.Name) && modelAttribute.Name.ToLower() == "id") continue; segments.Clear(); if (modelAttribute.IsIdentity) @@ -634,18 +710,20 @@ void WritePersistentProperties(ModelClass modelClass) modelAttribute.SetterVisibility == SetterAccessModifier.Internal ? "internal " : string.Empty; - GeneratePropertyAnnotations(modelAttribute); + GeneratePropertyAnnotations(modelAttribute, b_attr_hasCustomKeyOverride); + String overrideText = (b_isIdentityBase)?"override ":""; + if (!string.IsNullOrWhiteSpace(modelAttribute.CustomAttributes)) Output($"[{modelAttribute.CustomAttributes.Trim('[',']')}]"); if (modelAttribute.IsConcurrencyToken || modelAttribute.AutoProperty) { - Output($"public {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name} {{ get; {setterVisibility}set; }}"); + Output($"public {overrideText}{modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name} {{ get; {setterVisibility}set; }}"); } else { - Output($"public {modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name}"); + Output($"public {overrideText}{modelAttribute.FQPrimitiveType}{nullable} {modelAttribute.Name}"); Output("{"); Output("get"); Output("{"); @@ -682,9 +760,9 @@ void WritePersistentProperties(ModelClass modelClass) } } -void GeneratePropertyAnnotations(ModelAttribute modelAttribute, string namePrefix = null) +void GeneratePropertyAnnotations(ModelAttribute modelAttribute, bool b_attr_hasCustomKeyOverride=false, string namePrefix = null) { - if (modelAttribute.IsIdentity) Output("[Key]"); + if (!b_attr_hasCustomKeyOverride && modelAttribute.IsIdentity) Output("[Key]"); if (modelAttribute.Required) Output("[Required]"); if (modelAttribute.IsConcurrencyToken) Output("[ConcurrencyCheck]"); if (modelAttribute.FQPrimitiveType == "string")