From 9592038a67f98c5e644eeccceb9e4363310b4c6a Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Sun, 7 May 2023 10:34:01 -0400 Subject: [PATCH 01/58] docs: authorize with cookies doc, toc entry --- .../Tutorials/Authentication/HowTo-Cookies.md | 85 ++++++++++++++++++- doc/toc.yml | 2 + 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md b/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md index 4a563beaf0..a43c6d44ba 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md @@ -3,11 +3,88 @@ uid: Learn.Tutorials.Authentication.HowToCookieAuthorization --- # How-To: Using Cookies to Authorize -`Uno.Extensions.Authentication` provides you with a consistent way to add authentication to your application. It is recommended to use one of the built in `IAuthenticationService` implementations. This tutorial will use the custom authorization to validate user credentials. +An authorization method conceptually similar to using the system credential store is to use **cookies**. Cookies are a common way to store tokens that can be used to authenticate a user. When a HTTP request is succesfully authenticated, the server will return a response that we can use to create a cookie. The cookie can then be used to authenticate future requests. Uno Extensions makes these cookie-related authorization steps less tedious by doing the work of extracting the token(s) from the authentication response, storing it in a cookie, and applying the cookie to future requests. This tutorial will teach you how to configure authentication to apply tokens from a cookie when they are available. -> [!TIP] -> This guide assumes you used the Uno.Extensions template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions). +> [!IMPORTANT] +> To follow these steps, you first need to have an authentication system set up. We recommend choosing one of the `IAuthenticationProvider` implementations provided by Uno Extensions. Cookie authorization can complement any of the tutorials such as [Get Started with Authentication](xref:Learn.Tutorials.Authentication.HowToAuthentication). ## Step-by-steps -### 1. \ No newline at end of file +### 1. Enable cookies + +- The first step is to opt-in to using cookies. This will allow for writing of returned access and refresh tokens to a cookie, and enables future reading of tokens from the cookie when they are available. You will also be able to name the tokens. Your app should already have an `IHostBuilder` configured to use an authentication provider like below: + + ```csharp + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + var builder = this.CreateBuilder(args) + .Configure(host => + { + host.UseAuthentication(auth => + auth.AddCustom(custom => + custom.Login( + async (sp, dispatcher, tokenCache, credentials, cancellationToken) => + { + var isValid = credentials.TryGetValue("Username", out var username) && username == "Bob"; + return isValid ? + credentials : default; + }) + )); + }); + ... + ``` + +- Modify the app to add the `Cookies()` extension method. It configures the `IAuthenticationBuilder` to use cookies by registering a special HTTP request handler that will parse the response for tokens and store them in a cookie. It will apply them to future requests. + + ```csharp + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + var builder = this.CreateBuilder(args) + .Configure(host => + { + host.UseAuthentication(auth => + auth.AddCustom(custom => + custom.Login( + async (sp, dispatcher, tokenCache, credentials, cancellationToken) => + { + var isValid = credentials.TryGetValue("Username", out var username) && username == "Bob"; + return isValid ? + credentials : default; + }) + ), + configureAuthorization: builder => + { + builder + .Cookies(/* options */); + }); + }); + ... + ``` + +### 2. Configure cookie options + +- The `Cookies()` extension method takes two parameters; the first represents a name for the [access token](https://oauth.net/2/access-tokens/) cookie, and the second represents a name for the [refresh token](https://oauth.net/2/refresh-tokens/) cookie. + + ```csharp + configureAuthorization: builder => + { + builder + .Cookies("AccessToken", "RefreshToken"); + ... + ``` + +- Specifying a value for the latter is optional. + +### 3. Authorize with token value from cookie + +- Now, when you attempt to authenticate with a provider, the `Cookies()` extension method will attempt to authorize from a cookie by including any token information in the request. If the cookie is not found, it will attempt to authenticate with the provider as normal. + +- For more information on how to call the authentication service from a view model, see [Get Started with Authentication](xref:Learn.Tutorials.Authentication.HowToAuthentication). + +## See also + +- [Authentication](xref:Overview.Authentication) +- [Get Started with Authentication](xref:Learn.Tutorials.Authentication.HowToAuthentication) +- [What is a cookie?](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) +- [Access tokens](https://oauth.net/2/access-tokens/) +- [Refresh tokens](https://oauth.net/2/refresh-tokens/) \ No newline at end of file diff --git a/doc/toc.yml b/doc/toc.yml index fd3a106c15..2b29a6df11 100644 --- a/doc/toc.yml +++ b/doc/toc.yml @@ -18,6 +18,8 @@ href: Learn/Tutorials/Authentication/HowTo-OidcAuthentication.md - name: Use Web authentication href: Learn/Tutorials/Authentication/HowTo-WebAuthentication.md + - name: Authorize with cookies + href: Learn/Tutorials/Authentication/HowTo-Cookies.md - name: Configuration items: - name: Overview From f958a8099d1a949ec73a9649120d99cd7ba417cb Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Sun, 7 May 2023 10:34:08 -0400 Subject: [PATCH 02/58] docs: fix typo --- doc/Learn/Tutorials/Authentication/HowTo-Authentication.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md b/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md index 02cc11dc34..6b00522899 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md @@ -31,11 +31,8 @@ uid: Learn.Tutorials.Authentication.HowToAuthentication async (sp, dispatcher, tokenCache, credentials, cancellationToken) => { var isValid = credentials.TryGetValue("Username", out var username) && username == "Bob"; - if(isValid) - { - credentials; - } - return null; + return isValid ? + credentials : default; }) )); }); From d4bd822970c83ebd2b296ad945ec44b4243bf2b0 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Mon, 8 May 2023 11:59:14 -0400 Subject: [PATCH 03/58] docs(localization): Update for IApplicationBuilder --- .../Localization/LocalizationOverview.md | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/doc/Overview/Localization/LocalizationOverview.md b/doc/Overview/Localization/LocalizationOverview.md index 79749a92b4..0dd1c34222 100644 --- a/doc/Overview/Localization/LocalizationOverview.md +++ b/doc/Overview/Localization/LocalizationOverview.md @@ -1,46 +1,51 @@ --- uid: Overview.Localization --- + # Localization -Uno.Extensions.Localization uses [Microsoft.Extensions.Localization](https://www.nuget.org/packages/Microsoft.Extensions.Localization) for any localization related work. -For more documentation on localization, read the references listed at the bottom. +It is often necessary to adapt an application to a specific subset of users within a market. **Localization** includes the type of actions developers take to modify both user interface elements and content to adhere to more languages or cultures. Specifically, text **translation** is done by applying alternate strings of text at runtime which accomodate a user's language preference. Many apps store these pieces of text in dedicated resource files that the app parses and assigns as text throughout the application. `Uno.Extensions.Localization` provides a consistent way to resolve text of a specific culture or locale across platforms. This feature allows for modifying them to be applied upon app restart. -## Text localization +It uses [Microsoft.Extensions.Localization](https://www.nuget.org/packages/Microsoft.Extensions.Localization) for any localization related work. For documentation on the broader process of localization, read the references listed at the bottom. -Save locale specific resources in `.resw` files in folder corresponding to locale (eg en-US). +## Resolving translated strings -An implementation of `IStringLocalizer` (`ResourceLoaderStringLocalizer`) is registered as service. +Once locale specific resources are included in `.resw` files corresponding to the desired locale folder (eg en-US), the localization feature can be used to resolve those localized texts. ```csharp -private IHost Host { get; } - -public App() +protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLocalization() - .Build(); - // ........ // -} + var appBuilder = this.CreateBuilder(args) + .Configure(host => + { + host + .UseLocalization() + }); +... ``` -We use `IStringLocalizer` to resolve those localized texts +An implementation of `IStringLocalizer` (`ResourceLoaderStringLocalizer`) will be registered as a service. This service offers a consistent way to resolve localized strings. Behind the scenes, it will automatically use `ResourceManager` on Windows and `ResourceLoader` on other platforms. ```csharp var stringLocalizer = serviceProvider.GetService(); +``` + +Localized strings can be resolved using the indexer on the IStringLocalizer as a dictionary. This indexer takes a key and returns the localized string. -// Using IStringLocalizer as a dictionary +```csharp string myString = stringLocalizer["MyKey"]; +``` + +LocalizedString objects can also be resolved from the IStringLocalizer. These objects contain the localized string and a boolean indicating whether the resource was found. -// You can get a LocalizedString object too +```csharp LocalizedString myString = stringLocalizer["MyKey"]; var isResourceNotFound = myString.ResourceNotFound; ``` ## UI Culture -Current culture/locale can be changed using the ILocalizationService. This requires an app restart. +Current culture or locale can be changed using `ILocalizationService`. This action requires an app restart. ```csharp @@ -53,16 +58,18 @@ public class MainViewModel this.localizationService = localizationService; } - public async Task ToggleLocalization() + public Task ToggleLocalizationAsync() { var currentCulture = localizationService.CurrentCulture; - var culture = localizationService.SupportedCultures.First(culture => culture.Name != currentCulture.Name); - await localizationService.SetCurrentCultureAsync(culture); + + return localizationService.SetCurrentCultureAsync(culture); } } ``` -## References +## See also -- [Using IStringLocalizer](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-3.1) \ No newline at end of file +- [Software Localization](https://learn.microsoft.com/globalization/localization/localization) +- [Localization](https://learn.microsoft.com/dotnet/core/extensions/localization) +- [Using IStringLocalizer](https://learn.microsoft.com/aspnet/core/fundamentals/localization) \ No newline at end of file From 1cc82392530d9349caba177308f6a536f420fa0d Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Mon, 8 May 2023 12:03:00 -0400 Subject: [PATCH 04/58] docs: Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Agnès ZITTE <16295702+agneszitte@users.noreply.github.com> --- doc/Learn/Tutorials/Authentication/HowTo-Cookies.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md b/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md index a43c6d44ba..a09418cc7c 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md @@ -3,7 +3,7 @@ uid: Learn.Tutorials.Authentication.HowToCookieAuthorization --- # How-To: Using Cookies to Authorize -An authorization method conceptually similar to using the system credential store is to use **cookies**. Cookies are a common way to store tokens that can be used to authenticate a user. When a HTTP request is succesfully authenticated, the server will return a response that we can use to create a cookie. The cookie can then be used to authenticate future requests. Uno Extensions makes these cookie-related authorization steps less tedious by doing the work of extracting the token(s) from the authentication response, storing it in a cookie, and applying the cookie to future requests. This tutorial will teach you how to configure authentication to apply tokens from a cookie when they are available. +An authorization method is conceptually similar to using the system credential store to use **cookies**. Cookies are a common way to store tokens that can be used to authenticate a user. When an HTTP request is successfully authenticated, the server will return a response that we can use to create a cookie. The cookie can then be used to authenticate future requests. Uno Extensions makes these cookie-related authorization steps less tedious by doing the work of extracting the token(s) from the authentication response, storing it in a cookie, and applying the cookie to future requests. This tutorial will teach you how to configure authentication to apply tokens from a cookie when they are available. > [!IMPORTANT] > To follow these steps, you first need to have an authentication system set up. We recommend choosing one of the `IAuthenticationProvider` implementations provided by Uno Extensions. Cookie authorization can complement any of the tutorials such as [Get Started with Authentication](xref:Learn.Tutorials.Authentication.HowToAuthentication). @@ -75,7 +75,7 @@ An authorization method conceptually similar to using the system credential stor - Specifying a value for the latter is optional. -### 3. Authorize with token value from cookie +### 3. Authorize with a token value from a cookie - Now, when you attempt to authenticate with a provider, the `Cookies()` extension method will attempt to authorize from a cookie by including any token information in the request. If the cookie is not found, it will attempt to authenticate with the provider as normal. From 3517f97bd34964bd512ceaa51a6ac63e45284921 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 08:40:41 -0400 Subject: [PATCH 05/58] docs(logging): initial updated doc --- doc/Overview/Logging/LoggingOverview.md | 185 ++++++++++++++++-------- 1 file changed, 123 insertions(+), 62 deletions(-) diff --git a/doc/Overview/Logging/LoggingOverview.md b/doc/Overview/Logging/LoggingOverview.md index 1ae06c27a6..9f7ad48d81 100644 --- a/doc/Overview/Logging/LoggingOverview.md +++ b/doc/Overview/Logging/LoggingOverview.md @@ -1,108 +1,169 @@ --- uid: Overview.Logging --- + # Logging -Uno.Extensions.Logging use [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) for logging abstractions. -For more documentation on logging, read the references listed at the bottom. +Apps that record events typically do so for informational or diagnostic purposes. **Logging** is the process of recording events to a persistent store, such as a text file or database. The `Uno.Extensions.Logging` library leverages logging capabilities tailored to the target platform to easily record events for XAML layout, Uno-internal messages, and custom events. It allows for control over specific severity or verbosity levels. This feature also allows for a simple way to wire up custom log providers. It uses [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) for logging abstractions. +For more documentation about logging, read the references listed at the bottom. ## Platform Log Providers -Wire up platform specific log providers (iOS: Uno.Extensions.Logging.OSLogLoggerProvider, WASM: Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider), Debug and Console logging. +This library comes with two platform specific log providers: -```csharp -private IHost Host { get; } + - **Uno.Extensions.Logging.OSLogLoggerProvider** : Used for iOS + - **Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider** : Used for WebAssembly (WASM) -public App() +To wire up platform specific log providers for debug and console logging, use the extension method `UseLogging()` on the `IHostBuilder` instance: + +```csharp +protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLogging() - .Build(); - // ........ // -} + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging(); + }); +... ``` -## Log levels +## Logging -We use the following convention for log levels: +When the `UseLogging()` extension method is called, an `ILogger` is registered in the service provider. This logger can be used to log messages to the console. It can then be injected into any class that needs to log messages. The `ILogger` interface is generic, where `T` is the class that is logging the message. While the `ILogger` interface is generic, it is not required to be used in this way. It can be used without the generic type parameter, but it is recommended to use the generic type parameter to scope the logger to a specific class. - - **Trace** : Used for parts of a method to capture a flow. - - **Debug** : Used for diagnostics information. - - **Information** : Used for general successful information. Generally the default minimum. - - **Warning** : Used for anything that can potentially cause application oddities. Automatically recoverable. - - **Error** : Used for anything that is fatal to the current operation but not to the whole process. Potentially recoverable. - - **Critical** : Used for anything that is forcing a shutdown to prevent data loss or corruption. Not recoverable. +The library offers a number of extension methods to simplify the logging of messages for a specific log level. The following example shows how to log a message using the `LogInformation()` extension method: ```csharp -private IHost Host { get; } - -public App() +public class MyClass { - Host = UnoHost - .CreateDefaultBuilder() - .UseLogging() - .ConfigureLogging(logBuilder => - { - logBuilder - .SetMinimumLevel(LogLevel.Debug) - .XamlLogLevel(LogLevel.Information) - .XamlLayoutLogLevel(LogLevel.Information); - }) - .Build(); - // ........ // + private readonly ILogger _logger; + + public MyClass(ILogger logger) + { + _logger = logger; + } + + public void MyMethod() + { + _logger.LogInformation("My message"); + } } ``` -## Serilog +## Log Levels -```csharp -private IHost Host { get; } +Because these extension methods are scoped to a specific log level, the standard `Log()` method on the `ILogger` interface does not need to be used. + +Sometimes it is necessary to write information to the log that varies in its verbosity or severity. For this reason, there are six log levels available to delineate the severity of an entry. + +Use the following convention as a guide when logging messages: + +| Level | Description | +|-------|-------------| +| Trace | Used for parts of a method to capture a flow. | +| Debug | Used for diagnostics information. | +| Information | Used for general successful information. Generally the default minimum. | +| Warning | Used for anything that can potentially cause application oddities. Automatically recoverable. | +| Error | Used for anything that is fatal to the current operation but not to the whole process. Potentially recoverable. | +| Critical | Used for anything that is forcing a shutdown to prevent data loss or corruption. Not recoverable. | + +## Configuring Logging Output + +The `UseLogging()` extension method accepts an optional `Action` parameter that can be used to configure the logging output. + +### Setting the Minimum Log Level -public App() +The minimum log level can be set by calling the `SetMinimumLevel()` extension method on the `ILoggingBuilder` instance. If no LogLevel is specified, logging defaults to the `Information` level which may not always be desirable. The following example shows how to change the minimum log level to `Debug`: + +```csharp +protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLogging() - .UseSerilog() - .Build(); - // ........ // -} + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging( + builder => { + builder.SetMinimumLevel(LogLevel.Debug); + }); + }); +... ``` -## Logging +### Setting the XAML Log Level -To log, you simply need to get a `ILogger` and use the appropriate methods. +The XAML log level can be set by calling the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: ```csharp -var myLogger = myServiceProvider.GetService>(); +protected override void OnLaunched(LaunchActivatedEventArgs e) +{ + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging( + builder => { + builder.XamlLogLevel(LogLevel.Information); + }); + }); +... +``` + +### Setting the XAML Layout Log Level + +The XAML layout log level can be set by calling the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: -myLogger.LogInformation("This is an information log."); +```csharp +protected override void OnLaunched(LaunchActivatedEventArgs e) +{ + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging( + builder => { + builder.XamlLayoutLogLevel(LogLevel.Information); + }); + }); +... ``` -Alternatively you can add `ILogger` as a constructor parameter for any type that's being created by the dependency injection container. +## Serilog + +Serilog is a third-party logging framework that is otherwise available as a [NuGet package](https://www.nuget.org/packages/Serilog). +The option to use Serilog is available by calling the `UseSerilog()` extension method on the `IHostBuilder` instance. This method will configure the Serilog logger to not use the console or file sink by default. The following example shows how to enable Serilog: + +```csharp +protected override void OnLaunched(LaunchActivatedEventArgs e) +{ + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseLogging() + .UseSerilog(); + }); +... +``` + +For more information about Serilog, check out [Getting started with Serilog](https://github.com/serilog/serilog/wiki/Getting-Started). ## Uno Internal Logging -To use the same logging for Uno internal messages call EnableUnoLogging after the Build method on the IHost instance. +To use the same logging system for Uno internal messages use `ConnectUnoLogging()` on the built `IHost` instance: ```csharp private IHost Host { get; } -public App() +protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLogging() - .Build() - .EnableUnoLogging(); - // ........ // -} + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseLogging() + }); + Host = appBuilder.Build(); + // Connect Uno internal logging to the same logging provider + Host.ConnectUnoLogging(); +... ``` -## References -- [Understanding logging providers](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0) +## See also + +- [Understanding logging providers](https://learn.microsoft.com/aspnet/core/fundamentals/logging/) - [Getting started with Serilog](https://github.com/serilog/serilog/wiki/Getting-Started) - [List of Serilog sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks) From 9cd095575d2940a54182d3e8947b8de76ed041c1 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 09:09:09 -0400 Subject: [PATCH 06/58] docs(serialization): Use IApplicationBuilder --- .../Serialization/SerializationOverview.md | 76 ++++++++++++------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/doc/Overview/Serialization/SerializationOverview.md b/doc/Overview/Serialization/SerializationOverview.md index 64d13fe3d7..eacbb125bb 100644 --- a/doc/Overview/Serialization/SerializationOverview.md +++ b/doc/Overview/Serialization/SerializationOverview.md @@ -1,43 +1,67 @@ --- uid: Overview.Serialization --- -## Serialization -Register serializer that implements `ISerializer` +# Serialization + +Accessing the serialized and deserialized representation of an object can be important for dynamic, data-rich applications. `Uno.Extensions.Serialization` allows for simplified access to serializer objects as dependencies. This library supports the new serialization [technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) powered by code generation. + +## Using a Serializer + +The `ISerializer` interface provides a simple API for serializing and deserializing objects. Once serialization is enabled for an application, the `ISerializer` can be accessed as a dependency. The following example shows how to enable serialization for an application with the `UseSerialization()` extension method. ```csharp -private IHost Host { get; } +protected override void OnLaunched(LaunchActivatedEventArgs e) +{ + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization() + }); +... +``` + +This extension method registers the `ISerializer` interface as a singleton service. The `ISerializer` interface can then be accessed as a dependency in any class that is registered with the host to serialize or deserialize objects. -public App() +```csharp +public class MyViewModel : ObservableObject { - Host = UnoHost - .CreateDefaultBuilder() - .UseSerialization() - .Build(); - // ........ // + private ISerializer _serializer; + + public MyViewModel(ISerializer serializer) + { + this._serializer = serializer; + } + + public void Serialize() + { + var myObject = new MyObject(); + var json = _serializer.Serialize(myObject); + } } ``` -Access `ISerializer` to serialize and deserialize json data - ## Configuring ISerializer -The default serializer implementation uses System.Text.Json. The serialization can be configured by registering an instance of JsonSerializerOptions +The default serializer implementation uses `System.Text.Json`. The serialization can be configured by registering an instance of `JsonSerializerOptions` with the host. The following example shows how to register an instance of `JsonSerializerOptions` with the host. ```csharp -private IHost Host { get; } - -public App() +protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseSerialization() - .ConfigureServices(services => - { - services - .AddSingleton(new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - } - .Build(); - // ........ // -} + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization(services => + { + services + .AddSingleton(new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + }); + }); +... ``` + +## See also + +- [New serialization technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) +- [System.Text.Json](https://learn.microsoft.com/dotnet/api/system.text.json) +- [Configuring the serializer](https://learn.microsoft.com/dotnet/api/system.text.json.jsonserializeroptions#properties) \ No newline at end of file From aa2bdd9452648e5519cb964dfb6a06a60e320bb8 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 10:16:24 -0400 Subject: [PATCH 07/58] docs: Address feedback --- .../Serialization/SerializationOverview.md | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/doc/Overview/Serialization/SerializationOverview.md b/doc/Overview/Serialization/SerializationOverview.md index eacbb125bb..ddf536799b 100644 --- a/doc/Overview/Serialization/SerializationOverview.md +++ b/doc/Overview/Serialization/SerializationOverview.md @@ -4,7 +4,7 @@ uid: Overview.Serialization # Serialization -Accessing the serialized and deserialized representation of an object can be important for dynamic, data-rich applications. `Uno.Extensions.Serialization` allows for simplified access to serializer objects as dependencies. This library supports the new serialization [technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) powered by code generation. +**Serialization** involves converting an object into a format that can be easily stored or transmitted. On the receiving end, the object is reconstructed back into its original form through **deserialization**. These two operations complement each other and can be important for dynamic, data-rich applications. `Uno.Extensions.Serialization` allows for simplified access to serializer objects as dependencies. This library supports the new serialization [technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) powered by code generation. ## Using a Serializer @@ -43,7 +43,9 @@ public class MyViewModel : ObservableObject ## Configuring ISerializer -The default serializer implementation uses `System.Text.Json`. The serialization can be configured by registering an instance of `JsonSerializerOptions` with the host. The following example shows how to register an instance of `JsonSerializerOptions` with the host. +### JSON + +The default serializer implementation only supports serializing to JSON. Because it uses `System.Text.Json`, the specifics of this behavior can be configured with `JsonSerializerOptions`. The following example shows how to register an instance of `JsonSerializerOptions` with the host. ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) @@ -60,6 +62,25 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ... ``` +### Custom + +The `ISerializer` interface can be implemented to support custom serialization to formats like XML or binary. This example shows how to register a custom `ISerializer` based type named `XmlSerializerImpl` with the host. + +```csharp +protected override void OnLaunched(LaunchActivatedEventArgs e) +{ + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization(services => + { + services + .AddSingleton(); + }); + }); +... +``` + ## See also - [New serialization technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) From 45d91471e0d8bbadafa34030117068478d22e253 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 10:38:57 -0400 Subject: [PATCH 08/58] docs: address feedback from @weitzhandler --- doc/Overview/Logging/LoggingOverview.md | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/Overview/Logging/LoggingOverview.md b/doc/Overview/Logging/LoggingOverview.md index 9f7ad48d81..2f962ad2ac 100644 --- a/doc/Overview/Logging/LoggingOverview.md +++ b/doc/Overview/Logging/LoggingOverview.md @@ -4,7 +4,7 @@ uid: Overview.Logging # Logging -Apps that record events typically do so for informational or diagnostic purposes. **Logging** is the process of recording events to a persistent store, such as a text file or database. The `Uno.Extensions.Logging` library leverages logging capabilities tailored to the target platform to easily record events for XAML layout, Uno-internal messages, and custom events. It allows for control over specific severity or verbosity levels. This feature also allows for a simple way to wire up custom log providers. It uses [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) for logging abstractions. +Apps that record events typically do so for informational or diagnostic purposes. **Logging** is the process of recording events to either a _persistent_ store such as a text file or database, or a _transient_ location like the standard output console. The `Uno.Extensions.Logging` library leverages logging capabilities tailored to the target platform to easily record events for XAML layout, Uno-internal messages, and custom events. It allows for control over specific severity or verbosity levels. This feature also allows for a simple way to wire up custom log providers. It uses [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) for logging abstractions. For more documentation about logging, read the references listed at the bottom. @@ -52,8 +52,6 @@ public class MyClass ## Log Levels -Because these extension methods are scoped to a specific log level, the standard `Log()` method on the `ILogger` interface does not need to be used. - Sometimes it is necessary to write information to the log that varies in its verbosity or severity. For this reason, there are six log levels available to delineate the severity of an entry. Use the following convention as a guide when logging messages: @@ -67,6 +65,8 @@ Use the following convention as a guide when logging messages: | Error | Used for anything that is fatal to the current operation but not to the whole process. Potentially recoverable. | | Critical | Used for anything that is forcing a shutdown to prevent data loss or corruption. Not recoverable. | +Because these extension methods are scoped to a specific log level, the standard `Log()` method on the `ILogger` interface does not need to be used. + ## Configuring Logging Output The `UseLogging()` extension method accepts an optional `Action` parameter that can be used to configure the logging output. @@ -90,7 +90,14 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ### Setting the XAML Log Level -The XAML log level can be set by calling the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: +Sometimes it's necessary to filter messages recorded for specific XAML types to reduce noise. When a preference is specified for the **XAML log level**, the logger will change the verbosity of events from a set of specific XAML-related namespaces and types: +- Microsoft.UI.Xaml +- Microsoft.UI.Xaml.VisualStateGroup +- Microsoft.UI.Xaml.StateTriggerBase +- Microsoft.UI.Xaml.UIElement +- Microsoft.UI.Xaml.FrameworkElement + +It can be set by calling the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) @@ -105,9 +112,15 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ... ``` -### Setting the XAML Layout Log Level +### Setting the Layout Log Level + +Similar to the _XAML Log Level_ described above, the **layout log level** can be used to filter messages recorded for specific types to reduce noise. When a preference is specified for the layout log level, the logger will change the verbosity of events from a set of specific layout-related XAML namespaces and types: + +- Microsoft.UI.Xaml.Controls +- Microsoft.UI.Xaml.Controls.Layouter +- Microsoft.UI.Xaml.Controls.Panel -The XAML layout log level can be set by calling the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: +It can be set by calling the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) @@ -124,9 +137,9 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ## Serilog -Serilog is a third-party logging framework that is otherwise available as a [NuGet package](https://www.nuget.org/packages/Serilog). +Serilog is a third-party logging framework that is otherwise available as a [NuGet package](https://www.nuget.org/packages/Serilog). The benefit of using Serilog is that it provides a more robust logging framework that can be configured to log to a variety of different sinks, including the console and file. Examples of other sinks include Azure Application Insights, Seq, and many more. -The option to use Serilog is available by calling the `UseSerilog()` extension method on the `IHostBuilder` instance. This method will configure the Serilog logger to not use the console or file sink by default. The following example shows how to enable Serilog: +An option to use Serilog is available by calling the `UseSerilog()` extension method on the `IHostBuilder` instance. This method will configure the Serilog logger to specifically not use the console or file sink by default. The following example shows how to enable Serilog: ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) From 9fa1105656285a146ad4f4eae6d5a13afbc6ab13 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 10:41:22 -0400 Subject: [PATCH 09/58] docs: add line break --- doc/Overview/Serialization/SerializationOverview.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/Overview/Serialization/SerializationOverview.md b/doc/Overview/Serialization/SerializationOverview.md index ddf536799b..2e8ed4adfc 100644 --- a/doc/Overview/Serialization/SerializationOverview.md +++ b/doc/Overview/Serialization/SerializationOverview.md @@ -4,7 +4,9 @@ uid: Overview.Serialization # Serialization -**Serialization** involves converting an object into a format that can be easily stored or transmitted. On the receiving end, the object is reconstructed back into its original form through **deserialization**. These two operations complement each other and can be important for dynamic, data-rich applications. `Uno.Extensions.Serialization` allows for simplified access to serializer objects as dependencies. This library supports the new serialization [technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) powered by code generation. +**Serialization** involves converting an object into a format that can be easily stored or transmitted. On the receiving end, the object is reconstructed back into its original form through **deserialization**. These two operations complement each other and can be important for dynamic, data-rich applications. + +`Uno.Extensions.Serialization` allows for simplified access to serializer objects as dependencies. This library supports the new serialization [technique](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) powered by code generation. ## Using a Serializer From 1cab3c541ea25ff2f6d4d0e92515fffff29284c6 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 16:52:52 -0400 Subject: [PATCH 10/58] docs(tutorials): Use IApplicationBuilder, updates --- .../Configuration/HowTo-Configuration.md | 62 +++++++------- .../HowTo-CommunityToolkit.md | 3 - .../HowTo-DependencyInjectionSetup.md | 80 +++++++++---------- .../Tutorials/Hosting/HowTo-HostingSetup.md | 4 +- doc/Learn/Tutorials/Http/HowTo-Http.md | 27 +++---- .../Localization/HowTo-Localization.md | 16 ++-- .../Logging/HowTo-InternalLogging.md | 74 ++++++++++------- doc/Learn/Tutorials/Logging/HowTo-Logging.md | 21 +++-- .../Serialization/HowTo-Serialization.md | 63 ++++++--------- 9 files changed, 172 insertions(+), 178 deletions(-) diff --git a/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md b/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md index 45b3a29249..2e59beeed6 100644 --- a/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md +++ b/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md @@ -14,31 +14,33 @@ uid: Learn.Tutorials.Configuration.HowToConfiguration * Use the `EmbeddedSource()` extension method on `IConfigBuilder` to load configuration information from an assembly type you specify: ```csharp - public App() + protected override void OnLaunched(LaunchActivatedEventArgs args) { - Host = UnoHost - .CreateDefaultBuilder() - .UseConfiguration(configure: configBuilder => - configBuilder - // Load configuration information from appsettings.json - .EmbeddedSource() - ) + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseConfiguration(configure: configBuilder => + configBuilder + // Load configuration information from appsettings.json + .EmbeddedSource() + ); + }); ... ``` * By default, this method will extract values from an embedded resource (using the [EmbeddedResource](https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.compilerparameters.embeddedresources?view=dotnet-plat-ext-6.0#remarks) file build action) called `appsettings.json`, unless you optionally denote a different file name. The string you pass into the extension method will be concatenated in-between `appsettings` and its file extension. For instance, the following will also retrieve values from the file `appsettings.platform.json` embedded inside the `App` assembly: ```csharp - public App() + protected override void OnLaunched(LaunchActivatedEventArgs args) { - Host = UnoHost - .CreateDefaultBuilder() - .UseConfiguration(configure: configBuilder => - configBuilder - .EmbeddedSource() - // Load configuration information from appsettings.platform.json - .EmbeddedSource("platform") - ) + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseConfiguration(configure: configBuilder => + configBuilder + .EmbeddedSource() + // Load configuration information from appsettings.platform.json + .EmbeddedSource("platform") + ); + }); ... ``` @@ -63,19 +65,19 @@ uid: Learn.Tutorials.Configuration.HowToConfiguration * You can now use the `Section()` extension method on `IConfigBuilder` to load configuration information for class or record of the type argument you specify: ```csharp - public App() + protected override void OnLaunched(LaunchActivatedEventArgs args) { - Host = UnoHost - .CreateDefaultBuilder() - .UseConfiguration(configure: configBuilder => - configBuilder - // Load configuration information from appsettings.json - .EmbeddedSource() - // Load configuration information from appsettings.platform.json - .EmbeddedSource("platform") - // Load Auth configuration section - .Section() - ) + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseConfiguration(configure: configBuilder => + configBuilder + .EmbeddedSource() + // Load configuration information from appsettings.platform.json + .EmbeddedSource("platform") + // Load configuration information for the Auth section + .Section() + ); + }); ... ``` @@ -92,4 +94,4 @@ uid: Learn.Tutorials.Configuration.HowToConfiguration { var authSettings = settings.Value; ... - ``` + ``` \ No newline at end of file diff --git a/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md b/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md index b8322fca8e..2f30751c7a 100644 --- a/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md +++ b/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md @@ -3,9 +3,6 @@ uid: Learn.Tutorials.DependencyInjection.HowToCommunityToolkit --- ## CommunityToolkit.Mvvm -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - If you want to access the DI container via the Ioc.Default API exposed via the CommunityToolkit.Mvvm, you need to configure the service collection after building the Host. ```csharp diff --git a/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md b/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md index e75baf3907..cec8e4921d 100644 --- a/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md +++ b/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md @@ -3,10 +3,7 @@ uid: Learn.Tutorials.DependencyInjection.HowToDependencyInjection --- # How-To: Use Services with Dependency Injection -Dependency Injection (DI) is an important design pattern for building loosely-coupled software that allows for maintainability and testing. This tutorial will walk you through how to register services so that they can be consumed throughout your application. - -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) +Dependency Injection (DI) is an important design pattern when building loosely-coupled software that allows for maintainability and testing. This tutorial will walk you through how to register services so that they can be consumed throughout your application. ## Step-by-steps @@ -26,31 +23,33 @@ Dependency Injection (DI) is an important design pattern for building loosely-co { public async Task GetAsync(CancellationToken ct) { - . . . + ... } } ``` ### 3. Register your service -* Register this service implementation with the `IServiceCollection` instance provided by your application's `IHostBuilder`, in the `app.xaml.host.cs` file: - ```cs - private IHost Host { get; } = BuildAppHost(); +* Register this service implementation with the `IServiceCollection` instance provided by your application's `IHostBuilder`: - private static IHost BuildAppHost() - { - return UnoHost - .CreateDefaultBuilder() - .ConfigureServices(services => - { - // Register your services - services.AddSingleton(); - }) - .Build(); - } + ```csharp + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + var appBuilder = this.CreateBuilder(args) + .Configure(hostBuilder => + { + hostBuilder + .ConfigureServices(services => + { + // Register your services + services.AddSingleton(); + } + ); + }); + ... ``` ### 4. Use the service * Create a new view model class, `MainViewModel`, that will use the functionality offered by your service. Add a constructor with a parameter of the same type as the service interface you defined earlier: ```cs - public class MainViewModel + public class MainViewModel : ObservableObject { private readonly IProfilePictureService userPhotoService; @@ -62,34 +61,33 @@ Dependency Injection (DI) is an important design pattern for building loosely-co ``` * For the dependency injection framework to handle instantiation of the service as a constructor argument, you must also register your view model with the `IServiceCollection`: ```cs - private IHost Host { get; } = BuildAppHost(); - - private static IHost BuildAppHost() - { - return UnoHost - .CreateDefaultBuilder() - .ConfigureServices(services => - { - // Register your services - services.AddSingleton(); - // Register view model - services.AddTransient(); - }) - .Build(); - } + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + var appBuilder = this.CreateBuilder(args) + .Configure(hostBuilder => + { + hostBuilder + .ConfigureServices(services => + { + // Register your services + services.AddSingleton(); + services.AddTransient(); + } + ); + }); + ... ``` * Now, `MainViewModel` has access to the functionality provided by the implementation of your service resolved by `IServiceProvider`: ```cs byte[] profilePhotoBytes = await userPhotoService.GetAsync(cancellationToken); ``` -### 5. Create ViewModel -* From the code behind of a view, directly reference the application's `IHost` instance to request an instance of the desired view model. Set this as the `DataContext`: + +### 5. Set DataContext to view model +* From the code behind of a view, get an instance of the desired view model. Set this as the `DataContext`: ```cs public MainPage() { this.InitializeComponent(); - DataContext = (Application.Current as App).Host.Services.GetRequiredService(); + DataContext = Ioc.Default.GetRequiredService(); } - ``` -> [!TIP] -> By default the `Host` property is marked as `private`, so you'll need to change it to `public` in order for the above code to work. Alternatively if you use [Navigation](xref:Overview.Navigation), view model classes are automatically connected with the corresponding page, avoiding having to access the `IServiceProvider` directly. + ``` \ No newline at end of file diff --git a/doc/Learn/Tutorials/Hosting/HowTo-HostingSetup.md b/doc/Learn/Tutorials/Hosting/HowTo-HostingSetup.md index 763007e162..22d52c0f78 100644 --- a/doc/Learn/Tutorials/Hosting/HowTo-HostingSetup.md +++ b/doc/Learn/Tutorials/Hosting/HowTo-HostingSetup.md @@ -6,12 +6,12 @@ uid: Learn.Tutorials.Hosting.HowToHostingSetup `Uno.Extensions.Hosting` can be used to register services that will be accessible throughout the application via dependency injection (DI). This tutorial will walk you through the critical steps needed to leverage hosting in your application. > [!WARNING] -> The steps outlined here are unnecessary if you used the `dotnet new unoapp` template to create your solution. Otherwise, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. +> The steps outlined here are unnecessary if you used the new project wizard template to create your solution. Otherwise, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. ## Step-by-steps ### 1. Installation -Install the Uno.Extensions.Hosting library from NuGet, making sure you reference the package that is appropriate for the flavor of Uno in use. Likewise, projects with Uno.WinUI require installation of [Microsoft.Extensions.Hosting.WinUI](https://www.nuget.org/packages/Uno.Extensions.Hosting.WinUI) while those with Uno.UI need [Microsoft.Extensions.Hosting.UWP](https://www.nuget.org/packages/Uno.Extensions.Hosting.UWP) instead. +* Install the [Uno.Extensions.Hosting.WinUI](https://www.nuget.org/packages/Uno.Extensions.Hosting.WinUI) package from NuGet. ### 2. Create and Configure IApplicationBuilder diff --git a/doc/Learn/Tutorials/Http/HowTo-Http.md b/doc/Learn/Tutorials/Http/HowTo-Http.md index 30417e7623..c37004e017 100644 --- a/doc/Learn/Tutorials/Http/HowTo-Http.md +++ b/doc/Learn/Tutorials/Http/HowTo-Http.md @@ -12,14 +12,13 @@ When working with a complex application, centralized registration of your API en * Call the `UseHttp()` method to register a HTTP client with the `IHostBuilder` which implements `IHttpClient`: ```csharp - private IHost Host { get; } - protected override void OnLaunched(LaunchActivatedEventArgs args) { - var builder = this.CreateBuilder(args) - .Configure(host => - host.UseHttp() - ); + var appBuilder = this.CreateBuilder(args) + .Configure(hostBuilder => + { + hostBuilder.UseHttp(); + }); ... ``` @@ -30,16 +29,15 @@ When working with a complex application, centralized registration of your API en * While the `AddClient()` extension method can take a delegate as its argument, the recommended way to configure the HTTP client is to specify a configuration section name. This allows you to configure the added HTTP client using the `appsettings.json` file. ```csharp - private IHost Host { get; } - protected override void OnLaunched(LaunchActivatedEventArgs args) { - var builder = this.CreateBuilder(args) - .Configure(host => - host - .UseHttp((services) => - services.AddClient("ShowService")) - ); + var appBuilder = this.CreateBuilder(args) + .Configure(hostBuilder => + { + hostBuilder.UseHttp(services => + services.AddClient("ShowService") + ); + }); ... ``` @@ -79,6 +77,5 @@ When working with a complex application, centralized registration of your API en public async Task LoadShowAsync() { var show = await _showService.GetShowAsync(); - ... ``` \ No newline at end of file diff --git a/doc/Learn/Tutorials/Localization/HowTo-Localization.md b/doc/Learn/Tutorials/Localization/HowTo-Localization.md index 5b8e9c2073..41e4db6c3d 100644 --- a/doc/Learn/Tutorials/Localization/HowTo-Localization.md +++ b/doc/Learn/Tutorials/Localization/HowTo-Localization.md @@ -14,16 +14,14 @@ uid: Learn.Tutorials.Localization.HowToUseLocalization * Call the `UseLocalization()` method to register the implementation of `IStringLocalizer` with the DI container: ```csharp - private IHost Host { get; } - - public App() + protected override void OnLaunched(LaunchActivatedEventArgs args) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLocalization() - .Build(); - // ........ // - } + var appBuilder = this.CreateBuilder(args) + .Configure(hostBuilder => + { + hostBuilder.UseLocalization(); + }); + ... ``` ### 2. Use the localization service to resolve localized text diff --git a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md index f0b519c141..8e8c38c3de 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md @@ -5,57 +5,73 @@ uid: Learn.Tutorials.Logging.UseInternalLogging `Uno.Extensions.Logging` provides logging capabilities tailored to your target platform. It allows the recording of events for XAML layout, Uno-internal messages, and custom events with severity and verbosity levels of your choice. -> [!Tip] +> [!IMPORTANT] > This guide assumes your application has already opted into logging. To find out how to do this, refer to the tutorial [here](xref:Learn.Tutorials.Logging.UseLogging) ## Step-by-steps ### 1. Enable Uno internal logging -* To log Uno-internal messages, pass `true` for the `Build` method's `enableUnoLogging` argument: +* To log Uno-internal messages, use `ConnectUnoLogging()` on the built `IHost` instance: ```csharp private IHost Host { get; } - public App() + protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLogging() - .Build(enableUnoLogging: true); - } + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseLogging() + }); + + Host = appBuilder.Build(); + + // Connect Uno internal logging to the same logging provider + Host.ConnectUnoLogging(); + ... ``` ### 2. Set verbosity level of XAML logging * There are multiple log levels that correspond to differing degrees of severity: - - **Trace** : Used for parts of a method to capture a flow. - - **Debug** : Used for diagnostics information. - - **Information** : Used for general successful information. _Generally the default minimum._ - - **Warning** : Used for anything causing application oddities. It is automatically recoverable. - - **Error** : Used for anything fatal to the current operation but not to the whole process. It is potentially recoverable. - - **Critical** : Used for anything forcing a shutdown to prevent data loss or corruption. It is not recoverable. +| Level | Description | +|-------|-------------| +| Trace | Used for parts of a method to capture a flow. | +| Debug | Used for diagnostics information. | +| Information | Used for general successful information. Generally the default minimum. | +| Warning | Used for anything that can potentially cause application oddities. Automatically recoverable. | +| Error | Used for anything that is fatal to the current operation but not to the whole process. Potentially recoverable. | +| Critical | Used for anything that is forcing a shutdown to prevent data loss or corruption. Not recoverable. | -* To increase the verbosity of the events recorded when using the development hosting environment, you can tweak the minimum levels as well as those for the XAML layout. +* To increase the verbosity of the events recorded when using the development hosting environment, you can adjust the minimum levels as well as those for the XAML layout. * Add a call for `UseLogging` to the `IHostBuilder` chain from above, and conditionally enable the recording of debug events depending on the hosting environment: ```csharp - public App() + private IHost Host { get; } + + protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseLogging(configure: + (context, services) => + services.SetMinimumLevel( + context.HostingEnvironment.IsDevelopment() ? + LogLevel.Trace : + LogLevel.Error) + ) + }); + + Host = appBuilder.Build(); + #if DEBUG - .UseEnvironment(Environments.Development) + Host.UseEnvironment(Environments.Development); #endif - .UseLogging(configure: - (context, services) => - services.SetMinimumLevel( - context.HostingEnvironment.IsDevelopment() ? - LogLevel.Trace : - LogLevel.Error) - ) - .Build(enableUnoLogging: true); - } - ``` \ No newline at end of file + + Host.ConnectUnoLogging(); + ... + ``` diff --git a/doc/Learn/Tutorials/Logging/HowTo-Logging.md b/doc/Learn/Tutorials/Logging/HowTo-Logging.md index 1d92ad7fb5..7801cb5931 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-Logging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-Logging.md @@ -11,19 +11,16 @@ uid: Learn.Tutorials.Logging.UseLogging * Uno.Extensions offers a simple way to wire up platform-specific log providers such as `Uno.Extensions.Logging.OSLogLoggerProvider` for iOS and `Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider` for WASM as both debug and console logging. - Call the `UseLogging()` method to register the resultant implementation of `ILogger` with the DI container: +* Call the `UseLogging()` method to register the resultant implementation of `ILogger` with the DI container: ```csharp - private IHost Host { get; } - - public App() + protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseLogging() - .Build(); - // ........ // - } + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging(); + }); + ... ``` ### 2. Use the injected service to log application events @@ -31,7 +28,7 @@ uid: Learn.Tutorials.Logging.UseLogging * Add a constructor parameter of `ILogger` type to a view model you registered with the service collection: ```cs - public class MainViewModel + public class MainViewModel : ObservableObject { private readonly ILogger logger; @@ -39,7 +36,7 @@ uid: Learn.Tutorials.Logging.UseLogging { this.logger = logger; } - } + ... ``` * You can now record application events using the injected `ILogger` service implementation: diff --git a/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md b/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md index 6377308217..2ddfad4730 100644 --- a/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md +++ b/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md @@ -12,16 +12,14 @@ Accessing the serialized and deserialized representation of an object can be imp * Call the `UseSerialization()` method to register a serializer that implements `ISerializer` with the service collection: ```csharp - private IHost Host { get; } - - public App() + protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseSerialization( /* ..type info.. */) - .Build(); - // ........ // - } + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization(); + }); + ... ``` ### 2. Preparing the class to be serialized efficiently @@ -48,7 +46,7 @@ Accessing the serialized and deserialized representation of an object can be imp } ``` -* As of .NET 6, a code generation-enabled serializer is supported. To leverage this in your Uno.Extensions application, define a partial class which derives from a `JsonSerializerContext`, and specify which type is serializable with the `JsonSerializable` attribute: +* From .NET 6+, a code generation-enabled serializer is supported. To leverage this in your Uno.Extensions application, define a partial class which derives from a `JsonSerializerContext`, and specify which type is serializable with the `JsonSerializable` attribute: ```csharp using System.Text.Json.Serialization; @@ -61,18 +59,14 @@ Accessing the serialized and deserialized representation of an object can be imp * The `JsonSerializable` attribute will generate several new members on the `PersonContext` class, allowing you to access the `JsonTypeInfo` in a static context. Get the `JsonTypeInfo` for your class using `PersonContext.Default.Person` and add it within the serializer registration from above: ```csharp - private IHost Host { get; } - - public App() + protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseSerialization(services => services - .AddJsonTypeInfo(PersonContext.Default.Person) - ) - .Build(); - // ........ // - } + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization(services => services.AddJsonTypeInfo(PersonContext.Default.Person)); + }); + ... ``` ### 3. Configuring the serializer @@ -80,23 +74,18 @@ Accessing the serialized and deserialized representation of an object can be imp * The default serializer implementation uses `System.Text.Json`. The serialization can be configured by registering an instance of `JsonSerializerOptions`: ```csharp - private IHost Host { get; } - - public App() + protected override void OnLaunched(LaunchActivatedEventArgs e) { - Host = UnoHost - .CreateDefaultBuilder() - .UseSerialization(services => services - .AddJsonTypeInfo(PersonContext.Default.Person) - ) - .ConfigureServices(services => - { - services - .AddSingleton(new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - }) - .Build(); - // ........ // - } + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization(services => + { + services.AddJsonTypeInfo(PersonContext.Default.Person); + services.AddSingleton(new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + }); + }); + ... ``` ### 4. Serialize and deserialize JSON data From 2221b83175e5067da3403fd4b8f2776385d16082 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 9 May 2023 17:12:27 -0400 Subject: [PATCH 11/58] docs: fix section heading --- doc/GettingStarted.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/GettingStarted.md b/doc/GettingStarted.md index cd620fbaf3..2b9f532dc8 100644 --- a/doc/GettingStarted.md +++ b/doc/GettingStarted.md @@ -37,7 +37,7 @@ The generated solution will contain: ![The structure of the generated solution](./Overview/images/ProjectStructure-min.png) -#### 3. Running the Application +### 3. Running the Application * Select a target from the drop-down as pictured below From 211fb39a143793a7491fc6dd7a85a9f5ed7918a4 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 07:26:35 -0400 Subject: [PATCH 12/58] docs: addressn feedback --- doc/Learn/Tutorials/Configuration/HowTo-Configuration.md | 2 ++ .../HowTo-DependencyInjectionSetup.md | 8 +++++--- doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md | 9 +++------ doc/Learn/Tutorials/Logging/HowTo-Logging.md | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md b/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md index 2e59beeed6..46f098fe69 100644 --- a/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md +++ b/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md @@ -36,6 +36,7 @@ uid: Learn.Tutorials.Configuration.HowToConfiguration .Configure(host => { host.UseConfiguration(configure: configBuilder => configBuilder + // Load configuration information from appsettings.json .EmbeddedSource() // Load configuration information from appsettings.platform.json .EmbeddedSource("platform") @@ -71,6 +72,7 @@ uid: Learn.Tutorials.Configuration.HowToConfiguration .Configure(host => { host.UseConfiguration(configure: configBuilder => configBuilder + // Load configuration information from appsettings.json .EmbeddedSource() // Load configuration information from appsettings.platform.json .EmbeddedSource("platform") diff --git a/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md b/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md index cec8e4921d..cc5380e7a4 100644 --- a/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md +++ b/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md @@ -49,7 +49,7 @@ Dependency Injection (DI) is an important design pattern when building loosely-c ### 4. Use the service * Create a new view model class, `MainViewModel`, that will use the functionality offered by your service. Add a constructor with a parameter of the same type as the service interface you defined earlier: ```cs - public class MainViewModel : ObservableObject + public class MainViewModel { private readonly IProfilePictureService userPhotoService; @@ -88,6 +88,8 @@ Dependency Injection (DI) is an important design pattern when building loosely-c public MainPage() { this.InitializeComponent(); - DataContext = Ioc.Default.GetRequiredService(); + DataContext = (Application.Current as App).Host.Services.GetRequiredService(); } - ``` \ No newline at end of file + ``` +> [!TIP] +> By default the `Host` property is marked as `private`, so you'll need to change it to `public` in order for the above code to work. Alternatively if you use [Navigation](xref:Overview.Navigation), view model classes are automatically connected with the corresponding page, avoiding having to access the `IServiceProvider` directly. \ No newline at end of file diff --git a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md index 8e8c38c3de..933702e9e0 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md @@ -57,6 +57,9 @@ uid: Learn.Tutorials.Logging.UseInternalLogging var appBuilder = this.CreateBuilder(args) .Configure(host => { host + #if DEBUG + .UseEnvironment(Environments.Development) + #endif .UseLogging(configure: (context, services) => services.SetMinimumLevel( @@ -67,11 +70,5 @@ uid: Learn.Tutorials.Logging.UseInternalLogging }); Host = appBuilder.Build(); - - #if DEBUG - Host.UseEnvironment(Environments.Development); - #endif - - Host.ConnectUnoLogging(); ... ``` diff --git a/doc/Learn/Tutorials/Logging/HowTo-Logging.md b/doc/Learn/Tutorials/Logging/HowTo-Logging.md index 7801cb5931..f2a313b455 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-Logging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-Logging.md @@ -28,7 +28,7 @@ uid: Learn.Tutorials.Logging.UseLogging * Add a constructor parameter of `ILogger` type to a view model you registered with the service collection: ```cs - public class MainViewModel : ObservableObject + public class MainViewModel { private readonly ILogger logger; From f22a33a231b9d69a1c40c5e2dd0f01e1cd67f4e9 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 07:57:16 -0400 Subject: [PATCH 13/58] docs: address feedback --- .../Serialization/SerializationOverview.md | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/doc/Overview/Serialization/SerializationOverview.md b/doc/Overview/Serialization/SerializationOverview.md index 2e8ed4adfc..431a5db0e9 100644 --- a/doc/Overview/Serialization/SerializationOverview.md +++ b/doc/Overview/Serialization/SerializationOverview.md @@ -37,7 +37,7 @@ public class MyViewModel : ObservableObject public void Serialize() { - var myObject = new MyObject(); + var myObject = new Person(); var json = _serializer.Serialize(myObject); } } @@ -64,6 +64,49 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ... ``` +#### Source Generation + +As of .NET 6, a code generation-enabled serializer is supported. The type to serialize is named `Person` in this example. + +```csharp +public class Person +{ + public Person() { } + + public Person(string name, int age, double height, double weight) + { + Name = name; + Age = age; + Height = height; + Weight = weight; + } + + public string? Name { get; set; } + public int Age { get; set; } + public double Height { get; set; } + public double Weight { get; set; } +} +``` + +To leverage the source generation feature for JSON serialization in an Uno.Extensions application, define a partial class which derives from a `JsonSerializerContext`, and specify which type is serializable with the `JsonSerializable` attribute: + +```csharp +using System.Text.Json.Serialization; + +[JsonSerializable(typeof(Person))] +public partial class PersonJsonContext : JsonSerializerContext +{ +} +``` + +The `PersonJsonContext` class is then used to serialize and deserialize instances of `Person`: + +```csharp +var person = new Person("Megan", 23, 1.8, 90.0); + +var json = JsonSerializer.Serialize(person!, PersonJsonContext.Default.Person); +``` + ### Custom The `ISerializer` interface can be implemented to support custom serialization to formats like XML or binary. This example shows how to register a custom `ISerializer` based type named `XmlSerializerImpl` with the host. From 613457f8962d755f5088b0b2b5a11673302d89d5 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 10:15:18 -0400 Subject: [PATCH 14/58] docs: address feedback from @nickrandolph --- doc/Overview/Logging/LoggingOverview.md | 91 ++++++++++++------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/doc/Overview/Logging/LoggingOverview.md b/doc/Overview/Logging/LoggingOverview.md index 2f962ad2ac..68e3a4b866 100644 --- a/doc/Overview/Logging/LoggingOverview.md +++ b/doc/Overview/Logging/LoggingOverview.md @@ -10,12 +10,7 @@ For more documentation about logging, read the references listed at the bottom. ## Platform Log Providers -This library comes with two platform specific log providers: - - - **Uno.Extensions.Logging.OSLogLoggerProvider** : Used for iOS - - **Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider** : Used for WebAssembly (WASM) - -To wire up platform specific log providers for debug and console logging, use the extension method `UseLogging()` on the `IHostBuilder` instance: +To wire-up the platform specific log providers for debug and console logging, use the extension method `UseLogging()` on the `IHostBuilder` instance: ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) @@ -80,98 +75,96 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) { var appBuilder = this.CreateBuilder(args) .Configure(host => { - host.UseLogging( - builder => { - builder.SetMinimumLevel(LogLevel.Debug); - }); + host +#if DEBUG + .UseEnvironment(Environments.Development) +#endif + .UseLogging(configure: + (context, services) => + services.SetMinimumLevel( + context.HostingEnvironment.IsDevelopment() ? + LogLevel.Trace : + LogLevel.Error) + ) }); ... ``` -### Setting the XAML Log Level +## Serilog -Sometimes it's necessary to filter messages recorded for specific XAML types to reduce noise. When a preference is specified for the **XAML log level**, the logger will change the verbosity of events from a set of specific XAML-related namespaces and types: -- Microsoft.UI.Xaml -- Microsoft.UI.Xaml.VisualStateGroup -- Microsoft.UI.Xaml.StateTriggerBase -- Microsoft.UI.Xaml.UIElement -- Microsoft.UI.Xaml.FrameworkElement +Serilog is a third-party logging framework that is otherwise available as a [NuGet package](https://www.nuget.org/packages/Serilog). The benefit of using Serilog is that it provides a more robust logging framework that can be configured to log to a variety of different sinks, including the console and file. Examples of other sinks include Azure Application Insights, Seq, and many more. -It can be set by calling the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: +An option to use Serilog is available by calling the `UseSerilog()` extension method on the `IHostBuilder` instance. This method will configure the Serilog logger to specifically not use the console or file sink by default. The following example shows how to enable Serilog: ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) { var appBuilder = this.CreateBuilder(args) .Configure(host => { - host.UseLogging( - builder => { - builder.XamlLogLevel(LogLevel.Information); - }); + host + .UseLogging() + .UseSerilog(); }); ... ``` -### Setting the Layout Log Level - -Similar to the _XAML Log Level_ described above, the **layout log level** can be used to filter messages recorded for specific types to reduce noise. When a preference is specified for the layout log level, the logger will change the verbosity of events from a set of specific layout-related XAML namespaces and types: +For more information about Serilog, check out [Getting started with Serilog](https://github.com/serilog/serilog/wiki/Getting-Started). -- Microsoft.UI.Xaml.Controls -- Microsoft.UI.Xaml.Controls.Layouter -- Microsoft.UI.Xaml.Controls.Panel +## Uno Internal Logging -It can be set by calling the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: +To use the same logging system for Uno internal messages use `ConnectUnoLogging()` on the built `IHost` instance: ```csharp +private IHost Host { get; } + protected override void OnLaunched(LaunchActivatedEventArgs e) { var appBuilder = this.CreateBuilder(args) .Configure(host => { - host.UseLogging( - builder => { - builder.XamlLayoutLogLevel(LogLevel.Information); - }); + host + .UseLogging() }); + Host = appBuilder.Build(); + // Connect Uno internal logging to the same logging provider + Host.ConnectUnoLogging(); ... ``` -## Serilog +### Setting the XAML Log Level -Serilog is a third-party logging framework that is otherwise available as a [NuGet package](https://www.nuget.org/packages/Serilog). The benefit of using Serilog is that it provides a more robust logging framework that can be configured to log to a variety of different sinks, including the console and file. Examples of other sinks include Azure Application Insights, Seq, and many more. +Sometimes it's necessary to filter messages recorded for specific XAML types to reduce noise. When a preference is specified for the **XAML log level**, the logger will change the verbosity of events from a set of XAML-related types. -An option to use Serilog is available by calling the `UseSerilog()` extension method on the `IHostBuilder` instance. This method will configure the Serilog logger to specifically not use the console or file sink by default. The following example shows how to enable Serilog: +It can be set by calling the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) { var appBuilder = this.CreateBuilder(args) .Configure(host => { - host - .UseLogging() - .UseSerilog(); + host.UseLogging( + builder => { + builder.XamlLogLevel(LogLevel.Information); + }); }); ... ``` -For more information about Serilog, check out [Getting started with Serilog](https://github.com/serilog/serilog/wiki/Getting-Started). +### Setting the Layout Log Level -## Uno Internal Logging +Similar to the _XAML Log Level_ described above, the **layout log level** can be used to filter messages recorded for specific types to reduce noise. When a preference is specified for the layout log level, the logger will change the verbosity of events from a set of layout-related types. -To use the same logging system for Uno internal messages use `ConnectUnoLogging()` on the built `IHost` instance: +It can be set by calling the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: ```csharp -private IHost Host { get; } - protected override void OnLaunched(LaunchActivatedEventArgs e) { var appBuilder = this.CreateBuilder(args) .Configure(host => { - host - .UseLogging() + host.UseLogging( + builder => { + builder.XamlLayoutLogLevel(LogLevel.Information); + }); }); - Host = appBuilder.Build(); - // Connect Uno internal logging to the same logging provider - Host.ConnectUnoLogging(); ... ``` From 636887a0ee7e5d681c136d0ad9712b829ec36ff9 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 11:53:54 -0400 Subject: [PATCH 15/58] docs: address feedback --- .../Localization/LocalizationOverview.md | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/doc/Overview/Localization/LocalizationOverview.md b/doc/Overview/Localization/LocalizationOverview.md index 0dd1c34222..86aa44b980 100644 --- a/doc/Overview/Localization/LocalizationOverview.md +++ b/doc/Overview/Localization/LocalizationOverview.md @@ -4,13 +4,13 @@ uid: Overview.Localization # Localization -It is often necessary to adapt an application to a specific subset of users within a market. **Localization** includes the type of actions developers take to modify both user interface elements and content to adhere to more languages or cultures. Specifically, text **translation** is done by applying alternate strings of text at runtime which accomodate a user's language preference. Many apps store these pieces of text in dedicated resource files that the app parses and assigns as text throughout the application. `Uno.Extensions.Localization` provides a consistent way to resolve text of a specific culture or locale across platforms. This feature allows for modifying them to be applied upon app restart. +It is often necessary to adapt an application to a specific subset of users within a market. **Localization** includes the type of actions developers take to modify both user interface elements and content to adhere to more languages or cultures. Specifically, text **translation** is done by applying alternate strings of text at runtime which accomodate a user's language preference. -It uses [Microsoft.Extensions.Localization](https://www.nuget.org/packages/Microsoft.Extensions.Localization) for any localization related work. For documentation on the broader process of localization, read the references listed at the bottom. +Many apps store these pieces of text in dedicated resource files that the app parses and assigns as text throughout the application. `Uno.Extensions.Localization` provides a consistent way to resolve text of a specific culture or locale across platforms. This feature allows for modifying them to be applied upon app restart. -## Resolving translated strings +It uses [Microsoft.Extensions.Localization](https://www.nuget.org/packages/Microsoft.Extensions.Localization) for any localization related work. For documentation on the broader process of localization, read the references listed at the bottom. -Once locale specific resources are included in `.resw` files corresponding to the desired locale folder (eg en-US), the localization feature can be used to resolve those localized texts. +## Set up localization ```csharp protected override void OnLaunched(LaunchActivatedEventArgs e) @@ -18,14 +18,33 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) var appBuilder = this.CreateBuilder(args) .Configure(host => { - host - .UseLocalization() + host.UseLocalization() }); ... ``` An implementation of `IStringLocalizer` (`ResourceLoaderStringLocalizer`) will be registered as a service. This service offers a consistent way to resolve localized strings. Behind the scenes, it will automatically use `ResourceManager` on Windows and `ResourceLoader` on other platforms. +### Adding language specific resources + +The `ResourceLoaderStringLocalizer` will look for `.resw` files in folders corresponding to the well-known language tag (eg en-US). For example, if the current culture is `en-US`, the `ResourceLoaderStringLocalizer` will look for `.resw` files in the `en-US` folder. If the current culture is `fr-FR`, the `ResourceLoaderStringLocalizer` will look for `.resw` files in the `fr-FR` folder. + +To add a new resource file, right click on the project and select **Add > New Item...**. Select **Resource File (.resw)** and name it according to the language tag (eg `en-US.resw`). Resource files have a key-value pair structure. The key is used to identify the resource and the value is the localized text. The key contains a name which corresponds to the `x:Uid` property of the element in XAML. The value contains the localized text. + +For example, if the `x:Uid` property of a `TextBlock` is `MyTextBlock`, the key in the resource file should be `MyTextBlock.Text`. The value of the key is the localized text. + +### Resolving localized strings + +Once locale specific resources are included, the localization feature can be used to resolve those localized texts. + +In XAML, assigning the `x:Uid` property a value looks like this: + +```xml + +``` + +However, the `x:Uid` property is not required to resolve localized strings. The `IStringLocalizer` service can be resolved from the service provider. + ```csharp var stringLocalizer = serviceProvider.GetService(); ``` From f51c4a2e3967a293ddd3c859192a647596efb490 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 12:42:06 -0400 Subject: [PATCH 16/58] docs: address feedback --- doc/Learn/Tutorials/Authentication/HowTo-Cookies.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md b/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md index a09418cc7c..e0b1573470 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-Cookies.md @@ -3,7 +3,7 @@ uid: Learn.Tutorials.Authentication.HowToCookieAuthorization --- # How-To: Using Cookies to Authorize -An authorization method is conceptually similar to using the system credential store to use **cookies**. Cookies are a common way to store tokens that can be used to authenticate a user. When an HTTP request is successfully authenticated, the server will return a response that we can use to create a cookie. The cookie can then be used to authenticate future requests. Uno Extensions makes these cookie-related authorization steps less tedious by doing the work of extracting the token(s) from the authentication response, storing it in a cookie, and applying the cookie to future requests. This tutorial will teach you how to configure authentication to apply tokens from a cookie when they are available. +Using **cookies** is a common way to store tokens that are needed to authenticate a user. When an HTTP request is successfully authenticated, the server will return a response that creates a cookie containing a token value. Uno Extensions makes these cookie-related authorization steps less tedious by doing the work of extracting these values and applying them to future requests. This tutorial will teach you how to configure authentication to apply tokens from a cookie when they are available. > [!IMPORTANT] > To follow these steps, you first need to have an authentication system set up. We recommend choosing one of the `IAuthenticationProvider` implementations provided by Uno Extensions. Cookie authorization can complement any of the tutorials such as [Get Started with Authentication](xref:Learn.Tutorials.Authentication.HowToAuthentication). @@ -34,7 +34,7 @@ An authorization method is conceptually similar to using the system credential s ... ``` -- Modify the app to add the `Cookies()` extension method. It configures the `IAuthenticationBuilder` to use cookies by registering a special HTTP request handler that will parse the response for tokens and store them in a cookie. It will apply them to future requests. +- Modify the app to add the `Cookies()` extension method. Since the default HTTP request handler used does _not_ read tokens from cookies, this method will configure the `IAuthenticationBuilder` by registering a special handler that will parse the response for tokens and store them in a cookie. It will apply them to future requests. ```csharp protected override void OnLaunched(LaunchActivatedEventArgs args) @@ -77,7 +77,7 @@ An authorization method is conceptually similar to using the system credential s ### 3. Authorize with a token value from a cookie -- Now, when you attempt to authenticate with a provider, the `Cookies()` extension method will attempt to authorize from a cookie by including any token information in the request. If the cookie is not found, it will attempt to authenticate with the provider as normal. +- With the appropriate handler enabled using the `Cookies()` extension method, attempts to authenticate with a provider will now try to authorize from a cookie. Access and refresh token information will be included in subsequent requests. If the cookie is not found, it will instead authenticate with the provider as normal. - For more information on how to call the authentication service from a view model, see [Get Started with Authentication](xref:Learn.Tutorials.Authentication.HowToAuthentication). From d8b13ecdb252ecfa4dc98dfcbb973db6edcdacd3 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 12:47:47 -0400 Subject: [PATCH 17/58] docs: change method name --- doc/Overview/Logging/LoggingOverview.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/Overview/Logging/LoggingOverview.md b/doc/Overview/Logging/LoggingOverview.md index 68e3a4b866..36c765c55e 100644 --- a/doc/Overview/Logging/LoggingOverview.md +++ b/doc/Overview/Logging/LoggingOverview.md @@ -112,7 +112,7 @@ For more information about Serilog, check out [Getting started with Serilog](htt ## Uno Internal Logging -To use the same logging system for Uno internal messages use `ConnectUnoLogging()` on the built `IHost` instance: +To use the same logging system for Uno internal messages call `UseLogging()` on the `IHost` instance: ```csharp private IHost Host { get; } @@ -124,9 +124,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) host .UseLogging() }); + Host = appBuilder.Build(); - // Connect Uno internal logging to the same logging provider - Host.ConnectUnoLogging(); ... ``` From d8874e3553631a70f8c3cb6f193a74e4b480e330 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Thu, 11 May 2023 13:03:47 -0400 Subject: [PATCH 18/58] docs: address additional feedback --- .../Logging/HowTo-InternalLogging.md | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md index 933702e9e0..86408eeec4 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md @@ -12,7 +12,7 @@ uid: Learn.Tutorials.Logging.UseInternalLogging ### 1. Enable Uno internal logging -* To log Uno-internal messages, use `ConnectUnoLogging()` on the built `IHost` instance: +* To log Uno-internal messages, you first need to call `UseLogging()` on the `IHost` instance: ```csharp private IHost Host { get; } @@ -26,9 +26,6 @@ uid: Learn.Tutorials.Logging.UseInternalLogging }); Host = appBuilder.Build(); - - // Connect Uno internal logging to the same logging provider - Host.ConnectUnoLogging(); ... ``` @@ -47,7 +44,7 @@ uid: Learn.Tutorials.Logging.UseInternalLogging * To increase the verbosity of the events recorded when using the development hosting environment, you can adjust the minimum levels as well as those for the XAML layout. -* Add a call for `UseLogging` to the `IHostBuilder` chain from above, and conditionally enable the recording of debug events depending on the hosting environment: +* Conditionally enable the recording of debug events depending on the hosting environment: ```csharp private IHost Host { get; } @@ -72,3 +69,49 @@ uid: Learn.Tutorials.Logging.UseInternalLogging Host = appBuilder.Build(); ... ``` + +### 3. Enable specific types of internal logging + +#### Setting the XAML Log Level + +* Filter out messages recorded for specific XAML types by setting the **XAML log level**. + +* To adjust the verbosity of logged events raised by a set of XAML-related types, you should call the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: + + ```csharp + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging( + builder => { + builder.XamlLogLevel(LogLevel.Information); + }); + }); + ... + ``` + +#### Setting the Layout Log Level + +* The **layout log level** can be used to filter messages recorded from a set of layout-related types. + +* To set this up, call the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: + + ```csharp + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host.UseLogging( + builder => { + builder.XamlLayoutLogLevel(LogLevel.Information); + }); + }); + ... + ``` + +### 4. Testing the logging configuration + +* Run the application in debug mode and open the **Output** window in Visual Studio. + +* Since you have configured Uno-internal logging, messages with a severity level of `Information` or higher will now be recorded for the specified categories. \ No newline at end of file From c717b689ffef62f8ed3fc4fdf55b1ecedb73f2bd Mon Sep 17 00:00:00 2001 From: Matt Lacey Date: Fri, 12 May 2023 11:27:35 +0100 Subject: [PATCH 19/58] docs: fix formatting of [!CAUTION] callout so it displays correctly --- doc/Reference/Reactive/listfeed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Reference/Reactive/listfeed.md b/doc/Reference/Reactive/listfeed.md index 05d4211014..c3c6799738 100644 --- a/doc/Reference/Reactive/listfeed.md +++ b/doc/Reference/Reactive/listfeed.md @@ -26,7 +26,7 @@ which will trigger the load of the next page using the delegate that you provide public IListFeed Cities => ListFeed.AsyncPaginated(async (page, ct) => _service.GetCities(pageIndex: page.Index, perPage: 20)); ``` -[!CAUTION] +> [!CAUTION] > On the `Page` struct you have a `DesiredSize` property. > This is the number of items the view is requesting to properly fill its "viewport", > **BUT** there is no garantee that this value remains the same between multi pages, espcially if the user resize the app. From 08b3e3d6cb02ee32b273a981485b109f70f7643d Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 16 May 2023 08:13:40 -0400 Subject: [PATCH 20/58] docs: address feedback from @nickrandolph --- .../Localization/LocalizationOverview.md | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/doc/Overview/Localization/LocalizationOverview.md b/doc/Overview/Localization/LocalizationOverview.md index 86aa44b980..7d3e54fbd9 100644 --- a/doc/Overview/Localization/LocalizationOverview.md +++ b/doc/Overview/Localization/LocalizationOverview.md @@ -29,33 +29,53 @@ An implementation of `IStringLocalizer` (`ResourceLoaderStringLocalizer`) will b The `ResourceLoaderStringLocalizer` will look for `.resw` files in folders corresponding to the well-known language tag (eg en-US). For example, if the current culture is `en-US`, the `ResourceLoaderStringLocalizer` will look for `.resw` files in the `en-US` folder. If the current culture is `fr-FR`, the `ResourceLoaderStringLocalizer` will look for `.resw` files in the `fr-FR` folder. -To add a new resource file, right click on the project and select **Add > New Item...**. Select **Resource File (.resw)** and name it according to the language tag (eg `en-US.resw`). Resource files have a key-value pair structure. The key is used to identify the resource and the value is the localized text. The key contains a name which corresponds to the `x:Uid` property of the element in XAML. The value contains the localized text. +#### Planning to support different locales + +The cultures which the app will support are enumerated in a specific section of the `appsettings.json` configuration file. The `LocalizationConfiguration` section of the file should look like the code example below: + +```json +{ + "LocalizationConfiguration": { + "Cultures": [ "fr", "en" ] + }, + ... +} +``` + +#### Add resource files + +To add a new resource file, right click on the project and select **Add > New Item...**. Select **Resource File (.resw)** and name it `Resources.resw`. Resource files have a key-value pair structure. The key is used to identify the resource, and the value can represent any valid property value such as translated text, width of an item, or a color. -For example, if the `x:Uid` property of a `TextBlock` is `MyTextBlock`, the key in the resource file should be `MyTextBlock.Text`. The value of the key is the localized text. ### Resolving localized strings -Once locale specific resources are included, the localization feature can be used to resolve those localized texts. +Once locale specific resources are included, the localization feature can be used to resolve those localized values. + +#### Using resources in XAML -In XAML, assigning the `x:Uid` property a value looks like this: +The key contains a name which corresponds to the `x:Uid` and intended property of the XAML element. The value contains the localized text. + +For example, if the `x:Uid` property of a `TextBlock` is `MyTextBlock`, the key in the resource file should be `MyTextBlock.Text`. In XAML, assigning a localized value to an element with a `x:Uid` property looks like this: ```xml ``` -However, the `x:Uid` property is not required to resolve localized strings. The `IStringLocalizer` service can be resolved from the service provider. +#### Using resources in code-behind + +Setting the `x:Uid` property in markup is not required to resolve localized resources like text. The `IStringLocalizer` service can be resolved from the service provider. This service can be used to resolve localized strings in code-behind. ```csharp var stringLocalizer = serviceProvider.GetService(); ``` -Localized strings can be resolved using the indexer on the IStringLocalizer as a dictionary. This indexer takes a key and returns the localized string. +Strings can be resolved using the indexer on the `IStringLocalizer` as a dictionary. This indexer takes a key and returns the localized string value. ```csharp string myString = stringLocalizer["MyKey"]; ``` -LocalizedString objects can also be resolved from the IStringLocalizer. These objects contain the localized string and a boolean indicating whether the resource was found. +`LocalizedString` objects are the primary type resolved from `IStringLocalizer`. While these objects can be implicitly converted to strings, they also contain additional information which may be desirable. For instance, `LocalizedString` includes a boolean indicating whether the resource was not found. This can be used to determine whether a fallback value should be used. ```csharp LocalizedString myString = stringLocalizer["MyKey"]; From 11afd4707b3b0d5b840098622c1fc59a3595a345 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 16 May 2023 17:03:43 -0400 Subject: [PATCH 21/58] docs: improve based on feedback --- doc/Overview/Logging/LoggingOverview.md | 101 +++++++----------------- 1 file changed, 29 insertions(+), 72 deletions(-) diff --git a/doc/Overview/Logging/LoggingOverview.md b/doc/Overview/Logging/LoggingOverview.md index 36c765c55e..bbb8fa832e 100644 --- a/doc/Overview/Logging/LoggingOverview.md +++ b/doc/Overview/Logging/LoggingOverview.md @@ -24,9 +24,28 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ## Logging -When the `UseLogging()` extension method is called, an `ILogger` is registered in the service provider. This logger can be used to log messages to the console. It can then be injected into any class that needs to log messages. The `ILogger` interface is generic, where `T` is the class that is logging the message. While the `ILogger` interface is generic, it is not required to be used in this way. It can be used without the generic type parameter, but it is recommended to use the generic type parameter to scope the logger to a specific class. +When the `UseLogging()` extension method is called, an `ILogger` is registered in the service provider. This logger can be used to log messages to the console. It can then be injected into any class that needs to log messages. The `ILogger` interface is generic, where `T` is the class that is logging the message. The `ILogger` interface is generic, and it must be retrieved from the service provider this way. In order to scope the logger to a specific class, it cannot be used without the generic type parameter. -The library offers a number of extension methods to simplify the logging of messages for a specific log level. The following example shows how to log a message using the `LogInformation()` extension method: +The library offers a number of extension methods to simplify logging messages for a multitude of situations. + +## Log Levels + +Sometimes it is necessary to write information to the log that varies in its verbosity or severity. For this reason, there are six log levels available to delineate the severity of an entry. + +Use the following convention as a guide when logging messages: + +| Level | Description | +|-------|-------------| +| Trace | Used for parts of a method to capture a flow. | +| Debug | Used for diagnostics information. | +| Information | Used for general successful information. Generally the default minimum. | +| Warning | Used for anything that can potentially cause application oddities. Automatically recoverable. | +| Error | Used for anything that is fatal to the current operation but not to the whole process. Potentially recoverable. | +| Critical | Used for anything that is forcing a shutdown to prevent data loss or corruption. Not recoverable. | + +_These descriptions are adapted from those documented for the `LogLevel` enum [here](https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.loglevel)._ + +Because these extension methods are scoped to a specific log level, the standard `Log()` method on the `ILogger` interface does not need to be used. This example shows how to log a message using the `LogInformation()` extension method: ```csharp public class MyClass @@ -45,23 +64,6 @@ public class MyClass } ``` -## Log Levels - -Sometimes it is necessary to write information to the log that varies in its verbosity or severity. For this reason, there are six log levels available to delineate the severity of an entry. - -Use the following convention as a guide when logging messages: - -| Level | Description | -|-------|-------------| -| Trace | Used for parts of a method to capture a flow. | -| Debug | Used for diagnostics information. | -| Information | Used for general successful information. Generally the default minimum. | -| Warning | Used for anything that can potentially cause application oddities. Automatically recoverable. | -| Error | Used for anything that is fatal to the current operation but not to the whole process. Potentially recoverable. | -| Critical | Used for anything that is forcing a shutdown to prevent data loss or corruption. Not recoverable. | - -Because these extension methods are scoped to a specific log level, the standard `Log()` method on the `ILogger` interface does not need to be used. - ## Configuring Logging Output The `UseLogging()` extension method accepts an optional `Action` parameter that can be used to configure the logging output. @@ -90,6 +92,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) ... ``` +This example configures the minimum log level to `Trace` when the app is running in the `Development` environment, and `Error` otherwise. This is useful for increasing the amount of logging that occurs in that environment, and only emitting `Error` or `Critical` messages when in production. + ## Serilog Serilog is a third-party logging framework that is otherwise available as a [NuGet package](https://www.nuget.org/packages/Serilog). The benefit of using Serilog is that it provides a more robust logging framework that can be configured to log to a variety of different sinks, including the console and file. Examples of other sinks include Azure Application Insights, Seq, and many more. @@ -102,7 +106,6 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) var appBuilder = this.CreateBuilder(args) .Configure(host => { host - .UseLogging() .UseSerilog(); }); ... @@ -112,60 +115,14 @@ For more information about Serilog, check out [Getting started with Serilog](htt ## Uno Internal Logging -To use the same logging system for Uno internal messages call `UseLogging()` on the `IHost` instance: +The same logging system is wired up for Uno internal messages when `UseLogging()` is called on the `IHost` instance. This is useful for debugging Uno internals like XAML parsing and layout. While this system is enabled as part of the standard logger, it is set by default to filter out messages with a level lower than `Warning`. This is to reduce noise in the log output. -```csharp -private IHost Host { get; } +The following table describes the aspects of this Uno internal logging system which can be configured for a more suitable experience while diagnosing issues during the development process. -protected override void OnLaunched(LaunchActivatedEventArgs e) -{ - var appBuilder = this.CreateBuilder(args) - .Configure(host => { - host - .UseLogging() - }); - - Host = appBuilder.Build(); -... -``` - -### Setting the XAML Log Level - -Sometimes it's necessary to filter messages recorded for specific XAML types to reduce noise. When a preference is specified for the **XAML log level**, the logger will change the verbosity of events from a set of XAML-related types. - -It can be set by calling the `XamlLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML log level to `Information`: - -```csharp -protected override void OnLaunched(LaunchActivatedEventArgs e) -{ - var appBuilder = this.CreateBuilder(args) - .Configure(host => { - host.UseLogging( - builder => { - builder.XamlLogLevel(LogLevel.Information); - }); - }); -... -``` - -### Setting the Layout Log Level - -Similar to the _XAML Log Level_ described above, the **layout log level** can be used to filter messages recorded for specific types to reduce noise. When a preference is specified for the layout log level, the logger will change the verbosity of events from a set of layout-related types. - -It can be set by calling the `XamlLayoutLogLevel()` extension method on the `ILoggingBuilder` instance. The following example shows how to set the XAML layout log level to `Information`: - -```csharp -protected override void OnLaunched(LaunchActivatedEventArgs e) -{ - var appBuilder = this.CreateBuilder(args) - .Configure(host => { - host.UseLogging( - builder => { - builder.XamlLayoutLogLevel(LogLevel.Information); - }); - }); -... -``` +| ILoggingBuilder Extension method | Filtered Events | Affected Namespace(s) | +|------------------|-------------|---------------------| +| `XamlLogLevel()` | Messages emitted by specific XAML types | `Microsoft.UI.Xaml`, `Microsoft.UI.Xaml.VisualStateGroup`, `Microsoft.UI.Xaml.StateTriggerBase`, `Microsoft.UI.Xaml.UIElement`, `Microsoft.UI.Xaml.FrameworkElement` | +| `XamlLayoutLogLevel()` | Messages emitted by the layout system | `Microsoft.UI.Xaml.Controls`, `Microsoft.UI.Xaml.Controls.Layouter`, `Microsoft.UI.Xaml.Controls.Panel` | ## See also From 36ae7f7fe7ed4599eea77a809b682ac6e127132f Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Tue, 16 May 2023 17:20:52 -0400 Subject: [PATCH 22/58] docs: fix intro --- doc/Overview/Logging/LoggingOverview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Overview/Logging/LoggingOverview.md b/doc/Overview/Logging/LoggingOverview.md index bbb8fa832e..37596828ca 100644 --- a/doc/Overview/Logging/LoggingOverview.md +++ b/doc/Overview/Logging/LoggingOverview.md @@ -4,9 +4,9 @@ uid: Overview.Logging # Logging -Apps that record events typically do so for informational or diagnostic purposes. **Logging** is the process of recording events to either a _persistent_ store such as a text file or database, or a _transient_ location like the standard output console. The `Uno.Extensions.Logging` library leverages logging capabilities tailored to the target platform to easily record events for XAML layout, Uno-internal messages, and custom events. It allows for control over specific severity or verbosity levels. This feature also allows for a simple way to wire up custom log providers. It uses [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) for logging abstractions. +Apps that record events typically do so for informational or diagnostic purposes, depending on the desired level of verbosity. **Logging** is the process of recording events to either a _persistent_ store such as a text file or database, or a _transient_ location like the standard output console. The `Uno.Extensions.Logging` library leverages logging capabilities tailored to the target platform to easily write entries for both app specific and Uno internal events to one or more locations. These locations where logs can be written are referred to as **providers**. This feature enables a simple way to wire up custom log providers for locations not included by the runtime libraries or extensions. -For more documentation about logging, read the references listed at the bottom. +It uses [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) for logging abstractions. For more documentation about logging, read the references listed at the bottom. ## Platform Log Providers From aefe56ccc20a6c8d40e982e1c064867820751da0 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Wed, 17 May 2023 07:03:26 -0400 Subject: [PATCH 23/58] docs: improve based on feedback from @nickrandolph --- .../Serialization/SerializationOverview.md | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/doc/Overview/Serialization/SerializationOverview.md b/doc/Overview/Serialization/SerializationOverview.md index 431a5db0e9..ca4f0d6109 100644 --- a/doc/Overview/Serialization/SerializationOverview.md +++ b/doc/Overview/Serialization/SerializationOverview.md @@ -69,23 +69,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) As of .NET 6, a code generation-enabled serializer is supported. The type to serialize is named `Person` in this example. ```csharp -public class Person -{ - public Person() { } - - public Person(string name, int age, double height, double weight) - { - Name = name; - Age = age; - Height = height; - Weight = weight; - } - - public string? Name { get; set; } - public int Age { get; set; } - public double Height { get; set; } - public double Weight { get; set; } -} +public record Person(string name, int age, double height, double weight); ``` To leverage the source generation feature for JSON serialization in an Uno.Extensions application, define a partial class which derives from a `JsonSerializerContext`, and specify which type is serializable with the `JsonSerializable` attribute: @@ -99,12 +83,29 @@ public partial class PersonJsonContext : JsonSerializerContext } ``` -The `PersonJsonContext` class is then used to serialize and deserialize instances of `Person`: +This partial class can then be registered with the host using the `AddJsonTypeInfo` extension method: + +```csharp +protected override void OnLaunched(LaunchActivatedEventArgs e) +{ + var appBuilder = this.CreateBuilder(args) + .Configure(host => { + host + .UseSerialization(services => + { + services + .AddJsonTypeInfo(PersonJsonContext.Default.Person) + }); + }); +... +``` + +It follows that the `PersonJsonContext` will be automatically used by the injected `ISerializer` to serialize and deserialize instances of `Person`: ```csharp -var person = new Person("Megan", 23, 1.8, 90.0); +Person person = new("Megan", 23, 1.8, 90.0); -var json = JsonSerializer.Serialize(person!, PersonJsonContext.Default.Person); +var json = _serializer.ToString(person); ``` ### Custom From d4e1f4a5f92bf778588376dede02b3567bb55d0e Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Wed, 17 May 2023 07:41:57 -0400 Subject: [PATCH 24/58] docs: update community toolkit mvvm page --- .../HowTo-CommunityToolkit.md | 100 +++++++++++++++--- doc/toc.yml | 2 +- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md b/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md index 2f30751c7a..d6bc8340cc 100644 --- a/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md +++ b/doc/Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md @@ -1,20 +1,94 @@ --- uid: Learn.Tutorials.DependencyInjection.HowToCommunityToolkit --- -## CommunityToolkit.Mvvm -If you want to access the DI container via the Ioc.Default API exposed via the CommunityToolkit.Mvvm, you need to configure the service collection after building the Host. +# How-To: Manually Resolving Dependencies with CommunityToolkit.Mvvm -```csharp -private IHost Host { get; } +While making gradual changes to an existing app's codebase, you may find it necessary to access the DI container to manually resolve dependencies. For instance, if you are overhauling a view model to separate its logic into services, you may need to resolve the service without using constructor injection. The [CommunityToolkit.Mvvm](https://www.nuget.org/packages/CommunityToolkit.Mvvm) package provides a static `Ioc.Default` property that exposes the DI container used by the application. -public App() -{ - Host = UnoHost - .CreateDefaultBuilder() - .Build(); - Ioc.Default.ConfigureServices(Host.Services); - // ........ // -} -``` +This tutorial will walk you through how to set up this feature and use it to manually resolve dependencies. +> [!WARNING] +> This approach to resolving dependencies is _not_ recommended, and should serve primarily as a stopgap measure while you refactor your codebase to use constructor injection. + +## Step-by-steps + +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + +### 1. Add the CommunityToolkit.Mvvm package to your project + +* Add the [CommunityToolkit.Mvvm](https://www.nuget.org/packages/CommunityToolkit.Mvvm) package to your project. + +### 2. Register services with the DI container + +* Register services with the DI container as you normally would. For instance, you can use the `Configure` method on `IHostBuilder` to register services with the DI container: + + ```csharp + private IHost host { get; set; } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + var appBuilder = this.CreateBuilder(args) + .Configure(hostBuilder => { + hostBuilder.ConfigureServices(services => + services + .AddSingleton() + .AddSingleton() + ); + }); + + host = appBuilder.Build(); + } + ``` + +### 3. Configure the DI container to use the CommunityToolkit.Mvvm service provider + +* Observe that the built `IHost` instance is available to the `App.cs` file. It is stored in a property on the `App` class: + + ```csharp + private IHost host { get; set; } + ``` + +* Because the `IHost` instance is available to the `App.cs` file, you can get the `IHost.Services` collection and configure the service provider to use it. + +* To do so, add the following line to the `OnLaunched` method: + + ```csharp + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + ... + Ioc.Default.ConfigureServices(host.Services); + } + ``` + +### 4. Resolve services from the DI container + +* You can now resolve services from the DI container using the `Ioc.Default` property. For instance, you can resolve the `IPrimaryService` service in a view model that is not registered with the DI container. + +* Do this by calling `GetService` or `GetRequiredService` method on the singleton provider instance: + + ```csharp + public class MainViewModel : ObservableRecipient + { + private readonly IPrimaryService? primaryService; + + private readonly ISecondaryService secondaryService; + + public MainViewModel() + { + // Get the IPrimaryService instance if available; otherwise returns null. + primaryService = Ioc.Default.GetService(); + + // Get the ISecondaryService instance if available; otherwise throws an exception. + secondaryService = Ioc.Default.GetRequiredService(); + } + } + ``` + +## See also + +* [Dependency injection in Uno Extensions tutorial](xref:Learn.Tutorials.DependencyInjection.HowToDependencyInjection) +* [CommunityToolkit.Mvvm NuGet package](https://www.nuget.org/packages/CommunityToolkit.Mvvm) +* [Ioc class](https://learn.microsoft.com/dotnet/api/communitytoolkit.mvvm.dependencyinjection.ioc) +* [Inversion of control](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/ioc) \ No newline at end of file diff --git a/doc/toc.yml b/doc/toc.yml index fd3a106c15..df2b9304c0 100644 --- a/doc/toc.yml +++ b/doc/toc.yml @@ -32,7 +32,7 @@ href: Overview/DependencyInjection/DependencyInjectionOverview.md - name: Register and use services with dependency injection href: Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md - - name: Use the Community Toolkit for inversion of control + - name: Manually Resolving Dependencies with CommunityToolkit.Mvvm href: Learn/Tutorials/DependencyInjection/HowTo-CommunityToolkit.md - name: Hosting items: From 1595bfe4e71724439c37f4b65568bb92ddf20965 Mon Sep 17 00:00:00 2001 From: Luke Blevins Date: Wed, 17 May 2023 07:48:28 -0400 Subject: [PATCH 25/58] docs(tutorials): add template check banner --- .../Tutorials/Authentication/HowTo-Authentication.md | 6 +++--- .../Tutorials/Authentication/HowTo-MsalAuthentication.md | 6 +++--- .../Tutorials/Authentication/HowTo-OidcAuthentication.md | 6 +++--- .../Tutorials/Authentication/HowTo-WebAuthentication.md | 6 +++--- doc/Learn/Tutorials/Configuration/HowTo-Configuration.md | 3 +++ .../DependencyInjection/HowTo-DependencyInjectionSetup.md | 3 +++ doc/Learn/Tutorials/Http/HowTo-Http.md | 3 +++ doc/Learn/Tutorials/Localization/HowTo-Localization.md | 3 +++ doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md | 3 +++ doc/Learn/Tutorials/Logging/HowTo-Logging.md | 3 +++ .../Navigation/Advanced/HowTo-AdvancedPageNavigation.md | 6 +++--- .../Navigation/Advanced/HowTo-ResponsiveShell.md | 6 +++--- .../Navigation/Advanced/HowTo-UseContentControl.md | 6 +++--- .../Navigation/Advanced/HowTo-UseNavigationView.md | 3 +++ doc/Learn/Tutorials/Navigation/Advanced/HowTo-UsePanel.md | 3 +++ .../Tutorials/Navigation/Advanced/HowTo-UseTabBar.md | 6 +++--- doc/Learn/Tutorials/Navigation/HowTo-DisplayItem.md | 6 +++--- .../Tutorials/Navigation/HowTo-DisplayMessageDialog.md | 6 +++--- .../Tutorials/Navigation/HowTo-NavigateBetweenPages.md | 6 +++--- doc/Learn/Tutorials/Navigation/HowTo-NavigateInCode.md | 8 ++++---- doc/Learn/Tutorials/Navigation/HowTo-NavigateInXAML.md | 6 +++--- doc/Learn/Tutorials/Navigation/HowTo-SelectValue.md | 6 +++--- doc/Learn/Tutorials/Navigation/HowTo-ShowFlyout.md | 6 +++--- doc/Learn/Tutorials/Serialization/HowTo-Serialization.md | 3 +++ 24 files changed, 73 insertions(+), 46 deletions(-) diff --git a/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md b/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md index 02cc11dc34..38d74374ba 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-Authentication.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Authentication.HowToAuthentication `Uno.Extensions.Authentication` provides you with a consistent way to add authentication to your application. It is recommended to use one of the built in `IAuthenticationService` implementations. This tutorial will use the custom authorization to validate user credentials. -> [!TIP] -> This guide assumes you used the Uno.Extensions template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions). - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Basic Credential Checking - Install `Uno.Extensions.Authentication` into all projects diff --git a/doc/Learn/Tutorials/Authentication/HowTo-MsalAuthentication.md b/doc/Learn/Tutorials/Authentication/HowTo-MsalAuthentication.md index e93ea42833..7a6dd0c635 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-MsalAuthentication.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-MsalAuthentication.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Authentication.HowToMsalAuthentication `MsalAuthenticationProvider` allows your users to sign in using their Microsoft identities. It wraps the [MSAL library](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) from Microsoft into an implementation of `IAuthenticationProvider` This tutorial will use MSAL authorization to validate user credentials. -> [!TIP] -> This guide assumes you used the Uno.Extensions template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions). - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Prepare for MSAL authentication - For this type of authentication, the application must be registered with the Microsoft identity platform. For more information, see [Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app). diff --git a/doc/Learn/Tutorials/Authentication/HowTo-OidcAuthentication.md b/doc/Learn/Tutorials/Authentication/HowTo-OidcAuthentication.md index 536dfbe7f5..f83fbf0da7 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-OidcAuthentication.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-OidcAuthentication.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Authentication.HowToOidcAuthentication `OidcAuthenticationProvider` allows your users to sign in using their identities from a participating identity provider. It can wrap support for any [OpenID Connect](https://openid.net/connect/) backend, such as [IdentityServer](https://duendesoftware.com/products/identityserver) into an implementation of `IAuthenticationProvider`. This tutorial will use the OIDC authorization to validate user credentials. -> [!TIP] -> This guide assumes you used the Uno.Extensions template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions). - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Prepare for OIDC authentication - For this type of authentication, the application must already be registered with the desired identity provider. diff --git a/doc/Learn/Tutorials/Authentication/HowTo-WebAuthentication.md b/doc/Learn/Tutorials/Authentication/HowTo-WebAuthentication.md index 617de0e864..cf11469b5c 100644 --- a/doc/Learn/Tutorials/Authentication/HowTo-WebAuthentication.md +++ b/doc/Learn/Tutorials/Authentication/HowTo-WebAuthentication.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Authentication.HowToWebAuthentication `WebAuthenticationProvider` provides an implementation that displays a web view in order for the user to login. After login, the web view redirects back to the application, along with any tokens. This tutorial will use web authorization to validate user credentials. -> [!TIP] -> This guide assumes you used the Uno.Extensions template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions). - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Prepare for web authentication - For this type of authentication, the application must already be registered with the desired identity provider. diff --git a/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md b/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md index 46f098fe69..4bd7e735a8 100644 --- a/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md +++ b/doc/Learn/Tutorials/Configuration/HowTo-Configuration.md @@ -7,6 +7,9 @@ uid: Learn.Tutorials.Configuration.HowToConfiguration ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Specify configuration information to load on `IConfigBuilder` * Uno.Extensions apps specify which configuration information to load by calling the `UseConfiguration()` extension method for `IHostBuilder`. diff --git a/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md b/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md index cc5380e7a4..8d65a06df5 100644 --- a/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md +++ b/doc/Learn/Tutorials/DependencyInjection/HowTo-DependencyInjectionSetup.md @@ -7,6 +7,9 @@ Dependency Injection (DI) is an important design pattern when building loosely-c ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Plan the contract for your service * Create a new interface which declares the method(s) your service offers: ```cs diff --git a/doc/Learn/Tutorials/Http/HowTo-Http.md b/doc/Learn/Tutorials/Http/HowTo-Http.md index c37004e017..6ddfeccdbf 100644 --- a/doc/Learn/Tutorials/Http/HowTo-Http.md +++ b/doc/Learn/Tutorials/Http/HowTo-Http.md @@ -7,6 +7,9 @@ When working with a complex application, centralized registration of your API en ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Enable HTTP * Call the `UseHttp()` method to register a HTTP client with the `IHostBuilder` which implements `IHttpClient`: diff --git a/doc/Learn/Tutorials/Localization/HowTo-Localization.md b/doc/Learn/Tutorials/Localization/HowTo-Localization.md index 41e4db6c3d..649380a100 100644 --- a/doc/Learn/Tutorials/Localization/HowTo-Localization.md +++ b/doc/Learn/Tutorials/Localization/HowTo-Localization.md @@ -7,6 +7,9 @@ uid: Learn.Tutorials.Localization.HowToUseLocalization ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Opt into localization * Organize your application's localized `resw` resources into folders corresponding to a language tag diff --git a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md index 86408eeec4..6677feaea3 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-InternalLogging.md @@ -10,6 +10,9 @@ uid: Learn.Tutorials.Logging.UseInternalLogging ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Enable Uno internal logging * To log Uno-internal messages, you first need to call `UseLogging()` on the `IHost` instance: diff --git a/doc/Learn/Tutorials/Logging/HowTo-Logging.md b/doc/Learn/Tutorials/Logging/HowTo-Logging.md index f2a313b455..2e557dd13d 100644 --- a/doc/Learn/Tutorials/Logging/HowTo-Logging.md +++ b/doc/Learn/Tutorials/Logging/HowTo-Logging.md @@ -7,6 +7,9 @@ uid: Learn.Tutorials.Logging.UseLogging ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Opt into logging * Uno.Extensions offers a simple way to wire up platform-specific log providers such as `Uno.Extensions.Logging.OSLogLoggerProvider` for iOS and `Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider` for WASM as both debug and console logging. diff --git a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-AdvancedPageNavigation.md b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-AdvancedPageNavigation.md index 897c8a7119..b751a84453 100644 --- a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-AdvancedPageNavigation.md +++ b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-AdvancedPageNavigation.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.Advanced.PageNavigation Sometimes when you navigate you don't want to leave the current page in the back-stack. For example after signing into an application, you might want to navigate to the main page of the application; you don't want to have the login page still in the back-stack for a user to accidentally to go back to. -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Navigating to a Page and Clearing Back Stack - Add an additional button to `MainPage.xaml` with the `Click` event bound to the `GoToSecondPageClearBackStack` method diff --git a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-ResponsiveShell.md b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-ResponsiveShell.md index 6033c28f37..de1bdbef62 100644 --- a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-ResponsiveShell.md +++ b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-ResponsiveShell.md @@ -3,11 +3,11 @@ uid: Learn.Tutorials.Navigation.Advanced.ResponsiveShell --- # How-To: Build a Responsive Layout using NavigationView and TabBar -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Add necessary XAML namespaces * Update the `Page` element in `MainPage.xaml` to include XAML namespace mappings for Navigation, WinUI, and Uno Toolkit: diff --git a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseContentControl.md b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseContentControl.md index c0fcd56bcc..9153824cbd 100644 --- a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseContentControl.md +++ b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseContentControl.md @@ -3,11 +3,11 @@ uid: Learn.Tutorials.Navigation.Advanced.ContentControl --- # How-To: Use a ContentControl to Display a View -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Displaying Content with Content Control - Add two new controls using the `UserControl` template, `LeftControl` and `RightControl` with the following XAML diff --git a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseNavigationView.md b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseNavigationView.md index d79ed5a582..1a59b4dbee 100644 --- a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseNavigationView.md +++ b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseNavigationView.md @@ -7,6 +7,9 @@ Choosing the right control for your navigation needs is important, and one commo ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Add XAML namespace mapping * Add the following namespace mapping to the root element of your XAML page: diff --git a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UsePanel.md b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UsePanel.md index b25743d479..41e16865fa 100644 --- a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UsePanel.md +++ b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UsePanel.md @@ -8,6 +8,9 @@ Sometimes your application may need to switch between multiple views without the ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Add necessary XAML namespaces * Update the `Page` element in `MainPage.xaml` to include XAML namespace mappings for Navigation and Uno Toolkit: diff --git a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseTabBar.md b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseTabBar.md index 49870158b2..a05aa0654a 100644 --- a/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseTabBar.md +++ b/doc/Learn/Tutorials/Navigation/Advanced/HowTo-UseTabBar.md @@ -3,11 +3,11 @@ uid: Learn.Tutorials.Navigation.Advanced.TabBar --- # How-To: Use a TabBar to Switch Views -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + The Navigation capabilities offered by Uno.Extensions include regions. Regions allow you to associate a specific sector of the view with an individual item on a navigation control from the same `Page`. Likewise, the Uno.Extensions library has built-in support for responding to navigation gestures from the [Toolkit](https://github.com/unoplatform/uno.toolkit.ui) `TabBar`. Follow the steps below to define a user interface centered around navigating with this control. ### 1. Add necessary XAML namespaces diff --git a/doc/Learn/Tutorials/Navigation/HowTo-DisplayItem.md b/doc/Learn/Tutorials/Navigation/HowTo-DisplayItem.md index 86e409cf5c..0162ad0a33 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-DisplayItem.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-DisplayItem.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.HowToDisplayItem This topic walks through how to use Navigation to display the details of an item selected from a list. This demonstrates an important aspect of Navigation which is the ability to pass data as part of a navigation request. -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + Often it is necessary to pass a data item from one page to another. This scenario will start with passing a newly created object along with the navigation request, and how the specified object can be accessed by the destination ViewModel. ### 1. Define the type of data to pass diff --git a/doc/Learn/Tutorials/Navigation/HowTo-DisplayMessageDialog.md b/doc/Learn/Tutorials/Navigation/HowTo-DisplayMessageDialog.md index 9c792c142c..0c5d80f429 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-DisplayMessageDialog.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-DisplayMessageDialog.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.HowToDisplayMessageDialog This topic walks through using Navigation to display a prompt using a `MessageDialog`. This can also be used for simple user interactions, such as a confirmation dialog, where the user is prompted with an Ok/Cancel, or Yes/No, question. -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Show an ad-hoc `MessageDialog` - Update the existing `Button` on `MainPage.xaml` diff --git a/doc/Learn/Tutorials/Navigation/HowTo-NavigateBetweenPages.md b/doc/Learn/Tutorials/Navigation/HowTo-NavigateBetweenPages.md index cab409599a..7703f6799b 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-NavigateBetweenPages.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-NavigateBetweenPages.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.HowToNavigateBetweenPages This topic covers using Navigation to navigate between two pages using frame-based navigation. -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Navigating to a New Page - Add a new `Page` to navigate to, `SamplePage.xaml`, in the UI (shared) project diff --git a/doc/Learn/Tutorials/Navigation/HowTo-NavigateInCode.md b/doc/Learn/Tutorials/Navigation/HowTo-NavigateInCode.md index 5420db7ed1..6a3ab3c9b6 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-NavigateInCode.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-NavigateInCode.md @@ -3,13 +3,13 @@ uid: Learn.Tutorials.Navigation.HowToNavigateInCode --- # How-To: Navigate in Code -This topic walks through controlling Navigation from code, either in the code-behind file of a Page, or in the corresponding view model. One of the Navigation objectives was a single navigation construct that applies where ever you choose to write your navigation code. - -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) +This topic walks through controlling Navigation from code, either in the code-behind file of a Page, or in the corresponding view model. One of the Navigation objectives was a single navigation construct that applies where ever you choose to write your navigation code. ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Navigating to a New Page Navigation can be invoked in the code-behind file of a `Page` by using the `Navigator` extension method to get an `INavigator` instance. diff --git a/doc/Learn/Tutorials/Navigation/HowTo-NavigateInXAML.md b/doc/Learn/Tutorials/Navigation/HowTo-NavigateInXAML.md index 07eb32ff58..eb7cbac037 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-NavigateInXAML.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-NavigateInXAML.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.HowToNavigateInXAML This topic walks through controlling Navigation from XAML. This includes specifying data that should be attached to the navigation request. -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Navigation.Request Navigation can be defined in XAML by placing the `Navigation.Request` attached property on a specific XAML element. The string value specified in the `Navigation.Request` is the route to be navigated to. diff --git a/doc/Learn/Tutorials/Navigation/HowTo-SelectValue.md b/doc/Learn/Tutorials/Navigation/HowTo-SelectValue.md index 97fc6fae67..82fc19c9ce 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-SelectValue.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-SelectValue.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.HowToSelectValue This topic walks through using Navigation to request a value from the user. For example selecting a value from a list of items. -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. GetDataAsync This scenario will use Navigation to navigate to a page in order for the user to select an item. The item will be returned via Navigation to the calling code. diff --git a/doc/Learn/Tutorials/Navigation/HowTo-ShowFlyout.md b/doc/Learn/Tutorials/Navigation/HowTo-ShowFlyout.md index f14c7bff6a..add43ae93b 100644 --- a/doc/Learn/Tutorials/Navigation/HowTo-ShowFlyout.md +++ b/doc/Learn/Tutorials/Navigation/HowTo-ShowFlyout.md @@ -5,11 +5,11 @@ uid: Learn.Tutorials.Navigation.HowToShowFlyout This topic walks through using Navigation to display a modal flyout -> [!TIP] -> This guide assumes you used the Uno.Extensions `dotnet new unoapp-extensions` template to create the solution. Instructions for creating an application from the template can be found [here](xref:Overview.Extensions) - ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Displaying flyout from code - Add new `Page`, `SamplePage.xaml`, which will be used to display content inside the flyout. diff --git a/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md b/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md index 2ddfad4730..c1a6477591 100644 --- a/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md +++ b/doc/Learn/Tutorials/Serialization/HowTo-Serialization.md @@ -7,6 +7,9 @@ Accessing the serialized and deserialized representation of an object can be imp ## Step-by-steps +> [!IMPORTANT] +> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template. + ### 1. Opt into Serialization * Call the `UseSerialization()` method to register a serializer that implements `ISerializer` with the service collection: From 1f07f7e490344d24ff3be948b435a1d006a1c84b Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Thu, 18 May 2023 18:25:09 +1000 Subject: [PATCH 26/58] fix: Linker correction for localization --- .../LinkerDefinition.UI.xml | 6 +++++ .../LinkerDefinition.WinUI.xml | 6 +++++ .../Uno.Extensions.Localization.UI.csproj | 6 +++++ .../Uno.Extensions.Localization.WinUI.csproj | 6 +++++ .../common.props | 22 +++++++++++-------- 5 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml create mode 100644 src/Uno.Extensions.Localization.UI/LinkerDefinition.WinUI.xml diff --git a/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml b/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml new file mode 100644 index 0000000000..f56fd8d47f --- /dev/null +++ b/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Uno.Extensions.Localization.UI/LinkerDefinition.WinUI.xml b/src/Uno.Extensions.Localization.UI/LinkerDefinition.WinUI.xml new file mode 100644 index 0000000000..f56fd8d47f --- /dev/null +++ b/src/Uno.Extensions.Localization.UI/LinkerDefinition.WinUI.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.UI.csproj b/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.UI.csproj index 909ccfc83d..e85b76e6b8 100644 --- a/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.UI.csproj +++ b/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.UI.csproj @@ -10,4 +10,10 @@ + + + + $(AssemblyName).xml + + diff --git a/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.WinUI.csproj b/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.WinUI.csproj index 2cc6d3f37e..aceed8f9ef 100644 --- a/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.WinUI.csproj +++ b/src/Uno.Extensions.Localization.UI/Uno.Extensions.Localization.WinUI.csproj @@ -14,4 +14,10 @@ + + + + $(AssemblyName).xml + + diff --git a/src/Uno.Extensions.Localization.UI/common.props b/src/Uno.Extensions.Localization.UI/common.props index f47b7599fc..76219643e9 100644 --- a/src/Uno.Extensions.Localization.UI/common.props +++ b/src/Uno.Extensions.Localization.UI/common.props @@ -1,17 +1,21 @@  - - - $(WarningsNotAsErrors);CS1591 - + + + $(WarningsNotAsErrors);CS1591 + - - - - - + + + + + + + + + From fe2417d297e929e87332cf48f3480c577e5d487f Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Thu, 18 May 2023 21:48:43 +1000 Subject: [PATCH 27/58] Update src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml Co-authored-by: Dan Siegel --- src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml b/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml index f56fd8d47f..6db55607c9 100644 --- a/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml +++ b/src/Uno.Extensions.Localization.UI/LinkerDefinition.UI.xml @@ -1,5 +1,5 @@  - + From 6050a4fd9ec6d16a8d7d5e81c68a02fe260deb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 19 May 2023 11:05:06 -0400 Subject: [PATCH 28/58] ci: Update chrome for wasm tests --- build/ci/scripts/wasm-uitest-run.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/ci/scripts/wasm-uitest-run.sh b/build/ci/scripts/wasm-uitest-run.sh index 10c2c02733..6086bbba6b 100644 --- a/build/ci/scripts/wasm-uitest-run.sh +++ b/build/ci/scripts/wasm-uitest-run.sh @@ -4,7 +4,7 @@ IFS=$'\n\t' export UNO_UITEST_TARGETURI=http://localhost:5000 export UNO_UITEST_DRIVERPATH_CHROME=$BUILD_SOURCESDIRECTORY/build/node_modules/chromedriver/lib/chromedriver -export UNO_UITEST_CHROME_BINARY_PATH=$BUILD_SOURCESDIRECTORY/build/node_modules/puppeteer/.local-chromium/linux-800071/chrome-linux/chrome +export UNO_UITEST_CHROME_BINARY_PATH=$BUILD_SOURCESDIRECTORY/build/node_modules/puppeteer/.local-chromium/linux-991974/chrome-linux/chrome export UNO_UITEST_SCREENSHOT_PATH=$BUILD_ARTIFACTSTAGINGDIRECTORY/screenshots/wasm export BIN_LOG_PATH=$BUILD_ARTIFACTSTAGINGDIRECTORY/wasm-uitest.binlog export UNO_UITEST_PLATFORM=Browser @@ -28,8 +28,8 @@ dotnet run --project $UNO_UITEST_WASM_PROJECT -c Release --no-build & cd $BUILD_SOURCESDIRECTORY/build -npm i chromedriver@86.0.0 -npm i puppeteer@5.3.1 +npm i chromedriver@102.0.0 +npm i puppeteer@14.1.0 wget $UNO_UITEST_NUGET_URL mono nuget.exe install NUnit.ConsoleRunner -Version $UNO_UITEST_NUNIT_VERSION From f873b8434a15fc17dd5cccb5f6fe126163230d2a Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Sat, 20 May 2023 01:52:11 +1000 Subject: [PATCH 29/58] chore: Correct packaging of generators and fixing Nuget overriding --- src/Directory.Build.targets | 4 ++-- src/Uno.CrossTargeting.props | 6 +++--- src/crosstargeting_override.props.sample | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index bbfb1bc011..79eb91ddee 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -92,13 +92,13 @@ $(MSBuildProjectDirectory)\$(IntermediateOutputPath)\PackGenerators\ + Properties="Configuration=$(Configuration);OutputPath=$(GeneratorOutputPath);TargetFramework=netstandard2.0" /> - + diff --git a/src/Uno.CrossTargeting.props b/src/Uno.CrossTargeting.props index af452143b2..863e4695ae 100644 --- a/src/Uno.CrossTargeting.props +++ b/src/Uno.CrossTargeting.props @@ -6,13 +6,13 @@ - + <_TargetNugetPackageId Condition="'$(PackageId)'!=''">$(PackageId) <_TargetNugetPackageId Condition="'$(PackageId)'==''">$(AssemblyName) - <_TargetNugetFolder Condition="'$(ToolOfPackage)'!=''">$(USERPROFILE)\.nuget\packages\$(ToolOfPackage)\$(UnoNugetOverrideVersion)\analyzers\dotnet\cs - <_TargetNugetFolder Condition="'$(ToolOfPackage)'==''">$(USERPROFILE)\.nuget\packages\$(_TargetNugetPackageId)\$(UnoNugetOverrideVersion)\lib\$(TargetFramework) + <_TargetNugetFolder Condition="'$(ToolOfPackage)'!=''">$(USERPROFILE)\.nuget\packages\$(ToolOfPackage)\$(NugetOverrideVersion)\analyzers\dotnet\cs + <_TargetNugetFolder Condition="'$(ToolOfPackage)'==''">$(USERPROFILE)\.nuget\packages\$(_TargetNugetPackageId)\$(NugetOverrideVersion)\lib\$(TargetFramework) diff --git a/src/crosstargeting_override.props.sample b/src/crosstargeting_override.props.sample index a9a2f6e4dd..cd4180c5c1 100644 --- a/src/crosstargeting_override.props.sample +++ b/src/crosstargeting_override.props.sample @@ -26,7 +26,18 @@ The UnoTargetFrameworkMobileOverride property is use only in Mobile (i.e. the single-project used by net6-ios/android/mac/catalyst) --> - net6.0-ios - $(UnoTargetFrameworkMobileOverride);netstandard2.0 + + + + + + From ef3749f7db8f599b21d09d3fadecfc8e6fedc384 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Sat, 20 May 2023 02:01:11 +1000 Subject: [PATCH 30/58] fix: Fix AddConsole for Skia and Wasm --- src/Uno.Extensions.Logging/HostBuilderExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Uno.Extensions.Logging/HostBuilderExtensions.cs b/src/Uno.Extensions.Logging/HostBuilderExtensions.cs index 68f9874560..ff12b5c656 100644 --- a/src/Uno.Extensions.Logging/HostBuilderExtensions.cs +++ b/src/Uno.Extensions.Logging/HostBuilderExtensions.cs @@ -23,14 +23,14 @@ public static IHostBuilder UseLogging( #if !__WASM__ #if __IOS__ #pragma warning disable CA1416 // Validate platform compatibility: The net6.0 version is not used on older versions of OS - builder.AddProvider(new global::Uno.Extensions.Logging.OSLogLoggerProvider()); + builder.AddProvider(new global::Uno.Extensions.Logging.OSLogLoggerProvider()); #pragma warning restore CA1416 // Validate platform compatibility -#elif NET6_0_OR_GREATER // Console isn't supported on all Xamarin targets, so only adding for net6.0 and above - builder.AddConsole(); +#elif NET6_0_OR_GREATER || __SKIA__ // Console isn't supported on all Xamarin targets, so only adding for net6.0 and above + builder.AddConsole(); #endif builder.AddDebug(); #elif __WASM__ - builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider()); + builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider()); #endif } From d37521ad7a81547f0567c8d39b81e01b477ff388 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Tue, 23 May 2023 00:06:12 +1000 Subject: [PATCH 31/58] fix: Call Bindings.Update to initialize x:Bind bindings --- samples/Playground/Directory.Build.props | 1 + samples/Playground/Playground.sln | 59 +++++++++++++ samples/Playground/Playground.winui.slnf | 1 + src/Uno.Extensions-packageonly.slnf | 3 +- .../ForceBindingsUpdateGenTool_1.cs | 68 +++++++++++++++ .../ForceBindingsUpdateGenerationContext.cs | 87 +++++++++++++++++++ .../ForceBindingsUpdateGenerator.cs | 42 +++++++++ ...no.Extensions.Navigation.Generators.csproj | 29 +++++++ .../Controls/ForceUpdateAttribute.cs | 24 +++++ .../FrameworkElementExtensions.cs | 8 +- .../Navigators/ContentDialogNavigator.cs | 2 +- .../Navigators/ControlNavigator.cs | 2 +- .../Navigators/FlyoutNavigator.cs | 2 +- .../IForceBindingsUpdate.cs | 16 ++++ .../Uno.Extensions.Navigation.csproj | 4 + .../build/Package.targets | 11 ++- src/Uno.Extensions.sln | 19 ++++ testing/TestHarness/TestHarness-winui.slnf | 2 +- .../TestHarness.Mobile.csproj | 5 +- .../Ext/AdHoc/AdHocOnePage.xaml | 2 +- .../Ext/AdHoc/AdHocOnePage.xaml.cs | 3 + .../Ext/AdHoc/AdHocOneViewModel.cs | 11 +++ .../TestHarness.Shared/GlobalUsings.cs | 1 + .../TestHarness.Skia.Gtk.csproj | 5 +- .../TestHarness.Skia.WPF.csproj | 5 +- .../TestHarness.Wasm/TestHarness.Wasm.csproj | 1 + .../TestHarness.Windows.csproj | 3 +- testing/TestHarness/TestHarness.sln | 59 +++++++++++++ 28 files changed, 459 insertions(+), 16 deletions(-) create mode 100644 src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenTool_1.cs create mode 100644 src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerationContext.cs create mode 100644 src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerator.cs create mode 100644 src/Uno.Extensions.Navigation.Generators/Uno.Extensions.Navigation.Generators.csproj create mode 100644 src/Uno.Extensions.Navigation.UI/Controls/ForceUpdateAttribute.cs create mode 100644 src/Uno.Extensions.Navigation/IForceBindingsUpdate.cs diff --git a/samples/Playground/Directory.Build.props b/samples/Playground/Directory.Build.props index 6ca5392f53..3caf4186c1 100644 --- a/samples/Playground/Directory.Build.props +++ b/samples/Playground/Directory.Build.props @@ -15,6 +15,7 @@ + diff --git a/samples/Playground/Playground.sln b/samples/Playground/Playground.sln index f04d7cd841..b19db07329 100644 --- a/samples/Playground/Playground.sln +++ b/samples/Playground/Playground.sln @@ -155,6 +155,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Extensions.Core.UI", ". EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Extensions.Core.WinUI", "..\..\src\Uno.Extensions.Core.UI\Uno.Extensions.Core.WinUI.csproj", "{0706A3E8-9DB3-4344-979B-376CD04D76B9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Extensions.Navigation.Generators", "..\..\src\Uno.Extensions.Navigation.Generators\Uno.Extensions.Navigation.Generators.csproj", "{B4F14C65-EC7C-4641-81A6-576BA122E8C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -3218,6 +3220,62 @@ Global {0706A3E8-9DB3-4344-979B-376CD04D76B9}.Release|x64.Build.0 = Release|Any CPU {0706A3E8-9DB3-4344-979B-376CD04D76B9}.Release|x86.ActiveCfg = Release|Any CPU {0706A3E8-9DB3-4344-979B-376CD04D76B9}.Release|x86.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|ARM64.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|ARM64.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|ARM.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|ARM64.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|ARM64.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|iPhone.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|x64.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|x64.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|x86.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.AppStore|x86.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|ARM.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|ARM64.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|iPhone.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|x64.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|x64.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|x86.ActiveCfg = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Debug|x86.Build.0 = Debug|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|Any CPU.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|ARM.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|ARM.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|ARM64.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|ARM64.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|iPhone.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|iPhone.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|x64.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|x64.Build.0 = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|x86.ActiveCfg = Release|Any CPU + {B4F14C65-EC7C-4641-81A6-576BA122E8C2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3290,6 +3348,7 @@ Global {349E684D-BC28-4D6E-8CA6-6646DE7FD478} = {235663AE-4E7D-4FA4-964E-012C2F143FE5} {BDAB734F-2622-4591-B06B-87C6EC53B282} = {F9372730-80BC-47B6-9E23-B5F69E36768F} {0706A3E8-9DB3-4344-979B-376CD04D76B9} = {F9372730-80BC-47B6-9E23-B5F69E36768F} + {B4F14C65-EC7C-4641-81A6-576BA122E8C2} = {A7450AE8-53E1-4AD2-8E3A-4BF626269DC0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1A645832-60E5-4DA0-9EE7-69F7E34333DC} diff --git a/samples/Playground/Playground.winui.slnf b/samples/Playground/Playground.winui.slnf index c396968714..f9d06071ce 100644 --- a/samples/Playground/Playground.winui.slnf +++ b/samples/Playground/Playground.winui.slnf @@ -25,6 +25,7 @@ "..\\..\\src\\Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.Skia.csproj", "..\\..\\src\\Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.Wasm.csproj", "..\\..\\src\\Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.csproj", + "..\\..\\src\\Uno.Extensions.Navigation.Generators\\Uno.Extensions.Navigation.Generators.csproj", "..\\..\\src\\Uno.Extensions.Navigation.Toolkit\\Uno.Extensions.Navigation.Toolkit.UI.csproj", "..\\..\\src\\Uno.Extensions.Navigation.Toolkit\\Uno.Extensions.Navigation.Toolkit.WinUI.csproj", "..\\..\\src\\Uno.Extensions.Navigation.UI\\Uno.Extensions.Navigation.UI.csproj", diff --git a/src/Uno.Extensions-packageonly.slnf b/src/Uno.Extensions-packageonly.slnf index 718871551a..6e6753827f 100644 --- a/src/Uno.Extensions-packageonly.slnf +++ b/src/Uno.Extensions-packageonly.slnf @@ -36,6 +36,7 @@ "Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.Skia.csproj", "Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.Wasm.csproj", "Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.csproj", + "Uno.Extensions.Navigation.Generators\\Uno.Extensions.Navigation.Generators.csproj", "Uno.Extensions.Navigation.Toolkit\\Uno.Extensions.Navigation.Toolkit.UI.csproj", "Uno.Extensions.Navigation.Toolkit\\Uno.Extensions.Navigation.Toolkit.WinUI.csproj", "Uno.Extensions.Navigation.UI.Markup\\Uno.Extensions.Navigation.WinUI.Markup.csproj", @@ -62,4 +63,4 @@ "Uno.Extensions\\Uno.Extensions.csproj" ] } -} +} \ No newline at end of file diff --git a/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenTool_1.cs b/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenTool_1.cs new file mode 100644 index 0000000000..bcc44e7f56 --- /dev/null +++ b/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenTool_1.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Uno.Extensions.Generators; + +namespace Uno.Extensions.Navigation.Generators; + +internal class ForceBindingsUpdateGenTool_1 : ICodeGenTool +{ + /// + public string Version => "1"; + + private readonly ForceBindingsUpdateGenerationContext _ctx; + private readonly IAssemblySymbol _assembly; + + public ForceBindingsUpdateGenTool_1(ForceBindingsUpdateGenerationContext ctx) + { + _ctx = ctx; + _assembly = ctx.Context.Compilation.Assembly; + } + + private bool IsSupported(INamedTypeSymbol? type) + => type is not null + && (_ctx.IsGenerationEnabled(type) + ?? ( + type.Is(_ctx.Page) && + type.IsPartial() + )) + && _ctx.ContainsXBind(type); + + public IEnumerable<(string fileName, string code)> Generate() + { + var pages = from module in _assembly.Modules + from type in module.GetNamespaceTypes() + where + type is not null && + IsSupported(type) + select type; + + foreach (var page in pages) + { + yield return (page.ToDisplayString(), Generate(page)); + } + } + + private string Generate(INamedTypeSymbol model) + { + var className = model.Name; + + var updateInterface = _ctx.ForceBindingsUpdateInterface.ToDisplayString(); + var fileCode = this.AsPartialOf( + model, + attributes: default, + bases: updateInterface, + code: $@" + ValueTask IForceBindingsUpdate.ForceBindingsUpdateAsync() + {{ + this.Bindings.Update(); + return ValueTask.CompletedTask; + }} + "); + + return fileCode.Align(0); + } + + +} diff --git a/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerationContext.cs b/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerationContext.cs new file mode 100644 index 0000000000..2aa31c4b21 --- /dev/null +++ b/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerationContext.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Uno.Extensions.Generators; +using Uno.Extensions.Navigation.UI.Controls; + +namespace Uno.Extensions.Navigation.Generators; + +internal record ForceBindingsUpdateGenerationContext( + GeneratorExecutionContext Context, + + // Core types + [ContextType("Uno.Extensions.Navigation.IForceBindingsUpdate")] INamedTypeSymbol ForceBindingsUpdateInterface, + + // Attributes + [ContextType(typeof(ForceUpdateAttribute))] INamedTypeSymbol UpdateAttribute, + + // General stuff types + [ContextType("Microsoft.UI.Xaml.Controls.Page")] INamedTypeSymbol Page) +{ + public bool IsGenerationNotDisable(ISymbol symbol) + => IsGenerationEnabled(symbol) ?? true; + + public bool? IsGenerationEnabled(ISymbol symbol) + => symbol.FindAttributeValue(UpdateAttribute, nameof(ForceUpdateAttribute.IsEnabled), 0) is { isDefined: true } attribute + ? attribute.value ?? true + : null; + + private IImmutableSet? _XBindFiles; + private static Regex ClassRegEx = new Regex("x:Class=\"([\\w.]+)\""); + private static string xBind = "{x:Bind "; + + private IImmutableSet XBindFiles + { + get + { + if (_XBindFiles is null) + { + var files = ImmutableHashSet.CreateBuilder(); + foreach (var file in Context.AdditionalFiles) + { + using (var reader = new StreamReader(file.Path)) + { + string? className = null; + var hasXBind = false; + while ( + !reader.EndOfStream && + (className is null || + !hasXBind)) + { + var txt = reader.ReadLine(); + if(className is null) + { + var classNameMatch = ClassRegEx.Match(txt); + if (classNameMatch.Success && + classNameMatch.Groups.Count > 1) + { + className = classNameMatch.Groups[1].Value; + } + } + if (txt is not null && + txt.Contains(xBind)) + { + hasXBind = true; + } + } + if(className is not null && hasXBind) + { + files.Add(className); + } + } + } + _XBindFiles = files.ToImmutableHashSet(); + } + return _XBindFiles; + } + } + + public bool ContainsXBind(INamedTypeSymbol symbol) + { + var name = symbol.ToDisplayString(); + return XBindFiles.Contains(name); + } +} diff --git a/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerator.cs b/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerator.cs new file mode 100644 index 0000000000..3eaea5487f --- /dev/null +++ b/src/Uno.Extensions.Navigation.Generators/ForceBindingsUpdateGenerator.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; +using Microsoft.CodeAnalysis; +using Uno.Extensions.Generators; + +namespace Uno.Extensions.Navigation.Generators; + +/// +/// A generator that generates bindable VM for the reactive framework +/// +[Generator] +public partial class ForceBindingsUpdateGenerator : ISourceGenerator +{ + /// + public void Initialize(GeneratorInitializationContext context) { } + + /// + public void Execute(GeneratorExecutionContext context) + { +#if DEBUGGING_GENERATOR + var process = Process.GetCurrentProcess().ProcessName; + if (process.IndexOf("VBCSCompiler", StringComparison.OrdinalIgnoreCase) is not -1 + || process.IndexOf("csc", StringComparison.OrdinalIgnoreCase) is not -1) + { + Debugger.Launch(); + } +#endif + + if (GenerationContext.TryGet(context, out var error) is { } bindableContext) + { + foreach (var generated in new ForceBindingsUpdateGenTool_1(bindableContext).Generate()) + { + context.AddSource(PathHelper.SanitizeFileName(generated.fileName) + ".g.cs", generated.code); + } + } + else + { + throw new InvalidOperationException(error); + } + + } +} diff --git a/src/Uno.Extensions.Navigation.Generators/Uno.Extensions.Navigation.Generators.csproj b/src/Uno.Extensions.Navigation.Generators/Uno.Extensions.Navigation.Generators.csproj new file mode 100644 index 0000000000..0c7879f3c9 --- /dev/null +++ b/src/Uno.Extensions.Navigation.Generators/Uno.Extensions.Navigation.Generators.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + false + + + Uno.Extensions.Navigation + + $(NoWarn);RS2008 + + + + + + + + + + + + + + + + diff --git a/src/Uno.Extensions.Navigation.UI/Controls/ForceUpdateAttribute.cs b/src/Uno.Extensions.Navigation.UI/Controls/ForceUpdateAttribute.cs new file mode 100644 index 0000000000..63d2ac792b --- /dev/null +++ b/src/Uno.Extensions.Navigation.UI/Controls/ForceUpdateAttribute.cs @@ -0,0 +1,24 @@ +using System; +namespace Uno.Extensions.Navigation.UI.Controls; + +/// +/// Flags the default constructor to use to create an instance of a record that is being de-normalized for bindings. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class ForceUpdateAttribute : Attribute +{ + /// + /// Gets a value indicating whether the method should be generated or not. + /// + public bool IsEnabled { get; } + + /// + /// Configure generation the force update method. + /// + /// Indicates if the method should be generated or not. + public ForceUpdateAttribute(bool isEnabled = true) + { + IsEnabled = isEnabled; + } + +} diff --git a/src/Uno.Extensions.Navigation.UI/FrameworkElementExtensions.cs b/src/Uno.Extensions.Navigation.UI/FrameworkElementExtensions.cs index 1f4f32a3da..4c2d89f16c 100644 --- a/src/Uno.Extensions.Navigation.UI/FrameworkElementExtensions.cs +++ b/src/Uno.Extensions.Navigation.UI/FrameworkElementExtensions.cs @@ -81,7 +81,7 @@ internal static async Task Startup(this IServiceProvider services, Func af Windows.ApplicationModel.Core.CoreApplication.MainView.DispatcherQueue; #endif - public static async Task EnsureLoaded(this FrameworkElement? element, int? timeoutInSeconds = default) + internal static async Task EnsureLoaded(this FrameworkElement? element, int? timeoutInSeconds = default) { if (element is null) { @@ -192,7 +192,7 @@ private static Task EnsureElementLoaded(this FrameworkElement? element, Cancella return completion.Task; } - public static void InjectServicesAndSetDataContext( + internal static async ValueTask InjectServicesAndSetDataContextAsync( this FrameworkElement view, IServiceProvider services, INavigator navigation, @@ -204,6 +204,10 @@ public static void InjectServicesAndSetDataContext( view.DataContext != viewModel) { view.DataContext = viewModel; + if(view is IForceBindingsUpdate updatableView) + { + await updatableView.ForceBindingsUpdateAsync(); + } } } diff --git a/src/Uno.Extensions.Navigation.UI/Navigators/ContentDialogNavigator.cs b/src/Uno.Extensions.Navigation.UI/Navigators/ContentDialogNavigator.cs index 94ffee2540..67a65d256d 100644 --- a/src/Uno.Extensions.Navigation.UI/Navigators/ContentDialogNavigator.cs +++ b/src/Uno.Extensions.Navigation.UI/Navigators/ContentDialogNavigator.cs @@ -56,7 +56,7 @@ services is null || dialog.SetInstance(Region); - dialog.InjectServicesAndSetDataContext(services, navigation, viewModel); + await dialog.InjectServicesAndSetDataContextAsync(services, navigation, viewModel); var showTask = dialog.ShowAsync(); _ = showTask.AsTask() diff --git a/src/Uno.Extensions.Navigation.UI/Navigators/ControlNavigator.cs b/src/Uno.Extensions.Navigation.UI/Navigators/ControlNavigator.cs index a505a484c7..65ea644c28 100644 --- a/src/Uno.Extensions.Navigation.UI/Navigators/ControlNavigator.cs +++ b/src/Uno.Extensions.Navigation.UI/Navigators/ControlNavigator.cs @@ -88,7 +88,7 @@ viewModel is null || viewModel = await CreateViewModel(services, request, route, mapping); } - view.InjectServicesAndSetDataContext(services, navigator, viewModel); + await view.InjectServicesAndSetDataContextAsync(services, navigator, viewModel); return viewModel; } diff --git a/src/Uno.Extensions.Navigation.UI/Navigators/FlyoutNavigator.cs b/src/Uno.Extensions.Navigation.UI/Navigators/FlyoutNavigator.cs index 2c8b86b416..09c7e4e01d 100644 --- a/src/Uno.Extensions.Navigation.UI/Navigators/FlyoutNavigator.cs +++ b/src/Uno.Extensions.Navigation.UI/Navigators/FlyoutNavigator.cs @@ -116,7 +116,7 @@ private void CloseFlyout() { if (!injectedFlyout) { - flyoutElement.InjectServicesAndSetDataContext(services, navigation, viewModel); + await flyoutElement.InjectServicesAndSetDataContextAsync(services, navigation, viewModel); } flyoutElement.SetInstance(Region); diff --git a/src/Uno.Extensions.Navigation/IForceBindingsUpdate.cs b/src/Uno.Extensions.Navigation/IForceBindingsUpdate.cs new file mode 100644 index 0000000000..0e1f31be63 --- /dev/null +++ b/src/Uno.Extensions.Navigation/IForceBindingsUpdate.cs @@ -0,0 +1,16 @@ +namespace Uno.Extensions.Navigation; + +/// +/// Interface that defines the ForceBindingsUpdate method which will +/// be invoked when a data context has been set on a view. +/// +public interface IForceBindingsUpdate +{ + /// + /// Method to be implemented by a type so that it can be notified + /// when the data context has been set. Useful so that Bindings.Update + /// can be invoked by navigation after setting a datacontext on a page + /// + /// awaitable ValueTask + ValueTask ForceBindingsUpdateAsync(); +} diff --git a/src/Uno.Extensions.Navigation/Uno.Extensions.Navigation.csproj b/src/Uno.Extensions.Navigation/Uno.Extensions.Navigation.csproj index 3d13828832..654ec0d207 100644 --- a/src/Uno.Extensions.Navigation/Uno.Extensions.Navigation.csproj +++ b/src/Uno.Extensions.Navigation/Uno.Extensions.Navigation.csproj @@ -16,6 +16,10 @@ + + + + diff --git a/src/Uno.Extensions.Navigation/build/Package.targets b/src/Uno.Extensions.Navigation/build/Package.targets index f6823de647..ddf6d8ca36 100644 --- a/src/Uno.Extensions.Navigation/build/Package.targets +++ b/src/Uno.Extensions.Navigation/build/Package.targets @@ -1,5 +1,8 @@ - - - - \ No newline at end of file + + + + + + + diff --git a/src/Uno.Extensions.sln b/src/Uno.Extensions.sln index 3e066d6215..e9a9d814a2 100644 --- a/src/Uno.Extensions.sln +++ b/src/Uno.Extensions.sln @@ -188,6 +188,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Extensions.Core.UI", "U EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Extensions.Core.WinUI", "Uno.Extensions.Core.UI\Uno.Extensions.Core.WinUI.csproj", "{FBD4CA61-6709-40BA-9E64-C34672A27568}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.Extensions.Navigation.Generators", "Uno.Extensions.Navigation.Generators\Uno.Extensions.Navigation.Generators.csproj", "{2F076ADB-04CE-4C75-A63E-90A160BB4C30}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1050,6 +1052,22 @@ Global {FBD4CA61-6709-40BA-9E64-C34672A27568}.Release|x64.Build.0 = Release|Any CPU {FBD4CA61-6709-40BA-9E64-C34672A27568}.Release|x86.ActiveCfg = Release|Any CPU {FBD4CA61-6709-40BA-9E64-C34672A27568}.Release|x86.Build.0 = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|arm64.ActiveCfg = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|arm64.Build.0 = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|x64.Build.0 = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Debug|x86.Build.0 = Debug|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|Any CPU.Build.0 = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|arm64.ActiveCfg = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|arm64.Build.0 = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|x64.ActiveCfg = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|x64.Build.0 = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|x86.ActiveCfg = Release|Any CPU + {2F076ADB-04CE-4C75-A63E-90A160BB4C30}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1119,6 +1137,7 @@ Global {2FADB746-5211-44DB-8F2B-5B4C9617290B} = {069DD21F-46E5-4379-ADF6-81AE5FCD54B5} {F0668EB6-9C39-42A9-BE4A-AF57FD7E9A1D} = {B1477A2C-41F4-4970-8640-5CCF8DA3EDFD} {EEF81643-E541-4298-92FF-F0AF8433C44B} = {B1477A2C-41F4-4970-8640-5CCF8DA3EDFD} + {2F076ADB-04CE-4C75-A63E-90A160BB4C30} = {2B4C9E46-EA70-4ADE-B301-E2BB8CC31365} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6E7B035D-9A64-4D95-89AA-9D4653F17C42} diff --git a/testing/TestHarness/TestHarness-winui.slnf b/testing/TestHarness/TestHarness-winui.slnf index 338d16c25a..299e7a6bad 100644 --- a/testing/TestHarness/TestHarness-winui.slnf +++ b/testing/TestHarness/TestHarness-winui.slnf @@ -24,6 +24,7 @@ "..\\..\\src\\Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.Wasm.csproj", "..\\..\\src\\Uno.Extensions.Logging\\Uno.Extensions.Logging.WinUI.csproj", "..\\..\\src\\Uno.Extensions.Navigation.Toolkit\\Uno.Extensions.Navigation.Toolkit.WinUI.csproj", + "..\\..\\src\\Uno.Extensions.Navigation.Generators\\Uno.Extensions.Navigation.Generators.csproj", "..\\..\\src\\Uno.Extensions.Navigation.UI\\Uno.Extensions.Navigation.WinUI.csproj", "..\\..\\src\\Uno.Extensions.Navigation\\Uno.Extensions.Navigation.csproj", "..\\..\\src\\Uno.Extensions.Reactive.Generator\\Uno.Extensions.Reactive.Generator.csproj", @@ -47,7 +48,6 @@ "TestHarness.Skia.WPF\\TestHarness.Skia.WPF.csproj", "TestHarness.UITest\\TestHarness.UITest.csproj", "TestHarness.Wasm\\TestHarness.Wasm.csproj", - "TestHarness.Windows.Package\\TestHarness.Windows.Package.wapproj", "TestHarness.Windows\\TestHarness.Windows.csproj" ] } diff --git a/testing/TestHarness/TestHarness.Mobile/TestHarness.Mobile.csproj b/testing/TestHarness/TestHarness.Mobile/TestHarness.Mobile.csproj index 69ec030b65..f1f245f0a2 100644 --- a/testing/TestHarness/TestHarness.Mobile/TestHarness.Mobile.csproj +++ b/testing/TestHarness/TestHarness.Mobile/TestHarness.Mobile.csproj @@ -47,6 +47,7 @@ + @@ -54,8 +55,10 @@ + + @@ -118,6 +121,6 @@ - + diff --git a/testing/TestHarness/TestHarness.Shared/Ext/AdHoc/AdHocOnePage.xaml b/testing/TestHarness/TestHarness.Shared/Ext/AdHoc/AdHocOnePage.xaml index 123cc39464..4388d66932 100644 --- a/testing/TestHarness/TestHarness.Shared/Ext/AdHoc/AdHocOnePage.xaml +++ b/testing/TestHarness/TestHarness.Shared/Ext/AdHoc/AdHocOnePage.xaml @@ -17,7 +17,7 @@ -