From b2fa463da624d17bad07fdf14dbb2597ff8315fe Mon Sep 17 00:00:00 2001
From: beppemarazzi <beppemarazzi@gmail.com>
Date: Tue, 12 Nov 2024 15:20:44 +0100
Subject: [PATCH 1/2] Fix ignored exceptions in MqttHostedServer startup

---
 .../MQTTnet.AspNetCore.csproj                 |  5 ++
 Source/MQTTnet.AspnetCore/MqttHostedServer.cs | 46 ++++++++++++-
 .../ServiceCollectionExtensions.cs            |  8 +++
 .../ASP/MqttHostedServerStartup.cs            | 66 +++++++++++++++++++
 4 files changed, 124 insertions(+), 1 deletion(-)
 create mode 100644 Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs

diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index 5357dd702..022747b51 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -1,5 +1,10 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+    <!--TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 WIP: Fix ignored exceptions in MqttHostedServer startup #2102 -->
+    <PropertyGroup>
+        <DefineConstants>$(DefineConstants);HOSTEDSERVER_WITHOUT_STARTUP_YIELD</DefineConstants>
+    </PropertyGroup>
+    
     <PropertyGroup>
         <TargetFramework>net8.0</TargetFramework>
         <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
diff --git a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs
index 4c74f6a43..69ce0b39a 100644
--- a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs
+++ b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs
@@ -1,3 +1,6 @@
+//TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102
+////WIP: Fix ignored exceptions in MqttHostedServer startup #2102
+#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD
 // 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.
@@ -45,4 +48,45 @@ void OnStarted()
     {
         _ = StartAsync();
     }
-}
\ No newline at end of file
+}
+
+#else
+// 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.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using MQTTnet.Diagnostics.Logger;
+using MQTTnet.Server;
+
+namespace MQTTnet.AspNetCore;
+
+public sealed class MqttHostedServer : BackgroundService
+{
+    readonly MqttServerFactory _mqttFactory;
+    public MqttHostedServer(
+        MqttServerFactory mqttFactory,
+        MqttServerOptions options,
+        IEnumerable<IMqttServerAdapter> adapters,
+        IMqttNetLogger logger
+        )
+    {
+        MqttServer = new(options, adapters, logger);
+        _mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
+    }
+
+    public MqttServer MqttServer { get; }
+    protected override Task ExecuteAsync(CancellationToken stoppingToken)
+        => MqttServer.StartAsync();
+    public override async Task StopAsync(CancellationToken cancellationToken)
+    {
+        await MqttServer.StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
+        await base.StopAsync(cancellationToken);
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
index 915f6791c..6f889ae7c 100644
--- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
@@ -45,8 +45,16 @@ public static void AddHostedMqttServer(this IServiceCollection services)
         services.TryAddSingleton(new MqttServerFactory());
 
         services.AddSingleton<MqttHostedServer>();
+        //TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102
+        ////WIP: Fix ignored exceptions in MqttHostedServer startup #2102
+#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD
         services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
         services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
+#else
+        services.AddHostedService(s => s.GetService<MqttHostedServer>());
+        services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>().MqttServer);
+#endif
+
     }
 
     public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
diff --git a/Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs b/Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs
new file mode 100644
index 000000000..d3b1e9cd5
--- /dev/null
+++ b/Source/MQTTnet.Tests/ASP/MqttHostedServerStartup.cs
@@ -0,0 +1,66 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.AspNetCore;
+using System.Net.Sockets;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using MQTTnet.Server;
+using Microsoft.AspNetCore.Hosting;
+
+namespace MQTTnet.Tests.ASP
+{
+    [TestClass]
+    public class MqttHostedServerStartup
+    {
+        private async Task TestStartup(bool useOccupiedPort)
+        {
+            using TcpListener l = new TcpListener(IPAddress.Any, 0);
+            l.Start();
+            int port = ((IPEndPoint)l.LocalEndpoint).Port;
+
+            if(!useOccupiedPort)
+                l.Stop();
+
+
+            var builder = WebApplication.CreateBuilder();
+            builder.WebHost.UseUrls("http://127.0.0.1:0");
+
+            builder.Services.AddMqttTcpServerAdapter();
+            builder.Services.AddHostedMqttServer(cfg =>
+            {
+                cfg
+                    .WithDefaultEndpoint()
+                    .WithDefaultEndpointPort(port);
+            });
+
+
+            var app = builder.Build();
+
+            if(!useOccupiedPort)
+            {
+                await app.StartAsync();
+                var server = app.Services.GetRequiredService<MqttServer>();
+                Assert.IsTrue(server.IsStarted);
+                await app.StopAsync();
+            }
+            else
+            {
+                await Assert.ThrowsExceptionAsync<SocketException>(() =>
+                    app.StartAsync()
+                    );
+            }
+        }
+
+        [TestMethod]
+        [DoNotParallelize]
+        public Task TestSuccessfullyStartup()
+            => TestStartup(useOccupiedPort: false);
+
+        [TestMethod]
+        [DoNotParallelize]
+        public Task TestFailedStartup()
+            => TestStartup(useOccupiedPort: true);
+
+    }
+}

From 43bb94a704067403b372e94746bfcde2a4d45ab6 Mon Sep 17 00:00:00 2001
From: beppemarazzi <beppemarazzi@gmail.com>
Date: Mon, 3 Mar 2025 09:19:12 +0100
Subject: [PATCH 2/2] Cleaning WIP

---
 .../MQTTnet.AspNetCore.csproj                 |  7 +--
 Source/MQTTnet.AspnetCore/MqttHostedServer.cs | 55 -------------------
 .../ServiceCollectionExtensions.cs            |  9 +--
 3 files changed, 2 insertions(+), 69 deletions(-)

diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index 022747b51..fb585a66b 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -1,10 +1,5 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
-    <!--TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102 WIP: Fix ignored exceptions in MqttHostedServer startup #2102 -->
-    <PropertyGroup>
-        <DefineConstants>$(DefineConstants);HOSTEDSERVER_WITHOUT_STARTUP_YIELD</DefineConstants>
-    </PropertyGroup>
-    
     <PropertyGroup>
         <TargetFramework>net8.0</TargetFramework>
         <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
diff --git a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs
index 69ce0b39a..3dfd6c648 100644
--- a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs
+++ b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs
@@ -1,56 +1,3 @@
-//TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102
-////WIP: Fix ignored exceptions in MqttHostedServer startup #2102
-#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD
-// 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.
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Hosting;
-using MQTTnet.Diagnostics.Logger;
-using MQTTnet.Server;
-
-namespace MQTTnet.AspNetCore;
-
-public sealed class MqttHostedServer : MqttServer, IHostedService
-{
-    readonly IHostApplicationLifetime _hostApplicationLifetime;
-    readonly MqttServerFactory _mqttFactory;
-
-    public MqttHostedServer(
-        IHostApplicationLifetime hostApplicationLifetime,
-        MqttServerFactory mqttFactory,
-        MqttServerOptions options,
-        IEnumerable<IMqttServerAdapter> adapters,
-        IMqttNetLogger logger) : base(options, adapters, logger)
-    {
-        _mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
-        _hostApplicationLifetime = hostApplicationLifetime;
-    }
-
-    public async Task StartAsync(CancellationToken cancellationToken)
-    {
-        // The yield makes sure that the hosted service is considered up and running.
-        await Task.Yield();
-
-        _hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
-    }
-
-    public Task StopAsync(CancellationToken cancellationToken)
-    {
-        return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
-    }
-
-    void OnStarted()
-    {
-        _ = StartAsync();
-    }
-}
-
-#else
 // 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.
@@ -88,5 +35,3 @@ public override async Task StopAsync(CancellationToken cancellationToken)
         await base.StopAsync(cancellationToken);
     }
 }
-
-#endif
\ No newline at end of file
diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
index 6f889ae7c..3816ad700 100644
--- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
@@ -45,15 +45,8 @@ public static void AddHostedMqttServer(this IServiceCollection services)
         services.TryAddSingleton(new MqttServerFactory());
 
         services.AddSingleton<MqttHostedServer>();
-        //TODO: remove before close https://github.com/dotnet/MQTTnet/pull/2102
-        ////WIP: Fix ignored exceptions in MqttHostedServer startup #2102
-#if !HOSTEDSERVER_WITHOUT_STARTUP_YIELD
-        services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
-        services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
-#else
         services.AddHostedService(s => s.GetService<MqttHostedServer>());
-        services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>().MqttServer);
-#endif
+        services.AddSingleton(s => s.GetService<MqttHostedServer>().MqttServer);
 
     }