The Anatomy of an Uno Platform Solution

The Anatomy of an Uno Platform Solution

It’s been almost a month since I left home to attend the Microsoft MVP Summit in Seattle. I’m still on the road, currently in Athens, Greece, with numerous notes for upcoming articles. While traveling makes writing challenging, I want to maintain the order of my Uno Platform series to ensure everything makes sense for readers.

In this article, we’ll dive into the structure of an Uno Platform solution. There’s some “black magic” happening behind the scenes, so understanding how everything works will make development significantly easier.

What is Uno Platform?

Before we dive into the anatomy, let’s briefly explain what Uno Platform is. Uno Platform is an open-source framework that enables developers to build cross-platform applications from a single codebase. Using C# and XAML, you can create applications that run on Windows, iOS, Android, macOS, Linux, and WebAssembly.

Root Solution Structure

An Uno Platform solution follows a specific structure that facilitates cross-platform development. Let’s break down the key components:

Main (and only) Project

The core of an Uno Platform solution is the main shared project (in our example, “UnoAnatomy”). This project contains cross-platform code shared across all target platforms and includes:

  • Assets: Contains shared resources like images and icons used across all platforms. These assets may be adapted for different screen densities and platforms as needed.
  • Serialization: Here is where the JsonSerializerContext lives, Since .NET 6 serialization context allows controlling how objects are serialized through the JsonSerializerContext class. It provides ahead-of-time metadata generation for better performance and reduces reflection usage, particularly beneficial for AOT compilation scenarios like Blazor WebAssembly and native apps.
  • Models: Contains business model classes representing core domain entities in your application.
  • Presentation: Holds UI components including pages, controls, and views. This typically includes files like Shell.xaml.cs and MainPage.xaml.cs that implement the application’s UI elements and layout.
  • Platforms:
    • Android: Contains the Android-specific entry point (MainActivity.Android.cs) and any other Android-specific configurations or code.
    • iOS: Contains the iOS-specific entry point (Main.iOS.cs).
    • MacCatalyst: Contains the MacCatalyst-specific entry point (Main.maccatalyst.cs).
    • BrowserWasm: Contains the Browser WASM specific configurations or code.
    • Desktop: Contains the Desktop specific configurations or code.
  • Services: Contains service classes implementing business logic, data access, etc. This folder often includes subfolders like:
  • Strings: the purpose of this folder is to store the localized string resources for the application so it can be translated to multiple languages.
  • Styles: this folder contains the styles or color configuration for the app.

Build Configuration Files

Several build configuration files in the root of the solution control the build process:

  • Directory.Build.props: Contains global MSBuild properties applied to all projects.
  • Directory.Build.targets: Contains global MSBuild targets for all projects.
  • Directory.Packages.props: Centralizes package versions for dependency management.
  • global.json: Specifies the Uno.SDK version and other .NET SDK configurations.

The Power of Uno.Sdk

One of the most important aspects of modern Uno Platform development is the Uno.Sdk, which significantly simplifies the development process.

What is Uno.Sdk?

Uno.Sdk is a specialized MSBuild SDK that streamlines Uno Platform development by providing:

  1. A cross-platform development experience that simplifies targeting multiple platforms from a single project
  2. Automatic management of platform-specific dependencies and configurations
  3. A simplified build process that handles the complexity of building for different target platforms
  4. Feature-based configuration that enables adding functionality through the UnoFeatures property

In your project file, you’ll see <Project Sdk="Uno.Sdk"> at the top, indicating that this project uses the Uno SDK rather than the standard .NET SDK.

Key Components of the Project File

TargetFrameworks

<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst;net9.0-windows10.0.26100;net9.0-browserwasm;net9.0-desktop</TargetFrameworks>

This line specifies that your application targets:

  • Android
  • iOS
  • macOS (via Mac Catalyst)
  • Windows (Windows 10/11 with SDK version 10.0.26100)
  • WebAssembly (for browser-based applications)
  • Desktop (for cross-platform desktop applications)

All of these targets use .NET 9 as the base framework.

Single Project Configuration

<OutputType>Exe</OutputType>
<UnoSingleProject>true</UnoSingleProject>
  • OutputType: Specifies this project builds an executable application
  • UnoSingleProject: Enables Uno’s single-project approach, allowing you to maintain one codebase for all platforms

Application Metadata

<ApplicationTitle>UnoAnatomy</ApplicationTitle>
<ApplicationId>com.companyname.UnoAnatomy</ApplicationId>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationPublisher>joche</ApplicationPublisher>
<Description>UnoAnatomy powered by Uno Platform.</Description>

These properties define your app’s identity and metadata used in app stores and installation packages.

UnoFeatures

The most powerful aspect of Uno.Sdk is the UnoFeatures property:

<UnoFeatures>
  Material;
  Dsp;
  Hosting;
  Toolkit;
  Logging;
  Mvvm;
  Configuration;
  Http;
  Serialization;
  Localization;
  Navigation;
  ThemeService;
</UnoFeatures>

This automatically adds relevant NuGet packages for each listed feature:

  • Material: Material Design UI components
  • Dsp: Digital Signal Processing capabilities
  • Hosting: Dependency injection and host builder pattern
  • Toolkit: Community Toolkit components
  • Logging: Logging infrastructure
  • Mvvm: Model-View-ViewModel pattern implementation
  • Configuration: Application configuration framework
  • Http: HTTP client capabilities
  • Serialization: Data serialization/deserialization
  • Localization: Multi-language support
  • Navigation: Navigation services
  • ThemeService: Dynamic theme support

The UnoFeatures property eliminates the need to manually add numerous NuGet packages and ensures compatibility between components.

Benefits of the Uno Platform Structure

This structured approach to cross-platform development offers several advantages:

  1. Code Sharing: Most code is shared across platforms, reducing duplication and maintenance overhead.
  2. Platform-Specific Adaptation: When needed, the structure allows for platform-specific implementations.
  3. Simplified Dependencies: The Uno.Sdk handles complex dependency management behind the scenes.
  4. Consistent Experience: Ensures a consistent development experience across all target platforms.
  5. Future-Proofing: The architecture makes it easier to add support for new platforms in the future.

Conclusion

Understanding the anatomy of an Uno Platform solution is crucial for effective cross-platform development. The combination of shared code, platform-specific heads, and the powerful Uno.Sdk creates a development experience that makes it much easier to build and maintain applications across multiple platforms from a single codebase.

By leveraging this structure and the features provided by the Uno Platform, you can focus on building your application’s functionality rather than dealing with the complexities of cross-platform development.

In my next article in this series, we’ll dive deeper into the practical aspects of developing with Uno Platform, exploring how to leverage these structural components to build robust cross-platform applications.

 

Related articles

Getting Started with Uno Platform: First Steps and Configuration Choices | Joche Ojeda

My Adventures Picking a UI Framework: Why I Chose Uno Platform | Joche Ojeda

Exploring the Uno Platform: Handling Unsafe Code in Multi-Target Applications | Joche Ojeda

About Us

YouTube

https://www.youtube.com/c/JocheOjedaXAFXAMARINC

Our sites

Let’s discuss your XAF

This call/zoom will give you the opportunity to define the roadblocks in your current XAF solution. We can talk about performance, deployment or custom implementations. Together we will review you pain points and leave you with recommendations to get your app back in track

https://calendly.com/bitframeworks/bitframeworks-free-xaf-support-hour

Our free A.I courses on Udemy

Troubleshooting MAUI Android HTTP Client Issues: Native vs Managed Implementation

Troubleshooting MAUI Android HTTP Client Issues: Native vs Managed Implementation

When developing cross-platform mobile applications with .NET MAUI (or previously Xamarin), you may encounter situations where your app works perfectly with public APIs but fails when connecting to internal network services. These issues often stem from HTTP client implementation differences, certificate validation, and TLS compatibility. This article explores how to identify, troubleshoot, and resolve these common networking challenges.

Understanding HTTP Client Options in MAUI/Xamarin

In the MAUI/.NET ecosystem, developers have access to two primary HTTP client implementations:

1. Managed HttpClient (Microsoft’s implementation)

  • Cross-platform implementation built into .NET
  • Consistent behavior across different operating systems
  • May handle SSL/TLS differently than platform-native implementations
  • Uses the .NET certificate validation system

2. Native HttpClient (Android’s implementation)

  • Leverages the platform’s native networking stack
  • Typically offers better performance on the specific platform
  • Uses the device’s system certificate trust store
  • Follows platform-specific security policies and restrictions

Switching Between Native and Managed HttpClient

In MAUI Applications

MAUI provides a flexible handler registration system that lets you explicitly choose which implementation to use:

// In your MauiProgram.cs
public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp<App>()
        .ConfigureMauiHandlers(handlers =>
        {
            // Use the managed implementation (Microsoft's .NET HttpClient)
            handlers.AddHandler(typeof(HttpClient), typeof(ManagedHttpMessageHandler));
            
            // OR use the native implementation (platform-specific)
            // handlers.AddHandler(typeof(HttpClient), typeof(PlatformHttpMessageHandler));
        });
    
    return builder.Build();
}

In Xamarin.Forms Legacy Applications

For Xamarin.Forms applications, set this in your platform-specific initialization code:

// In MainActivity.cs (Android) or AppDelegate.cs (iOS)
HttpClientHandler.UseNativePlatformHandler = false; // Use managed handler
// OR
HttpClientHandler.UseNativePlatformHandler = true;  // Use native handler

Creating Specific Client Instances

You can also explicitly create HttpClient instances with specific handlers when needed:

// Use the managed handler
var managedHandler = new HttpClientHandler();
var managedClient = new HttpClient(managedHandler);

// Use the native handler (with DependencyService in Xamarin)
var nativeHandler = DependencyService.Get<INativeHttpClientHandler>();
var nativeClient = new HttpClient(nativeHandler);

Using HttpClientFactory (Recommended for MAUI)

For better control, testability, and lifecycle management, consider using HttpClientFactory:

// In your MauiProgram.cs
builder.Services.AddHttpClient("ManagedClient", client => {
    client.BaseAddress = new Uri("https://your.api.url/");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler());

// Then inject and use it in your services
public class MyApiService
{
    private readonly HttpClient _client;
    
    public MyApiService(IHttpClientFactory clientFactory)
    {
        _client = clientFactory.CreateClient("ManagedClient");
    }
}

Common Issues and Troubleshooting

1. Self-Signed Certificates

Internal APIs often use self-signed certificates that aren’t trusted by default. Here’s how to handle them:

// Option 1: Create a custom handler that bypasses certificate validation 
// (ONLY for development/testing environments)
var handler = new HttpClientHandler
{
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
var client = new HttpClient(handler);

For production environments, instead of bypassing validation:

  • Add your self-signed certificate to the Android trust store
  • Configure your app to trust specific certificates
  • Generate proper certificates from a trusted Certificate Authority

2. TLS Version Mismatches

Different Android versions support different TLS versions by default:

  • Android 4.1-4.4: TLS 1.0 by default
  • Android 5.0+: TLS 1.0, 1.1, 1.2
  • Android 10+: TLS 1.3 support

If your server requires a specific TLS version:

// Force specific TLS versions
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;

3. Network Configuration

Ensure your app has the proper permissions in the AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

For Android 9+ (API level 28+), configure network security:

<!-- Create a network_security_config.xml file in Resources/xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">your.internal.domain</domain>
    </domain-config>
</network-security-config>

Then reference it in your AndroidManifest.xml:

<application android:networkSecurityConfig="@xml/network_security_config">

Practical Troubleshooting Steps

  1. Test with both HTTP client implementationsSwitch between native and managed implementations to isolate whether the issue is specific to one implementation
  2. Test the API endpoint outside your appUse tools like Postman or curl on the same network
  3. Enable logging for network calls
    // Add this before making requests
    HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "YourApp/1.0");
  4. Capture and inspect network trafficUse Charles Proxy or Fiddler to inspect the actual requests/responses
  5. Check certificate information
    # On your development machine
    openssl s_client -connect your.internal.server:443 -showcerts
  6. Verify which implementation you’re using
    var client = new HttpClient();
    var handlerType = client.GetType().GetField("_handler", 
        System.Reflection.BindingFlags.Instance | 
        System.Reflection.BindingFlags.NonPublic)?.GetValue(client);
    
    Console.WriteLine($"Using handler: {handlerType?.GetType().FullName}");
  7. Debug specific errors
    • For Java.IO.IOException: “Trust anchor for certification path not found” – this means your app doesn’t trust the certificate
    • For HttpRequestException with “The SSL connection could not be established” – likely a TLS version mismatch

Conclusion

When your MAUI Android app connects successfully to public APIs but fails with internal network services, the issue often lies with HTTP client implementation differences, certificate validation, or TLS compatibility. By systematically switching between native and managed HTTP clients and applying the troubleshooting techniques outlined above, you can identify and resolve these networking challenges.

Remember that each implementation has its advantages – the native implementation typically offers better performance and follows platform-specific security policies, while the managed implementation provides more consistent cross-platform behavior. Choose the one that best fits your specific requirements and security considerations.

About Us

YouTube

https://www.youtube.com/c/JocheOjedaXAFXAMARINC

Our sites

Let’s discuss your XAF

https://www.udemy.com/course/microsoft-ai-extensions/

Our free A.I courses on Udemy