Skip to content

Commit

Permalink
Add List-Services command to Prompt (#3932)
Browse files Browse the repository at this point in the history
* Load DNN types first in DI startup

Fixes #3335

* Simplify main DI Startup class

It doesn't need to implement IDnnStartup, it can just configure the
service provider and be done.  This also allows us to stop ignoring
other IDnnStartup instances in the DotNetNuke.Web project, were they to
be created later.

* Fix error message when calling ConfigureServices

* Move AddWebApi call to regular IDnnStartup

This ensures that it's called at the right time relative to all other DI
configuration.

* Fix ordering of IDnnStartup for real

* Add List-Services debugging command for Prompt
  • Loading branch information
bdukes authored Jul 27, 2020
1 parent 63ea5cd commit 9fd6b58
Show file tree
Hide file tree
Showing 6 changed files with 731 additions and 621 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ private void Application_Start(object sender, EventArgs eventArgs)
var name = Config.GetSetting("ServerName");
Globals.ServerName = string.IsNullOrEmpty(name) ? Dns.GetHostName() : name;

Globals.DependencyProvider = new LazyServiceProvider();
var startup = new Startup();
(Globals.DependencyProvider as LazyServiceProvider).SetProvider(startup.DependencyProvider);
var dependencyProvider = new LazyServiceProvider();
Globals.DependencyProvider = dependencyProvider;
dependencyProvider.SetProvider(DependencyInjectionInitialize.BuildServiceProvider());
ServiceRequestScopeModule.SetServiceProvider(Globals.DependencyProvider);

ComponentFactory.Container = new SimpleContainer();
Expand Down
72 changes: 72 additions & 0 deletions DNN Platform/DotNetNuke.Web/DependencyInjectionInitialize.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information
namespace DotNetNuke.Web
{
using System;
using System.Linq;

using DotNetNuke.DependencyInjection;
using DotNetNuke.DependencyInjection.Extensions;
using DotNetNuke.Instrumentation;
using DotNetNuke.Services.DependencyInjection;

using Microsoft.Extensions.DependencyInjection;

/// <summary>Initializes the Dependency Injection container.</summary>
public static class DependencyInjectionInitialize
{
private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(DependencyInjectionInitialize));

internal static IServiceCollection ServiceCollection { get; private set; }

/// <summary>Builds the service provider.</summary>
/// <returns>An <see cref="IServiceProvider"/> instance.</returns>
public static IServiceProvider BuildServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<IScopeAccessor, ScopeAccessor>();
ConfigureAllStartupServices(services);

ServiceCollection = services;
return services.BuildServiceProvider();
}

private static void ConfigureAllStartupServices(IServiceCollection services)
{
var startupTypes = AppDomain.CurrentDomain.GetAssemblies()
.OrderBy(
assembly => assembly.FullName.StartsWith("DotNetNuke", StringComparison.OrdinalIgnoreCase) ? 0 :
assembly.FullName.StartsWith("DNN", StringComparison.OrdinalIgnoreCase) ? 1 : 2)
.ThenBy(assembly => assembly.FullName)
.SelectMany(assembly => assembly.SafeGetTypes().OrderBy(type => type.FullName ?? type.Name))
.Where(type => typeof(IDnnStartup).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract);

var startupInstances = startupTypes.Select(CreateInstance).Where(x => x != null);
foreach (var startup in startupInstances)
{
try
{
startup.ConfigureServices(services);
}
catch (Exception ex)
{
Logger.Error($"Unable to configure services for {startup.GetType().FullName}, see exception for details", ex);
}
}
}

private static IDnnStartup CreateInstance(Type startupType)
{
try
{
return (IDnnStartup)Activator.CreateInstance(startupType);
}
catch (Exception ex)
{
Logger.Error($"Unable to instantiate startup code for {startupType.FullName}", ex);
return null;
}
}
}
}
2 changes: 2 additions & 0 deletions DNN Platform/DotNetNuke.Web/DotNetNuke.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
<Compile Include="ConfigSection\AuthServicesConfiguration.cs" />
<Compile Include="ConfigSection\MessageHandlerEntry.cs" />
<Compile Include="ConfigSection\MessageHandlersCollection.cs" />
<Compile Include="Prompt\ListServices.cs" />
<Compile Include="Models\ModuleDetail.cs" />
<Compile Include="Models\ModuleInstance.cs" />
<Compile Include="Models\SiteDetail.cs" />
Expand Down Expand Up @@ -259,6 +260,7 @@
<Compile Include="Common\SharedConstants.cs" />
<Compile Include="Services\MobileHelperController.cs" />
<Compile Include="Services\ServiceRouteMapper.cs" />
<Compile Include="DependencyInjectionInitialize.cs" />
<Compile Include="Startup.cs" />
<Compile Include="UI\WebControls\DnnDropDownListState.cs" />
<Compile Include="UI\WebControls\DnnFileDropDownList.cs" />
Expand Down
85 changes: 85 additions & 0 deletions DNN Platform/DotNetNuke.Web/Prompt/ListServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information
namespace DotNetNuke.Web.Prompt
{
using System;
using System.Linq;
using System.Net;

using DotNetNuke.Abstractions.Portals;
using DotNetNuke.Abstractions.Prompt;
using DotNetNuke.Abstractions.Users;
using DotNetNuke.Prompt;

using Microsoft.Extensions.DependencyInjection;

/// <summary>
/// This is a (Prompt) Console Command. You should not reference this class directly. It is to be used solely through Prompt.
/// </summary>
[ConsoleCommand("list-services", "Prompt_DebugCategory", "Prompt_ListServices_Description")]
public class ListServices : ConsoleCommand
{
public override string LocalResourceFile => Constants.DefaultPromptResourceFile;

public override void Initialize(string[] args, IPortalSettings portalSettings, IUserInfo userInfo, int activeTabId)
{
base.Initialize(args, portalSettings, userInfo, activeTabId);
if (!userInfo.IsSuperUser)
{
this.AddMessage(this.LocalizeString("Prompt_ListServices_Unauthorized"));
}

this.ParseParameters(this);
}

public override IConsoleResultModel Run()
{
if (!this.User.IsSuperUser)
{
return new ConsoleErrorResultModel(this.LocalizeString("Prompt_ListServices_Unauthorized"));
}

var services = DependencyInjectionInitialize.ServiceCollection.Select(
descriptor => new
{
LifeTime = descriptor.Lifetime.ToString("G"),
Service = this.GetTypeName(descriptor.ServiceType),
Implementation = this.GetImplementationText(descriptor),
})
.OrderBy(desc => desc.Service)
.ThenBy(desc => desc.Implementation)
.ToList();
return new ConsoleResultModel
{
Data = services,
Records = services.Count,
};
}

private string GetImplementationText(ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance != null)
{
return this.LocalizeString("Prompt_ListServices_ImplementationInstance");
}

if (descriptor.ImplementationFactory != null)
{
return this.LocalizeString("Prompt_ListServices_ImplementationFactory");
}

return this.GetTypeName(descriptor.ImplementationType);
}

private string GetTypeName(Type type)
{
if (type == null)
{
return this.LocalizeString("Prompt_ListServices_None");
}

return WebUtility.HtmlEncode(type.FullName ?? type.Name);
}
}
}
91 changes: 15 additions & 76 deletions DNN Platform/DotNetNuke.Web/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,81 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information
namespace DotNetNuke.Web
{
using DotNetNuke.DependencyInjection;
using DotNetNuke.Web.Extensions;

namespace DotNetNuke.Web
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;

using DotNetNuke.DependencyInjection;
using DotNetNuke.DependencyInjection.Extensions;
using DotNetNuke.Instrumentation;
using DotNetNuke.Services.DependencyInjection;
using DotNetNuke.Web.Extensions;
using Microsoft.Extensions.DependencyInjection;

public class Startup : IDnnStartup
{
private static readonly ILog _logger = LoggerSource.Instance.GetLogger(typeof(Startup));

public Startup()
{
this.Configure();
}

public IServiceProvider DependencyProvider { get; private set; }

public void ConfigureServices(IServiceCollection services)
{
var startupTypes = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x != Assembly.GetAssembly(typeof(Startup)))
.SelectMany(x => x.SafeGetTypes())
.Where(x => typeof(IDnnStartup).IsAssignableFrom(x) &&
x.IsClass &&
!x.IsAbstract);

var startupInstances = startupTypes
.Select(x => this.CreateInstance(x))
.Where(x => x != null);

foreach (IDnnStartup startup in startupInstances)
{
try
{
startup.ConfigureServices(services);
}
catch (Exception ex)
{
_logger.Error($"Unable to configure services for {typeof(Startup).FullName}, see exception for details", ex);
}
}

services.AddWebApi();
}

private void Configure()
{
var services = new ServiceCollection();
services.AddSingleton<IScopeAccessor, ScopeAccessor>();
this.ConfigureServices(services);
this.DependencyProvider = services.BuildServiceProvider();
}

private object CreateInstance(Type startupType)
{
IDnnStartup startup = null;
try
{
startup = (IDnnStartup)Activator.CreateInstance(startupType);
}
catch (Exception ex)
{
_logger.Error($"Unable to instantiate startup code for {startupType.FullName}", ex);
}

return startup;
}
}
}
/// <summary>Configures services for The DotNetNuke.Web project.</summary>
public class Startup : IDnnStartup
{
/// <inheritdoc />
public void ConfigureServices(IServiceCollection services)
{
services.AddWebApi();
}
}
}
Loading

0 comments on commit 9fd6b58

Please sign in to comment.