Skip to content

Commit

Permalink
feat!: Finished implementation, added tests, added description and RE…
Browse files Browse the repository at this point in the history
…ADME (#4)

* feat: Finished implementation, added tests, added description and README

* docs: Updated exception infos

* fix: Registered `IFormularProvider`

* chore: Added Integration Tests

* chore: Removed unused classes and activated sonarqube

* chore: Hide TestForm
  • Loading branch information
samtrion authored Mar 28, 2024
1 parent bf45396 commit 9885dd6
Show file tree
Hide file tree
Showing 23 changed files with 1,476 additions and 243 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ insert_final_newline = true
indent_style = space
trim_trailing_whitespace = true
charset = utf-8
end_of_line = lf
end_of_line = crlf

# Code files
[*.{cs,csx,vb,vbx}]
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ jobs:
name: Build & Tests
uses: dailydevops/pipelines/.github/workflows/cicd-dotnet.yml@main
with:
enableSonarQube: true
dotnet-logging: ${{ inputs.dotnet-logging }}
dotnet-version: |
6.x
7.x
8.x
solution: ./Extensions.Hosting.WinForms.sln
runs-on-build: windows-latest
runs-on-tests: windows-latest
Expand Down
25 changes: 18 additions & 7 deletions Extensions.Hosting.WinForms.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{63A3C280-DDDF-4BC1-9B8F-AC8D9F949B3E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.Extensions.Hosting.WinForms", "src\NetEvolve.Extensions.Hosting.WinForms\NetEvolve.Extensions.Hosting.WinForms.csproj", "{5501B170-5917-4568-9EAC-8853D14238A1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Extensions.Hosting.WinForms", "src\NetEvolve.Extensions.Hosting.WinForms\NetEvolve.Extensions.Hosting.WinForms.csproj", "{5501B170-5917-4568-9EAC-8853D14238A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8A14AF25-8CF0-494E-9D50-2FAE3CC9A50A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.Extensions.Hosting.WinForms.Tests.Unit", "tests\NetEvolve.Extensions.Hosting.WinForms.Tests.Unit\NetEvolve.Extensions.Hosting.WinForms.Tests.Unit.csproj", "{F7BA85AD-CBD1-4F99-BC28-42DF1253BAE8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Extensions.Hosting.WinForms.Tests.Unit", "tests\NetEvolve.Extensions.Hosting.WinForms.Tests.Unit\NetEvolve.Extensions.Hosting.WinForms.Tests.Unit.csproj", "{F7BA85AD-CBD1-4F99-BC28-42DF1253BAE8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.Extensions.Hosting.WinForms.Tests.Integration", "tests\NetEvolve.Extensions.Hosting.WinForms.Tests.Integration\NetEvolve.Extensions.Hosting.WinForms.Tests.Integration.csproj", "{22F991BF-06C3-4B35-BCA2-DD61959C187F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Extensions.Hosting.WinForms.Tests.Integration", "tests\NetEvolve.Extensions.Hosting.WinForms.Tests.Integration\NetEvolve.Extensions.Hosting.WinForms.Tests.Integration.csproj", "{22F991BF-06C3-4B35-BCA2-DD61959C187F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.Extensions.Hosting.WinForms.Tests.Architecture", "tests\NetEvolve.Extensions.Hosting.WinForms.Tests.Architecture\NetEvolve.Extensions.Hosting.WinForms.Tests.Architecture.csproj", "{DF47FD5F-2F7F-470B-98A2-9C5266AA0FBD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.Extensions.Hosting.WinForms.Tests.Architecture", "tests\NetEvolve.Extensions.Hosting.WinForms.Tests.Architecture\NetEvolve.Extensions.Hosting.WinForms.Tests.Architecture.csproj", "{DF47FD5F-2F7F-470B-98A2-9C5266AA0FBD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "xample", "xample", "{B241FEA0-8C20-49E2-BD16-36ACBD4A00C9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xample.Simple", "xample\Xample.Simple\Xample.Simple.csproj", "{F8CFBE08-1E6F-4CC6-BF56-CE2E72B19A16}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5501B170-5917-4568-9EAC-8853D14238A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5501B170-5917-4568-9EAC-8853D14238A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
Expand All @@ -58,11 +59,21 @@ Global
{DF47FD5F-2F7F-470B-98A2-9C5266AA0FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF47FD5F-2F7F-470B-98A2-9C5266AA0FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF47FD5F-2F7F-470B-98A2-9C5266AA0FBD}.Release|Any CPU.Build.0 = Release|Any CPU
{F8CFBE08-1E6F-4CC6-BF56-CE2E72B19A16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8CFBE08-1E6F-4CC6-BF56-CE2E72B19A16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8CFBE08-1E6F-4CC6-BF56-CE2E72B19A16}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5501B170-5917-4568-9EAC-8853D14238A1} = {63A3C280-DDDF-4BC1-9B8F-AC8D9F949B3E}
{F7BA85AD-CBD1-4F99-BC28-42DF1253BAE8} = {8A14AF25-8CF0-494E-9D50-2FAE3CC9A50A}
{22F991BF-06C3-4B35-BCA2-DD61959C187F} = {8A14AF25-8CF0-494E-9D50-2FAE3CC9A50A}
{DF47FD5F-2F7F-470B-98A2-9C5266AA0FBD} = {8A14AF25-8CF0-494E-9D50-2FAE3CC9A50A}
{F8CFBE08-1E6F-4CC6-BF56-CE2E72B19A16} = {B241FEA0-8C20-49E2-BD16-36ACBD4A00C9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {89AAE175-2ADC-48FB-8667-A87EA55FD851}
EndGlobalSection
EndGlobal
69 changes: 67 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,67 @@
# template-dotnet
.NET template for repositories
# NetEvolve.Extensions.Hosting.WinForms

[![Nuget](https://img.shields.io/nuget/v/NetEvolve.Extensions.Hosting.WinForms?logo=nuget)](https://www.nuget.org/packages/NetEvolve.Extensions.Hosting.WinForms/)
[![Nuget](https://img.shields.io/nuget/dt/NetEvolve.Extensions.Hosting.WinForms?logo=nuget)](https://www.nuget.org/packages/NetEvolve.Extensions.Hosting.WinForms/)

The main purpose of this package is to provide a way to use the `Microsoft.Extensions.Hosting` for WinForms applications, allowing the use of dependency injection, configuration, logging, and other features provided by the `Microsoft.Extensions` libraries.

:bulb: This package is available for .NET 6.0 and later.

## Contribution

If you have any suggestions, bug reports, or any other form of feedback, please feel free to open an issue or a pull request. Any contributions are welcome!

## Why not .NET Standard?
With the .NET Standard Microsoft created a specification for APIs that are intended to be available on all .NET implementations. This was a great idea, but it also has some drawbacks. The main drawback is that the .NET Standard is a specification and not an implementation. This means that the real work is done by .NET implementations, such as .NET 5.0 and later versions. Which is why we decided us against the .NET Standard and for the concrete .NET implementations.

See [The future of .NET Standard](https://devblogs.microsoft.com/dotnet/the-future-of-net-standard/) for more details.

## Installation
To use this package, you need to add the package to your project. You can do this by using the NuGet package manager or by using the dotnet CLI.
```powershell
dotnet add package NetEvolve.Extensions.Hosting.WinForms
```

## Usage
To use the `Microsoft.Extensions.Hosting` in a WinForms application, you just need to create a new `HostBuilder` and configure it as you would do in a console application.

```csharp
namespace WinForms;

using Microsoft.Extensions.Hosting;
using NetEvolve.Extensions.Hosting.WinForms;

internal static class Program
{
internal static async Task Main() =>
await CreateHostBuilder().Build().RunAsync().ConfigureAwait(false);

public static IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder().UseWindowsForms<Form1>();
}
```

Therefore, you can use for example the `Microsoft.Extensions.DependencyInjection` to register services and inject them into your forms.

```csharp
namespace WinForms;

using Microsoft.Extensions.DependencyInjection;
using System.Windows.Forms;

public partial class Form1 : Form
{
private readonly ILogger<Form1> _logger;

public Form1(ILogger<Form1> logger)
{
_logger = logger;
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
_logger.LogInformation("Form loaded.");
}
}
```
54 changes: 50 additions & 4 deletions src/NetEvolve.Extensions.Hosting.WinForms/IFormularProvider.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,76 @@
namespace NetEvolve.Extensions.Hosting.WinForms;

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Unified access to provide windows forms, which can be used to create and manage forms.
/// </summary>
public interface IFormularProvider
{
/// <summary>
/// Gets the formular of the specified type.
/// </summary>
/// <typeparam name="T">The specified forms.</typeparam>
/// <returns>The requested form.</returns>
T GetFormular<T>()
where T : Form;

/// <summary>
/// Gets the formular of the specified type asynchronously.
/// </summary>
/// <typeparam name="T">The specified forms.</typeparam>
/// <returns>The requested form.</returns>
ValueTask<T> GetFormularAsync<T>()
where T : Form;

/// <summary>
/// Gets the main formular.
/// </summary>
/// <returns>The requested form.</returns>
Form GetMainFormular();

/// <summary>
/// Gets the main formular asynchronously.
/// </summary>
/// <returns>The requested form.</returns>
ValueTask<Form> GetMainFormularAsync();

ValueTask<T> GetScopedFormAsync<T>()
/// <summary>
/// Gets the scoped formular of the specified type.
/// </summary>
/// <typeparam name="T">The specified forms.</typeparam>
/// <returns>The requested form.</returns>
T GetScopedForm<T>()
where T : Form;

ValueTask<T> GetScopedFormAsync<T>(IServiceScope scope)
/// <summary>
/// Gets the scoped formular of the specified type.
/// </summary>
/// <typeparam name="T">The specified forms.</typeparam>
/// <param name="scope">The scope.</param>
/// <exception cref="ArgumentNullException">Throws a <see cref="ArgumentNullException"/>, if <paramref name="scope"/> is <see langword="null"/>.</exception>
/// <returns>The requested form.</returns>
T GetScopedForm<T>(IServiceScope scope)
where T : Form;

T GetScopedForm<T>()
/// <summary>
/// Gets the scoped formular of the specified type asynchronously.
/// </summary>
/// <typeparam name="T">The specified forms.</typeparam>
/// <returns>The requested form.</returns>
ValueTask<T> GetScopedFormAsync<T>()
where T : Form;

T GetScopedForm<T>(IServiceScope scope)
/// <summary>
/// Gets the scoped formular of the specified type asynchronously.
/// </summary>
/// <typeparam name="T">The specified forms.</typeparam>
/// <param name="scope">The scope.</param>
/// <exception cref="ArgumentNullException">Throws a <see cref="ArgumentNullException"/>, if <paramref name="scope"/> is <see langword="null"/>.</exception>
/// <returns>The requested form.</returns>
ValueTask<T> GetScopedFormAsync<T>(IServiceScope scope)
where T : Form;
}
122 changes: 14 additions & 108 deletions src/NetEvolve.Extensions.Hosting.WinForms/IHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
using Microsoft.Extensions.Hosting;
using NetEvolve.Extensions.Hosting.WinForms.Internals;

#if NET8_0_OR_GREATER
/// <summary>
/// Extension methods for <see cref="IHostBuilder"/> or <see cref="IHostApplicationBuilder"/> to configure Windows Forms Lifetime.
/// </summary>
#elif NET7_0
#if NET7_0_OR_GREATER
/// <summary>
/// Extension methods for <see cref="IHostBuilder"/> or <see cref="HostApplicationBuilder"/> to configure Windows Forms Lifetime.
/// </summary>
Expand Down Expand Up @@ -68,13 +64,14 @@ public static IHostBuilder UseWindowsForms<TApplicationContext>(
return builder.ConfigureServices(services =>
{
services = contextFactory is null
? services.AddSingleton<TApplicationContext>()
: services.AddSingleton(sp => contextFactory.Invoke(sp));
? services.AddSingleton<ApplicationContext, TApplicationContext>()
: services.AddSingleton<ApplicationContext, TApplicationContext>(sp =>
contextFactory.Invoke(sp)
);

_ = services
.AddSingleton<ApplicationContext, TApplicationContext>()
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);
});
}

Expand Down Expand Up @@ -115,7 +112,7 @@ public static IHostBuilder UseWindowsForms<TApplicationContext, TStartForm>(
);
}

#if NET7_0
#if NET7_0_OR_GREATER
/// <summary>
/// Enables Windows Forms support, builds and starts the host with the specified <typeparamref name="TStartForm"/>,
/// then waits for the host to close the <typeparamref name="TStartForm"/> before shutting down.
Expand Down Expand Up @@ -160,13 +157,14 @@ public static HostApplicationBuilder UseWindowsForms<TApplicationContext>(
ArgumentNullException.ThrowIfNull(builder);

var services = contextFactory is null
? builder.Services.AddSingleton<TApplicationContext>()
: builder.Services.AddSingleton(sp => contextFactory.Invoke(sp));
? builder.Services.AddSingleton<ApplicationContext, TApplicationContext>()
: builder.Services.AddSingleton<ApplicationContext, TApplicationContext>(sp =>
contextFactory.Invoke(sp)
);

_ = services
.AddSingleton<ApplicationContext>(sp => sp.GetRequiredService<TApplicationContext>())
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);

return builder;
}
Expand Down Expand Up @@ -206,96 +204,4 @@ public static HostApplicationBuilder UseWindowsForms<TApplicationContext, TStart
return builder;
}
#endif

#if NET8_0_OR_GREATER
/// <summary>
/// Enables Windows Forms support, builds and starts the host with the specified <typeparamref name="TStartForm"/>,
/// then waits for the host to close the <typeparamref name="TStartForm"/> before shutting down.
/// </summary>
/// <typeparam name="TStartForm">Form with which the application is to be started.</typeparam>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="configure">The action to be executed for the configuration of the <see cref="WindowsFormsOptions"/>.</param>
/// <returns><see cref="IHostApplicationBuilder"/> with enabled Windows Forms support.</returns>
public static IHostApplicationBuilder UseWindowsForms<TStartForm>(
this IHostApplicationBuilder builder,
Action<WindowsFormsOptions>? configure = null
)
where TStartForm : Form
{
ArgumentNullException.ThrowIfNull(builder);

_ = builder
.Services.AddSingleton<TStartForm>()
.AddSingleton(sp => new ApplicationContext(sp.GetRequiredService<TStartForm>()))
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);

return builder;
}

/// <summary>
/// Enables Windows Forms support, builds and starts the host with the specified <typeparamref name="TApplicationContext"/>,
/// then waits for the host to close the <typeparamref name="TApplicationContext"/> before shutting down.
/// </summary>
/// <typeparam name="TApplicationContext"></typeparam>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="contextFactory">The <see cref="ApplicationContext"/> factory.</param>
/// <param name="configure">The action to be executed for the configuration of the <see cref="WindowsFormsOptions"/>.</param>
/// <returns><see cref="IHostApplicationBuilder"/> with enabled Windows Forms support.</returns>
public static IHostApplicationBuilder UseWindowsForms<TApplicationContext>(
this IHostApplicationBuilder builder,
Func<IServiceProvider, TApplicationContext>? contextFactory = null,
Action<WindowsFormsOptions>? configure = null
)
where TApplicationContext : ApplicationContext
{
ArgumentNullException.ThrowIfNull(builder);

var services = contextFactory is null
? builder.Services.AddSingleton<TApplicationContext>()
: builder.Services.AddSingleton(sp => contextFactory(sp));

_ = services
.AddSingleton<ApplicationContext>(sp => sp.GetRequiredService<TApplicationContext>())
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);

return builder;
}

/// <summary>
/// Enables Windows Forms support, builds and starts the host with the specified <typeparamref name="TApplicationContext"/>,
/// which is created by the <paramref name="contextFactory"/> function, then waits for the host to close the <typeparamref name="TApplicationContext"/> before shutting down.
/// </summary>
/// <typeparam name="TApplicationContext"></typeparam>
/// <typeparam name="TStartForm">Form with which the application is to be started.</typeparam>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="contextFactory">The <see cref="ApplicationContext"/> factory.</param>
/// <param name="configure">The action to be executed for the configuration of the <see cref="WindowsFormsOptions"/>.</param>
/// <returns><see cref="IHostApplicationBuilder"/> with enabled Windows Forms support.</returns>
public static IHostApplicationBuilder UseWindowsForms<TApplicationContext, TStartForm>(
this IHostApplicationBuilder builder,
Func<IServiceProvider, TStartForm, TApplicationContext> contextFactory,
Action<WindowsFormsOptions>? configure = null
)
where TApplicationContext : ApplicationContext
where TStartForm : Form
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(contextFactory);

_ = builder
.Services.AddSingleton<TStartForm>()
.AddSingleton(sp =>
{
var startForm = sp.GetRequiredService<TStartForm>();
return contextFactory(sp, startForm);
})
.AddSingleton<ApplicationContext>(sp => sp.GetRequiredService<TApplicationContext>())
// Default WindowsForms services
.AddWindowsFormsLifetime(configure);

return builder;
}
#endif
}
Loading

0 comments on commit 9885dd6

Please sign in to comment.