Skip to content

feat!: Replace CommonServiceLocator with IServiceProvider in LogExtensionPoint - #94

Merged
MartinZikmund merged 3 commits into
masterfrom
dev/mz/drop-commonservicelocator
Sep 8, 2026
Merged

MartinZikmund merged 3 commits into
masterfrom
dev/mz/drop-commonservicelocator

Conversation

@MartinZikmund

Copy link
Copy Markdown
Member

What

Uno.Core.Extensions.Logging.Singleton is referenced by Uno.WinUI, so its dependency on CommonServiceLocator ends up shipping CommonServiceLocator.dll inside every Uno application. This PR removes that dependency by swapping the service locator lookup for System.IServiceProvider.

Why

Two reasons, one legal and one architectural.

Licensing. CommonServiceLocator is licensed under the MS-PL, which the FSF classifies as incompatible with the GPL. Every GPL/AGPL-licensed Uno app currently redistributes an assembly it cannot relicense under its own terms. This came up while producing third-party licence notices for an AGPL-licensed Uno app — it is the only non-permissive licence in the entire shipped dependency graph.

Age. The package is Microsoft.Practices.ServiceLocation, copyright Microsoft 2008 — the patterns & practices service locator, predating Microsoft.Extensions.DependencyInjection.

Verified it genuinely ships (not a dev-time dependency): CommonServiceLocator.dll is copied to the output for net10.0-desktop, net10.0-windows10.0.26100 (including AppX/) and net10.0-android in a stock Uno app.

How

The entire dependency was one optional lookup in GetFactory():

if (ServiceLocator.IsLocationProviderSet)
{
    var service = ServiceLocator.Current.GetService(typeof(ILoggerFactory));

Replaced with System.IServiceProvider — in the BCL on every target framework here, and the abstraction Microsoft.Extensions.DependencyInjection already implements. No new package reference, and nothing reflective (so no trimming or AOT cost).

  • Adds LogExtensionPoint.ServiceProvider, an optional IServiceProvider used to resolve the ambient ILoggerFactory on first use
  • GetFactory() resolves through it, keeping the existing fallback to a default LoggerFactory
  • Exception behaviour preserved exactly: NullReferenceException and InvalidOperationException fall back to a default factory, anything else still propagates — now expressed as an exception filter instead of catch-and-rethrow
  • Drops the CommonServiceLocator PackageReference

Breaking change

Consumers that supplied an ILoggerFactory via ServiceLocator.SetLocatorProvider(...) must now assign LogExtensionPoint.ServiceProvider instead:

// before
ServiceLocator.SetLocatorProvider(() => myLocator);

// after
LogExtensionPoint.ServiceProvider = myServiceProvider;

Consumers that never set a locator provider — every app using Microsoft.Extensions.DependencyInjection — are unaffected, since that path already fell through to new LoggerFactory().

Scope

Deliberately limited to Uno.Core.Extensions.Logging.Singleton, the only app-shipped package that referenced CommonServiceLocator (Collections, Disposables, Equality and Logging never did).

Uno.Core.Extensions.Compatibility keeps its reference: it exposes IServiceLocator in its public API by design (IServiceLocatorProvider, ServiceLocatorProviderExtensions), and it is not shipped as part of Uno applications. Removing it there would be a much larger API break for no redistribution benefit — happy to follow up separately if you'd like it gone repo-wide.

Testing

The replaced branch had no test coverage. Added 5 tests to LogExtensionPointTests covering resolution from a stub provider, both fallback paths, exception propagation, and the property round-trip.

dotnet test Uno.Core.Tests — 267 passed, 1 failed, 2 skipped.

The single failure is pre-existing and unrelated: ValidationHelperFixture.CurrencyValidation asserts IsCurrency("1 121,2", fr-fr) using a plain space (U+0020), but ICU on modern Windows returns U+202F (narrow no-break space) as the fr-FR group separator. It touches no logging code. Happy to file that separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XD6CXamcmygU1ErZvjfc9v

Copilot AI lite review requested due to automatic review settings September 3, 2026 11:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The PR description does not include a required linked GitHub issue, and the reported dotnet test Uno.Core.Tests run indicates a failing test that should be addressed before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR removes the runtime dependency on CommonServiceLocator from Uno.Core.Extensions.Logging.Singleton by switching the ambient ILoggerFactory lookup to an optional System.IServiceProvider, avoiding shipping CommonServiceLocator.dll in downstream Uno apps and reducing licensing risk.

Changes:

  • Removed the CommonServiceLocator package reference and ServiceLocator usage from LogExtensionPoint.
  • Added LogExtensionPoint.ServiceProvider for optional first-use resolution of ILoggerFactory, preserving the existing fallback behavior to a default LoggerFactory.
  • Added unit tests covering service-provider resolution, fallback paths, and exception behavior.
File summaries
File Description
src/Uno.Core.Extensions.Logging.Singleton/LogExtensionPoint.cs Replaces service-locator-based factory resolution with an optional IServiceProvider and retains fallback logic.
src/Uno.Core.Extensions.Logging.Singleton/Uno.Core.Extensions.Logging.Singleton.csproj Drops the CommonServiceLocator package reference so it no longer ships transitively.
src/Uno.Core.Tests/LogExtensionPointTests.cs Adds tests validating ServiceProvider behavior, default fallback, and exception propagation.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

MartinZikmund and others added 2 commits September 3, 2026 13:26
…sionPoint

BREAKING CHANGE: consumers that supplied an ILoggerFactory through
ServiceLocator.SetLocatorProvider must now assign
LogExtensionPoint.ServiceProvider instead. Consumers that never set a locator
provider - which is every app using Microsoft.Extensions.DependencyInjection -
are unaffected, since that path already fell through to a default LoggerFactory.

Uno.Core.Extensions.Logging.Singleton is referenced by Uno.WinUI, so its
dependency on CommonServiceLocator ships CommonServiceLocator.dll inside every
Uno application. That package is Microsoft.Practices.ServiceLocation,
copyright Microsoft 2008, and it is licensed under the MS-PL, which the FSF
classifies as incompatible with the GPL and AGPL. Every GPL/AGPL-licensed Uno
app therefore redistributes an assembly it cannot relicense, over a service
locator API that predates Microsoft.Extensions.DependencyInjection.

The whole dependency was one optional lookup in GetFactory(), so replace it
with System.IServiceProvider, which is in the BCL and is what MS.DI already
implements.

 - Add LogExtensionPoint.ServiceProvider, an optional IServiceProvider used to
   resolve the ambient ILoggerFactory on first use
 - Resolve through it in GetFactory() instead of ServiceLocator.Current,
   keeping the existing fallback to a default LoggerFactory
 - Preserve the original exception behaviour exactly: NullReferenceException
   and InvalidOperationException fall back, anything else still propagates,
   now expressed as an exception filter rather than a catch-and-rethrow
 - Drop the CommonServiceLocator PackageReference

Uno.Core.Extensions.Compatibility keeps its CommonServiceLocator reference:
it exposes IServiceLocator in its public API by design, and it is not shipped
as part of Uno applications.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XD6CXamcmygU1ErZvjfc9v
The ServiceLocator branch this replaces had no test coverage.

 - Resolve the ambient factory from a stub IServiceProvider
 - Fall back to a default factory when no provider is set, and when the
   provider returns something that is not an ILoggerFactory
 - Propagate exceptions other than NullReference/InvalidOperation
 - Reset LogExtensionPoint.ServiceProvider between tests

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XD6CXamcmygU1ErZvjfc9v
agneszitte added a commit that referenced this pull request Sep 4, 2026
+semver: major

master moves to the 5.0 line so servicing/4.2 can keep publishing 4.2.0-dev.*
for consumers on the 4.x API.

The 5.0 line carries the LogExtensionPoint change in #94, which replaces
CommonServiceLocator with System.IServiceProvider and is a breaking API change.
Consumers needing the old surface stay on 4.2.x.
Addresses PR review: the LoggerFactory created in
AmbientLoggerFactory_ServiceProviderResolvesFactory_UsesResolvedFactory
was never disposed.

Disposing it alone would leave the static AmbientLoggerFactory cache
pointing at a disposed instance, so Cleanup now clears that cache too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnbcC6sgycChA1huPHfnnF
@MartinZikmund

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@mergify

mergify Bot commented Sep 6, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@morning4coffe-dev morning4coffe-dev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at df01d38.

The IServiceProvider substitution is scoped to Logging.Singleton and preserves optional first-use resolution, cached-factory behavior, the existing interceptor path, and the expected fallback/exception-propagation policy. The Compatibility package retains its legacy locator contracts, and the breaking configuration migration is explicitly documented.

No blocking findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants