Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions adapter/MTConnect.NET-Applications-Adapter/Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@

using MTConnect.Services;
using NLog;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif

namespace MTConnect.Applications
{
/// <summary>
/// Class used to implement a Windows Service for an MTConnect Agent Application
/// </summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public class Service : MTConnectAdapterService
{
private static readonly Logger _serviceLogger = LogManager.GetLogger("service-logger");
Expand Down
4 changes: 4 additions & 0 deletions agent/MTConnect.NET-Applications-Agents/Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@

using MTConnect.Services;
using NLog;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif

namespace MTConnect.Applications
{
/// <summary>
/// Class used to implement a Windows Service for an MTConnect Agent Application
/// </summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public class Service : MTConnectAgentService
{
private static readonly Logger _serviceLogger = LogManager.GetLogger("service-logger");
Expand Down
12 changes: 9 additions & 3 deletions agent/Modules/MTConnect.NET-AgentModule-MqttRelay/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -941,11 +941,17 @@ await AsyncVoidGuard.Run(
if (!conditionObservations.IsNullOrEmpty())
{
var multipleObservations = new List<IObservation>(conditionObservations.Count());
foreach (var observation in conditionObservations)
// Rename to avoid CS0136: the
// enclosing AgentObservationAdded
// handler takes `observation` as its
// parameter; declaring an inner
// `observation` here shadows it and
// fails to compile under net4x.
foreach (var condObservation in conditionObservations)
{
multipleObservations.Add(CloneAsObservation(observation));
multipleObservations.Add(CloneAsObservation(condObservation));
}

var result = await _entityServer.PublishObservations(_mqttClient, multipleObservations);
if (result != null && result.IsSuccess)
{
Expand Down
6 changes: 6 additions & 0 deletions libraries/MTConnect.NET-HTTP/Ceen/Httpd/HttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Net;
using System.Linq;
using System.Net.Sockets;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using System.Threading.Tasks;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -172,7 +174,9 @@ public void Setup(bool usessl, ServerConfig config)
/// <param name="socket">The socket handle.</param>
/// <param name="remoteEndPoint">The remote endpoint.</param>
/// <param name="logtaskid">The task ID to use.</param>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public void HandleRequest(SocketInformation socket, EndPoint remoteEndPoint, string logtaskid)
{
RunClient(socket, remoteEndPoint, logtaskid, Controller);
Expand Down Expand Up @@ -865,7 +869,9 @@ public static Task ListenAsync(
/// <param name="remoteEndPoint">The remote endpoint.</param>
/// <param name="logtaskid">The log task ID.</param>
/// <param name="controller">The controller instance</param>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
private static void RunClient(SocketInformation socketinfo, EndPoint remoteEndPoint, string logtaskid, RunnerControl controller)
{
RunClient(new Socket(socketinfo), remoteEndPoint, logtaskid, controller);
Expand Down
4 changes: 4 additions & 0 deletions libraries/MTConnect.NET-Services/MTConnectAdapterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using System.ServiceProcess;

namespace MTConnect.Services
{
/// <summary>
/// Class used to implement an MTConnect Adapter as a Windows Service
/// </summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public abstract class MTConnectAdapterService : ServiceBase
{
private const string DefaultServiceName = "MTConnect-Adapter";
Expand Down
4 changes: 4 additions & 0 deletions libraries/MTConnect.NET-Services/MTConnectAgentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using System.ServiceProcess;

namespace MTConnect.Services
{
/// <summary>
/// Class used to implement an MTConnect Agent as a Windows Service
/// </summary>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public abstract class MTConnectAgentService : ServiceBase
{
private const string DefaultServiceName = "MTConnect.NET-Agent";
Expand Down
4 changes: 4 additions & 0 deletions libraries/MTConnect.NET-Services/WindowsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@

using System.Linq;
using System.Runtime.InteropServices;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using System.Security.Principal;
using System.ServiceProcess;

namespace MTConnect.Services
{
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
internal static class WindowsService
{
public static bool ServiceExists(string serviceName)
Expand Down
13 changes: 12 additions & 1 deletion libraries/MTConnect.NET-XML/MTConnect.NET-XML.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,18 @@
<PropertyGroup>
<RootNamespace>MTConnect</RootNamespace>
<Configurations>Debug;Release;Package</Configurations>


<!--
XsdPreprocessor.StripXsd11Constructs uses a C# 8.0 `using var`
declaration (`using var reader = new StringReader(...)`). The
Roslyn default LangVersion on netstandard2.0 / net4x falls
back to 7.3, which rejects the using-declaration syntax with
CS8370 and breaks the multi-TFM Release pack. Pin LangVersion
to 8.0 — the lowest version that accepts the syntax — so the
Release pack survives every declared TFM.
-->
<LangVersion>8.0</LangVersion>

<Description>MTConnect.NET-XML implements the XML Document Format for use with the MTConnect.NET library. Supports MTConnect versions up to 2.7. Supports .NET Framework 4.6.1 up to .NET 9</Description>
<PackageReadmeFile>README-Nuget.md</PackageReadmeFile>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) 2026 TrakHound Inc., All Rights Reserved.
// TrakHound Inc. licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using NUnit.Framework;

namespace MTConnect.AgentModule.MqttRelay.Tests
{
// Pins the rename PR #194 lands on Module.cs to resolve the
// CS0136 shadowed-local diagnostic that fires under net4x. The
// original code in `AgentObservationAdded` declared an inner
// `var observation` inside a `foreach` loop that lived in the
// same scope as the handler's outer `observation` parameter:
//
// private async void AgentObservationAdded(object sender,
// IObservation observation) // <- outer name
// {
// ...
// foreach (var observation in conditionObservations) // <- shadow
// { ... }
// }
//
// The C# 5+ language spec section 7.6.2.1 forbids a local
// variable declaration from shadowing an enclosing
// parameter/local of the same name. The .NET 5+ compiler emits
// a warning that TreatWarningsAsErrors escalates to an error on
// net8.0, but the project's net4x roslyn pinned compiler emits
// CS0136 directly. PR #194 renames the inner declaration to
// `condObservation` (or similar non-shadowing name) so the
// multi-TFM build path stays green.
//
// This fixture reads the Module.cs source via reflection on the
// assembly's location and scans the relevant block, asserting
// that no `foreach (var observation` declaration remains inside
// the handler. A future contributor who re-introduces the
// shadow fails this test under net8.0 — the test TFM that the
// CI matrix exercises — instead of waiting for the next Release
// pack to surface the failure under net4x.
/// <summary>Pins the rename that resolves the net4x CS0136 shadow.</summary>
[TestFixture]
[Category("MultiTfmCompat")]
public class ConditionObservationVariableScopeTests
{
/// <summary>Module.cs AgentObservationAdded must not declare an inner `observation` loop variable.</summary>
[Test]
public void AgentObservationAdded_does_not_declare_inner_observation_loop_variable()
{
// Locate the Module.cs source by walking up from the test
// assembly to the repo root, then descending into the
// module's source tree. The path is stable in-tree;
// CI runs with the same layout.
var moduleSourcePath = FindModuleSourcePath();
Assert.That(moduleSourcePath, Is.Not.Null,
"Could not locate Module.cs for MTConnect.NET-AgentModule-MqttRelay.");

var source = File.ReadAllText(moduleSourcePath!);

// Extract the AgentObservationAdded handler body. The
// method ends at the matching close-brace of the
// `try { ... } finally { ... }` pair, which is the last
// brace at column 8 before the next `private` member.
var startIndex = source.IndexOf(
"private async void AgentObservationAdded",
StringComparison.Ordinal);
Assert.That(startIndex, Is.GreaterThanOrEqualTo(0),
"AgentObservationAdded handler not found in Module.cs.");

// Find the start of the next private member after the
// handler so we scope the scan to AgentObservationAdded's
// body and don't reach into AgentAssetAdded.
var nextMemberIndex = source.IndexOf(
"private async void AgentAssetAdded",
startIndex, StringComparison.Ordinal);
Assert.That(nextMemberIndex, Is.GreaterThan(startIndex),
"Next handler AgentAssetAdded not found after AgentObservationAdded; " +
"Module.cs structure has changed.");

var handlerBody = source.Substring(startIndex, nextMemberIndex - startIndex);

// Assert: the handler must NOT contain the shadowing
// declaration `foreach (var observation in`. The renamed
// form uses a different identifier such as
// `condObservation`.
var shadowingPatterns = new[]
{
"foreach (var observation in",
"foreach(var observation in",
};
foreach (var pattern in shadowingPatterns)
{
Assert.That(
handlerBody.Contains(pattern, StringComparison.Ordinal),
Is.False,
$"AgentObservationAdded contains `{pattern}` which shadows " +
"the outer `observation` parameter and fails CS0136 under net4x. " +
"Rename the inner loop variable.");
}
}

private static string FindModuleSourcePath()
{
// The test assembly's location lives under
// tests/MTConnect.NET-AgentModule-MqttRelay-Tests/bin/...
// walk up to find the repo root marker, then descend.
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
var candidate = Path.Combine(
dir.FullName,
"agent", "Modules", "MTConnect.NET-AgentModule-MqttRelay", "Module.cs");
if (File.Exists(candidate)) return candidate;
dir = dir.Parent;
}
return null;
}
}
}
14 changes: 14 additions & 0 deletions tests/MTConnect.NET-Common-Tests/MTConnect.NET-Common-Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@

<ItemGroup>
<ProjectReference Include="..\..\libraries\MTConnect.NET-Common\MTConnect.NET-Common.csproj" />
<!--
Project references added for SupportedOSPlatformAttributePresenceTests
(tests/MTConnect.NET-Common-Tests/Platform/). The fixture loads each
assembly that owns one of the six Windows-only Service / HTTP-server
types decorated by commit dd2eb424 (2026-05-22) and reflection-asserts
the [SupportedOSPlatform("windows")] attribute is still present. The
references are deliberately reflection-only — no test instantiates
WindowsService or starts an HTTP server, so the heavier transitive
surface is fine to drag in.
-->
<ProjectReference Include="..\..\libraries\MTConnect.NET-Services\MTConnect.NET-Services.csproj" />
<ProjectReference Include="..\..\libraries\MTConnect.NET-HTTP\MTConnect.NET-HTTP.csproj" />
<ProjectReference Include="..\..\agent\MTConnect.NET-Applications-Agents\MTConnect.NET-Applications-Agents.csproj" />
<ProjectReference Include="..\..\adapter\MTConnect.NET-Applications-Adapter\MTConnect.NET-Applications-Adapter.csproj" />
</ItemGroup>

</Project>
Loading
Loading