Drop the per-sample StandardServer subclasses for a builder-only DI surface - #861
Merged
Merged
Conversation
…urface Every server sample now registers its server as services.AddSampleServer(file, server => server.AddNodeManager<TFactory>()) on one shared SampleServer (the stack's DependencyInjectionStandardServer with ServerProperties derived from the configuration). What the deleted server classes overrode is a registration on the server builder instead: ConfigureRoles for the role mappings, AddIdentityAuthenticator for the authenticators, AddStartupTask for the event history capabilities and the resource translations, AddNodeManagers for the per-endpoint aggregation node managers. The samples built around their ApplicationInstance (Server.Net4, Client.Net4, GDS) run their server on the same stack pipeline through IOpcUaApplicationConfigurationProvider, which removes the IServerBase path, AddSampleNodeManager and the factory overloads from the hosting library. AliasNames keeps a subclass of SampleServer for the standard TagVariables alias store, ReferenceServer uses the quickstart library class, and GDS registers its server itself before AddSampleServer<TServer>. The seams the stack still lacks are filed as OPCFoundation/UA-.NETStandard#4410. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
romanett
added a commit
that referenced
this pull request
Sep 4, 2026
Master #861 dropped the per-sample StandardServer subclasses for a builder-only DI surface, which touched three of the files this branch changes. The interesting resolution is Samples/Hosting/SampleApplicationHostedService.cs, where this branch is taken out entirely in favour of master. This branch had that service assign the container's ITransportBindingRegistry to the server, because a server started down the ApplicationInstance path would otherwise fall back to a private registry that only knows opc.tcp - so an AddHttpsTransport() registration never reached it. After #861 no sample starts a server that way: every one of them goes through AddSampleServer(configureServer) and the hosted server of the stack, which already assigns TransportBindings from dependency injection (OpcUaServerHostedService.cs:219). The workaround is obsolete and master's simpler service is correct. The other three keep master's shape and re-apply the transport registration on top: the two WinForms entry points move to AddSampleServer(server => server.AddUaSampleServer()) and the reference server to AddSampleServer<ReferenceServer>(...) with its node managers on the builder, each still calling services.AddOpcUa().AddHttpsTransport() so the https base address in their configuration is actually served. Everything else merged cleanly. The ITimeProviderProvider seams, the ModelChangeTracker in the NodeManagement client and the deletions all survive. All four tiers pass on the merged tree: 227 configuration, 116 server (one more than before, from master), 165 node manager and 61 client tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
romanett
added a commit
that referenced
this pull request
Sep 4, 2026
…ver surface #861 landed on master while this was open and deleted every per-sample StandardServer subclass, RoleManagementServer.cs among them, in favour of registrations on the server builder. The conflict is a real one rather than textual: this branch had added a Role configuration and an OnServerStarted override to exactly the class master removed. Resolved by keeping master's shape and porting the additions onto the new seams: * the X509Subject rule which grants ConfigureAdmin for the certificate of the maintenance workstation joins the other role mappings in SampleUsers.ConfigureRoles, as a RoleDefinitionOptions on the well known Role; WorkstationCertificateSubject and WorkstationRoleId move there with it. * the Endpoints filter becomes WorkstationEndpoints, an IServerStartupTask registered with AddStartupTask<T>. It still cannot be declared with the rest of the role configuration: AddEndpoint refuses an entry with an empty EndpointUrl (UA-.NETStandard#4412), so the endpoints the server advertises have to be copied, and those are only known once it has started. Declaring the wildcard in RoleConfigurationOptions would be worse than the startup task, because the stack applies those entries without looking at what AddEndpoint answered - the Role would end up granted on every endpoint. The tier 1.5 fixture follows the constant to SampleUsers; the README and the node manager comment follow the code. Tier 1 116 passed, tier 1.5 170 passed with 4 skipped (one of them the recorded audit known issue), tier 2 63 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
The DI surface of the server samples is now one shape, and no sample has a
StandardServersubclass any more.Every
Add<Sample>Servercomposition root isThe server behind it is one shared
SampleServerinSamples/Hosting- the stack'sDependencyInjectionStandardServer, whose only override derivesServerPropertiesfrom the configuration. The 13 samples whose server class did nothing but name the product (Boiler, AlarmCondition, DataAccess, DataTypes, Empty, FileTransfer, HistoricalAccess, Methods, NodeManagement, PerfTest, SimpleEvents, StateMachines, Views) lost it outright.What the other server classes overrode became a registration on the server builder of the stack:
CreateRoleManager+AddIdentity(RoleManagement, AliasNames)server.ConfigureRoles(...)of the stackOnServerStartedregistering authenticators (RoleManagement, AliasNames, UserAuthentication, UA sample)server.AddIdentityAuthenticator((sp, validator) => new UserNamePasswordAuthenticator(...))OnNodeManagerStartedAsyncsetting the history capabilities (HistoricalEvents)server.AddStartupTask<HistoricalEventsCapabilities>(), anIServerStartupTaskCreateResourceManagertranslations (UserAuthentication)server.AddStartupTask<UserAuthenticationTranslations>()server.AddNodeManagers(provider => ...), run with the configuration loadedLoadServerPropertieseverywhereApplicationName/ProductUriof the configurationThe samples built around their
ApplicationInstance(UA sample server, UA sample client, GDS WinForms server) run their server on the same stack pipeline:SampleApplicationimplements the stack'sIOpcUaApplicationConfigurationProvider, andAddSampleServer(server => ...)without a file hosts the server on it. That removes theIServerBasepath,AddSampleNodeManager<T>(), theFunc<IServiceProvider, TServer>overloads and two unused options from the hosting library;SampleApplicationHostedServiceonly loads the configuration eagerly for the forms now.Three
AddSampleServer<TServer>users remain, each for a reason stated at the call site: AliasNames (AliasNamesServer : SampleServerregisters and materializes the standardTagVariablesalias store before the address space is built, which the stack'sAddAliasNameStoredoes too late), ReferenceServer (the quickstart library's own class), GDS (the server takes databases and certificate groups and is registered withAddSingleton(CreateServer)first).The helpers the stack lacks are one-liners over its public registration types in
SampleServerBuilderExtensions(AddNodeManager(instance),AddNodeManagers(factories),AddIdentityAuthenticator(factory),AddStartupTask<T>()). The corresponding SDK gaps are filed as OPCFoundation/UA-.NETStandard#4410; once they land,SampleServer, those helpers and theAliasNamesServersubclass can go.Behavior changes a reviewer should know about:
DependencyInjectionStandardServeris not aReverseConnectServer. Both configurations had no reverse connect clients enabled; the console aggregation server no longer prints that section.ApplicationName,ProductUri) instead of per-sample strings.X509Authenticatoragainst the Users trust list. The UserAuthentication sample lost its dead impersonation stubs (#if TODO, Kerberos placeholders).Also documented in
Samples/Hosting/README.md.Related Issues
Types of changes
Checklist
Further comments
Verified locally: full
UA Samples.slnxbuild with no new warnings;SampleServers.Tests115 passed (hosts every sample server, the GDS and aggregation consoles included);SampleNodeManagers.Tests160 passed, 3 skipped as before (roles, alias names, user authentication, event history capabilities);HostedServerBootstrapTests3 passed, including the newAServerRunsOnTheApplicationInstanceOfTheSample, which covers the application-instance path the UA sample and GDS servers now use.Alternatives considered: a shared
IMainNodeManagerFactorydecorator seam in the hosting library for AliasNames (rejected, one sample does not justify an invented seam), and keepingReverseConnectServeras the shared base (rejected, it would lose the DI hooks ofDependencyInjectionStandardServerfor everyone).🤖 Generated with Claude Code