private static void AddRelatedParts(IServiceCollection services, string framework) { var _assemblyMap = new Dictionary <UIFramework, string>() { [UIFramework.Bootstrap5] = "Microsoft.AspNetCore.Identity.UI.Views.V5", [UIFramework.Bootstrap4] = "Microsoft.AspNetCore.Identity.UI.Views.V4", }; var mvcBuilder = services .AddMvc() .ConfigureApplicationPartManager(partManager => { var thisAssembly = typeof(IdentityBuilderUIExtensions).Assembly; var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(thisAssembly, throwOnError: true); var relatedParts = relatedAssemblies.ToDictionary( ra => ra, CompiledRazorAssemblyApplicationPartFactory.GetDefaultApplicationParts); var selectedFrameworkAssembly = _assemblyMap[framework == "V4" ? UIFramework.Bootstrap4 : UIFramework.Bootstrap5]; foreach (var kvp in relatedParts) { var assemblyName = kvp.Key.GetName().Name; if (!IsAssemblyForFramework(selectedFrameworkAssembly, assemblyName)) { RemoveParts(partManager, kvp.Value); } else { AddParts(partManager, kvp.Value); } } bool IsAssemblyForFramework(string frameworkAssembly, string assemblyName) => string.Equals(assemblyName, frameworkAssembly, StringComparison.OrdinalIgnoreCase); void RemoveParts( ApplicationPartManager manager, IEnumerable <ApplicationPart> partsToRemove) { for (var i = 0; i < manager.ApplicationParts.Count; i++) { var part = manager.ApplicationParts[i]; if (partsToRemove.Any(p => string.Equals( p.Name, part.Name, StringComparison.OrdinalIgnoreCase))) { manager.ApplicationParts.Remove(part); } } } void AddParts( ApplicationPartManager manager, IEnumerable <ApplicationPart> partsToAdd) { foreach (var part in partsToAdd) { if (!manager.ApplicationParts.Any(p => p.GetType() == part.GetType() && string.Equals(p.Name, part.Name, StringComparison.OrdinalIgnoreCase))) { manager.ApplicationParts.Add(part); } } } }); }
private static void AddRelatedParts(IdentityBuilder builder) { var mvcBuilder = builder.Services .AddMvc() .ConfigureApplicationPartManager(partManager => { // 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 // (Bootstrap4) if (!TryResolveUIFramework(Assembly.GetEntryAssembly(), out var framework) && !TryResolveUIFramework(GetApplicationAssembly(builder), out framework)) { framework = default; } var thisAssembly = typeof(IdentityBuilderUIExtensions).Assembly; var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(thisAssembly, throwOnError: true); var relatedParts = relatedAssemblies.ToDictionary( ra => ra, CompiledRazorAssemblyApplicationPartFactory.GetDefaultApplicationParts); var selectedFrameworkAssembly = _assemblyMap[framework]; foreach (var kvp in relatedParts) { var assemblyName = kvp.Key.GetName().Name; if (!IsAssemblyForFramework(selectedFrameworkAssembly, assemblyName)) { RemoveParts(partManager, kvp.Value); } else { AddParts(partManager, kvp.Value); } } bool IsAssemblyForFramework(string frameworkAssembly, string assemblyName) => string.Equals(assemblyName, frameworkAssembly, StringComparison.OrdinalIgnoreCase); void RemoveParts( ApplicationPartManager manager, IEnumerable <ApplicationPart> partsToRemove) { for (var i = 0; i < manager.ApplicationParts.Count; i++) { var part = manager.ApplicationParts[i]; if (partsToRemove.Any(p => string.Equals( p.Name, part.Name, StringComparison.OrdinalIgnoreCase))) { manager.ApplicationParts.Remove(part); } } } void AddParts( ApplicationPartManager manager, IEnumerable <ApplicationPart> partsToAdd) { foreach (var part in partsToAdd) { if (!manager.ApplicationParts.Any(p => p.GetType() == part.GetType() && string.Equals(p.Name, part.Name, StringComparison.OrdinalIgnoreCase))) { manager.ApplicationParts.Add(part); } } } }); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure <CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); var rootDir = new DirectoryInfo(_hostingEnvironment.ContentRootPath); var dir = new DirectoryInfo(Path.Combine(rootDir.ToString(), "Modules")); List <string> assmemblyNameList = new List <string>(); foreach (var file in dir.GetFileSystemInfos("*.dll", SearchOption.AllDirectories)) { Assembly assembly; try { assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file.FullName); } catch (FileLoadException) { // Get loaded assembly. This assembly might be loaded assembly = Assembly.Load(new AssemblyName(Path.GetFileNameWithoutExtension(file.Name))); if (assembly == null) { throw; } } assmemblyNameList.Add(assembly.GetName().Name); } services.Configure <RazorViewEngineOptions>( options => { options.ViewLocationExpanders.Add(new ThemeableViewLocationExpander()); }); var mvcBuilder = services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); foreach (var item in assmemblyNameList) { mvcBuilder.AddRazorOptions(o => { o.AdditionalCompilationReferences.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName(item)).Location)); }); var pluginAssembly = Assembly.Load(new AssemblyName(item)); var partFactory = ApplicationPartFactory.GetApplicationPartFactory(pluginAssembly); foreach (var part in partFactory.GetApplicationParts(pluginAssembly)) { mvcBuilder.PartManager.ApplicationParts.Add(part); } var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(pluginAssembly, throwOnError: true); foreach (var assembly in relatedAssemblies) { partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); foreach (var part in partFactory.GetApplicationParts(assembly)) { mvcBuilder.PartManager.ApplicationParts.Add(part); } } } }
/// <summary> /// Initialize /// </summary> /// <param name="applicationPartManager">Application part manager</param> /// <param name="config">Config</param> public static void Initialize(ApplicationPartManager applicationPartManager, IHostingEnvironment hostingEnvironment, FrameworkPluginOptions options) { if (applicationPartManager == null) { throw new ArgumentNullException(nameof(applicationPartManager)); } if (hostingEnvironment == null) { throw new ArgumentNullException(nameof(hostingEnvironment)); } using (new DisposableWriteLock(Locker)) { // TODO: Add verbose exception handling / raising here since this is happening on app startup and could // prevent app from starting altogether var pluginFolder = new DirectoryInfo(CommonHelper.MapPath(PluginsPath)); shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(ShadowCopyPath)); reserveShadowCopyFolder = new DirectoryInfo(Path.Combine(CommonHelper.MapPath(ShadowCopyPath), $"{RESERVE_SHADOW_COPY_FOLDER_NAME}{DateTime.Now.ToFileTimeUtc()}")); var referencedPlugins = new List <PluginDescriptor>(); var incompatiblePlugins = new List <string>(); try { var installedPluginSystemNames = GetInstalledPluginNames(CommonHelper.MapPath(InstalledPluginsFilePath)); Debug.WriteLine("Creating shadow copy folder and querying for DLLs"); //ensure folders are created Directory.CreateDirectory(pluginFolder.FullName); Directory.CreateDirectory(shadowCopyFolder.FullName); //get list of all files in bin var binFiles = shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories); if (options.ClearPluginShadowDirectoryOnStartup) { //clear out shadow copied plugins foreach (var f in binFiles) { if (f.Name.Equals("placeholder.txt", StringComparison.InvariantCultureIgnoreCase)) { continue; } Debug.WriteLine("Deleting " + f.Name); try { //ignore index.htm var fileName = Path.GetFileName(f.FullName); if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) { continue; } File.Delete(f.FullName); } catch (Exception exc) { Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc); } } //delete all reserve folders foreach (var directory in shadowCopyFolder.GetDirectories(RESERVE_SHADOW_COPY_FOLDER_NAME_PATTERN, SearchOption.TopDirectoryOnly)) { try { CommonHelper.DeleteDirectory(directory.FullName); } catch { //do nothing } } } //load description files foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder)) { var descriptionFile = dfd.Key; var pluginDescriptor = dfd.Value; //ensure that version of plugin is valid if (!pluginDescriptor.SupportedVersions.Contains(CurrentVersion, StringComparer.InvariantCultureIgnoreCase)) { incompatiblePlugins.Add(pluginDescriptor.SystemName); continue; } //some validation if (string.IsNullOrWhiteSpace(pluginDescriptor.SystemName)) { throw new Exception($"A plugin '{descriptionFile.FullName}' has no system name. Try assigning the plugin a unique name and recompiling."); } if (referencedPlugins.Contains(pluginDescriptor)) { throw new Exception($"A plugin with '{pluginDescriptor.SystemName}' system name is already defined"); } //set 'Installed' property pluginDescriptor.Installed = installedPluginSystemNames .FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null; try { if (descriptionFile.Directory == null) { throw new Exception($"Directory cannot be resolved for '{descriptionFile.Name}' description file"); } //get list of all DLLs in plugins (not in bin!) var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories) //just make sure we're not registering shadow copied plugins .Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName)) .Where(x => IsPackagePluginFolder(x.Directory)) .ToList(); //other plugin description info var mainPluginFile = pluginFiles .FirstOrDefault(x => x.Name.Equals(pluginDescriptor.AssemblyFileName, StringComparison.InvariantCultureIgnoreCase)); //plugin have wrong directory if (mainPluginFile == null) { incompatiblePlugins.Add(pluginDescriptor.SystemName); continue; } pluginDescriptor.OriginalAssemblyFile = mainPluginFile; //shadow copy main plugin file var pluginAssembly = PerformFileDeploy(mainPluginFile, options); pluginDescriptor.ReferencedAssembly = pluginAssembly; var partFactory = ApplicationPartFactory.GetApplicationPartFactory(pluginAssembly); foreach (var part in partFactory.GetApplicationParts(pluginAssembly)) { applicationPartManager.ApplicationParts.Add(part); } var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(pluginAssembly, throwOnError: true); foreach (var assembly in relatedAssemblies) { partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); foreach (var part in partFactory.GetApplicationParts(assembly)) { applicationPartManager.ApplicationParts.Add(part); } } ////load all other referenced assemblies now //foreach (var plugin in pluginFiles // .Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase)) // .Where(x => !IsAlreadyLoaded(x))) // PerformFileDeploy(plugin, options); //init plugin type (only one plugin per assembly is allowed) foreach (var t in pluginAssembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(t)) { if (!t.IsInterface) { if (t.IsClass && !t.IsAbstract) { pluginDescriptor.PluginType = t; break; } } } } referencedPlugins.Add(pluginDescriptor); } catch (ReflectionTypeLoadException ex) { //add a plugin name. this way we can easily identify a problematic plugin var msg = $"Plugin '{pluginDescriptor.FriendlyName}'. "; foreach (var e in ex.LoaderExceptions) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); throw fail; } catch (Exception ex) { //add a plugin name. this way we can easily identify a problematic plugin var msg = $"Plugin '{pluginDescriptor.FriendlyName}'. {ex.Message}"; var fail = new Exception(msg, ex); throw fail; } } } catch (Exception ex) { var msg = string.Empty; for (var e = ex; e != null; e = e.InnerException) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); throw fail; } ReferencedPlugins = referencedPlugins; IncompatiblePlugins = incompatiblePlugins; } }