Use Generic Host to enable DI for BlinForms apps

- Wrap BlinForms startup logic in an IHostedService
- Update BlinForms TodoApp sample to use DI to store the counter state
This commit is contained in:
Eilon Lipton 2019-09-10 09:48:52 -07:00
Родитель 4dd96140e8 2da0337c77
Коммит 9f6430bd77
14 изменённых файлов: 215 добавлений и 43 удалений

Просмотреть файл

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xamarin.Forms;
namespace Blaxamarin.Framework
@ -11,7 +12,7 @@ namespace Blaxamarin.Framework
var serviceCollection = new ServiceCollection();
var serviceProvider = serviceCollection.BuildServiceProvider();
var renderer = new BlaxamarinRenderer(serviceProvider);
var renderer = new BlaxamarinRenderer(serviceProvider, new LoggerFactory());
var result = renderer.Dispatcher.InvokeAsync(async () =>
{
await renderer.AddComponent<T>();

Просмотреть файл

@ -1,4 +1,5 @@
using Emblazon;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using Xamarin.Forms;
@ -7,8 +8,8 @@ namespace Blaxamarin.Framework
{
public class BlaxamarinRenderer : EmblazonRenderer<IFormsControlHandler>
{
public BlaxamarinRenderer(IServiceProvider serviceProvider)
: base(serviceProvider)
public BlaxamarinRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
: base(serviceProvider, loggerFactory)
{
}

Просмотреть файл

@ -11,6 +11,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0-preview9.19423.4" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -1,25 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using System.Windows.Forms;
namespace BlinForms.Framework
{
public static class BlinForms
{
public static void Run<T>() where T : IComponent
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var serviceCollection = new ServiceCollection();
var serviceProvider = serviceCollection.BuildServiceProvider();
var renderer = new BlinFormsRenderer(serviceProvider);
renderer.Dispatcher.InvokeAsync(() =>
{
renderer.AddComponent<T>();
Application.Run(renderer.RootForm);
});
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace BlinForms.Framework
{
public static class BlinFormsHostBuilderExtensions
{
/// <summary>
/// Registers <see cref="BlinFormsHostedService"/> in the DI container. Call this as part of configuring the
/// host to enable BlinForms.
/// </summary>
/// <param name="hostBuilder"></param>
/// <returns></returns>
public static IHostBuilder AddBlinForms(this IHostBuilder hostBuilder)
{
if (hostBuilder is null)
{
throw new ArgumentNullException(nameof(hostBuilder));
}
hostBuilder.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<BlinFormsHostedService>();
});
return hostBuilder;
}
}
}

Просмотреть файл

@ -0,0 +1,57 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BlinForms.Framework
{
/// <summary>
/// An implementation of <see cref="IHostedService"/> that controls the lifetime of a BlinForms application.
/// When this service starts, it loads the main form registered by
/// <see cref="BlinFormsServiceCollectionExtensions.AddBlinFormsMainForm{TComponent}(Microsoft.Extensions.DependencyInjection.IServiceCollection)"/>.
/// The service will request that the application stops when the main form is closed.
/// </summary>
public class BlinFormsHostedService : IHostedService
{
private readonly IBlinFormsMainFormType _blinFormsMainForm;
private readonly ILoggerFactory _loggerFactory;
private readonly IServiceProvider _serviceProvider;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
public BlinFormsHostedService(IBlinFormsMainFormType blinFormsMainForm, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IHostApplicationLifetime hostApplicationLifetime)
{
_blinFormsMainForm = blinFormsMainForm;
_loggerFactory = loggerFactory;
_serviceProvider = serviceProvider;
_hostApplicationLifetime = hostApplicationLifetime;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var renderer = new BlinFormsRenderer(_serviceProvider, _loggerFactory);
await renderer.Dispatcher.InvokeAsync(() =>
{
renderer.AddComponent(_blinFormsMainForm.MainFormType);
var rootForm = renderer.RootForm;
rootForm.FormClosed += OnRootFormFormClosed;
Application.Run(rootForm);
});
}
private void OnRootFormFormClosed(object sender, FormClosedEventArgs e)
{
// When the main form closes, request for the application to stop
_hostApplicationLifetime.StopApplication();
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Components;
using System;
namespace BlinForms.Framework
{
public class BlinFormsMainFormType<TComponent> : IBlinFormsMainFormType
where TComponent : IComponent
{
public BlinFormsMainFormType()
{
MainFormType = typeof(TComponent);
}
public Type MainFormType { get; }
}
}

Просмотреть файл

@ -1,4 +1,5 @@
using Emblazon;
using Microsoft.Extensions.Logging;
using System;
using System.Windows.Forms;
@ -6,8 +7,8 @@ namespace BlinForms.Framework
{
public class BlinFormsRenderer : EmblazonRenderer<IWindowsFormsControlHandler>
{
public BlinFormsRenderer(IServiceProvider serviceProvider)
: base(serviceProvider)
public BlinFormsRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
: base(serviceProvider, loggerFactory)
{
}

Просмотреть файл

@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace BlinForms.Framework
{
public static class BlinFormsServiceCollectionExtensions
{
/// <summary>
/// Registers a BlinForms component (typically from a .razor file) as the initial form to load when the
/// application start.
/// </summary>
/// <typeparam name="TComponent"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddBlinFormsMainForm<TComponent>(this IServiceCollection services)
where TComponent : class, IComponent
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton<IBlinFormsMainFormType, BlinFormsMainFormType<TComponent>>();
return services;
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
using System;
namespace BlinForms.Framework
{
public interface IBlinFormsMainFormType
{
Type MainFormType { get; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
namespace BlinFormsSample
{
public class AppState
{
public List<TodoItem> items = new List<TodoItem>();
public int Counter { get; set; }
public void ResetAppState()
{
items.AddRange(
new[]
{
new TodoItem { Text = "sell dog", IsDone = true },
new TodoItem { Text = "buy cat" },
new TodoItem { Text = "buy cat food" },
});
Counter = 0;
}
public bool IsEmptyAppState()
{
return
!items.Any() &&
Counter == 0;
}
}
}

Просмотреть файл

@ -1,13 +1,13 @@
<Panel Width="200" Height="100" BackColor="SystemColors.Info">
@inject AppState AppState
<Panel Width="200" Height="100" BackColor="SystemColors.Info">
<Button Width="40" Text="+1" OnClick="@HandleClick" />
<Label Left="50" Width="200" Text="@("You have pressed " + clickCount + " times")" />
<Label Left="50" Width="200" Text="@("You have pressed " + AppState.Counter + " times")" />
</Panel>
@code {
int clickCount = 0;
void HandleClick()
{
clickCount++;
AppState.Counter++;
}
}

Просмотреть файл

@ -1,13 +1,28 @@
using System;
using BlinForms.Framework;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace BlinFormsSample
{
static class Program
{
[STAThread]
static void Main()
static async Task Main()
{
BlinForms.Framework.BlinForms.Run<TodoApp>();
await Host.CreateDefaultBuilder()
.AddBlinForms()
.ConfigureServices((hostContext, services) =>
{
// Register app-specific services
services.AddSingleton<AppState>();
// Configure main form to load at startup
services.AddBlinFormsMainForm<TodoApp>();
})
.Build()
.RunAsync();
}
}
}

Просмотреть файл

@ -15,8 +15,8 @@ namespace Emblazon
private readonly Dictionary<ulong, Action> _eventRegistrations = new Dictionary<ulong, Action>();
public EmblazonRenderer(IServiceProvider serviceProvider)
: base(serviceProvider, new LoggerFactory())
public EmblazonRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
: base(serviceProvider, loggerFactory)
{
}
@ -27,9 +27,14 @@ namespace Emblazon
public override Dispatcher Dispatcher { get; }
= Dispatcher.CreateDefault();
public Task AddComponent<T>() where T : IComponent
public async Task AddComponent<TComponent>() where TComponent : IComponent
{
var component = InstantiateComponent(typeof(T));
await AddComponent(typeof(TComponent));
}
public async Task AddComponent(Type componentType)
{
var component = InstantiateComponent(componentType);
var componentId = AssignRootComponentId(component);
var rootControl = CreateRootControl();
@ -39,7 +44,7 @@ namespace Emblazon
};
_componentIdToAdapter[componentId] = rootAdapter;
return RenderRootComponentAsync(componentId);
await RenderRootComponentAsync(componentId);
}
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)