| File: IdentityBuilderUIExtensions.cs | Web Access |
| Project: src\aspnetcore\src\Identity\UI\src\Microsoft.AspNetCore.Identity.UI.csproj (Microsoft.AspNetCore.Identity.UI) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity.UI; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.AspNetCore.Identity; /// <summary> /// Default UI extensions to <see cref="IdentityBuilder"/>. /// </summary> public static class IdentityBuilderUIExtensions { /// <summary> /// Adds a default, self-contained UI for Identity to the application using /// Razor Pages in an area named Identity. /// </summary> /// <remarks> /// In order to use the default UI, the application must be using <see cref="Microsoft.AspNetCore.Mvc"/>, /// <see cref="Microsoft.AspNetCore.StaticFiles"/> and contain a <c>_LoginPartial</c> partial view that /// can be found by the application. /// </remarks> /// <param name="builder">The <see cref="IdentityBuilder"/>.</param> /// <returns>The <see cref="IdentityBuilder"/>.</returns> public static IdentityBuilder AddDefaultUI(this IdentityBuilder builder) { builder.AddSignInManager(); builder.Services .AddMvc() .ConfigureApplicationPartManager(apm => { // We try to resolve the UI framework that was used by looking at the entry assembly. // When an app runs, the entry assembly will point to the built app. In some rare cases // (functional testing) the app assembly will be different, and we'll try to locate it through // the same mechanism that MVC uses today. // Finally, if for some reason we aren't able to find the assembly, we'll use our default value // (Bootstrap5) if (!TryResolveUIFramework(Assembly.GetEntryAssembly(), out var framework) && !TryResolveUIFramework(GetApplicationAssembly(builder), out framework)) { framework = default; } var parts = new ConsolidatedAssemblyApplicationPartFactory().GetApplicationParts(typeof(IdentityBuilderUIExtensions).Assembly); foreach (var part in parts) { apm.ApplicationParts.Add(part); } apm.FeatureProviders.Add(new ViewVersionFeatureProvider(framework)); }); builder.Services.ConfigureOptions( typeof(IdentityDefaultUIConfigureOptions<>) .MakeGenericType(builder.UserType)); builder.Services.TryAddTransient<IEmailSender, NoOpEmailSender>(); builder.Services.TryAddTransient(typeof(IEmailSender<>), typeof(DefaultMessageEmailSender<>)); return builder; } private static Assembly? GetApplicationAssembly(IdentityBuilder builder) { // This is the same logic that MVC follows to find the application assembly. var environment = builder.Services.Where(d => d.ServiceType == typeof(IWebHostEnvironment)).ToArray(); var applicationName = ((IWebHostEnvironment?)environment.LastOrDefault()?.ImplementationInstance) ?.ApplicationName; if (applicationName == null) { return null; } var appAssembly = Assembly.Load(applicationName); return appAssembly; } private static bool TryResolveUIFramework(Assembly? assembly, out UIFramework uiFramework) { uiFramework = default; var metadata = assembly?.GetCustomAttributes<UIFrameworkAttribute>() .SingleOrDefault()?.UIFramework; // Bootstrap5 is the default if (metadata == null) { return false; } // If we find the metadata there must be a valid framework here. if (!Enum.TryParse(metadata, ignoreCase: true, out uiFramework)) { var enumValues = string.Join(", ", Enum.GetNames(typeof(UIFramework)).Select(v => $"'{v}'")); throw new InvalidOperationException( $"Found an invalid value for the 'IdentityUIFrameworkVersion'. Valid values are {enumValues}"); } return true; } internal sealed class ViewVersionFeatureProvider : IApplicationFeatureProvider<ViewsFeature> { private readonly UIFramework _framework; public ViewVersionFeatureProvider(UIFramework framework) => _framework = framework; public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature) { var viewsToRemove = new List<CompiledViewDescriptor>(); foreach (var descriptor in feature.ViewDescriptors) { if (IsIdentityUIView(descriptor)) { switch (_framework) { case UIFramework.Bootstrap4: if (descriptor.Type?.FullName?.Contains("V5", StringComparison.Ordinal) is true) { // Remove V5 views viewsToRemove.Add(descriptor); } else { // Fix up paths to eliminate version subdir descriptor.RelativePath = descriptor.RelativePath.Replace("V4/", ""); } break; case UIFramework.Bootstrap5: if (descriptor.Type?.FullName?.Contains("V4", StringComparison.Ordinal) is true) { // Remove V4 views viewsToRemove.Add(descriptor); } else { // Fix up paths to eliminate version subdir descriptor.RelativePath = descriptor.RelativePath.Replace("V5/", ""); } break; default: throw new InvalidOperationException($"Unknown framework: {_framework}"); } } } foreach (var descriptorToRemove in viewsToRemove) { feature.ViewDescriptors.Remove(descriptorToRemove); } } private static bool IsIdentityUIView(CompiledViewDescriptor desc) => desc.RelativePath.StartsWith("/Areas/Identity", StringComparison.OrdinalIgnoreCase) && desc.Type?.Assembly == typeof(IdentityBuilderUIExtensions).Assembly; } }