mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 14:43:08 +08:00
9b034f8291
# Description Nullable reference types are now enabled once, in `common.props`, and every project in the repository builds warning-clean with them. **Configuration** - `<Nullable>enable</Nullable>` lives in `common.props`. All per-project `<Nullable>` settings are removed: 178 project files (enable, annotations, and the `disable` in the three Aot test projects). - 419 per-file `#nullable enable`/`restore` directives and the 4 `#nullable disable` directives are removed. - Kept on purpose: - The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`, `PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the project setting in generated code, so the directive is required there. - `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against the 1.5.378 packages and does not import the repository props. - `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now describe the central setting. **Migration** (the 43 unset and 13 annotations-only projects, roughly 10k diagnostics) - **Production code: annotated at the declarations.** Covers `tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the LDS sample. - Members, parameters and returns are nullable where null actually flows. - Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`, `IsMethodTypeDesign`). - `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take `params object?[]`. `Template.AddReplacement` takes `string?` (rendering already skips null replacements). - `IRegisteredServerStore.RegisterAsync` returns `Task<RegistrationEntry?>`: it returns null when a registration is removed. - Dereferences backed by an invariant (for example, the validated model) use `!`, so runtime behaviour is unchanged. - **Behavioural changes in production code** (small, deliberate): - `NodeSetToModelDesign` throws `InvalidOperationException` when the file is not a NodeSet. Before, it threw a NullReferenceException on the next line. - `ModelDesignValidator` starts its lookup tables empty instead of null. A redundant `if (m_nodes == null) return;` guard is gone. - **Test code: minimal annotations.** - `?` on values that can be null, `!` where the test asserts non-null. - `= null!` for fixture members assigned in SetUp. - `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the `IEncodeable` constraint. - A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`) folded into the adjacent `IsNull`/`Count` checks. **Notes for reviewers** - The diff is large (~1,500 files) but almost entirely mechanical: `?`, `!` and directive removal. The non-annotation changes are listed above. - Known follow-up, not done here: `IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay non-nullable even though the validator returns null before it loads a dictionary. Making them nullable would ripple through every generator call site. ## Related Issues - Fixes #4439 ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. The "no new warnings" box covers net10.0 and net48: full non-incremental builds of `UA.slnx`, `tools/SourceGeneration.slnx`, `tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0 warnings and 0 errors under the repository's warnings-as-errors settings. net9.0 and net8.0 also had no nullable diagnostics, but they were not rebuilt after the final fixes. Tests have not been run locally yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
17 KiB
17 KiB
OPC UA .NET Standard Stack - GitHub Copilot Instructions
Project Overview
This is the official OPC UA .NET Standard Stack from the OPC Foundation. It provides a reference implementation of OPC UA (Open Platform Communications Unified Architecture) targeting .NET Standard, allowing development of applications that run on multiple platforms including Windows, Linux, iOS, and Android.
Key Technologies
- SDK: .net 10 SDK
- Language: C# with LangVersion 14.0
- Target Frameworks: .NET Standard 2.0/2.1, .NET Framework 4.8, .NET 8.0 (LTS), .NET 9.0, .NET 10.0 (LTS)
- NativeAOT
- Project Type: Class libraries, console applications, and reference implementations
- Architecture: OPC UA Stack with Client, Server, Configuration, Complex Types, GDS, and PubSub components
Build and Development
Building the Project
- Prerequisites: .NET SDK 10.0
- Restore dependencies:
dotnet restore 'UA.slnx' - Build: Use Visual Studio 2026 or
dotnet build - Key solutions:
UA.slnx- Contains all projects
- Contributor guide: see
docs/DeveloperGuide.mdfor prerequisites, building (including per-TFM), testing, coding standards, and how-to recipes. - Release operations: before changing a release version (
version.json,preview-version.props), cutting or advancing a release branch, backporting or forward-porting a fix to a release branch, or running/recoveringnuget-publish.yml/release.yml, readdocs/ReleaseProcess.mdand follow the matching procedure exactly. Releases are only ever produced from a canonicalrelease/<major>.<minor>branch, never frommaster.
Project Structure
src/- Core OPC UA libraries (Client, Server, Configuration, etc.)samples/- Reference applications (ConsoleReferenceServer, etc.)tests/- Unit and integration teststools/- Source generators, analyzers, and the OPC UA MCP tooldocs/- Documentation filesfuzzing/- Fuzz targets, corpora, and support scripts
Build Properties
Directory.Build.props- Central build propertiesDirectory.Packages.props- Centralized package management- Analysis level is set to "preview" with the "all" analysis mode
- .NET analyzers are enabled
- Package validation is enabled
- .editorconfig is enforced during build. Fix all errors, warnings, and informational messages. Do not suppress without marking the suppression with a comment explaining why and a TODO to fix later.
Core Concepts to Understand
- Nodes and NodeManagers: Understand the node structure and how to implement custom node managers
- Sessions and Subscriptions: Properly manage client sessions and subscriptions (including durable subscriptions)
- Data Encoding: Use the binary encoding/decoding system for OPC UA data types
- Complex Types: Support for complex type definitions and their serialization
- Transport: UA-TCP and HTTPS transports with reverse connect capability
IMPORTANT RULES
- Rules apply to any new code added and existing code that is changed.
- All new code should use Async/await (TAP), APM and synchronous to Async are not allowed.
- DO NOT create SYNC over ASYNC (GetAwaiter().GetResult(), Wait(), Result) unless explicitly requested/confirmed.
- All types implementing
INullablemust never be used or added via theSystem.Nullable<T>(T?). Instead use.IsNullcheck on the type to determine whether it is null and the.Nullor default to create a null value. - ALWAYS use
TryGetorTryGetValue, or similar on struct types vs casting. Particularly NEVER use.AsBoxedValueof theVarianttype or.ValueofSystem.IUnion. - NEVER use
objectorobject?in public API unless overridingEquals. If OPC UA related API, useVariant. - Prefer ArrayOf over read-only collection types and ReadOnlyMemory — including in new public API surfaces (parameters, properties, return types). When adding new APIs in 2.0 (or modifying existing 2.0 APIs), use
ArrayOf<T>rather thanIReadOnlyList<T>/IList<T>/T[]. This applies even when the value originates from aIReadOnlyList<T>source — wrap it inArrayOf<T>at the API boundary. - Prefer ByteString over ArrayOf, byte[], ReadOnlyMemory and byte readonly collections in public API.
- Prefer Span/ReadOnlySpan over byte[] in any API.
- DO NOT use API that is marked as [Obsolete] unless used inside "Test code".
- DO NOT add new API that is not compatible with NativeAOT (needs suppression, annotation).
- All new functionality added must wire into the DI infrastructure and be injectable. Direct "create"/construct path should also be available as fallback.
- Consider making new functionality available using a fluent API (and ideally integrated into the existing API if it can extend it).
- Maintain compatibility with the previous version (1.5.378, master378 branch) in master, mark any replaced API with [Obsolete]. Already marked [Obsolete] API in 1.5.378 (master378) can be removed/replaced in master.
- Use modern C# and .net and add a polyfill to Opc.Ua.Types/Polyfills if API is not available on older platforms.
- Use new "extension" keyword when you need to define "static" or "property" extensions. Use old style extension methods for the rest.
- Pass
DataValuevalues by ref when possible (useinfor one way arguments). Addinginkeyword is not considered a breaking change since the API can be invoked with and without ref. - Support the latest version of the OPC UA specifications (1.05.07 for OPC UA core)
- NEVER expose "locks" or locking in any internal or public API surface because nobody can effectively reason over the lock behavior. NEVER expose a "lock" as protected field to sub classes.
- NEVER use
private readonly object m_lock = new()or any otherobject-typed sync root. All synchronous locks MUST useSystem.Threading.Lock(a polyfill inOpc.Ua.Types/Polyfills/System.Threading.csmakes it available on every supported TFM). Treatprivate readonly object m_lock = new()as an analyzer-level error. Apply this rule to every code path you add or modify; do not introduce new object-based locks even when imitating surrounding code.
Architecture and patterns
- DO NOT rely on inheritance to provide "extensibility". Instead - make all non abstract public classes sealed by default and ask for exceptions.
- Prefer a provider model with injectable providers, see FileSystem or HistoricAccess providers as examples.
- Align all architectural decisions around the following high level goals:
- Running servers with High availability and in a distributed system. Especially consider what this means for "state".
- A "simple" API with progressive "advanced" feature sets, example: Minimal* samples and the focus on fluent API and ManagedSession API.
- Use SOLID principles. Encapsulate concerns and do not let implementation bleed through the APIs (e.g. GOOD: reconnect handling in managed session, BAD: ReconnectHandler (old)).
- ALWAYS REUSE existing features and concepts (docs/*) inside new features, e.g.
- All source generated code, in particular ObjectType proxies should be used over manually calling service calls inside new clients.
- Consider using the source generators to implement emitting "boilerplate", especially if it is related to the OPC UA standard (e.g. information model).
- Base services: File System, Certificate manager, Secret store, State machine, Alarms and conditions Streaming subscription, Sessions, etc. in new code. (Documented in docs/*).
- Observability is plumbed through via
ITelemetryContext. Use it to create anILoggerfor logging; follow the source-generated logging conventions indocs/DeveloperGuide.md.
- If reuse is not possible, ASK whether to extend an existing feature so it becomes reuseable.
Code Style
- Add the OPC Foundation MIT license header to all source files.
- NEVER use #region/#endregion directives. Remove them when you encounter them.
- NEVER use comment-only "region" dividers (e.g.
// === Section ===,// --- Section ---). Remove them when you encounter them. - Nullable reference types are enabled for every project centrally in
common.props. NEVER add<Nullable>to a.csprojor#nullable enableto a source file; fix nullable warnings instead of suppressing the context. The only exception is<auto-generated>files, where the compiler ignores the project setting and an explicit#nullable enableis required. A project or file that genuinely must opt out uses<Nullable>disable</Nullable>/#nullable disablewith a comment explaining why. - ALWAYS add a line break after a statement ending with
; - ALWAYS Follow the
.editorconfigsettings strictly. Fix all warnings, errors and informational messages before proposing a fix.- Use 4 spaces for indentation in C# files
- Maximum line length: 120 characters
- End-of-line: CRLF
- Always insert final newline
- Trim trailing whitespace
- Use UTF-8 encoding
- Order of members in classes, struct, records: Constructor, Properties and non-private Events, Methods, Fields. Each ordered from public->protected->internal->private.
- Braces: Use Allman style (braces on new line for control blocks, types, and methods)
- Null-checking: Use throw expressions and conditional delegate calls when appropriate but at minimum in all public API
- Access modifiers: Always specify access modifiers explicitly.
- Code analysis: All code must pass Roslyn analyzers (Roslynator.Analyzers and Roslynator.Formatting.Analyzers)
- Warnings: Treat warnings as errors (
TreatWarningsAsErrorsis enabled)
- Follow standard C# naming conventions. Do not use underscores in method names.
- Assembly prefix:
Opc.Ua(Except applications, or if otherwise requested) - Package prefix:
OPCFoundation.NetStandard - Add XML
<summary>documentation for internal types and members as well as public APIs, including classes, records, interfaces, constructors, methods, properties, events, and fields. - Always use a line break after
<summary>and before</summary>for all members (except for documentation of fields). This applies to every XML-doc summary, including in sample/application code — never write a single-line/// <summary> ... </summary>; always put the text on its own line between the opening and closing tags. - Use source-generated
[LoggerMessage]logging; never callILogger.LogInformation/LogError/...directly. Follow the per-file<Class>Logand per-assembly<AssemblyToken>EventIdsconventions indocs/DeveloperGuide.md.
Security Requirements
- Never hardcode credentials, certificates, or secrets in source code
- Certificate Management: All certificates must be managed through the certificate store system (see
docs/CertificateManager.md) - Secrets: All secrets must be managed through the secret store system (see
docs/CertificateManager.md) - Security Profiles: do not use hash algorithms other than SHA2 or higher.
- Authentication: Properly implement anonymous, username, and X.509 certificate user authentication
- Audit and Redaction: Use the audit and redaction APIs for sensitive information
Testing Standards
- Use NUnit or TUNIT framework (match existing test projects)
- DO NOT mix Nunit and TUNIT in the same project.
- DO Use NUnit asserts methods (Assert.That). Use the TUNIT assertions when using TUNIT.
- DO Use Moq for mocking in NUnit tests. Use the TUNIT mock libraries for TUNIT tests.
- DO NOT USE the classic Nunit asserts (E.g. Assert.AreEquals) or other libraries.
- All new features must include unit tests. Tests should be simple and cover positive and negative scenarios. Unit tests go into the corresponding .Tests project
- DO Maintain or improve code coverage
- All projects should have at least 80% coverage (except sample and test projects).
- All client/server as well as pub/sub features must also have Integration tests. Integration tests go into integration projects <component/area>.Tests.
- Tests must be deterministic and pass in CI/CD environment
- Code coverage is monitored via Coverlet and MUST NOT decrease
- Run all tests with
dotnet testfrom solution root on UA.slnx - When updating tests fix above for the test only.
- Mirror the structure of the code being tested
- Use descriptive test method names that explain what is being tested
- DO NOT use _ in test method names; use PascalCase
- Ensure that any null assertions do not assert null of a "struct" type. If structs implement
INullabletest for IsNull to be true or false.
Documentation
When to Update Documentation
- Add a new doc in
docs/when adding new features and link from/docs/README.md - Review all other docs for consistency and when needed, link the new doc from other docs.
- When manual migration from 1.5.378 (master378) is required or code was marked [Obsolete], update migrationguide.md
- Update
/README.mdfor significant changes - Keep
NugetREADME.mdupdated for package-related changes - Document breaking changes prominently
- Keep
docs/DeveloperGuide.md(the contributor onboarding guide) current when build, test, or coding-convention changes affect contributors.
Documentation Style
- Use clear, technical language
- Include code examples where appropriate. For new Client side features, always add code examples to show how to use the API and explain the developer experience.
- Reference OPC UA specifications when relevant
- Maintain consistent markdown formatting
Dependencies and Package Management
- Use centralized package management (Directory.Packages.props)
- Audit packages for security vulnerabilities (NuGetAudit is enabled)
- Only add necessary dependencies and ask for approval for new dependencies. Most functionality is implemented within the stack itself so check the repo first.
- Prefer stable, well-maintained packages.
- Do not use packages with incompatible license (e.g. GPL, AGPL or commercial)
- Check compatibility with all target frameworks
- Prefer AOT and trimmable annotated nuget packages over others.
- If a nuget dependencies is not explicitly marked as Native AOT compatible, add AOT tests to the AoT test project to exercise all code paths using the dependency.
Contributing
Before Submitting Changes
- Ensure all tests pass
- Run code analysis and fix all warnings
- Follow the existing code structure and patterns
- Check that your changes don't break compatibility
- Review security implications of your changes
- Contributors must agree to the OPC Foundation CLA
Pull Request Guidelines
- Provide clear description of changes
- Reference any related issues
- Ensure that net48 and net10.0 TFM tests in UA.slnx solution run successfully before creating the PR.
- Ensure CI/CD pipelines pass
- Use the opc-ua-codestyle-enforcer agent if needed to ensure compliance before opening pull requests.
Common Tasks
Adding New OPC UA Functionality
- Review OPC UA specification for the feature using the online reference or appropriate tools available (e.g. MCP).
- Implement in appropriate library/libraries (add new ones for new companion spec, or find the appropriate place in an existing one).
- Add comprehensive tests (see test requirements)
- Update documentation (see documentation requirements)
- Consider backward compatibility (see earlier)
Certificate Management
- NEVER commit certificates or secrets of any kind to the repository
- Use the certificate manager APIs
- Follow guidelines in
docs/Certificates.md - Test with different certificate configurations
Performance Considerations
- Be mindful of memory allocations in hot paths
- Use async/await properly to avoid blocking
- Consider thread safety
- Profile performance-critical code
- Avoid locking where possible.
- If needed use SemaphoreSlim to lock instead of lock keyword
- If a synchronous lock is required/desired, the field type MUST be
System.Threading.Lock(a polyfill is provided inOpc.Ua.Types/Polyfills/System.Threading.cs). NEVER declare a sync root asprivate readonly object m_lock = new()— useprivate readonly System.Threading.Lock m_lock = new()instead. This rule applies to every file in the repository.
Resources
- OPC UA Specification: https://reference.opcfoundation.org/
- Documentation: See
docs/directory - Samples Repository: https://github.com/OPCFoundation/UA-.NETStandard-Samples
- NuGet Packages:
- Types: https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Types/
- Core.Types: https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core.Types/
- Core: https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/
- Client: https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/
- Server: https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/
- Preview NuGet feed: https://nuget.pkg.github.com/OPCFoundation/index.json
(every successful
masterbuild; classic PAT withread:packagesrequired)