From e1bbf5405c57a058a1f6a3b275a8db1112ea51c6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Levesque Date: Mon, 30 Oct 2023 08:41:04 -0400 Subject: [PATCH 1/3] fix: Add Microsoft.VisualStudio.Threading.Analyzers to check for async void usages. --- .editorconfig | 9 + CHANGELOG.md | 2 + Directory.Build.props | 2 + .../ApplicationTemplate.Mobile.csproj | 1 + .../Reactive/IObservable.Extensions.cs | 3 +- .../DadJokes/DadJokesFiltersPageViewModel.cs | 11 +- .../ConfigurationDebuggerViewModel.cs | 2 +- ...ApplicationTemplate.Shared.Views.projitems | 1 - .../Behaviors/FormattingTextBoxBehavior.cs | 3 +- .../Controls/AppBarBackButton.cs | 3 +- .../Controls/Validation/DataValidationView.cs | 3 +- .../ExtendedSplashscreenController.cs | 5 +- .../Framework/Launcher/LauncherService.cs | 29 +-- .../Helpers/DispatcherQueueExtensions.cs | 237 ------------------ .../Startup.cs | 7 +- .../ApplicationTemplate.Windows.csproj | 1 + 16 files changed, 35 insertions(+), 284 deletions(-) delete mode 100644 src/app/ApplicationTemplate.Shared.Views/Helpers/DispatcherQueueExtensions.cs diff --git a/.editorconfig b/.editorconfig index a7a799e9..6feff84e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -391,3 +391,12 @@ dotnet_diagnostic.SA1210.severity = suggestion dotnet_diagnostic.CA1308.severity = none # CS1587: XML comment is not placed on a valid language element dotnet_diagnostic.CS1587.severity = none + +# VSTHRD110: This one is bugged: https://github.com/microsoft/vs-threading/issues/899 +dotnet_diagnostic.VSTHRD110.severity = none +# VSTHRD200: Use `Async` naming convention +dotnet_diagnostic.VSTHRD200.severity = none +# VSTHRD100: Avoid async void methods +dotnet_diagnostic.VSTHRD100.severity = error +# VSTHRD101: Avoid unsupported async delegates +dotnet_diagnostic.VSTHRD101.severity = error diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d821e2c..28c3af7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. ## 2.1.X +- Replace local `DispatcherQueue` extension methods with the ones from the WinUI and Uno.WinUI Community Toolkit. +- Add `Microsoft.VisualStudio.Threading.Analyzers` to check for async void usages and fix async void usages. - Enable `TreatWarningsAsErrors` for the Access, Business, and Presentation projects. - Update analyzers packages and severity of rules. diff --git a/Directory.Build.props b/Directory.Build.props index 7484b9ee..2a55efca 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,6 +8,8 @@ all runtime; build; native; contentfiles; analyzers + + diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index 9ed7d618..f571d104 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -28,6 +28,7 @@ + diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs b/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs index e2eb458f..ba25ab57 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs @@ -21,8 +21,7 @@ public static class ObservableExtensions2 /// Time delay /// Scheduler /// Output observable - [SuppressMessage("nventive.Globalization", "NV2005:NV2005 - Simple cyclomatic complexity​​", Justification = "Imported code")] - [SuppressMessage("nventive.Reliability", "NV0016:NV0016 - Do not create an async void lambda expression", Justification = "Imported code")] + [SuppressMessage("Usage", "VSTHRD101:Avoid unsupported async delegates", Justification = "Imported code")] public static IObservable ThrottleOrImmediate(this IObservable source, TimeSpan delay, IScheduler scheduler) { // Throttle behavior: diff --git a/src/app/ApplicationTemplate.Presentation/ViewModels/DadJokes/DadJokesFiltersPageViewModel.cs b/src/app/ApplicationTemplate.Presentation/ViewModels/DadJokes/DadJokesFiltersPageViewModel.cs index 05e6ca0d..2e8b98b6 100644 --- a/src/app/ApplicationTemplate.Presentation/ViewModels/DadJokes/DadJokesFiltersPageViewModel.cs +++ b/src/app/ApplicationTemplate.Presentation/ViewModels/DadJokes/DadJokesFiltersPageViewModel.cs @@ -16,15 +16,6 @@ public class DadJokesFiltersPageViewModel : ViewModel { public DadJokesFiltersPageViewModel() { - var pt = this.GetService().GetAndObservePostTypeFilter(); - var postType = GetPostType(pt); - - PostTypeFilter = postType.Result; - - async Task GetPostType(ReplaySubject ptArg) - { - return await ptArg.FirstAsync(); - } } public IDynamicCommand HandleCheck => this.GetCommand((string pt) => @@ -40,7 +31,7 @@ async Task GetPostType(ReplaySubject ptArg) public PostTypes PostTypeFilter { - get => this.Get(initialValue: PostTypes.Hot); + get => this.GetFromTask(ct => this.GetService().GetAndObservePostTypeFilter().FirstAsync(ct), initialValue: PostTypes.Hot); set => this.Set(value); } } diff --git a/src/app/ApplicationTemplate.Presentation/ViewModels/Diagnostics/Configuration/ConfigurationDebuggerViewModel.cs b/src/app/ApplicationTemplate.Presentation/ViewModels/Diagnostics/Configuration/ConfigurationDebuggerViewModel.cs index f399b796..8d8aa219 100644 --- a/src/app/ApplicationTemplate.Presentation/ViewModels/Diagnostics/Configuration/ConfigurationDebuggerViewModel.cs +++ b/src/app/ApplicationTemplate.Presentation/ViewModels/Diagnostics/Configuration/ConfigurationDebuggerViewModel.cs @@ -88,7 +88,7 @@ public string SelectedKey ConfigurationValue = this.GetService()[value]; // Erase the selection from the ComboBox because we put it in the TextBox. - Task.Run(() => RaisePropertyChanged(nameof(SelectedKey))); + _ = Task.Run(() => RaisePropertyChanged(nameof(SelectedKey))); } } diff --git a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems index 7d8960b4..4c6d4436 100644 --- a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems +++ b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems @@ -109,7 +109,6 @@ - diff --git a/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs b/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs index 77b5e614..8a589eb2 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Text; +using CommunityToolkit.WinUI; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -196,7 +197,7 @@ private static void FormatText(TextBox textbox, string textFormat) return; } - _ = textbox.DispatcherQueue.RunAsync(DispatcherQueuePriority.Normal, () => + _ = textbox.DispatcherQueue.EnqueueAsync(() => { textbox.Text = formattedText; textbox.SelectionStart = selectionStart; diff --git a/src/app/ApplicationTemplate.Shared.Views/Controls/AppBarBackButton.cs b/src/app/ApplicationTemplate.Shared.Views/Controls/AppBarBackButton.cs index 18d2a40d..2ca26bbf 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Controls/AppBarBackButton.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Controls/AppBarBackButton.cs @@ -3,6 +3,7 @@ using System.Windows.Input; using Chinook.DynamicMvvm; using Chinook.SectionsNavigation; +using CommunityToolkit.WinUI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.UI.Dispatching; @@ -77,7 +78,7 @@ private IDisposable ObserveBackButtonVisibility() void OnStateChanged(bool canNavigateBackOrCloseModal) { var dispatcherQueue = ServiceProvider.GetRequiredService(); - _ = dispatcherQueue.RunAsync(DispatcherQueuePriority.Normal, UpdateBackButtonUI); + _ = dispatcherQueue.EnqueueAsync(UpdateBackButtonUI); void UpdateBackButtonUI() // Runs on UI thread. { diff --git a/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationView.cs b/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationView.cs index 431afc4f..4fe53c38 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationView.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationView.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using CommunityToolkit.WinUI; namespace ApplicationTemplate; @@ -78,7 +79,7 @@ private void OnPropertyNamedChanged() private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) { - _ = DispatcherQueue.RunAsync(DispatcherQueuePriority.Normal, ErrorsChangedUI); + _ = DispatcherQueue.EnqueueAsync(ErrorsChangedUI); void ErrorsChangedUI() { diff --git a/src/app/ApplicationTemplate.Shared.Views/Framework/ExtendedSplashscreenController.cs b/src/app/ApplicationTemplate.Shared.Views/Framework/ExtendedSplashscreenController.cs index 6dcfb590..75f4a473 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Framework/ExtendedSplashscreenController.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Framework/ExtendedSplashscreenController.cs @@ -1,4 +1,5 @@ -using Microsoft.UI.Dispatching; +using CommunityToolkit.WinUI; +using Microsoft.UI.Dispatching; namespace ApplicationTemplate; @@ -13,7 +14,7 @@ public ExtendedSplashscreenController(DispatcherQueue dispatcherQueue) public void Dismiss() { - _ = _dispatcherQueue.RunAsync(DispatcherQueuePriority.Normal, DismissSplashScreen); + _ = _dispatcherQueue.EnqueueAsync(DismissSplashScreen); void DismissSplashScreen() // Runs on UI thread. { diff --git a/src/app/ApplicationTemplate.Shared.Views/Framework/Launcher/LauncherService.cs b/src/app/ApplicationTemplate.Shared.Views/Framework/Launcher/LauncherService.cs index 8eba2985..e1c75f81 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Framework/Launcher/LauncherService.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Framework/Launcher/LauncherService.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using CommunityToolkit.WinUI; using Microsoft.UI.Dispatching; namespace ApplicationTemplate; @@ -15,37 +16,15 @@ public LauncherService(DispatcherQueue dispatcherQueue) public async Task Launch(Uri uri) { - var launchSucceeded = await DispatcherRunTaskAsync(DispatcherQueuePriority.Normal, async () => await Windows.System.Launcher.LaunchUriAsync(uri)); + var launchSucceeded = await _dispatcherQueue.EnqueueAsync(InnerLaunch, DispatcherQueuePriority.Normal); if (!launchSucceeded) { throw new LaunchFailedException($"Failed to launch URI: {uri}"); } - } - - /// - /// This method allows for executing an async Task with result on the . - /// - /// The result type. - /// . - /// A function that will be execute on the . - /// of . - private async Task DispatcherRunTaskAsync(DispatcherQueuePriority dispatcherQueuePriority, Func> asyncFunc) - { - var completion = new TaskCompletionSource(); - await _dispatcherQueue.RunAsync(dispatcherQueuePriority, RunActionUI); - return await completion.Task; - async void RunActionUI() + async Task InnerLaunch() { - try - { - var result = await asyncFunc(); - completion.SetResult(result); - } - catch (Exception exception) - { - completion.SetException(new LaunchFailedException("An error occured while trying to launch an URI on the UI thread.", exception)); - } + return await Windows.System.Launcher.LaunchUriAsync(uri); } } } diff --git a/src/app/ApplicationTemplate.Shared.Views/Helpers/DispatcherQueueExtensions.cs b/src/app/ApplicationTemplate.Shared.Views/Helpers/DispatcherQueueExtensions.cs deleted file mode 100644 index 1a8d1b15..00000000 --- a/src/app/ApplicationTemplate.Shared.Views/Helpers/DispatcherQueueExtensions.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/License.md -// See reference: https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp/Extensions/DispatcherQueueExtensions.cs - -using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace Microsoft.UI.Dispatching; - -internal static class DispatcherQueueExtensions -{ - /// - /// Invokes a given function on the target and returns a - /// that completes when the invocation of the function is completed. - /// - /// The target to invoke the code on. - /// The priority level for the function to invoke. - /// The to invoke. - /// A that completes when the invocation of is over. - /// If the current thread has access to , will be invoked directly. - internal static Task RunAsync(this DispatcherQueue dispatcher, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal, DispatcherQueueHandler handler = default) - { - // Run the function directly when we have thread access. - // Also reuse Task.CompletedTask in case of success, - // to skip an unnecessary heap allocation for every invocation. - if (dispatcher.HasThreadAccess) - { - try - { - handler.Invoke(); - - return Task.CompletedTask; - } - catch (Exception e) - { - return Task.FromException(e); - } - } - - return TryEnqueueAsync(dispatcher, handler, priority); - } - - /// - /// Invokes a given function on the target and returns a - /// that completes when the invocation of the function is completed. - /// - /// The target to invoke the code on. - /// The priority level for the function to invoke. - /// The to invoke. - /// A that completes when the invocation of is over. - /// If the current thread has access to , will be invoked directly. - internal static Task RunTaskAsync(this DispatcherQueue dispatcher, DispatcherQueuePriority priority, Func asyncAction) - { - return EnqueueAsync(dispatcher, asyncAction, priority); - } - - /// - /// Invokes a given function on the target and returns a - /// that completes when the invocation of the function is completed. - /// - /// The target to invoke the code on. - /// The priority level for the function to invoke. - /// The to invoke. - /// The return type of the task. - /// A that completes when the invocation of is over. - /// If the current thread has access to , will be invoked directly. - internal static Task RunTaskAsync(this DispatcherQueue dispatcher, DispatcherQueuePriority priority, Func> asyncAction) - { - return EnqueueAsync(dispatcher, asyncAction, priority); - } - - internal static Task TryEnqueueAsync(DispatcherQueue dispatcher, DispatcherQueueHandler handler, DispatcherQueuePriority priority) - { - var taskCompletionSource = new TaskCompletionSource(); - - if (!dispatcher.TryEnqueue(() => - { - try - { - handler(); - - taskCompletionSource.SetResult(null); - } - catch (Exception e) - { - taskCompletionSource.SetException(e); - } - })) - { - taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation")); - } - - return taskCompletionSource.Task; - } - - /// - /// Invokes a given function on the target and returns a - /// that acts as a proxy for the one returned by the given function. - /// - /// The target to invoke the code on. - /// The to invoke. - /// The priority level for the function to invoke. - /// A that acts as a proxy for the one returned by . - /// If the current thread has access to , will be invoked directly. - internal static Task EnqueueAsync(this DispatcherQueue dispatcher, Func function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) - { - // If we have thread access, we can retrieve the task directly. - // We don't use ConfigureAwait(false) in this case, in order - // to let the caller continue its execution on the same thread - // after awaiting the task returned by this function. - if (dispatcher.HasThreadAccess) - { - try - { - if (function() is Task awaitableResult) - { - return awaitableResult; - } - - return Task.FromException(GetEnqueueException("The Task returned by function cannot be null.")); - } - catch (Exception e) - { - return Task.FromException(e); - } - } - - static Task TryEnqueueAsync(DispatcherQueue dispatcher, Func function, DispatcherQueuePriority priority) - { - var taskCompletionSource = new TaskCompletionSource(); - - if (!dispatcher.TryEnqueue(priority, async () => - { - try - { - if (function() is Task awaitableResult) - { - await awaitableResult.ConfigureAwait(false); - - taskCompletionSource.SetResult(null); - } - else - { - taskCompletionSource.SetException(GetEnqueueException("The Task returned by function cannot be null.")); - } - } - catch (Exception e) - { - taskCompletionSource.SetException(e); - } - })) - { - taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation")); - } - - return taskCompletionSource.Task; - } - - return TryEnqueueAsync(dispatcher, function, priority); - } - - /// - /// Invokes a given function on the target and returns a - /// that acts as a proxy for the one returned by the given function. - /// - /// The return type of to relay through the returned . - /// The target to invoke the code on. - /// The to invoke. - /// The priority level for the function to invoke. - /// A that relays the one returned by . - /// If the current thread has access to , will be invoked directly. - internal static Task EnqueueAsync(this DispatcherQueue dispatcher, Func> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) - { - if (dispatcher.HasThreadAccess) - { - try - { - if (function() is Task awaitableResult) - { - return awaitableResult; - } - - return Task.FromException(GetEnqueueException("The Task returned by function cannot be null.")); - } - catch (Exception e) - { - return Task.FromException(e); - } - } - - static Task TryEnqueueAsync(DispatcherQueue dispatcher, Func> function, DispatcherQueuePriority priority) - { - var taskCompletionSource = new TaskCompletionSource(); - - if (!dispatcher.TryEnqueue(priority, async () => - { - try - { - if (function() is Task awaitableResult) - { - var result = await awaitableResult.ConfigureAwait(false); - - taskCompletionSource.SetResult(result); - } - else - { - taskCompletionSource.SetException(GetEnqueueException("The Task returned by function cannot be null.")); - } - } - catch (Exception e) - { - taskCompletionSource.SetException(e); - } - })) - { - taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation")); - } - - return taskCompletionSource.Task; - } - - return TryEnqueueAsync(dispatcher, function, priority); - } - - /// - /// Creates an to return when an enqueue operation fails. - /// - /// The message of the exception. - /// An with a specified message. - [MethodImpl(MethodImplOptions.NoInlining)] - internal static InvalidOperationException GetEnqueueException(string message) - { - return new InvalidOperationException(message); - } -} diff --git a/src/app/ApplicationTemplate.Shared.Views/Startup.cs b/src/app/ApplicationTemplate.Shared.Views/Startup.cs index ce100b23..570df1d0 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Startup.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Startup.cs @@ -9,6 +9,7 @@ using Chinook.DataLoader; using Chinook.DynamicMvvm; using Chinook.SectionsNavigation; +using CommunityToolkit.WinUI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -125,7 +126,7 @@ private static void HandleUnhandledExceptions(IServiceProvider services) private static async Task SetShellViewModel() { - await App.Instance.Shell.DispatcherQueue.RunAsync(DispatcherQueuePriority.Normal, SetDataContextUI); + await App.Instance.Shell.DispatcherQueue.EnqueueAsync(SetDataContextUI); static void SetDataContextUI() // Runs on UI thread. { @@ -159,7 +160,7 @@ private void HandleSystemBackVisibility(IServiceProvider services) void OnStateChanged(bool canNavigateBackOrCloseModal) { var dispatcherQueue = services.GetRequiredService(); - _ = dispatcherQueue.RunAsync(DispatcherQueuePriority.Normal, UpdateBackButtonUI); + _ = dispatcherQueue.EnqueueAsync(UpdateBackButtonUI); void UpdateBackButtonUI() // Runs on UI thread. { @@ -181,7 +182,7 @@ private async Task AddSystemBackButtonSource(IServiceProvider services) #if __ANDROID__ || __IOS__ var dispatcherQueue = services.GetRequiredService(); var backButtonManager = services.GetRequiredService(); - await dispatcherQueue.RunAsync(DispatcherQueuePriority.High, AddSystemBackButtonSourceInner); + await dispatcherQueue.EnqueueAsync(AddSystemBackButtonSourceInner, DispatcherQueuePriority.High); // Runs on main thread. void AddSystemBackButtonSourceInner() diff --git a/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj b/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj index e7f82383..0d754b43 100644 --- a/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj +++ b/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj @@ -72,6 +72,7 @@ + From 0776773a18163de744fd16cc0e4f2bf8909e57f8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Levesque Date: Mon, 30 Oct 2023 12:01:58 -0400 Subject: [PATCH 2/3] fix: Improve analyzer rules by adding GooseAnalyzers. --- .editorconfig | 6 ++-- CHANGELOG.md | 1 + Directory.Build.props | 1 + .../IAuthenticationRepository.cs | 30 ++++++++-------- .../DadJokes/IDadJokesRepository.cs | 9 +++-- .../ApiClients/Posts/IPostRepository.cs | 34 ++++++++++--------- .../UserProfile/IUserProfileRepository.cs | 12 ++++--- .../Framework/BaseMock.cs | 16 ++++----- .../Framework/Serialization/JwtData.cs | 14 ++++---- .../IApplicationSettingsRepository.cs | 25 +++++++------- .../Authentication/IAuthenticationService.cs | 31 +++++++++-------- .../DadJokes/IDadJokesService.cs | 27 ++++++++------- .../Posts/IPostService.cs | 32 +++++++++-------- .../UserProfile/IUserProfileService.cs | 12 ++++--- .../ApplicationTemplate.Mobile.csproj | 3 +- .../Configuration/ApiConfiguration.cs | 8 ++--- .../IDeviceInformationProvider.cs | 3 ++ .../Diagnostics/IDiagnosticsService.cs | 17 ++++++++++ .../Framework/Email/IEmailService.cs | 3 ++ .../Framework/Launcher/ILauncherService.cs | 3 ++ .../IThreadCultureOverrideService.cs | 6 +++- .../Framework/Logging/ILogFilesProvider.cs | 10 ++++++ .../Reactive/IObservable.Extensions.cs | 8 ++--- .../Framework/Startup/CoreStartupBase.cs | 6 ++-- .../IExtendedSplashscreenController.cs | 5 ++- .../Framework/Startup/StartupBase.cs | 7 ++-- .../Framework/Version/IVersionProvider.cs | 4 +++ ...dTaskDynamicPropertyFromDynamicProperty.cs | 2 +- .../ISectionsNavigator.Extensions.cs | 2 +- .../Extensions/ValidatorExtensions.cs | 30 ++++++++-------- .../Behaviors/FormattingTextBoxBehavior.cs | 32 ++++++++++------- .../Validation/DataValidationStateType.cs | 2 +- .../Helpers/ObservableExtensions.cs | 8 ++--- .../ApplicationTemplate.Windows.csproj | 3 +- 34 files changed, 239 insertions(+), 173 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6feff84e..bb2905e0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -310,7 +310,7 @@ dotnet_diagnostic.SA1515.severity = none # SA1202:Elements should be ordered by access dotnet_diagnostic.SA1202.severity = suggestion # SA1600:Elements should be documented -dotnet_diagnostic.SA1600.severity = none +dotnet_diagnostic.SA1600.severity = warning # SA1601:Elements should be documented dotnet_diagnostic.SA1601.severity = suggestion # SA1116:Split parameters should start on line after declaration @@ -354,9 +354,9 @@ dotnet_diagnostic.SA1649.severity = suggestion # SA1201: Elements should appear in the correct order (Fields vs Properties) dotnet_diagnostic.SA1201.severity = suggestion # SA1629: Documentation text should end with a period -dotnet_diagnostic.SA1629.severity = suggestion +dotnet_diagnostic.SA1629.severity = warning # SA1514: Element documentation header should be preceded by blank line -dotnet_diagnostic.SA1514.severity = suggestion +dotnet_diagnostic.SA1514.severity = warning # SA1134: Attributes should not share line dotnet_diagnostic.SA1134.severity = none # SA1413: Use trailing comma in multi-line initializers diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c3af7a..b0fd6fca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) Prefix your items with `(Template)` if the change is about the template and not the resulting application. ## 2.1.X +- Install `GooseAnalyzers` to enable the `SA1600` rule with its scope limited to interfaces and improve xml documentation. - Replace local `DispatcherQueue` extension methods with the ones from the WinUI and Uno.WinUI Community Toolkit. - Add `Microsoft.VisualStudio.Threading.Analyzers` to check for async void usages and fix async void usages. - Enable `TreatWarningsAsErrors` for the Access, Business, and Presentation projects. diff --git a/Directory.Build.props b/Directory.Build.props index 2a55efca..f95d199d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,6 +10,7 @@ + diff --git a/src/app/ApplicationTemplate.Access/ApiClients/Authentication/IAuthenticationRepository.cs b/src/app/ApplicationTemplate.Access/ApiClients/Authentication/IAuthenticationRepository.cs index ccc4cbf5..e6512e98 100644 --- a/src/app/ApplicationTemplate.Access/ApiClients/Authentication/IAuthenticationRepository.cs +++ b/src/app/ApplicationTemplate.Access/ApiClients/Authentication/IAuthenticationRepository.cs @@ -6,39 +6,41 @@ namespace ApplicationTemplate.DataAccess; +/// +/// Provides access to the authentication API. +/// public interface IAuthenticationRepository { /// /// Logs the user in using the provided and . /// - /// - /// Email - /// Password - /// + /// The . + /// The email. + /// The password. + /// The . Task Login(CancellationToken ct, string email, string password); /// /// Refreshes the user token. /// - /// - /// Unauthorized token - /// + /// The . + /// The unauthorized token. + /// The . Task RefreshToken(CancellationToken ct, AuthenticationData unauthorizedToken); /// /// Creates a user account. /// - /// - /// Email - /// Password - /// + /// The . + /// The email. + /// The password. + /// The . Task CreateAccount(CancellationToken ct, string email, string password); /// /// Resets the password. /// - /// - /// Email - /// + /// The . + /// The email. Task ResetPassword(CancellationToken ct, string email); } diff --git a/src/app/ApplicationTemplate.Access/ApiClients/DadJokes/IDadJokesRepository.cs b/src/app/ApplicationTemplate.Access/ApiClients/DadJokes/IDadJokesRepository.cs index 30caa872..d9179bdf 100644 --- a/src/app/ApplicationTemplate.Access/ApiClients/DadJokes/IDadJokesRepository.cs +++ b/src/app/ApplicationTemplate.Access/ApiClients/DadJokes/IDadJokesRepository.cs @@ -7,14 +7,17 @@ namespace ApplicationTemplate.DataAccess; +/// +/// Provides access to the dad jokes API. +/// public interface IDadJokesRepository { /// /// Returns a list of dad jokes based on /r/dadjokes. /// - /// - /// - /// List of quotes + /// The . + /// The type of post. + /// A list of jokes. [Get("/{typePost}.json")] Task FetchData(CancellationToken ct, string typePost); } diff --git a/src/app/ApplicationTemplate.Access/ApiClients/Posts/IPostRepository.cs b/src/app/ApplicationTemplate.Access/ApiClients/Posts/IPostRepository.cs index 4e420e02..963abef8 100644 --- a/src/app/ApplicationTemplate.Access/ApiClients/Posts/IPostRepository.cs +++ b/src/app/ApplicationTemplate.Access/ApiClients/Posts/IPostRepository.cs @@ -7,51 +7,53 @@ namespace ApplicationTemplate.DataAccess; +/// +/// Provides access to the posts API. +/// [Headers("Authorization: Bearer")] public interface IPostsRepository { /// /// Gets the list of all posts. /// - /// - /// List of posts + /// The . + /// A list of posts. [Get("/posts")] Task GetAll(CancellationToken ct); /// /// Gets the post of the specified id. /// - /// - /// Post id - /// Post + /// The . + /// The post identifier. + /// The post. [Get("/posts/{id}")] Task Get(CancellationToken ct, [AliasAs("id")] long postId); /// /// Creates a post. /// - /// - /// Post - /// New + /// The . + /// The post data. + /// A new . [Post("/posts")] Task Create(CancellationToken ct, [Body] PostData post); /// /// Updates a post. /// - /// - /// Post id - /// Post - /// Updated + /// The . + /// The post identifier. + /// The updated post data. + /// The updated . [Put("/posts/{id}")] Task Update(CancellationToken ct, [AliasAs("id")] long postId, [Body] PostData post); /// - /// Delets a post. + /// Deletes a post. /// - /// - /// Post id - /// + /// The . + /// The post identifier. [Delete("/posts/{id}")] Task Delete(CancellationToken ct, [AliasAs("id")] long postId); } diff --git a/src/app/ApplicationTemplate.Access/ApiClients/UserProfile/IUserProfileRepository.cs b/src/app/ApplicationTemplate.Access/ApiClients/UserProfile/IUserProfileRepository.cs index 8cd60047..f2083776 100644 --- a/src/app/ApplicationTemplate.Access/ApiClients/UserProfile/IUserProfileRepository.cs +++ b/src/app/ApplicationTemplate.Access/ApiClients/UserProfile/IUserProfileRepository.cs @@ -7,23 +7,25 @@ namespace ApplicationTemplate.DataAccess; +/// +/// Provides access to the user profile API. +/// [Headers("Authorization: Bearer")] public interface IUserProfileRepository { /// /// Returns the current user profile. /// - /// - /// + /// The . + /// The . [Get("/me")] Task Get(CancellationToken ct); /// /// Updates the current user profile. /// - /// - /// User profile - /// + /// The . + /// The user profile. [Put("/me")] Task Update(CancellationToken ct, UserProfileData userProfile); } diff --git a/src/app/ApplicationTemplate.Access/Framework/BaseMock.cs b/src/app/ApplicationTemplate.Access/Framework/BaseMock.cs index 1b06afef..83fcec0b 100644 --- a/src/app/ApplicationTemplate.Access/Framework/BaseMock.cs +++ b/src/app/ApplicationTemplate.Access/Framework/BaseMock.cs @@ -22,10 +22,10 @@ public BaseMock(JsonSerializerOptions serializerOptions) /// /// Gets the deserialized value of the specified embedded resource. /// - /// Type of value - /// Name of the resource - /// Caller member name - /// Deserialized value + /// The type of value. + /// The name of the resource. + /// The caller member name. + /// The deserialized value. /// /// If left empty, the will implicitly be treated as "{callerTypeName}.{callerMemberName}.json". /// Note that this will deserialize the first embedded resource whose name ends with the specified . @@ -63,10 +63,10 @@ protected T GetFromEmbeddedResource( /// If left empty, the will implicitly be treated as "{callerTypeName}.{callerMemberName}.json". /// Note that this will deserialize the first embedded resource whose name ends with the specified . /// - /// Type of object - /// Name of the resource - /// Name of the caller (used if no resource name provided) - /// Deserialized object + /// The type of object. + /// The name of the resource. + /// The name of the caller (used if no resource name provided). + /// The deserialized object. protected Task GetTaskFromEmbeddedResource( string resourceName = null, [CallerMemberName] string callerMemberName = null diff --git a/src/app/ApplicationTemplate.Access/Framework/Serialization/JwtData.cs b/src/app/ApplicationTemplate.Access/Framework/Serialization/JwtData.cs index 7bf97eca..ab9e0de0 100644 --- a/src/app/ApplicationTemplate.Access/Framework/Serialization/JwtData.cs +++ b/src/app/ApplicationTemplate.Access/Framework/Serialization/JwtData.cs @@ -8,7 +8,7 @@ namespace ApplicationTemplate; /// /// Specialized type to hold RFC 7519 JSON Web Token (JWT) information. /// -/// The type of the Payload to deserialize +/// The type of the Payload to deserialize. public class JwtData where TPayload : class { @@ -37,34 +37,34 @@ public JwtData(string token, JsonSerializerOptions jsonSerializerOptions = null) } /// - /// Represents the raw token as received as constructor parameter + /// Gets the raw token as received as constructor parameter. /// public string Token { get; } /// - /// Represents the decoded (but non-deserialized) header part of the JWT - in JSON text + /// Gets the decoded (but non-deserialized) header part of the JWT - in JSON text. /// public string RawHeader { get; } /// - /// Represents the decoded (but non-deserialized) payload part of the JWT - in JSON text + /// Gets the decoded (but non-deserialized) payload part of the JWT - in JSON text. /// public string RawPayload { get; } /// - /// Deserialized header + /// Gets the deserialized header. /// public IDictionary Header => _header ?? (_header = JsonSerializer.Deserialize(RawHeader, typeof(IDictionary), _jsonSerializerOptions) as IDictionary); /// - /// Deserialized payload + /// Gets the deserialized payload. /// public TPayload Payload => _payload ?? (_payload = JsonSerializer.Deserialize(RawPayload, typeof(TPayload), _jsonSerializerOptions) as TPayload); /// - /// Decoded signature of the JWT + /// Gets the decoded signature of the JWT. /// public byte[] Signature { get; } diff --git a/src/app/ApplicationTemplate.Access/LocalStorage/IApplicationSettingsRepository.cs b/src/app/ApplicationTemplate.Access/LocalStorage/IApplicationSettingsRepository.cs index 1f917298..213fbd1e 100644 --- a/src/app/ApplicationTemplate.Access/LocalStorage/IApplicationSettingsRepository.cs +++ b/src/app/ApplicationTemplate.Access/LocalStorage/IApplicationSettingsRepository.cs @@ -5,48 +5,47 @@ namespace ApplicationTemplate.DataAccess; +/// +/// Provides access to the local storage. +/// public interface IApplicationSettingsRepository { /// /// Gets and observes the current . /// - /// Current + /// An observable sequence yielding the current . IObservable GetAndObserveCurrent(); /// /// Gets the current . /// - /// - /// Current + /// The . + /// The current . Task GetCurrent(CancellationToken ct); /// /// Discards any settings that are related to the user. /// - /// - /// + /// The . Task DiscardUserSettings(CancellationToken ct); /// /// Flags that the onboarding has been completed. /// - /// - /// + /// The . Task CompleteOnboarding(CancellationToken ct); /// /// Sets the current . /// - /// - /// - /// + /// The . + /// The . Task SetAuthenticationData(CancellationToken ct, AuthenticationData authenticationData); /// /// Sets the favorite quotes. /// - /// - /// Favorite quotes - /// + /// The . + /// The favorite quotes. Task SetFavoriteQuotes(CancellationToken ct, ImmutableDictionary quotes); } diff --git a/src/app/ApplicationTemplate.Business/Authentication/IAuthenticationService.cs b/src/app/ApplicationTemplate.Business/Authentication/IAuthenticationService.cs index 9aa714e1..79f40be9 100644 --- a/src/app/ApplicationTemplate.Business/Authentication/IAuthenticationService.cs +++ b/src/app/ApplicationTemplate.Business/Authentication/IAuthenticationService.cs @@ -7,12 +7,15 @@ namespace ApplicationTemplate.Business; +/// +/// Manages the authentication. +/// public interface IAuthenticationService : IAuthenticationTokenProvider { /// /// Gets and observes the current . /// - /// Current + /// An observable sequence containing the current . IObservable GetAndObserveAuthenticationData(); /// @@ -24,39 +27,37 @@ public interface IAuthenticationService : IAuthenticationTokenProvider /// Raised when the user has been automatically logged out because the session expired. /// - /// + /// A observable sequence notifying whenever the session expires. IObservable ObserveSessionExpired(); /// /// Logs the user in using the provided and . /// - /// - /// Email - /// Password - /// + /// The . + /// The email. + /// The password. + /// The . Task Login(CancellationToken ct, string email, string password); /// /// Logs the user out. /// - /// - /// + /// The . Task Logout(CancellationToken ct); /// /// Creates a user account. /// - /// - /// Email - /// Password - /// + /// The . + /// The email. + /// The password. + /// The . Task CreateAccount(CancellationToken ct, string email, string password); /// /// Resets the password. /// - /// - /// Email - /// + /// THe . + /// The email. Task ResetPassword(CancellationToken ct, string email); } diff --git a/src/app/ApplicationTemplate.Business/DadJokes/IDadJokesService.cs b/src/app/ApplicationTemplate.Business/DadJokes/IDadJokesService.cs index a8fbca57..bb540889 100644 --- a/src/app/ApplicationTemplate.Business/DadJokes/IDadJokesService.cs +++ b/src/app/ApplicationTemplate.Business/DadJokes/IDadJokesService.cs @@ -8,40 +8,41 @@ namespace ApplicationTemplate.Business; +/// +/// Provides access to the dad jokes. +/// public interface IDadJokesService { /// - /// Returns a list of dad jokes. + /// Gets and observes the post type filter. /// - /// List of quotes ReplaySubject GetAndObservePostTypeFilter(); /// - /// Set post type. + /// Sets the post type filter. /// - /// + /// The . void SetPostTypeFilter(PostTypes pt); /// /// Returns a list of dad jokes. /// - /// - /// List of quotes + /// The . + /// A list of quotes. Task FetchData(CancellationToken ct); /// - /// Returns the list of favorite quotes. + /// Gets the list of favorite quotes. /// - /// - /// List of favorite quotes + /// The . + /// An observable list of favorite quotes. Task> GetFavorites(CancellationToken ct); /// /// Sets whether or not a quote is favorite. /// - /// - /// - /// Is favorite or not - /// + /// The . + /// The to set. + /// Whether the quote is a favorite or not. Task SetIsFavorite(CancellationToken ct, DadJokesQuote quote, bool isFavorite); } diff --git a/src/app/ApplicationTemplate.Business/Posts/IPostService.cs b/src/app/ApplicationTemplate.Business/Posts/IPostService.cs index ee5aae33..b7822c7a 100644 --- a/src/app/ApplicationTemplate.Business/Posts/IPostService.cs +++ b/src/app/ApplicationTemplate.Business/Posts/IPostService.cs @@ -5,45 +5,47 @@ namespace ApplicationTemplate.Business; +/// +/// Provides access to the posts. +/// public interface IPostService { /// /// Gets the post of the specified id. /// - /// - /// Post id - /// + /// The . + /// The post identifier. + /// The requested . Task GetPost(CancellationToken ct, long postId); /// /// Gets the list of all posts. /// - /// - /// List of all posts + /// The . + /// A list containing all posts. Task> GetPosts(CancellationToken ct); /// /// Creates a post. /// - /// - /// - /// New + /// The . + /// The to create. + /// The newly created . Task Create(CancellationToken ct, Post post); /// /// Updates a post. /// - /// - /// Post id - /// - /// Updated + /// The . + /// The post identifier. + /// The to update. + /// The updated . Task Update(CancellationToken ct, long postId, Post post); /// /// Deletes a post. /// - /// - /// Post id - /// + /// The . + /// The post identifier. Task Delete(CancellationToken ct, long postId); } diff --git a/src/app/ApplicationTemplate.Business/UserProfile/IUserProfileService.cs b/src/app/ApplicationTemplate.Business/UserProfile/IUserProfileService.cs index e4000d8a..b6a7a5cb 100644 --- a/src/app/ApplicationTemplate.Business/UserProfile/IUserProfileService.cs +++ b/src/app/ApplicationTemplate.Business/UserProfile/IUserProfileService.cs @@ -6,20 +6,22 @@ namespace ApplicationTemplate.Business; +/// +/// Provides access to the user profile. +/// public interface IUserProfileService { /// /// Gets the current user profile. /// - /// - /// + /// The . + /// The current . Task GetCurrent(CancellationToken ct); /// /// Updates the current user profile. /// - /// - /// - /// + /// The . + /// The . Task Update(CancellationToken ct, UserProfile userProfile); } diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index f571d104..ab503430 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -1,10 +1,9 @@  10.0 - - net7.0-android;net7.0-ios true + true Exe true true diff --git a/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs b/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs index 5b4ca6ea..0f622457 100644 --- a/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs +++ b/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs @@ -157,10 +157,10 @@ private static void AddDefaultHeaders(HttpClient client, IServiceProvider servic /// /// Adds a Refit client to the service collection. /// - /// Type of the Refit interface - /// Service collection - /// Optional. Settings to configure the instance with - /// Updated IHttpClientBuilder + /// The type of the Refit interface. + /// The service collection. + /// Optional. The settings to configure the instance with. + /// The updated IHttpClientBuilder. private static IHttpClientBuilder AddRefitHttpClient(this IServiceCollection services, Func settings = null) where T : class { diff --git a/src/app/ApplicationTemplate.Presentation/Framework/DeviceInformation/IDeviceInformationProvider.cs b/src/app/ApplicationTemplate.Presentation/Framework/DeviceInformation/IDeviceInformationProvider.cs index e9ec18c0..8594eae6 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/DeviceInformation/IDeviceInformationProvider.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/DeviceInformation/IDeviceInformationProvider.cs @@ -1,5 +1,8 @@ namespace ApplicationTemplate; +/// +/// Provides information about the device. +/// public interface IDeviceInformationProvider { /// diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Diagnostics/IDiagnosticsService.cs b/src/app/ApplicationTemplate.Presentation/Framework/Diagnostics/IDiagnosticsService.cs index 36da0439..d64b5cae 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Diagnostics/IDiagnosticsService.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Diagnostics/IDiagnosticsService.cs @@ -6,13 +6,30 @@ namespace ApplicationTemplate; +/// +/// Offers methods to diagnose the application. +/// public interface IDiagnosticsService { + /// + /// Deletes the configuration override file. + /// void DeleteConfigurationOverrideFile(); + /// + /// Throws an exception from the main thread. + /// + /// The cancellation token. Task TestExceptionFromMainThread(CancellationToken ct); + /// + /// Opens the settings folder where the configuration override file is stored. + /// void OpenSettingsFolder(); + /// + /// Gets a value indicating whether the settings folder can be open. + /// This value is based on the platform. + /// bool CanOpenSettingsFolder { get; } } diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Email/IEmailService.cs b/src/app/ApplicationTemplate.Presentation/Framework/Email/IEmailService.cs index f96e1c81..e16d2788 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Email/IEmailService.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Email/IEmailService.cs @@ -2,6 +2,9 @@ namespace ApplicationTemplate; +/// +/// Provides methods to send emails. +/// public interface IEmailService { /// diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Launcher/ILauncherService.cs b/src/app/ApplicationTemplate.Presentation/Framework/Launcher/ILauncherService.cs index d7f96b1c..39a905b5 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Launcher/ILauncherService.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Launcher/ILauncherService.cs @@ -3,6 +3,9 @@ namespace ApplicationTemplate; +/// +/// Provides access to the launcher which allows the application to launch the default app associated with the specified URI scheme name. +/// public interface ILauncherService { /// diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Localization/IThreadCultureOverrideService.cs b/src/app/ApplicationTemplate.Presentation/Framework/Localization/IThreadCultureOverrideService.cs index 9d3d1818..be02b9d9 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Localization/IThreadCultureOverrideService.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Localization/IThreadCultureOverrideService.cs @@ -5,6 +5,10 @@ namespace ApplicationTemplate; +/// +/// Allows to override the culture of the threads. +/// This is used to change the language of the application. +/// public interface IThreadCultureOverrideService { /// @@ -17,7 +21,7 @@ public interface IThreadCultureOverrideService /// Sets the specified as the culture override. /// To apply these changes, use the method. /// - /// Culture + /// The culture to use. void SetCulture(CultureInfo culture); /// diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Logging/ILogFilesProvider.cs b/src/app/ApplicationTemplate.Presentation/Framework/Logging/ILogFilesProvider.cs index b234e6f5..86affc5c 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Logging/ILogFilesProvider.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Logging/ILogFilesProvider.cs @@ -4,10 +4,20 @@ namespace ApplicationTemplate; +/// +/// Provides access to the log files. +/// This is useful to send the log files. +/// public interface ILogFilesProvider { + /// + /// Gets the paths of the log files. + /// string[] GetLogFilesPaths(); + /// + /// Deletes the log files. + /// void DeleteLogFiles(); } diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs b/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs index ba25ab57..326d59bd 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Reactive/IObservable.Extensions.cs @@ -17,10 +17,10 @@ public static class ObservableExtensions2 /// This operator is like the throttle operator, but it doesn't wait for the for initial values. /// /// The type of the observable. - /// Source observable - /// Time delay - /// Scheduler - /// Output observable + /// The source observable. + /// The time delay. + /// The scheduler. + /// A throttled observable sequence. [SuppressMessage("Usage", "VSTHRD101:Avoid unsupported async delegates", Justification = "Imported code")] public static IObservable ThrottleOrImmediate(this IObservable source, TimeSpan delay, IScheduler scheduler) { diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Startup/CoreStartupBase.cs b/src/app/ApplicationTemplate.Presentation/Framework/Startup/CoreStartupBase.cs index 60725fc7..c8b999bc 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Startup/CoreStartupBase.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Startup/CoreStartupBase.cs @@ -54,7 +54,7 @@ public void PreInitialize() /// The folder path indicating where the override files are. /// The environment manager. /// The delegate to call to configure logging. - /// Extra host configuration + /// The extra host configuration. public void Initialize( string contentRootPath, string settingsFolderPath, @@ -132,7 +132,7 @@ public void Initialize( /// This method will be called once the app is initialized. /// This is a chance to apply any configuration required to start the app. /// - /// Services + /// The services. protected abstract void OnInitialized(IServiceProvider services); /// @@ -169,7 +169,7 @@ public async Task Start() /// This method can be called multiple times. /// This method will run on a background thread. /// - /// Services + /// The services. /// True if it's the first start; false otherwise. /// Task that completes when the services are started. protected abstract Task StartServices(IServiceProvider services, bool isFirstStart); diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Startup/IExtendedSplashscreenController.cs b/src/app/ApplicationTemplate.Presentation/Framework/Startup/IExtendedSplashscreenController.cs index 314361eb..9efa5ae7 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Startup/IExtendedSplashscreenController.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Startup/IExtendedSplashscreenController.cs @@ -4,10 +4,13 @@ namespace ApplicationTemplate; +/// +/// Controls the extended splash screen. +/// public interface IExtendedSplashscreenController { /// - /// Dismisses the extended splashscreen. + /// Dismisses the extended splash screen. /// void Dismiss(); } diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Startup/StartupBase.cs b/src/app/ApplicationTemplate.Presentation/Framework/Startup/StartupBase.cs index cd51d31d..32831c20 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Startup/StartupBase.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Startup/StartupBase.cs @@ -106,21 +106,20 @@ public void Initialize(string contentRootPath, string settingsFolderPath, Loggin /// /// Initializes view services into the provided . /// - /// The hostbuilder in which services must be added. + /// The host builder in which services must be added. protected abstract void InitializeViewServices(IHostBuilder hostBuilder); /// /// This method will be called once the app is initialized. /// This is a chance to apply any configuration required to start the app. /// - /// Services + /// The services. protected abstract void OnInitialized(IServiceProvider services); /// /// Starts the application. /// This method can be called multiple times. /// - /// public async Task Start() { if (!State.IsInitialized) @@ -166,7 +165,7 @@ async Task StartViewServicesWithLogs(IServiceProvider services, bool isFirstStar /// This method can be called multiple times. /// This method will run on a background thread. /// - /// Services + /// The services. /// True if it's the first start; false otherwise. /// Task that completes when the services are started. protected abstract Task StartViewServices(IServiceProvider services, bool isFirstStart); diff --git a/src/app/ApplicationTemplate.Presentation/Framework/Version/IVersionProvider.cs b/src/app/ApplicationTemplate.Presentation/Framework/Version/IVersionProvider.cs index 0bf98010..41279f33 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/Version/IVersionProvider.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/Version/IVersionProvider.cs @@ -2,6 +2,10 @@ namespace ApplicationTemplate; +/// +/// Provides access to the application version. +/// This is useful when displaying the version in the application. +/// public interface IVersionProvider { /// diff --git a/src/app/ApplicationTemplate.Presentation/Framework/ViewModels/ValueChangedOnBackgroundTaskDynamicPropertyFromDynamicProperty.cs b/src/app/ApplicationTemplate.Presentation/Framework/ViewModels/ValueChangedOnBackgroundTaskDynamicPropertyFromDynamicProperty.cs index e708ad1f..07124fef 100644 --- a/src/app/ApplicationTemplate.Presentation/Framework/ViewModels/ValueChangedOnBackgroundTaskDynamicPropertyFromDynamicProperty.cs +++ b/src/app/ApplicationTemplate.Presentation/Framework/ViewModels/ValueChangedOnBackgroundTaskDynamicPropertyFromDynamicProperty.cs @@ -87,7 +87,7 @@ private void OnSourceValueChanged(IDynamicProperty property) /// /// This is an implementation of a using a that ensures is raised on a background thread. /// -/// Type of value +/// The type of value. public class ValueChangedOnBackgroundTaskDynamicPropertyFromDynamicProperty : ValueChangedOnBackgroundTaskDynamicPropertyFromDynamicProperty, IDynamicProperty { /// diff --git a/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ISectionsNavigator.Extensions.cs b/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ISectionsNavigator.Extensions.cs index ae1b5950..f89db028 100644 --- a/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ISectionsNavigator.Extensions.cs +++ b/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ISectionsNavigator.Extensions.cs @@ -10,7 +10,7 @@ namespace Chinook.SectionsNavigation; public static class SectionsNavigatorExtensions { /// - /// Observe the active section and return the name + /// Observes the active section and returns its name. /// /// The sections navigator. /// A string of the active section. diff --git a/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ValidatorExtensions.cs b/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ValidatorExtensions.cs index b4dea332..90d71bae 100644 --- a/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ValidatorExtensions.cs +++ b/src/app/ApplicationTemplate.Presentation/ViewModels/Extensions/ValidatorExtensions.cs @@ -10,11 +10,11 @@ namespace Presentation; public static class ValidatorExtensions { /// - /// Returns true if value is over 18 years olds. + /// Adds a validation rule that checks whether the age is over 18 years old based on the date of birth. /// - /// Date of birth - /// Rule builder - /// Validation result. + /// The type containing the property to validate. + /// The rule builder. + /// The modified rule builder. public static IRuleBuilderOptions MustBe18OrOlder(this IRuleBuilder ruleBuilder) { return ruleBuilder.Must(dateOfBirth => @@ -39,33 +39,33 @@ public static IRuleBuilderOptions MustBe18OrOlder(this IRu } /// - /// Returns true if value is over 18 years olds. + /// Adds a validation rule that checks whether a string is a phone number. /// - /// Date of birth - /// Rule builder - /// Validation result. + /// The type containing the property to validate. + /// The rule builder. + /// The modified rule builder. public static IRuleBuilderOptions MustBePhoneNumber(this IRuleBuilder ruleBuilder) { var charsToTrim = new char[] { '(', ')', '-', ' ' }; - return ruleBuilder.Must(phonenumber => + return ruleBuilder.Must(phoneNumber => { - phonenumber = phonenumber ?? string.Empty; + phoneNumber = phoneNumber ?? string.Empty; - var strippedNumber = new string(phonenumber + var strippedNumber = new string(phoneNumber .Where(x => x.ToString(CultureInfo.InvariantCulture).IsNumber() && !x.ToString(CultureInfo.InvariantCulture).IsNullOrWhiteSpace()) .ToArray()); - var trimmedNumber = phonenumber.Trim(charsToTrim); + var trimmedNumber = phoneNumber.Trim(charsToTrim); var result = strippedNumber.Length == 10 || strippedNumber.Length == 0; // 10 = "(000) 000-0000".Length - "() -".Length; return result; }) - .Must(phonenumber => + .Must(phoneNumber => { - phonenumber = phonenumber ?? string.Empty; - var result = phonenumber + phoneNumber = phoneNumber ?? string.Empty; + var result = phoneNumber .All(pn => pn.ToString(CultureInfo.InvariantCulture).IsNumber() || pn == '(' || pn == ')' || pn == '-' || pn == ' '); return result; diff --git a/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs b/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs index 8a589eb2..1f077c9e 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Behaviors/FormattingTextBoxBehavior.cs @@ -13,16 +13,22 @@ namespace ApplicationTemplate.Views.Behaviors; /// /// This text box behavior allows you to format the input text based on specified format. /// The specified format follows the following convention: -/// '#' allows anycharacters -/// 'A' : capital letter -/// 'a' : lower case letter -/// '0' : digit -/// 'N' : Capital alphanumeric -/// 'n' : lower case alphanumeric +/// +/// '#' : allows any characters +/// 'A' : capital letter +/// 'a' : lower case letter +/// '0' : digit +/// 'N' : Capital alphanumeric +/// 'n' : lower case alphanumeric +/// +/// /// Example of formats: -/// - (000) 000-000 = Phone number -/// - 0000 0000 0000 0000 = credit card -/// - A0A 0A0 = code postal +/// +/// - (000) 000-000 = Phone number +/// - 0000 0000 0000 0000 = credit card +/// - A0A 0A0 = code postal +/// +/// /// public sealed partial class FormattingTextBoxBehavior { @@ -330,11 +336,11 @@ private static string FormatInput(string text, string format, int maskedIndex, i } /// - /// Check if the text insertion is conform to the specified format + /// Checks if the text insertion is conform to the specified format. /// - /// text format - /// index where should be inserted - /// fragment to be inserted at + /// The text format. + /// The index where should be inserted. + /// The fragment to be inserted at . private static bool ValidateInput(string format, int offset, string input) { if (offset + input.Length > format.Length) diff --git a/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationStateType.cs b/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationStateType.cs index 43127831..83580017 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationStateType.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Controls/Validation/DataValidationStateType.cs @@ -3,7 +3,7 @@ public enum DataValidationStateType { /// - /// The field doesn't have any validation yet. (Not error, nor valid) + /// The field doesn't have any validation yet (not error, nor valid). /// Default = 0, diff --git a/src/app/ApplicationTemplate.Shared.Views/Helpers/ObservableExtensions.cs b/src/app/ApplicationTemplate.Shared.Views/Helpers/ObservableExtensions.cs index cfe261f6..0a64b5fd 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Helpers/ObservableExtensions.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Helpers/ObservableExtensions.cs @@ -17,17 +17,17 @@ internal enum SubscribeToElementOptions : byte None = 0, /// - /// Default is StartLoaded | ResubscribeOnLoadEvenIfCompleted + /// Default is StartLoaded | ResubscribeOnLoadEvenIfCompleted. /// Default = StartLoaded | ResubscribeOnLoadEvenIfCompleted, /// - /// Consider the control as loaded on subscribe + /// Consider the control as loaded on subscribe. /// StartLoaded = 1, /// - /// If observable completes or fails, re-subscribe on next loaded + /// If observable completes or fails, re-subscribe on next loaded. /// ResubscribeOnLoadEvenIfCompleted = 2, @@ -75,7 +75,7 @@ internal static class ObservableExtensions /// The framework element to subscribe to. /// The action invoked when the observable pushes a new value. /// The action invoked when the observable pushes an error. - /// The action invoked on when the + /// The action invoked on when the observable completes. /// The subscription options. /// A disposable that unsubscribes. public static IDisposable SubscribeToElement( diff --git a/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj b/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj index 0d754b43..c6198202 100644 --- a/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj +++ b/src/app/ApplicationTemplate.Windows/ApplicationTemplate.Windows.csproj @@ -1,13 +1,12 @@  10.0 - - WinExe net7.0-windows10.0.22621.0 10.0.19041.0 10.0.20348.0 ApplicationTemplate + true app.manifest x86;x64;arm64 win10-x86;win10-x64;win10-arm64 From 207b671a08d8e147342ae4747223c0d5a9691272 Mon Sep 17 00:00:00 2001 From: Laurent Proulx Date: Tue, 31 Oct 2023 10:05:41 -0400 Subject: [PATCH 3/3] fix: M1 mac crash on simulator --- CHANGELOG.md | 1 + .../ApplicationTemplate.Mobile.csproj | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0fd6fca..787d0c0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Prefix your items with `(Template)` if the change is about the template and not - Add `Microsoft.VisualStudio.Threading.Analyzers` to check for async void usages and fix async void usages. - Enable `TreatWarningsAsErrors` for the Access, Business, and Presentation projects. - Update analyzers packages and severity of rules. +- Fix crash from ARM base mac on net7.0-iOS. Add `ForceSimulatorX64ArchitectureInIDE` property to mobile head. ## 2.0.X - Renamed the classes providing data to use the `Repository` suffix instead of `Endpoint` or `Service`. diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index ab503430..1ac9b681 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -150,6 +150,8 @@ $(MtouchExtraArgs) --marshal-objectivec-exceptions:disable iOS\Entitlements.plist + + true