Skip to content

Commit

Permalink
add identity scaffolder to dotnet-scaffold-aspnet (#2993)
Browse files Browse the repository at this point in the history
* added files, not working yet

* templates good right now, taking care of nullability now

* looks all good now, getting the PR out soon.

* minor fixes

* minor fixes

* added TODOs
  • Loading branch information
deepchoudhery authored Oct 4, 2024
1 parent 3d4e2de commit 2420991
Show file tree
Hide file tree
Showing 313 changed files with 36,041 additions and 363 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal ScaffolderContext(IScaffolder scaffolder)
}

public IScaffolder Scaffolder { get; }
//TODO : add a 'T GetProperty<T>(string)' to easily fetch values from the 'Properties' bucket.
public Dictionary<string, object?> Properties { get; } = [];
public Dictionary<ScaffolderOption, object?> OptionResults { get; } = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public static class CodeModifierPropertyConstants
public const string DbContextName = "$(DbContextName)";
public const string DbContextNamespace = "$(DbContextNamespace)";
//Identity related constants
public const string UserClassName = "$(UserClassName)";
public const string UserClassNamespace = "$(UserClassNamespace)";
public const string BlazorIdentityNamespace = "$(BlazorIdentityNamespace)";
public const string IdentityNamespace = "$(IdentityNamespace)";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"Usings": [
"Microsoft.AspNetCore.Identity",
"Microsoft.AspNetCore.Components.Authorization",
"$(BlazorIdentityNamespace)"
"$(IdentityNamespace)"
]
},
{
Expand Down Expand Up @@ -132,10 +132,10 @@
{
"ReplaceSnippet": [ "<Router AppAssembly=\"typeof(Program).Assembly\">" ],
"MultiLineBlock": [
"@using $(BlazorIdentityNamespace).Shared",
"@using $(IdentityNamespace).Shared",
"<Router AppAssembly= \"typeof(Program).Assembly\">"
],
"CheckBlock": "$(BlazorIdentityNamespace).Shared"
"CheckBlock": "$(IdentityNamespace).Shared"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"Files": [
{
"FileName": "Program.cs",
"Usings": [
"Microsoft.AspNetCore.Identity",
"Microsoft.EntityFrameworkCore",
"$(UserClassNamespace)"
],
"Options": [
"EfScenario"
],
"Methods": {
"Global": {
"CodeChanges": [
{
"InsertAfter": "WebApplication.CreateBuilder",
"CheckBlock": "builder.Configuration.GetConnectionString",
"Block": "\nvar connectionString = builder.Configuration.GetConnectionString(\"$(ConnectionStringName)\") ?? throw new InvalidOperationException(\"Connection string '$(ConnectionStringName)' not found.\")"
},
{
"InsertAfter": "builder.Configuration.GetConnectionString",
"CheckBlock": "builder.Services.AddDbContext",
"Block": "builder.Services.AddDbContext<$(DbContextName)>(options => options.$(UseDbMethod))",
"LeadingTrivia": {
"Newline": true
}
},
{
"InsertAfter": "builder.Services.AddDbContext",
"CheckBlock": "builder.Services.AddDefaultIdentity",
"Block": "builder.Services.AddDefaultIdentity<$(UserClassName)>(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<$(DbContextName)>()\"",
"LeadingTrivia": {
"Newline": true
}
}
]
}
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class EfConstants
public const string CosmosDb = "cosmos-efcore";
public const string Postgres = "npgsql-efcore";
public const string EfCorePackageName = "Microsoft.EntityFrameworkCore";
public const string EfCoreToolsPackageName = "Microsoft.EntityFrameworkCore.Tools";
public const string SqlServerPackageName = "Microsoft.EntityFrameworkCore.SqlServer";
public const string SqlitePackageName = "Microsoft.EntityFrameworkCore.Sqlite";
public const string CosmosPakcageName = "Microsoft.EntityFrameworkCore.Cosmos";
Expand Down Expand Up @@ -45,5 +46,6 @@ public static class AspNetCorePackages
public const string AspNetCoreDiagnosticsEfCorePackageName = "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore";
public const string OpenApiPackageName = "Microsoft.AspNetCore.OpenApi";
public const string AspNetCoreIdentityEfPackageName = "Microsoft.AspNetCore.Identity.EntityFrameworkCore";
public const string AspNetCoreIdentityUiPackageName = "Microsoft.AspNetCore.Identity.UI";
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using Microsoft.DotNet.Scaffolding.CodeModification;
using Microsoft.DotNet.Scaffolding.Core.Builder;
using Microsoft.DotNet.Scaffolding.Core.Steps;
Expand All @@ -22,12 +21,12 @@ public static IScaffoldBuilder WithBlazorIdentityCodeChangeStep(this IScaffoldBu
var step = config.Step;
var codeModificationFilePath = GlobalToolFileFinder.FindCodeModificationConfigFile("blazorIdentityChanges.json", System.Reflection.Assembly.GetExecutingAssembly());
//get needed properties and cast them as needed
config.Context.Properties.TryGetValue(nameof(BlazorIdentitySettings), out var blazorSettingsObj);
config.Context.Properties.TryGetValue(nameof(BlazorIdentityModel), out var blazorIdentityModelObj);
config.Context.Properties.TryGetValue(nameof(IdentitySettings), out var blazorSettingsObj);
config.Context.Properties.TryGetValue(nameof(IdentityModel), out var blazorIdentityModelObj);
config.Context.Properties.TryGetValue(Internal.Constants.StepConstants.CodeModifierProperties, out var codeModifierPropertiesObj);
var blazorIdentitySettings = blazorSettingsObj as BlazorIdentitySettings;
var blazorIdentitySettings = blazorSettingsObj as IdentitySettings;
var codeModifierProperties = codeModifierPropertiesObj as Dictionary<string, string>;
var blazorIdentityModel = blazorIdentityModelObj as BlazorIdentityModel;
var blazorIdentityModel = blazorIdentityModelObj as IdentityModel;
//initialize CodeModificationStep's properties
if (!string.IsNullOrEmpty(codeModificationFilePath) &&
Expand Down Expand Up @@ -60,9 +59,9 @@ public static IScaffoldBuilder WithBlazorIdentityTextTemplatingStep(this IScaffo
{
var step = config.Step;
var context = config.Context;
context.Properties.TryGetValue(nameof(BlazorIdentityModel), out var blazorIdentityModelObj);
BlazorIdentityModel blazorIdentityModel = blazorIdentityModelObj as BlazorIdentityModel ??
throw new InvalidOperationException("missing 'BlazorIdentityModel' in 'ScaffolderContext.Properties'");
context.Properties.TryGetValue(nameof(IdentityModel), out var blazorIdentityModelObj);
IdentityModel blazorIdentityModel = blazorIdentityModelObj as IdentityModel ??
throw new InvalidOperationException("missing 'IdentityModel' in 'ScaffolderContext.Properties'");
var templateFolderUtilities = new TemplateFoldersUtilities();
var allBlazorIdentityFiles = templateFolderUtilities.GetAllT4Templates(["BlazorIdentity"]);
var applicationUserFile = templateFolderUtilities.GetAllT4Templates(["Files"])
Expand Down Expand Up @@ -98,9 +97,12 @@ public static IScaffoldBuilder WithBlazorIdentityAddPackagesStep(this IScaffoldB
var context = config.Context;
List<string> packageList = [
PackageConstants.AspNetCorePackages.AspNetCoreIdentityEfPackageName,
PackageConstants.AspNetCorePackages.AspNetCoreDiagnosticsEfCorePackageName ];
if (context.Properties.TryGetValue(nameof(BlazorIdentitySettings), out var commandSettingsObj) &&
commandSettingsObj is BlazorIdentitySettings commandSettings)
PackageConstants.AspNetCorePackages.AspNetCoreDiagnosticsEfCorePackageName,
PackageConstants.EfConstants.EfCoreToolsPackageName
];
if (context.Properties.TryGetValue(nameof(IdentitySettings), out var commandSettingsObj) &&
commandSettingsObj is IdentitySettings commandSettings)
{
step.ProjectPath = commandSettings.Project;
step.Prerelease = commandSettings.Prerelease;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using Microsoft.DotNet.Scaffolding.CodeModification;
using Microsoft.DotNet.Scaffolding.Core.Builder;
using Microsoft.DotNet.Scaffolding.Core.Steps;
using Microsoft.DotNet.Scaffolding.Internal;
using Microsoft.DotNet.Scaffolding.TextTemplating;
using Microsoft.DotNet.Tools.Scaffold.AspNet.Common;
using Microsoft.DotNet.Tools.Scaffold.AspNet.Helpers;
using Microsoft.DotNet.Tools.Scaffold.AspNet.Models;
using Microsoft.DotNet.Tools.Scaffold.AspNet.ScaffoldSteps.Settings;

namespace Microsoft.DotNet.Scaffolding.Core.Hosting;

internal static class IdentityScaffolderBuilderExtensions
{
public static IScaffoldBuilder WithIdentityAddPackagesStep(this IScaffoldBuilder builder)
{
return builder.WithStep<AddPackagesStep>(config =>
{
var step = config.Step;
var context = config.Context;
List<string> packageList = [
PackageConstants.AspNetCorePackages.AspNetCoreIdentityEfPackageName,
PackageConstants.AspNetCorePackages.AspNetCoreIdentityUiPackageName,
PackageConstants.EfConstants.EfCoreToolsPackageName
];
if (context.Properties.TryGetValue(nameof(IdentitySettings), out var commandSettingsObj) &&
commandSettingsObj is IdentitySettings commandSettings)
{
step.ProjectPath = commandSettings.Project;
step.Prerelease = commandSettings.Prerelease;
if (!string.IsNullOrEmpty(commandSettings.DatabaseProvider) &&
PackageConstants.EfConstants.IdentityEfPackagesDict.TryGetValue(commandSettings.DatabaseProvider, out string? dbProviderPackageName))
{
packageList.Add(dbProviderPackageName);
}
step.PackageNames = packageList;
}
else
{
step.SkipStep = true;
return;
}
});
}

public static IScaffoldBuilder WithIdentityTextTemplatingStep(this IScaffoldBuilder builder)
{
builder = builder.WithStep<TextTemplatingStep>(config =>
{
var step = config.Step;
var context = config.Context;
context.Properties.TryGetValue(nameof(IdentityModel), out var blazorIdentityModelObj);
IdentityModel identityModel = blazorIdentityModelObj as IdentityModel ??
throw new InvalidOperationException("missing 'IdentityModel' in 'ScaffolderContext.Properties'");
var templateFolderUtilities = new TemplateFoldersUtilities();
//all the .cshtml and their model class (.cshtml.cs) templates
var allIdentityPageFiles = templateFolderUtilities.GetAllT4Templates(["Identity"]);
//ApplicationUser.tt template
var applicationUserFile = templateFolderUtilities.GetAllT4Templates(["Files"])
.FirstOrDefault(x => x.EndsWith("ApplicationUser.tt", StringComparison.OrdinalIgnoreCase));
var identityFileProperties = IdentityHelper.GetTextTemplatingProperties(allIdentityPageFiles, identityModel);
var applicationUserProperty = IdentityHelper.GetApplicationUserTextTemplatingProperty(applicationUserFile, identityModel);
if (applicationUserProperty is not null)
{
identityFileProperties = identityFileProperties.Append(applicationUserProperty);
}
if (identityFileProperties is not null && identityFileProperties.Any())
{
step.TextTemplatingProperties = identityFileProperties;
step.DisplayName = "Identity files";
step.Overwrite = identityModel.Overwrite;
}
else
{
step.SkipStep = true;
return;
}
});

return builder;
}

public static IScaffoldBuilder WithIdentityCodeChangeStep(this IScaffoldBuilder builder)
{
builder = builder.WithStep<CodeModificationStep>(config =>
{
var step = config.Step;
var codeModificationFilePath = GlobalToolFileFinder.FindCodeModificationConfigFile("identityChanges.json", System.Reflection.Assembly.GetExecutingAssembly());
//get needed properties and cast them as needed
config.Context.Properties.TryGetValue(nameof(IdentitySettings), out var identitySettingsObj);
config.Context.Properties.TryGetValue(nameof(IdentityModel), out var identityModelObj);
config.Context.Properties.TryGetValue(Internal.Constants.StepConstants.CodeModifierProperties, out var codeModifierPropertiesObj);
var identitySettings = identitySettingsObj as IdentitySettings;
var codeModifierProperties = codeModifierPropertiesObj as Dictionary<string, string>;
var identityModel = identityModelObj as IdentityModel;
//initialize CodeModificationStep's properties
if (!string.IsNullOrEmpty(codeModificationFilePath) &&
identitySettings is not null &&
codeModifierProperties is not null &&
identityModel is not null)
{
step.CodeModifierConfigPath = codeModificationFilePath;
foreach (var kvp in codeModifierProperties)
{
step.CodeModifierProperties.TryAdd(kvp.Key, kvp.Value);
}
step.ProjectPath = identitySettings.Project;
step.CodeChangeOptions = identityModel.ProjectInfo.CodeChangeOptions ?? [];
}
else
{
step.SkipStep = true;
return;
}
});

return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

namespace Microsoft.DotNet.Tools.Scaffold.AspNet.Helpers;

//TODO : combine with 'IdentityHelper', should be quite easy.
internal static class BlazorIdentityHelper
{
internal static IEnumerable<TextTemplatingProperty> GetTextTemplatingProperties(IEnumerable<string> allT4TemplatePaths, BlazorIdentityModel blazorIdentityModel)
internal static IEnumerable<TextTemplatingProperty> GetTextTemplatingProperties(IEnumerable<string> allT4TemplatePaths, IdentityModel blazorIdentityModel)
{
var textTemplatingProperties = new List<TextTemplatingProperty>();
foreach (var templatePath in allT4TemplatePaths)
Expand All @@ -26,7 +27,7 @@ internal static IEnumerable<TextTemplatingProperty> GetTextTemplatingProperties(
if (!string.IsNullOrEmpty(templatePath) && templateType is not null && !string.IsNullOrEmpty(projectName))
{
string extension = templateFullName.StartsWith("identity", StringComparison.OrdinalIgnoreCase) ? ".cs" : ".razor";
string templateNameWithNamespace = $"{blazorIdentityModel.BlazorIdentityNamespace}.{templateFullName}";
string templateNameWithNamespace = $"{blazorIdentityModel.IdentityNamespace}.{templateFullName}";
string outputFileName = $"{StringUtil.ToPath(templateNameWithNamespace, blazorIdentityModel.BaseOutputPath, projectName)}{extension}";
textTemplatingProperties.Add(new()
{
Expand Down Expand Up @@ -56,7 +57,7 @@ private static string GetFormattedRelativeIdentityFile(string fullFileName)
return string.Empty;
}

internal static TextTemplatingProperty? GetApplicationUserTextTemplatingProperty(string? applicationUserTemplate, BlazorIdentityModel blazorIdentityModel)
internal static TextTemplatingProperty? GetApplicationUserTextTemplatingProperty(string? applicationUserTemplate, IdentityModel blazorIdentityModel)
{
var projectDirectory = Path.GetDirectoryName(blazorIdentityModel.ProjectInfo.ProjectPath);
if (string.IsNullOrEmpty(applicationUserTemplate) || string.IsNullOrEmpty(projectDirectory))
Expand Down
Loading

0 comments on commit 2420991

Please sign in to comment.