Skip to content

[Windows] Fixed URI images not displaying - #31566

Closed
Ahamed-Ali wants to merge 6 commits into
dotnet:mainfrom
Ahamed-Ali:fix-31363
Closed

[Windows] Fixed URI images not displaying#31566
Ahamed-Ali wants to merge 6 commits into
dotnet:mainfrom
Ahamed-Ali:fix-31363

Conversation

@Ahamed-Ali

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Root Cause of the issue

  • On Windows, URI image loading uses System.Net.Http.HttpClient in the GetStreamAsync method of the StreamWrapper class, which does not automatically include a User-Agent header in HTTP requests. Many web servers (such as Wikimedia) block requests without a User-Agent header as an anti-bot security measure. As a result, image loading fails on Windows with a 403 Forbidden error, and the image is not displayed

Description of Change

  • Implemented default User-Agent string retrieval from the Windows SDK for HTTP requests, resolving 403 Forbidden errors from servers (e.g., Wikimedia) that block requests without User-Agent headers.

Reference

Issues Fixed

Fixes #31363

Tested the behaviour in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Screenshot

Before Issue Fix After Issue Fix
UriImageNotShowing.mp4
UriImageShowing.mp4

@dotnet-policy-service dotnet-policy-service Bot added community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration labels Sep 11, 2025
@jsuarezruiz

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

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

@Ahamed-Ali
Ahamed-Ali marked this pull request as ready for review September 25, 2025 04:48
Copilot AI review requested due to automatic review settings September 25, 2025 04:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes an issue where URI-based images were not displaying on Windows due to HTTP requests lacking a User-Agent header, causing servers like Wikimedia to return 403 Forbidden errors.

  • Adds Windows-specific User-Agent header to HTTP requests for image loading
  • Implements P/Invoke call to Windows SDK to retrieve default User-Agent string
  • Includes comprehensive UI test coverage for the fix

Reviewed Changes

Copilot reviewed 4 out of 8 changed files in this pull request and generated 4 comments.

File Description
src/Controls/src/Core/StreamWrapper.cs Adds Windows-specific User-Agent header logic to HTTP requests
src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs Implements Windows SDK P/Invoke method to obtain default User-Agent string
src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs Creates UI test page with Wikimedia image to reproduce the issue
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs Adds automated test to verify URI image loading works correctly

@@ -0,0 +1,24 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 31363, "The images under the I-CollectionView category are not showing up", PlatformAffected.UWP)]

Copilot AI Sep 25, 2025

Copy link

Choose a reason for hiding this comment

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

The platform should be PlatformAffected.WinUI instead of PlatformAffected.UWP since this is a Windows-specific fix for the modern WinUI platform, not the legacy UWP platform.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +93
[DllImport("urlmon.dll", ExactSpelling = true, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
private static extern int ObtainUserAgentString(int dwOption, StringBuilder userAgent, ref int length);

Copilot AI Sep 25, 2025

Copy link

Choose a reason for hiding this comment

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

The P/Invoke declaration should include security attributes. Consider adding [SuppressUnmanagedCodeSecurity] if appropriate, or ensure proper security validation of the native method call.

Copilot uses AI. Check for mistakes.
int result = ObtainUserAgentString(0, userAgentBuffer, ref length);

// Handle buffer overflow case - ObtainUserAgentString can return a longer string if needed
if (result == unchecked((int)0x8007000E)) // E_OUTOFMEMORY

Copilot AI Sep 25, 2025

Copy link

Choose a reason for hiding this comment

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

The magic number 0x8007000E should be defined as a named constant (e.g., private const int E_OUTOFMEMORY = unchecked((int)0x8007000E);) to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.
{
try
{
const int maxPath = 260;

Copilot AI Sep 25, 2025

Copy link

Choose a reason for hiding this comment

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

The magic number 260 should use the well-known constant System.IO.Path.MaxPath or define it as a named constant specific to user agent string length to clarify its purpose.

Copilot uses AI. Check for mistakes.
@jsuarezruiz jsuarezruiz added platform/windows area-image Image loading, sources, caching labels Oct 16, 2025
@jsuarezruiz

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

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

@github-actions

github-actions Bot commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 31566

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 31566"

@MauiBot

This comment has been minimized.

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Mar 25, 2026
@MauiBot

This comment has been minimized.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The test failed :/

@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

MauiBot

This comment was marked as outdated.

@MauiBot MauiBot added s/agent-review-incomplete s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 24, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you please resolve conflicts?

@kubaflo

kubaflo commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p windows

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 2 findings

See inline comments for details.

AutomationId = "TestImage",
Source = new UriImageSource
{
Uri = new Uri("https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Gelada-Pavian.jpg/320px-Gelada-Pavian.jpg")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Regression Prevention / Test Reliability - This UI test depends on a live Wikimedia URL. Screenshot UI tests should be deterministic; network/CDN failures, redirects, remote content changes, or host policy changes can produce blank/different pixels unrelated to the Windows User-Agent fix. Please serve a stable in-repo image from a controlled local test endpoint, or use a test server/handler that verifies the User-Agent behavior and returns a fixed asset.

public void UriImageSourceShouldDisplayProperly()
{
App.WaitForElement("TestImage");
VerifyScreenshot();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[moderate] Regression Prevention / Test Reliability - App.WaitForElement("TestImage") only waits for the Image view to exist; it does not wait for the URI image download/decode to complete. The screenshot can race and capture a blank placeholder, especially on slower Windows CI. Add a deterministic ready signal from the page and/or use VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(...)) after the image has finished loading.

MauiBot

This comment was marked as outdated.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please check the ai's suggestions and failing tests?

@kubaflo

kubaflo commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

/review rerun

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 21, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

WidthRequest = 60,
AutomationId = "TestImage",
Source = new UriImageSource
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Regression Prevention — This regression test depends on a third-party Wikimedia URL that is not reliable in the PR validation environment. The recorded gate still fails with the PR fix applied (UriImageSourceShouldDisplayProperly.png differs by 1.64%), and try-fix probing found the original URL returned HTTP 400 even with a User-Agent. Please switch this to a deterministic repo-controlled HTTPS asset, such as a commit-pinned raw.githubusercontent.com/dotnet/maui/... image, and refresh the affected snapshot baseline so the regression test actually validates the fix.

@MauiBot MauiBot added s/agent-gate-failed AI could not verify tests catch the bug s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels Jun 21, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 21, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please check the failing test?

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 25, 2026
@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels Jun 25, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@Ahamed-Ali — new AI review results are available based on this last commit: 83946ad. To request a fresh review after new comments or commits, comment /review rerun.

Gate Failed Confidence Low Platform Windows


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: WINDOWS · Base: main · Merge base: 5ec887fa

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue31363 Issue31363 ✅ FAIL — 610s ❌ FAIL — 469s
🔴 Without fix — 🖥️ Issue31363: FAIL ✅ · 610s
  Determining projects to restore...
  Restored D:\a\1\s\src\Controls\src\Core\Controls.Core.csproj (in 50.11 sec).
  Restored D:\a\1\s\src\Controls\Foldable\src\Controls.Foldable.csproj (in 1.24 sec).
  Restored D:\a\1\s\src\Controls\Maps\src\Controls.Maps.csproj (in 52.06 sec).
  Restored D:\a\1\s\src\BlazorWebView\src\Maui\Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 4.04 sec).
  Restored D:\a\1\s\src\Graphics\src\Graphics.Win2D\Graphics.Win2D.csproj (in 7 ms).
  Restored D:\a\1\s\src\Essentials\src\Essentials.csproj (in 21 ms).
  Restored D:\a\1\s\src\Core\src\Core.csproj (in 190 ms).
  Restored D:\a\1\s\src\Core\maps\src\Maps.csproj (in 14 ms).
  Restored D:\a\1\s\src\Graphics\src\Graphics\Graphics.csproj (in 3.86 sec).
  Restored D:\a\1\s\src\Controls\src\Xaml\Controls.Xaml.csproj (in 22 ms).
  Restored D:\a\1\s\src\Controls\tests\TestCases.HostApp\Controls.TestCases.HostApp.csproj (in 512 ms).
  3 of 14 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Graphics.Win2D -> D:\a\1\s\artifacts\bin\Graphics.Win2D\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Maps -> D:\a\1\s\artifacts\bin\Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Maps.dll
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Microsoft.AspNetCore.Components.WebView.Maui -> D:\a\1\s\artifacts\bin\Microsoft.AspNetCore.Components.WebView.Maui\Debug\net10.0-windows10.0.19041.0\Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Foldable -> D:\a\1\s\artifacts\bin\Controls.Foldable\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> D:\a\1\s\artifacts\bin\Controls.Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Maps.dll
  Controls.Xaml -> D:\a\1\s\artifacts\bin\Controls.Xaml\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Xaml.dll
  Controls.TestCases.HostApp -> D:\a\1\s\artifacts\bin\Controls.TestCases.HostApp\Debug\net10.0-windows10.0.19041.0\win-x64\Controls.TestCases.HostApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:06:02.13
  Determining projects to restore...
  Restored D:\a\1\s\src\TestUtils\src\VisualTestUtils\VisualTestUtils.csproj (in 939 ms).
  Restored D:\a\1\s\src\TestUtils\src\UITest.NUnit\UITest.NUnit.csproj (in 1.83 sec).
  Restored D:\a\1\s\src\TestUtils\src\UITest.Core\UITest.Core.csproj (in 3 ms).
  Restored D:\a\1\s\src\TestUtils\src\UITest.Appium\UITest.Appium.csproj (in 2 sec).
  Restored D:\a\1\s\src\TestUtils\src\VisualTestUtils.MagickNet\VisualTestUtils.MagickNet.csproj (in 4.98 sec).
  Restored D:\a\1\s\src\TestUtils\src\UITest.Analyzers\UITest.Analyzers.csproj (in 9.03 sec).
  Restored D:\a\1\s\src\Controls\tests\CustomAttributes\Controls.CustomAttributes.csproj (in 13 ms).
  Restored D:\a\1\s\src\Controls\tests\TestCases.WinUI.Tests\Controls.TestCases.WinUI.Tests.csproj (in 9.63 sec).
  7 of 15 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Controls.CustomAttributes -> D:\a\1\s\artifacts\bin\Controls.CustomAttributes\Debug\net10.0\Controls.CustomAttributes.dll
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0\Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0\Microsoft.Maui.dll
  Controls.Core.Design -> D:\a\1\s\artifacts\bin\Controls.Core.Design\Debug\net472\Microsoft.Maui.Controls.DesignTools.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0\Microsoft.Maui.Controls.dll
  UITest.Core -> D:\a\1\s\artifacts\bin\UITest.Core\Debug\net10.0\UITest.Core.dll
  UITest.Appium -> D:\a\1\s\artifacts\bin\UITest.Appium\Debug\net10.0\UITest.Appium.dll
  UITest.NUnit -> D:\a\1\s\artifacts\bin\UITest.NUnit\Debug\net10.0\UITest.NUnit.dll
  VisualTestUtils -> D:\a\1\s\artifacts\bin\VisualTestUtils\Debug\netstandard2.0\VisualTestUtils.dll
  VisualTestUtils.MagickNet -> D:\a\1\s\artifacts\bin\VisualTestUtils.MagickNet\Debug\netstandard2.0\VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> D:\a\1\s\artifacts\bin\UITest.Analyzers\Debug\netstandard2.0\UITest.Analyzers.dll
  Controls.TestCases.WinUI.Tests -> D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
Test run for D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 6/25/2026 5:18:12 PM FixtureSetup for Issue31363(Windows)
>>>>> 6/25/2026 5:18:22 PM UriImageSourceShouldDisplayProperly Start
>>>>> 6/25/2026 5:18:37 PM UriImageSourceShouldDisplayProperly Stop
>>>>> 6/25/2026 5:18:38 PM Log types: 
  Failed UriImageSourceShouldDisplayProperly [16 s]
  Error Message:
   System.TimeoutException : Timed out waiting for element...
  Stack Trace:
     at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.Issues.Issue31363.UriImageSourceShouldDisplayProperly() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs:line 17
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.11]   Discovering: Controls.TestCases.WinUI.Tests
[xUnit.net 00:00:00.35]   Discovered:  Controls.TestCases.WinUI.Tests
Results File: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue31363.trx

Total tests: 1
     Failed: 1
Test Run Failed.
 Total time: 1.0565 Minutes
>>> TRX_RESULT_FILE: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue31363.trx

🟢 With fix — 🖥️ Issue31363: FAIL ❌ · 469s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Essentials.dll
  Graphics.Win2D -> D:\a\1\s\artifacts\bin\Graphics.Win2D\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Microsoft.AspNetCore.Components.WebView.Maui -> D:\a\1\s\artifacts\bin\Microsoft.AspNetCore.Components.WebView.Maui\Debug\net10.0-windows10.0.19041.0\Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> D:\a\1\s\artifacts\bin\Controls.Xaml\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Xaml.dll
  Maps -> D:\a\1\s\artifacts\bin\Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Maps.dll
  Controls.Foldable -> D:\a\1\s\artifacts\bin\Controls.Foldable\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Foldable.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Controls.Maps -> D:\a\1\s\artifacts\bin\Controls.Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> D:\a\1\s\artifacts\bin\Controls.TestCases.HostApp\Debug\net10.0-windows10.0.19041.0\win-x64\Controls.TestCases.HostApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:05:47.74
  Determining projects to restore...
  All projects are up-to-date for restore.
  Controls.CustomAttributes -> D:\a\1\s\artifacts\bin\Controls.CustomAttributes\Debug\net10.0\Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0\Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0\Microsoft.Maui.dll
  Controls.Core.Design -> D:\a\1\s\artifacts\bin\Controls.Core.Design\Debug\net472\Microsoft.Maui.Controls.DesignTools.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486946
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0\Microsoft.Maui.Controls.dll
  VisualTestUtils -> D:\a\1\s\artifacts\bin\VisualTestUtils\Debug\netstandard2.0\VisualTestUtils.dll
  VisualTestUtils.MagickNet -> D:\a\1\s\artifacts\bin\VisualTestUtils.MagickNet\Debug\netstandard2.0\VisualTestUtils.MagickNet.dll
  UITest.Core -> D:\a\1\s\artifacts\bin\UITest.Core\Debug\net10.0\UITest.Core.dll
  UITest.NUnit -> D:\a\1\s\artifacts\bin\UITest.NUnit\Debug\net10.0\UITest.NUnit.dll
  UITest.Appium -> D:\a\1\s\artifacts\bin\UITest.Appium\Debug\net10.0\UITest.Appium.dll
  UITest.Analyzers -> D:\a\1\s\artifacts\bin\UITest.Analyzers\Debug\netstandard2.0\UITest.Analyzers.dll
  Controls.TestCases.WinUI.Tests -> D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
Test run for D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 6/25/2026 5:26:16 PM FixtureSetup for Issue31363(Windows)
>>>>> 6/25/2026 5:26:25 PM UriImageSourceShouldDisplayProperly Start
>>>>> 6/25/2026 5:26:28 PM UriImageSourceShouldDisplayProperly Stop
>>>>> 6/25/2026 5:26:28 PM Log types: 
  Failed UriImageSourceShouldDisplayProperly [2 s]
  Error Message:
   VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: UriImageSourceShouldDisplayProperly.png (1.64% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow

  Stack Trace:
     at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
   at Microsoft.Maui.TestCases.Tests.Issues.Issue31363.UriImageSourceShouldDisplayProperly() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs:line 18
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.13]   Discovering: Controls.TestCases.WinUI.Tests
[xUnit.net 00:00:00.35]   Discovered:  Controls.TestCases.WinUI.Tests
Results File: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue31363.trx

Total tests: 1
     Failed: 1
Test Run Failed.
 Total time: 27.0683 Seconds
>>> TRX_RESULT_FILE: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue31363.trx

⚠️ Failure Details

  • Issue31363 FAILED with fix (should pass)
    • UriImageSourceShouldDisplayProperly [2 s]
    • VisualTestUtils.VisualTestFailedException : Snapshot different than baseline: UriImageSourceShouldDisplayProperly.png (1.64% difference) If the correct baseline has changed (this isn't a a bug), th...
📁 Fix files reverted (2 files)
  • src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
  • src/Controls/src/Core/StreamWrapper.cs

📱 UI Tests — CollectionView,Image

Detected UI test categories: CollectionView,Image

Deep UI tests — 359 passed, 2 failed across 2 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
CollectionView 317/329 (1 ❌) 2 diff PNGs
Image 42/45 (1 ❌) 2 diff PNGs
CollectionView — 1 failed test
VerifyDefaultScrollToRequested
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyDefaultScrollToRequested.png (0.52% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 ret
...
Image — 1 failed test
UriImageSourceShouldDisplayProperly
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: UriImageSourceShouldDisplayProperly.png (1.64% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`
...

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)


📋 Pre-Flight — Context & Validation

Issue: #31363 - The images under the I-CollectionView category are not showing up
PR: #31566 - Windows UriImageSource remote image User-Agent fix
Platforms Affected: Windows
Files Changed: 2 implementation, 6 test/snapshot

Key Findings

  • PR adds a Windows-only default User-Agent for UriImageSource HTTP downloads when the HttpClient has none.
  • The added UI regression test targets Image and uses a remote Wikimedia image.
  • GitHub CLI is unauthenticated in this environment, so PR narrative, comments, prior reviews, and required checks could not be queried.
  • Expert review found the product direction sound, but the added test URL is currently invalid and can fail independently of the fix.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 1 | Suggestions: 1

Key code review findings:

  • src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs:17 uses a Wikimedia thumbnail URL that currently returns HTTP 400 even with a User-Agent.
  • src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs:3 depends on the internet but is not marked isInternetRequired: true.
  • src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs:95 could cache the default Windows User-Agent to avoid repeated P/Invoke/allocation.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31566 Add Windows default User-Agent in StreamWrapper.GetStreamAsync by calling Platform.Extensions.GetDefaultWindowsUserAgent() and mutating HttpClient.DefaultRequestHeaders.UserAgent before download. ❌ Gate failed before this phase Extensions.cs, StreamWrapper.cs, UI test/snapshots Original PR; product direction appears sound but test URL is invalid.

🔬 Code Review — Deep Analysis

Code Review — PR #31566

Independent Assessment

What this changes: Adds a Windows-only default User-Agent to UriImageSource HTTP downloads when no UA is already present, using urlmon.dll's ObtainUserAgentString. Adds a UI screenshot regression test for a remote UriImageSource.
Inferred motivation: Some servers, including Wikimedia/upload endpoints, reject .NET HttpClient image requests with no User-Agent, causing Windows remote images not to render.

Reconciliation with PR Narrative

Author claims: GitHub CLI was unauthenticated, so PR narrative/comments could not be read. The local PR delta and issue/test naming indicate this addresses #31363: remote images under the image/CollectionView UI area do not display on Windows.
Agreement/disagreement: The code matches the inferred goal of adding a UA for Windows image downloads. The regression test currently uses an invalid thumbnail URL, so it does not reliably validate that goal.

Prior Review Reconciliation

No prior ❌ Error findings found. Prior review surfaces could not be queried because gh is unauthenticated.

Blast Radius Assessment

  • Runs for all instances: Yes, all Windows Controls.UriImageSource downloads with empty HttpClient.DefaultRequestHeaders.UserAgent.
  • Startup impact: No; only runs during image download.
  • Static/shared state: No new static mutable state.

CI Status

  • Required-check result: undetermined; gh pr checks could not be used because GitHub CLI is unauthenticated.
  • Classification: undetermined.
  • Action taken: confidence capped; no PR comments posted.

Findings

❌ Error — Regression test uses a currently invalid thumbnail URL

src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs:17

The added test image URL currently returns HTTP 400 even when a User-Agent is supplied. This means the UI test can fail regardless of the product fix and does not reliably prove the no-User-Agent failure mode. A valid Wikimedia thumbnail variant such as 250px-Gelada-Pavian.jpg is suitable for the intended behavior: no UA -> rejected, UA -> success.

⚠️ Warning — Internet-dependent issue page is not marked as internet-required

src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs:3

The test page depends on a live external HTTP request, but the Issue attribute does not set isInternetRequired: true, unlike similar internet-backed issue tests.

💡 Suggestion — Consider caching the default Windows UA

src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs:95

GetDefaultWindowsUserAgent() P/Invokes and allocates on every Windows remote image download. Since the value is effectively process-level, a cached lazy value would avoid repeated native calls, especially for image-heavy CollectionViews.

Failure-Mode Probing

  • Server rejects missing UA: Product approach should fix valid URLs; valid Wikimedia thumbnails reject no UA and accept requests with a UA.
  • App/user already set UA: Preserved; code only adds default when UserAgent.Count == 0.
  • URLMON fails/unavailable: Code logs and continues without UA; image may still fail, but no crash.
  • Many images in CollectionView: Current caller creates a new HttpClient per download, so header mutation is not racing, but repeated UA lookup is avoidable.
  • Added test URL: Currently fails independently of UA, so the regression test is not valid as written.

Verdict: NEEDS_CHANGES

Confidence: low overall due unauthenticated CI/prior-review gaps; high confidence in the invalid test URL finding.
Summary: The Windows product fix is directionally sound, but the added regression test currently points at an invalid Wikimedia thumbnail and can fail even with the fix. Update the test URL/dependency strategy before merge.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix-1 Request-scoped Windows User-Agent via HttpRequestMessage; cache URLMON UA; fix invalid internet UI test URL/metadata. ✅ PASS 4 files Passed BuildAndRunHostApp.ps1 -Platform windows -TestFilter "FullyQualifiedName~Issue31363"; stronger than PR because it avoids mutating HttpClient.DefaultRequestHeaders.
PR PR #31566 Mutate client.DefaultRequestHeaders.UserAgent in StreamWrapper.GetStreamAsync using URLMON default UA; adds UI screenshot test. ❌ Gate failed before this phase 8 files Directionally sound, but pre-flight found the added test URL is invalid and the test lacks internet metadata.

Cross-Pollination

Model Round New Ideas? Details
gpt-5.5 / maui-expert-reviewer 1 Yes Generated request-scoped header approach with cached UA and test reliability corrections.

Exhausted: No — stopped because Candidate #1 passed all executed tests and is demonstrably better than the PR's current fix.
Selected Fix: Candidate #1 — request-scoped User-Agent avoids persistent HttpClient default-header mutation, fixes the invalid regression-test URL, and passed the targeted Windows UI regression.


try-fix-1 — Request-Scoped Windows User-Agent

Approach Description

Use a Windows-only HttpRequestMessage in StreamWrapper.GetStreamAsync, attaching the fallback Windows User-Agent to that request only and using HttpClient.SendAsync. This differs from PR #31566's current fix, which mutates client.DefaultRequestHeaders.UserAgent before GetAsync.

Additional test reliability fixes included in this candidate:

  • Mark the issue page as isInternetRequired: true.
  • Change the Wikimedia test image from the invalid 320px thumbnail to a valid 120px thumbnail.
  • Call VerifyInternetConnectivity() before the screenshot assertion.
  • Cache the URLMON User-Agent lookup with Lazy<string>.

Expert Review Guidance

The MAUI expert review identified this as the strongest alternative because request-scoped headers avoid shared-client side effects if StreamWrapper.GetStreamAsync is ever called with a reused HttpClient. Self-review findings were clean (reviewer-findings.json contains []).

Baseline Note

EstablishBrokenBaseline.ps1 could not establish a broken baseline because the worktree already contained unrelated uncommitted changes outside this PR. Per autonomous execution rules, this phase continued from the current PR state and did not modify or discard those unrelated changes.

╔═══════════════════════════════════════════════════════════════════╗
║  ERROR: DIRTY WORKING DIRECTORY - Cannot establish baseline       ║
╚═══════════════════════════════════════════════════════════════════╝

The following files have uncommitted changes:

   M .github/scripts/BuildAndRunHostApp.ps1
   M .github/scripts/Find-RegressionRisks.ps1
   M .github/scripts/Invoke-RerunReviewTrigger.Tests.ps1
   M .github/scripts/Invoke-RerunReviewTrigger.ps1
   M .github/scripts/Post-AISummaryComment.Tests.ps1
   M .github/scripts/Query-RerunReadyPRs.ps1
   M .github/scripts/Resolve-RerunEligibility.Tests.ps1
   M .github/scripts/Resolve-RerunEligibility.ps1
   M .github/scripts/Review-PR.Tests.ps1
   M .github/scripts/Review-PR.ps1
   M .github/scripts/Review-Tests.ps1
   M .github/scripts/post-ai-summary-comment.ps1
   M .github/scripts/post-inline-review.ps1
   M .github/scripts/shared/Update-AgentLabels.ps1
   M .github/scripts/shared/shared-utils.ps1
   M .github/skills/azdo-build-investigator/SKILL.md
   M .github/skills/release-readiness/SKILL.md
   M .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1
   M .github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1
   D .github/skills/release-readiness/scripts/NightlyFeed.ps1
   M .github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1
   M .github/skills/review-test-failures/SKILL.md
   M .github/skills/review-test-failures/scripts/Gather-TestFailureContext.ps1
   M .github/skills/run-device-tests/scripts/Run-DeviceTests.ps1
   M .github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
   M eng/scripts/detect-ui-test-categories.ps1
   M eng/scripts/get-maui-pr.ps1
   M eng/scripts/get-maui-pr.sh

This usually means a previous try-fix attempt did not restore properly.

To fix:
  1. Run 'git status' to review the changes
  2. Either commit them: git add . && git commit -m 'Save changes'
  3. Or discard them:   git checkout -- .
  4. Then retry this script

Exception: D:\a\1\s\.github\scripts\EstablishBrokenBaseline.ps1:387
Line |
 387 |      throw "EstablishBrokenBaseline.ps1 failed: Working directory is n …
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | EstablishBrokenBaseline.ps1 failed: Working directory is not clean. Clean up before establishing baseline.
baseline exit code: 1

Diff

diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
index 3a65d0e7f6..95f5fc6487 100644
--- a/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
+++ b/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
@@ -92,7 +92,13 @@ namespace Microsoft.Maui.Controls.Platform
 		[DllImport("urlmon.dll", ExactSpelling = true, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
 		private static extern int ObtainUserAgentString(int dwOption, StringBuilder userAgent, ref int length);
 
-		internal static string GetDefaultWindowsUserAgent()
+		static readonly Lazy<string> s_defaultWindowsUserAgent =
+			new(GetDefaultWindowsUserAgentUncached, LazyThreadSafetyMode.ExecutionAndPublication);
+
+		internal static string GetDefaultWindowsUserAgent() =>
+			s_defaultWindowsUserAgent.Value;
+
+		static string GetDefaultWindowsUserAgentUncached()
 		{
 			try
 			{
diff --git a/src/Controls/src/Core/StreamWrapper.cs b/src/Controls/src/Core/StreamWrapper.cs
index 770938553f..0620bbb3cb 100644
--- a/src/Controls/src/Core/StreamWrapper.cs
+++ b/src/Controls/src/Core/StreamWrapper.cs
@@ -93,16 +93,21 @@ namespace Microsoft.Maui.Controls
 		public static async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken, HttpClient client)
 		{
 #if WINDOWS
+			using var request = new HttpRequestMessage(HttpMethod.Get, uri);
+
 			if (client.DefaultRequestHeaders.UserAgent.Count == 0)
 			{
 				var userAgent = Platform.Extensions.GetDefaultWindowsUserAgent();
-				if (!string.IsNullOrEmpty(userAgent))
+				if (!string.IsNullOrWhiteSpace(userAgent))
 				{
-					client.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
+					request.Headers.TryAddWithoutValidation("User-Agent", userAgent);
 				}
 			}
-#endif
+
+			var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
+#else
 			var response = await client.GetAsync(uri, cancellationToken).ConfigureAwait(false);
+#endif
 			if (!response.IsSuccessStatusCode)
 			{
 				Application.Current?.FindMauiContext()?.CreateLogger<StreamWrapper>()?
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs
index 0b7fe972d0..ee41965275 100644
--- a/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs
@@ -1,6 +1,6 @@
 namespace Maui.Controls.Sample.Issues;
 
-[Issue(IssueTracker.Github, 31363, "The images under the I-CollectionView category are not showing up", PlatformAffected.UWP)]
+[Issue(IssueTracker.Github, 31363, "The images under the I-CollectionView category are not showing up", PlatformAffected.UWP, isInternetRequired: true)]
 public class Issue31363 : ContentPage
 {
     public Issue31363()
@@ -14,7 +14,7 @@ public class Issue31363 : ContentPage
             AutomationId = "TestImage",
             Source = new UriImageSource
             {
-                Uri = new Uri("https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Gelada-Pavian.jpg/320px-Gelada-Pavian.jpg")
+                Uri = new Uri("https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Gelada-Pavian.jpg/120px-Gelada-Pavian.jpg")
             }
         };
 
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs
index dd1714706a..2a35bef3c2 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs
@@ -14,6 +14,7 @@ public class Issue31363 : _IssuesUITest
     [Category(UITestCategories.Image)]
     public void UriImageSourceShouldDisplayProperly()
     {
+        VerifyInternetConnectivity();
         App.WaitForElement("TestImage");
         VerifyScreenshot();
     }

Test Results

Result: PASS

Command: pwsh .github\scripts\BuildAndRunHostApp.ps1 -Platform windows -TestFilter "FullyQualifiedName~Issue31363"

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 55.7766 Seconds
>>> TRX_RESULT_FILE: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\FullyQualifiedName_Issue31363.trx

ℹ️  Test output saved to: D:\a\1\s\CustomAgentLogsTmp\UITests\test-output.log

🔹 Collecting test artifacts (screenshots, page source)...
ℹ️  Test artifacts collected: 0 screenshot(s), 0 page source(s) (copied 0 from assembly dir)

🔹 Capturing device logs...
ℹ️  Windows platform - logs captured from test output
ℹ️  Windows device log created: D:\a\1\s\CustomAgentLogsTmp\UITests\windows-device.log

═══════════════════════════════════════════════════════
  Windows App Logs (Last 100 lines)
═══════════════════════════════════════════════════════
Windows UI Test run at 06/25/2026 17:45:49

ℹ️  Full device log: D:\a\1\s\CustomAgentLogsTmp\UITests\windows-device.log
═══════════════════════════════════════════════════════

✅ All tests passed

╔═══════════════════════════════════════════════════════════╗
║                    Test Summary                           ║
╠═══════════════════════════════════════════════════════════╣
║  Platform:     WINDOWS                                ║
║  Device:       host                                          ║
║  Test Filter:  FullyQualifiedName~Issue31363                 ║
║  Result:       SUCCESS ✅                                 ║
║  Logs:         D:\a\1\s\CustomAgentLogsTmp\UITests
╚═══════════════════════════════════════════════════════════╝

Failure Analysis

No failure. The Windows HostApp built successfully and the targeted WinUI UI test UriImageSourceShouldDisplayProperly passed. This candidate is demonstrably better than the PR's current fix because it preserves the behavior fix while avoiding mutation of HttpClient.DefaultRequestHeaders, fixes the invalid regression-test URL, and marks the internet-dependent test correctly.


📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current metadata describes the raw PR approach but not the winning request-scoped header fix or the test reliability corrections.

Recommended title

[Windows] UriImageSource: Add request-scoped default User-Agent

Recommended description

### Root Cause of the issue

- On Windows, URI image loading uses `System.Net.Http.HttpClient` in `StreamWrapper.GetStreamAsync`, which does not automatically include a User-Agent header in HTTP requests. Many web servers (such as Wikimedia) block requests without a User-Agent header as an anti-bot security measure. As a result, image loading fails on Windows with a 403 Forbidden error, and the image is not displayed.

### Description of Change

- Retrieve the default Windows User-Agent string from URLMON/Windows SDK behavior for HTTP image requests.
- Apply the fallback User-Agent on the individual `HttpRequestMessage` used by `StreamWrapper.GetStreamAsync` instead of mutating `HttpClient.DefaultRequestHeaders`, avoiding shared-client side effects and header leakage to future requests.
- Cache the default Windows User-Agent lookup so repeated image downloads do not repeatedly call the native API.
- Add UI regression coverage for `UriImageSource` and make the internet-dependent test reliable by using a valid Wikimedia thumbnail URL, marking the issue with `isInternetRequired: true`, and checking `VerifyInternetConnectivity()` before the screenshot assertion.

### Reference

- [DefaultUserAgent](https://github.com/dotnet/wpf/blob/09a6800ac25e40fc636d22a3e78216840b386ba2/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/WpfWebRequestHelper.cs#L143)
- [Implementation](https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsOther.cs#L233)

### Issues Fixed

Fixes #31363

### Tested the behaviour in the following platforms

- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Screenshot

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video src="https://github.com/user-attachments/assets/6d69db09-97d8-4be6-beeb-eb64c67f7cc0"> | <video src="https://github.com/user-attachments/assets/19b33589-5444-4d2a-9ca1-461fdcbbff84"> |

🏁 Report — Final Recommendation

Comparative Report — PR #31566

Candidates compared

Rank Candidate Regression result Assessment
1 try-fix-1 ✅ PASS Best supported candidate. It uses a request-scoped Windows User-Agent, caches URLMON lookup, fixes the invalid test URL, and adds internet gating. It passed BuildAndRunHostApp.ps1 -Platform windows -TestFilter "FullyQualifiedName~Issue31363".
2 pr-plus-reviewer Inferred ✅ (same material changes as try-fix-1) Technically equivalent to the winning approach after applying expert feedback in a sandbox copy, but it was not independently re-run as a separate candidate in STEP 5a.
3 pr ❌ FAILED Raw PR direction is valid, but the gate failed after applying the PR fix, the implementation mutates shared HttpClient.DefaultRequestHeaders, and the added UI test uses an invalid external thumbnail without internet gating.

Analysis

The raw PR correctly identifies that Windows HTTP image loading may need a User-Agent to avoid 403 responses from some servers, but it applies that header by mutating the supplied HttpClient defaults. That is riskier than necessary because DefaultRequestHeaders is shared state: callers may reuse the client, concurrent downloads may observe mutation, and future requests can inherit a MAUI-injected User-Agent unexpectedly.

try-fix-1 preserves the product fix while narrowing the header to the single image request. It also addresses the test failures found in pre-flight and expert review: the original 320px Wikimedia URL is invalid, and the internet-dependent test needs the same connectivity handling used by other MAUI UI tests. Because try-fix-1 passed the targeted Windows UI regression and the raw PR failed, the failed raw PR must rank lower.

Winner

Winner: try-fix-1

Rationale: It is the only candidate with direct passing regression evidence and it incorporates all expert-review corrections: request-scoped User-Agent, cached URLMON lookup, valid regression URL, and internet-aware UI test metadata. pr-plus-reviewer is technically aligned but has only sandbox/inferred evidence, while the raw PR failed the gate and retains actionable review issues.


🧭 Next Steps — alternative fix proposed (try-fix-1)

Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against automatically generated candidates and selected try-fix-1 as the strongest fix.

Why: try-fix-1 wins because it is the only candidate with direct passing Windows regression evidence and it incorporates the expert-review corrections: request-scoped User-Agent, cached URLMON lookup, valid test URL, and internet gating. The raw PR failed the gate and retains shared HttpClient header mutation/test reliability issues.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (try-fix-1)
diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
index b3af4366ff..95f5fc6487 100644
--- a/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
+++ b/src/Controls/src/Core/Platform/Windows/Extensions/Extensions.cs
@@ -1,8 +1,11 @@
 #nullable disable
 using System;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
 using Microsoft.Maui.Controls.Internals;
 using Microsoft.UI.Xaml;
 using Microsoft.UI.Xaml.Controls;
@@ -85,5 +88,44 @@ namespace Microsoft.Maui.Controls.Platform
 					return UwpScrollBarVisibility.Auto;
 			}
 		}
+
+		[DllImport("urlmon.dll", ExactSpelling = true, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
+		private static extern int ObtainUserAgentString(int dwOption, StringBuilder userAgent, ref int length);
+
+		static readonly Lazy<string> s_defaultWindowsUserAgent =
+			new(GetDefaultWindowsUserAgentUncached, LazyThreadSafetyMode.ExecutionAndPublication);
+
+		internal static string GetDefaultWindowsUserAgent() =>
+			s_defaultWindowsUserAgent.Value;
+
+		static string GetDefaultWindowsUserAgentUncached()
+		{
+			try
+			{
+				const int maxPath = 260;
+				int length = maxPath;
+				var userAgentBuffer = new StringBuilder(length);
+				int result = ObtainUserAgentString(0, userAgentBuffer, ref length);
+
+				// Handle buffer overflow case - ObtainUserAgentString can return a longer string if needed
+				if (result == unchecked((int)0x8007000E)) // E_OUTOFMEMORY
+				{
+					userAgentBuffer = new StringBuilder(length);
+					result = ObtainUserAgentString(0, userAgentBuffer, ref length);
+				}
+
+				if (result >= 0) // SUCCEEDED(result)
+				{
+					return userAgentBuffer.ToString();
+				}
+			}
+			catch (Exception ex)
+			{
+				Application.Current?.FindMauiContext()?.CreateLogger<StreamWrapper>()?
+						.LogWarning("Failed to obtain Default Windows User-Agent string: {Exception}", ex.Message);
+			}
+
+			return null;
+		}
 	}
 }
\ No newline at end of file
diff --git a/src/Controls/src/Core/StreamWrapper.cs b/src/Controls/src/Core/StreamWrapper.cs
index 44b5662a33..dbdbe2d693 100644
--- a/src/Controls/src/Core/StreamWrapper.cs
+++ b/src/Controls/src/Core/StreamWrapper.cs
@@ -92,7 +92,22 @@ namespace Microsoft.Maui.Controls
 
 		public static async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken, HttpClient client)
 		{
+#if WINDOWS
+			using var request = new HttpRequestMessage(HttpMethod.Get, uri);
+
+			if (client.DefaultRequestHeaders.UserAgent.Count == 0)
+			{
+				var userAgent = Platform.Extensions.GetDefaultWindowsUserAgent();
+				if (!string.IsNullOrWhiteSpace(userAgent))
+				{
+					request.Headers.TryAddWithoutValidation("User-Agent", userAgent);
+				}
+			}
+
+			var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
+#else
 			var response = await client.GetAsync(uri, cancellationToken).ConfigureAwait(false);
+#endif
 			if (!response.IsSuccessStatusCode)
 			{
 				Application.Current?.FindMauiContext()?.CreateLogger<StreamWrapper>()?
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs
new file mode 100644
index 0000000000..890e4f7ef3
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue31363.cs
@@ -0,0 +1,24 @@
+namespace Maui.Controls.Sample.Issues;
+
+[Issue(IssueTracker.Github, 31363, "The images under the I-CollectionView category are not showing up", PlatformAffected.UWP, isInternetRequired: true)]
+public class Issue31363 : ContentPage
+{
+    public Issue31363()
+    {
+        var layout = new VerticalStackLayout();
+
+        var image = new Image
+        {
+            HeightRequest = 60,
+            WidthRequest = 60,
+            AutomationId = "TestImage",
+            Source = new UriImageSource
+            {
+                Uri = new Uri("https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Gelada-Pavian.jpg/120px-Gelada-Pavian.jpg")
+            }
+        };
+
+        layout.Children.Add(image);
+        Content = layout;
+    }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs
new file mode 100644
index 0000000000..2a35bef3c2
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31363.cs
@@ -0,0 +1,21 @@
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue31363 : _IssuesUITest
+{
+    public Issue31363(TestDevice device) : base(device) { }
+
+    public override string Issue => "The images under the I-CollectionView category are not showing up";
+
+    [Test]
+    [Category(UITestCategories.Image)]
+    public void UriImageSourceShouldDisplayProperly()
+    {
+        VerifyInternetConnectivity();
+        App.WaitForElement("TestImage");
+        VerifyScreenshot();
+    }
+}
\ No newline at end of file

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 25, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please update snapshots?

@kubaflo

kubaflo commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Closing as a stale one - please open a new one if this PR is still needed

@kubaflo kubaflo closed this Jul 5, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-image Image loading, sources, caching community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/windows s/agent-fix-win AI found a better alternative fix than the PR s/agent-gate-failed AI could not verify tests catch the bug s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The images under the I-CollectionView category are not showing up.

6 participants