Skip to content

Commit

Permalink
simple recipe for using minimal api projects with oakton
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy D. Miller authored and Jeremy D. Miller committed Nov 29, 2021
1 parent 5920c3c commit fa22314
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 60 deletions.
6 changes: 5 additions & 1 deletion build/build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ private static void Main(string[] args)
Directory.SetCurrentDirectory(Path.Combine(original, "src", "EnvironmentCheckDemonstrator"));
RunCurrentProject("help");
RunCurrentProject("describe");
Directory.SetCurrentDirectory(Path.Combine(original, "src", "MinimalApi"));
RunCurrentProject("help");
RunCurrentProject("describe");
});


Expand Down Expand Up @@ -105,7 +109,7 @@ private static void Main(string[] args)

private static void RunCurrentProject(string args)
{
Run("dotnet", $"run --framework net5.0 --no-build --no-restore -- {args}");
Run("dotnet", $"run --no-build --no-restore -- {args}");
}


Expand Down
13 changes: 13 additions & 0 deletions src/MinimalApi/MinimalApi.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Oakton\Oakton.csproj" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions src/MinimalApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Oakton;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
return app.RunOaktonCommandsSynchronously(args);
28 changes: 28 additions & 0 deletions src/MinimalApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43449",
"sslPort": 44365
}
},
"profiles": {
"MinimalApi": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7197;http://localhost:5197",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
8 changes: 8 additions & 0 deletions src/MinimalApi/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions src/MinimalApi/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
6 changes: 0 additions & 6 deletions src/MvcApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env)
app.UseStaticFiles();
app.UseCookiePolicy();

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
6 changes: 6 additions & 0 deletions src/Oakton.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerService", "WorkerServ
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net5WebApi", "Net5WebApi\Net5WebApi.csproj", "{59731519-F9A1-4FE2-9525-DDF4214D2E77}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinimalApi", "MinimalApi\MinimalApi.csproj", "{307092A0-7161-4FD0-9B4B-19A34F323C3A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -81,6 +83,10 @@ Global
{59731519-F9A1-4FE2-9525-DDF4214D2E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59731519-F9A1-4FE2-9525-DDF4214D2E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59731519-F9A1-4FE2-9525-DDF4214D2E77}.Release|Any CPU.Build.0 = Release|Any CPU
{307092A0-7161-4FD0-9B4B-19A34F323C3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{307092A0-7161-4FD0-9B4B-19A34F323C3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{307092A0-7161-4FD0-9B4B-19A34F323C3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{307092A0-7161-4FD0-9B4B-19A34F323C3A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
27 changes: 26 additions & 1 deletion src/Oakton/CommandLineHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace Oakton

public static class CommandLineHostingExtensions
{

/// <summary>
/// Execute the extended Oakton command line support for your configured WebHostBuilder.
/// This method would be called within the Task&lt;int&gt; Program.Main(string[] args) method
Expand All @@ -25,6 +24,32 @@ public static Task<int> RunOaktonCommands(this IHostBuilder builder, string[] ar
{
return Execute(builder, Assembly.GetEntryAssembly(), args);
}

/// <summary>
/// Execute the extended Oakton command line support for your configured IHost.
/// This method would be called within the Task&lt;int&gt; Program.Main(string[] args) method
/// of your AspNetCore application. This usage is appropriate for WebApplication bootstrapping
/// </summary>
/// <param name="host">An already built IHost</param>
/// <param name="args"></param>
/// <returns></returns>
public static Task<int> RunOaktonCommands(this IHost host, string[] args)
{
return Execute(new PreBuiltHostBuilder(host), Assembly.GetEntryAssembly(), args);
}

/// <summary>
/// Execute the extended Oakton command line support for your configured IHost.
/// This method would be called within the Task&lt;int&gt; Program.Main(string[] args) method
/// of your AspNetCore application. This usage is appropriate for WebApplication bootstrapping
/// </summary>
/// <param name="host">An already built IHost</param>
/// <param name="args"></param>
/// <returns></returns>
public static int RunOaktonCommandsSynchronously(this IHost host, string[] args)
{
return Execute(new PreBuiltHostBuilder(host), Assembly.GetEntryAssembly(), args).GetAwaiter().GetResult();
}


internal static Task<int> Execute(IHostBuilder runtimeSource, Assembly applicationAssembly, string[] args)
Expand Down
8 changes: 6 additions & 2 deletions src/Oakton/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ public override async Task<bool> Execute(RunInput input)
{
using var host = input.BuildHost();
if (input.CheckFlag)
(await EnvironmentChecker.ExecuteAllEnvironmentChecks(host.Services)).Assert();
{
var checks = await EnvironmentChecker.ExecuteAllEnvironmentChecks(host.Services);
checks.Assert();
}

var reset = new ManualResetEventSlim();
AssemblyLoadContext.GetLoadContext(typeof (RunCommand).GetTypeInfo().Assembly).Unloading += (Action<AssemblyLoadContext>) (context => reset.Set());
// ReSharper disable once PossibleNullReferenceException
AssemblyLoadContext.GetLoadContext(typeof (RunCommand).Assembly).Unloading += (Action<AssemblyLoadContext>) (context => reset.Set());
Console.CancelKeyPress += (ConsoleCancelEventHandler) ((sender, eventArgs) =>
{
reset.Set();
Expand Down
103 changes: 53 additions & 50 deletions src/Oakton/Descriptions/DescribeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,71 +16,74 @@ public class DescribeCommand : OaktonAsyncCommand<DescribeInput>
{
public override async Task<bool> Execute(DescribeInput input)
{
input.HostBuilder.ConfigureServices(x => x.AddTransient<IDescribedSystemPart, ConfigurationPreview>());
using var host = input.BuildHost();

var config = host.Services.GetRequiredService<IConfiguration>();
var configurationPreview = new ConfigurationPreview(config);

using (var host = input.BuildHost())
{
var config = host.Services.GetRequiredService<IConfiguration>();
var hosting = host.Services.GetService<IHostEnvironment>();
var about = new AboutThisAppPart(hosting, config);
var hosting = host.Services.GetService<IHostEnvironment>();
var about = new AboutThisAppPart(hosting, config);
var builtInDescribers = new IDescribedSystemPart[]{about, configurationPreview, new ReferencedAssemblies()};

var factories = host.Services.GetServices<IDescribedSystemPartFactory>();
var parts = host.Services.GetServices<IDescribedSystemPart>()
.Concat(factories.SelectMany(x => x.Parts()))
.Concat(new IDescribedSystemPart[]{about, new ReferencedAssemblies()}).ToArray();
var factories = host.Services.GetServices<IDescribedSystemPartFactory>();

var parts = host.Services.GetServices<IDescribedSystemPart>()
.Concat(factories.SelectMany(x => x.Parts()))
.Concat(builtInDescribers).ToArray();

foreach (var partWithServices in parts.OfType<IRequiresServices>())
{
partWithServices.Resolve(host.Services);
}

foreach (var partWithServices in parts.OfType<IRequiresServices>())
if (input.ListFlag)
{
Console.WriteLine("The registered system parts are");
foreach (var part in parts)
{
partWithServices.Resolve(host.Services);
Console.WriteLine("* " + part.Title);
}

if (input.ListFlag)
{
Console.WriteLine("The registered system parts are");
foreach (var part in parts)
{
Console.WriteLine("* " + part.Title);
}
return true;
}

return true;
}
if (input.TitleFlag.IsNotEmpty())
{
parts = parts.Where(x => x.Title == input.TitleFlag).ToArray();
}
else if (input.InteractiveFlag)
{
var prompt = new MultiSelectionPrompt<string>()
.Title("What part(s) of your application do you wish to view?")
.PageSize(10)
.AddChoices(parts.Select(x => x.Title));

if (input.TitleFlag.IsNotEmpty())
{
parts = parts.Where(x => x.Title == input.TitleFlag).ToArray();
}
else if (input.InteractiveFlag)
{
var prompt = new MultiSelectionPrompt<string>()
.Title("What part(s) of your application do you wish to view?")
.PageSize(10)
.AddChoices(parts.Select(x => x.Title));
var titles = AnsiConsole.Prompt(prompt);

var titles = AnsiConsole.Prompt(prompt);
parts = parts.Where(x => titles.Contains(x.Title)).ToArray();
}

parts = parts.Where(x => titles.Contains(x.Title)).ToArray();
}
if (!input.SilentFlag)
{
await WriteToConsole(parts);
}

if (!input.SilentFlag)
{
await WriteToConsole(parts);
}
if (!input.FileFlag.IsNotEmpty())
{
return true;
}

if (input.FileFlag.IsNotEmpty())
{
using (var stream = new FileStream(input.FileFlag, FileMode.CreateNew, FileAccess.Write))
{
var writer = new StreamWriter(stream);
await using (var stream = new FileStream(input.FileFlag, FileMode.CreateNew, FileAccess.Write))
{
var writer = new StreamWriter(stream);

await WriteText(parts, writer);
await writer.FlushAsync();
}
await WriteText(parts, writer);
await writer.FlushAsync();
}

Console.WriteLine("Wrote system description to file " + input.FileFlag);
}
Console.WriteLine("Wrote system description to file " + input.FileFlag);

return true;
}
return true;
}


Expand Down
57 changes: 57 additions & 0 deletions src/Oakton/PreBuiltHostBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Oakton
{
internal class PreBuiltHostBuilder : IHostBuilder
{
private readonly IHost _host;
private readonly string _notSupportedMessage;

public PreBuiltHostBuilder(IHost host)
{
_host = host;
_notSupportedMessage = $"The IHost ({_host}) is already constructed. See https://jasperfx.github.io/oakton for alternative bootstrapping to enable this feature.";
}

public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
throw new NotSupportedException(_notSupportedMessage);
}

public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
throw new NotSupportedException(_notSupportedMessage);
}

public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
throw new NotSupportedException(_notSupportedMessage);
}

public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
throw new NotSupportedException(_notSupportedMessage);
}

public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
{
throw new NotSupportedException(_notSupportedMessage);
}

public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
{
throw new NotSupportedException(_notSupportedMessage);
}

public IHost Build()
{
return _host;
}

public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
}
}

0 comments on commit fa22314

Please sign in to comment.