Settings Results in 4 milliseconds

Asp.Net Programming Bad Practices
Category: Algorithms

Do Not Do This1. Do not write a function like this <pre class="language-markup" ...


Views: 0 Likes: 39
Software Development Refactoring Wisdom I gained t ...
Category: Software Development

Software Development Refactoring Wisdom I gained through R ...


Views: 175 Likes: 84
The code execution cannot proceed because msodbcsq ...
Category: Other

Question The code execution cannot proceed because msodbcsql17.dll was not found. Reinstalling t ...


Views: 0 Likes: 14
An error occurred during the compilation of a reso ...
Category: .Net 7

Question Why is this error happening? "An error occurred during the compilation of a resource re ...


Views: 0 Likes: 33
.NET 8 Performance Improvements in .NET MAUI
.NET 8 Performance Improvements in .NET MAUI

The major focus for .NET MAUI in the .NET 8 release is quality. As such, alot of our focus has been fixing bugs instead of chasing lofty performance goals. In .NET 8, we merged 1,559 pull requests that closed 596 total issues. These include changes from the .NET MAUI team as well as the .NET MAUI community. We are optimistic that this should result in a significant increase in quality in .NET 8. However! We still have plenty of performance changes to showcase. Building upon the fundamental performance improvements in .NET 8 we discover “low-hanging” fruit constantly, and there were high-voted performance issues on GitHub we tried to tackle. Our goal is to continue to make .NET MAUI faster in each release, read on for details! For a review of the performance improvements in past releases, see our posts for .NET 6 and 7. This also gives you an idea of the improvements you would see migrating from Xamarin.Forms to .NET MAUI .NET 7 Performance Improvements in .NET MAUI .NET 6 Performance Improvements in .NET MAUI Table Of Contents New features AndroidStripILAfterAOT AndroidEnableMarshalMethods NativeAOT on iOS Build & Inner Loop Performance Filter Android ps -A output with grep Port WindowsAppSDK usage of vcmeta.dll to C# Improvements to remote iOS builds on Windows Improvements to Android inner-loop XAML Compilation no longer uses LoadInSeparateAppDomain Performance or App Size Improvements Structs and IEquatable in .NET MAUI Fix performance issue in {AppThemeBinding} Address CA1307 and CA1309 for performance Address CA1311 for performance Remove unused ViewAttachedToWindow event on Android Remove unneeded System.Reflection for {Binding} Use StringComparer.Ordinal for Dictionary and HashSet Reduce Java interop in MauiDrawable on Android Improve layout performance of Label on Android Reduce Java interop calls for controls in .NET MAUI Improve performance of Entry.MaxLength on Android Improve memory usage of CollectionView on Windows Use UnmanagedCallersOnlyAttribute on Apple platforms Faster Java interop for strings on Android Faster Java interop for C# events on Android Use Function Pointers for JNI Removed Xamarin.AndroidX.Legacy.Support.V4 Deduplication of generics on iOS and macOS Fix System.Linq.Expressions implementation on iOS-like platforms Set DynamicCodeSupport=false for iOS and Catalyst Memory Leaks Memory Leaks and Quality Diagnosing leaks in .NET MAUI Patterns that cause leaks C# events Circular references on Apple platforms Roslyn analyzer for Apple platforms Tooling and Documentation Simplified dotnet-trace and dotnet-dsrouter dotnet-gcdump Support for Mobile New Features AndroidStripILAfterAOT Once Upon A Time we had a brilliant thought if AOT pre-compiles C# methods, do we need the managed method anymore? Removing the C# method body would allow assemblies to be smaller. .NET iOS applications already do this, so why not Android as well? While the idea is straightforward, implementation was not iOS uses “Full” AOT, which AOT’s all methods into a form that doesn’t require a runtime JIT. This allowed iOS to run cil-strip, removing all method bodies from all managed types. At the time, Xamarin.Android only supported “normal” AOT, and normal AOT requires a JIT for certain constructs such as generic types and generic methods. This meant that attempting to run cil-strip would result in runtime errors if a method body was removed that was actually required at runtime. This was particularly bad because cil-strip could only remove all method bodies! We are re-intoducing IL stripping for .NET 8. Add a new $(AndroidStripILAfterAOT) MSBuild property. When true, the <MonoAOTCompiler/> task will track which method bodies were actually AOT’d, storing this information into %(_MonoAOTCompiledAssemblies.MethodTokenFile), and the new <ILStrip/> task will update the input assemblies, removing all method bodies that can be removed. By default enabling $(AndroidStripILAfterAOT) will override the default $(AndroidEnableProfiledAot) setting, allowing all trimmable AOT’d methods to be removed. This choice was made because $(AndroidStripILAfterAOT) is most useful when AOT-compiling your entire application. Profiled AOT and IL stripping can be used together by explicitly setting both within the .csproj, but with the only benefit being a small .apk size improvement <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT> <AndroidEnableProfiledAot>true</AndroidEnableProfiledAot> </PropertyGroup> .apk size results for a dotnet new android app $(AndroidStripILAfterAOT) $(AndroidEnableProfiledAot) .apk size true true 7.7MB true false 8.1MB false true 7.7MB false false 8.4MB Note that AndroidStripILAfterAOT=false and AndroidEnableProfiledAot=true is the default Release configuration environment, for 7.7MB. A project that only sets AndroidStripILAfterAOT=true implicitly sets AndroidEnableProfiledAot=false, resulting in an 8.1MB app. See xamarin-android#8172 and dotnet/runtime#86722 for details about this feature. AndroidEnableMarshalMethods .NET 8 introduces a new experimental setting for Release configurations <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <AndroidEnableMarshalMethods>true</AndroidEnableMarshalMethods> <!-- Note that single-architecture apps will be most successful --> <RuntimeIdentifier>android-arm64</RuntimeIdentifier> </PropertyGroup> We hope to enable this feature by default in .NET 9, but for now we are providing the setting as an opt-in, experimental feature. Applications that only target one architecture, such as RuntimeIdentifier=android-arm64, will likely be able to enable this feature without issue. Background on Marshal Methods A JNI marshal method is a JNI-callable function pointer provided to JNIEnvRegisterNatives(). Currently, JNI marshal methods are provided via the interaction between code we generate and JNINativeWrapper.CreateDelegate() Our code-generator emits the “actual” JNI-callable method. JNINativeWrapper.CreateDelegate() uses System.Reflection.Emit to wrap the method for exception marshaling. JNI marshal methods are needed for all Java-to-C# transitions. Consider the virtual Activity.OnCreate() method partial class Activity { static Delegate? cb_onCreate_Landroid_os_Bundle_; static Delegate GetOnCreate_Landroid_os_Bundle_Handler () { if (cb_onCreate_Landroid_os_Bundle_ == null) cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_); return cb_onCreate_Landroid_os_Bundle_; } static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) { var __this = globalJava.Lang.Object.GetObject<Android.App.Activity> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; var savedInstanceState = globalJava.Lang.Object.GetObject<Android.OS.Bundle> (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); __this.OnCreate (savedInstanceState); } // Metadata.xml XPath method reference path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='onCreate' and count(parameter)=1 and parameter[1][@type='android.os.Bundle']]" [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) => ... } Activity.n_OnCreate_Landroid_os_Bundle_() is the JNI marshal method, responsible for marshaling parameters from JNI values into C# types, forwarding the method invocation to Activity.OnCreate(), and (if necessary) marshaling the return value back to JNI. Activity.GetOnCreate_Landroid_os_Bundle_Handler() is part of the type registration infrastructure, providing a Delegate instance to RegisterNativeMembers .RegisterNativeMembers(), which is eventually passed to JNIEnvRegisterNatives(). While this works, it’s not incredibly performant unless using one of the optimized delegate types added in xamarin-android#6657, System.Reflection.Emit is used to create a wrapper around the marshal method, which is something we’ve wanted to avoid doing for years. Thus, the idea since we’re already bundling a native toolchain and using LLVM-IR to produce libxamarin-app.so, what if we emitted Java native method names and skipped all the done as part of Runtime.register() and JNIEnv.RegisterJniNatives()? Given class MyActivity Activity { protected override void OnCreate(Bundle? state) => ... } During the build, libxamarin-app.so would contain the function JNIEXPORT void JNICALL Java_crc..._MyActivity_n_1onCreate (JNIEnv *env, jobject self, jobject state); During App runtime, the Runtime.register() invocation present in Java Callable Wrappers would either be omitted or would be a no-op, and Android/JNI would instead resolve MyActivity.n_onCreate() as Java_crc..._MyActivity_n_1onCreate(). We call this effort “LLVM Marshal Methods”, which is currently experimental in .NET 8. Many of the specifics are still being investigated, and this feature will be spread across various areas. See xamarin-android#7351 for details about this experimental feature. NativeAOT on iOS In .NET 7, we started an experiment to see what it would take to support NativeAOT on iOS. Going from prototype to an initial implementation .NET 8 Preview 6 included NativeAOT as an experimental feature for iOS. To opt into NativeAOT in a MAUI iOS project, use the following settings in your project file <PropertyGroup Condition="$([MSBuild]GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' and '$(Configuration)' == 'Release'"> <!-- PublishAot=true indicates NativeAOT, while omitting this property would use Mono's AOT --> <PublishAot>true</PublishAot> </PropertyGroup> Then to build the application for an iOS device $ dotnet publish -f net8.0-ios -r ios-arm64 MSBuild version 17.8.0+6cdef4241 for .NET ... Build succeeded. 0 Error(s) Note We may consider unifying and improving MSBuild property names for this feature in future .NET releases. To do a one-off build at the command-line you may also need to specify -pPublishAotUsingRuntimePack=true in addition to -pPublishAot=true. One of the main culprits for the first release was how the iOS workload supports Objective-C interoperability. The problem was mainly related to the type registration system which is the key component for efficiently supporting iOS-like platforms (see docs for details). In its implementation, the type registration system depends on type metadata tokens which are not available with NativeAOT. Therefore, in order to leverage the benefits of highly efficient NativeAOT runtime, we had to adapt. dotnet/runtime#80912 includes the discussion around how to tackle this problem, and finally in xamarin-macios#18268 we implemented a new managed static registrar that works with NativeAOT. The new managed static registrar does not just benefit us with being compatible with NativeAOT, but is also much faster than the default one, and is available for all supported runtimes (see docs for details). Along the way, we had a great help from our GH community and their contribution (code reviews, PRs) was essential to helps us move forward quickly and deliver this feature on time. A few from many PR’s that helped and unblocked us on our journey were dotnet/runtime#77956 dotnet/runtime#78280 dotnet/runtime#82317 dotnet/runtime#85996 and the list goes on… As .NET 8 Preview 6 came along, we finally managed to release our first version of the NativeAOT on iOS which also supports MAUI. See the blog post on .NET 8 Preview 6 for details about what we were able to accomplish in the initial release. In subsequent .NET 8 releases, results improved quite a bit, as we were identifying and resolving issues along the way. The graph below shows the .NET MAUI iOS template app size comparison throughout the preview releases We had steady progress and estimated size savings reported, due to fixing the following issues dotnet/runtime#87924 – fixed major NativeAOT size issue with AOT-incompatible code paths in System.Linq.Expressions and also made fully NativeAOT compatible when targeting iOS xamarin-macios#18332 – reduced the size of __LINKEDIT Export Info section in stripped binaries Furthermore, in the latest RC 1 release the app size went even further down reaching -50% smaller apps for the template .NET MAUI iOS applications compared to Mono. Most impactful issues/PRs that contributed to this xamarin-macios#18734 – Make Full the default link mode for NativeAOT xamarin-macios#18584 – Make the codebase trimming compatible through a series of PRs. Even though app size was our primary metric to focus on, for the RC 1 release, we also measured startup time performance comparisons for a .NET MAUI iOS template app comparing NativeAOT and Mono where NativeAOT results with almost 2x faster startup time. Key Takeaways For NativeAOT scenarios on iOS, changing the default link mode to Full (xamarin-macios#18734) is probably the biggest improvement for application size. But at the same time, this change can also break applications which are not fully AOT and trim-compatible. In Full link mode, the trimmer might trim away AOT incompatible code paths (think about reflection usage) which are accessed dynamically at runtime. Full link mode is not the default configuration when using the Mono runtime, so it is possible that some applications are not fully AOT-compatible. Supporting NativeAOT on iOS is an experimental feature and still a work-in-progress, and our plan is to address the potential issues with Full link mode incrementally As a first step, we enabled trim, AOT, and single-file warnings by default in xamarin-macios#18571. The enabled warnings should make our customers aware at build-time, whether a use of a certain framework or a library, or some C# constructs in their code, is incompatible with NativeAOT – and could crash at runtime. This information should guide our customers to write AOT-compatible code, but also to help us improve our frameworks and libraries with the same goal of fully utilising the benefits of AOT compilation. The second step, was clearing up all the warnings coming from Microsoft.iOS and System.Private.CoreLib assemblies reported for a template iOS application with xamarin-macios#18629 and dotnet/runtime#91520. In future releases, we plan to address the warnings coming from the MAUI framework and further improve the overall user-experience. Our goal is to have fully AOT and trim-compatible frameworks. .NET 8 will support targeting iOS platforms with NativeAOT as an opt-in feature and shows great potential by generating up to 50% smaller and 50% faster startup compared to Mono. Considering the great performance that NativeAOT promises, please help us on this journey and try out your applications with NativeAOT and report any potential issues. At the same time, let us know when NativeAOT “just works” out-of-the-box. To follow future progress, see dotnet/runtime#80905. Last but not least, we would like to thank our GH contributors, who are helping us make NativeAOT on iOS possible. Build & Inner Loop Performance Filter Android ps -A output with grep When profiling the Android inner loop for a .NET MAUI project with PerfView we found around 1.2% of CPU time was spent just trying to get the process ID of the running Android application. When changing Tools > Options > Xamarin > Xamarin Diagnostics output verbosity to be Diagnostics, you could see -- Start GetProcessId - 12/02/2022 110557 (96.9929ms) -- [INPUT] ps -A [OUTPUT] USER PID PPID VSZ RSS WCHAN ADDR S NAME root 1 0 10943736 4288 0 0 S init root 2 0 0 0 0 0 S [kthreadd] ... Hundreds of more lines! u0_a993 14500 1340 14910808 250404 0 0 R com.companyname.mauiapp42 -- End GetProcessId -- The Xamarin/.NET MAUI extension in Visual Studio polls every second to see if the application has exited. This is useful for changing the play/stop button state if you force close the app, etc. Testing on a Pixel 5, we could see the command is actually 762 lines of output! > (adb shell ps -A).Count 762 What we could do instead is something like > adb shell "ps -A | grep -w -E 'PID|com.companyname.mauiapp42'" Where we pipe the output of ps -A to the grep command on the Android device. Yes, Android has a subset of unix commands available! We filter on either a line containing PID or your application’s package name. The result is now the IDE is only parsing 4 lines [INPUT] ps -A | grep -w -E 'PID|com.companyname.mauiapp42' [OUTPUT] USER PID PPID VSZ RSS WCHAN ADDR S NAME u0_a993 12856 1340 15020476 272724 0 0 S com.companyname.mauiapp42 This not only improves memory used to split and parse this information in C#, but adb is also transmitting way less bytes across your USB cable or virtually from an emulator. This feature shipped in recent versions of Visual Studio 2022, improving this scenario for all Xamarin and .NET MAUI customers. Port WindowsAppSDK usage of vcmeta.dll to C# We found that every incremental build of a .NET MAUI project running on Windows spent time in Top 10 most expensive tasks CompileXaml = 3.972 s ... various tasks ... This is the XAML compiler for WindowsAppSDK, that compiles the WinUI3 flavor of XAML (not .NET MAUI XAML). There is very little XAML of this type in .NET MAUI projects, in fact, the only file is Platforms/Windows/App.xaml in the project template. Interestingly, if you installed the Desktop development with C++ workload in the Visual Studio installer, this time just completely went away! Top 10 most expensive tasks ... various tasks ... CompileXaml = 9 ms The WindowsAppSDK XAML compiler p/invokes into a native library from the C++ workload, vcmeta.dll, to calculate a hash for .NET assembly files. This is used to make incremental builds fast — if the hash changes, compile the XAML again. If vcmeta.dll was not found on disk, the XAML compiler was effectively “recompiling everything” on every incremental build. For an initial fix, we simply included a small part of the C++ workload as a dependency of .NET MAUI in Visual Studio. The slightly larger install size was a good tradeoff for saving upwards of 4 seconds in incremental build time. Next, we implemented vcmeta.dll‘s hashing functionality in plain C# with System.Reflection.Metadata to compute indentical hash values as before. Not only was this implementation better, in that we could drop a dependency on the C++ workload, but it was also faster! The time to compute a single hash Method Mean Error StdDev Native 217.31 us 1.704 us 1.594 us Managed 86.43 us 1.700 us 2.210 us Some of the reasons this was faster No p/invoke or COM-interfaces involved. System.Reflection.Metadata has a fast struct-based API, perfect for iterating over types in a .NET assembly and computing a hash value. The end result being that CompileXaml might actually be even faster than 9ms in incremental builds. This feature shipped in WindowsAppSDK 1.3, which is now used by .NET MAUI in .NET 8. See WindowsAppSDK#3128 for details about this improvement. Improvements to remote iOS builds on Windows Comparing inner loop performance for iOS, there was a considerable gap between doing “remote iOS” development on Windows versus doing everything locally on macOS. Many small improvements were made, based on comparing inner-loop .binlog files recorded on macOS versus one recorded inside Visual Studio on Windows. Some examples include maui#12747 don’t explicitly copy files to the build server xamarin-macios#16752 do not copy files to build server for a Delete operation xamarin-macios#16929 batch file deletion via DeleteFilesAsync xamarin-macios#17033 cache AOT compiler path Xamarin/MAUI Visual Studio extension when running dotnet-install.sh on remote build hosts, set the explicit processor flag for M1 Macs. We also made some improvements for all iOS & MacCatalyst projects, such as xamarin-macios#16416 don’t process assemblies over and over again Improvements to Android inner-loop We also made many small improvements to the “inner-loop” on Android — most of which were focused in a specific area. Previously, Xamarin.Forms projects had the luxury of being organized into multiple projects, such as YourApp.Android.csproj Xamarin.Android application project YourApp.iOS.csproj Xamarin.iOS application project YourApp.csproj netstandard2.0 class library Where almost all of the logic for a Xamarin.Forms app was contained in the netstandard2.0 project. Nearly all the incremental builds would be changes to XAML or C# in the class library. This structure enabled the Xamarin.Android MSBuild targets to completely skip many Android-specific MSBuild steps. In .NET MAUI, the “single project” feature means that every incremental build has to run these Android-specific build steps. In focusing specifically improving this area, we made many small changes, such as java-interop#1061 avoid string.Format() java-interop#1064 improve ToJniNameFromAttributesForAndroid java-interop#1065 avoid File.Exists() checks java-interop#1069 fix more places to use TypeDefinitionCache java-interop#1072 use less System.Linq for custom attributes java-interop#1103 use MemoryMappedFile when using Mono.Cecil xamarin-android#7621 avoid File.Exists() checks xamarin-android#7626 perf improvements for LlvmIrGenerator xamarin-android#7652 fast path for <CheckClientHandlerType/> xamarin-android#7653 delay ToJniName when generating AndroidManifest.xml xamarin-android#7686 lazily populate Resource lookup These changes should improve incremental builds in all .NET 8 Android project types. XAML Compilation no longer uses LoadInSeparateAppDomain Looking at the JITStats report in PerfView (for MSBuild.exe) Name JitTime (ms) Microsoft.Maui.Controls.Build.Tasks.dll 214.0 Mono.Cecil 119.0 It appears that Microsoft.Maui.Controls.Build.Tasks.dll was spending a lot of time in the JIT. What was confusing, is this was an incremental build where everything should already be loaded. The JIT’s work should be done already? The cause appears to be usage of the [LoadInSeparateAppDomain] attribute defined by the <XamlCTask/> in .NET MAUI. This is an MSBuild feature that gives MSBuild tasks to run in an isolated AppDomain — with an obvious performance drawback. However, we couldn’t just remove it as there would be complications… [LoadInSeparateAppDomain] also conveniently resets all static state when <XamlCTask/> runs again. Meaning that future incremental builds would potentially use old (garbage) values. There are several places that cache Mono.Cecil objects for performance reasons. Really weird bugs would result if we didn’t address this. So, to actually make this change, we reworked all static state in the XAML compiler to be stored in instance fields & properties instead. This is a general software design improvement, in addition to giving us the ability to safely remove [LoadInSeparateAppDomain]. The results of this change, for an incremental build on a Windows PC Before XamlCTask = 743 ms XamlCTask = 706 ms XamlCTask = 692 ms After XamlCTask = 128 ms XamlCTask = 134 ms XamlCTask = 117 ms This saved about ~587ms on incremental builds on all platforms, an 82% improvement. This will help even more on large solutions with multiple .NET MAUI projects, where <XamlCTask/> runs multiple times. See maui#11982 for further details about this improvement. Performance or App Size Improvements Structs and IEquatable in .NET MAUI Using the Visual Studio’s .NET Object Allocation Tracking profiler on a customer .NET MAUI sample application, we saw Microsoft.Maui.WeakEventManager+Subscription Allocations 686,114 Bytes 21,955,648 This seemed like an exorbitant amount of memory to be used in a sample application’s startup! Drilling in to see where these struct‘s were being created System.Collections.Generic.ObjectEqualityComparer<Microsoft.Maui.WeakEventManager+Subscription>.IndexOf() The underlying problem was this struct didn’t implement IEquatable<T> and was being used as the key for a dictionary. The CA1815 code analysis rule was designed to catch this problem. This is not a rule that is enabled by default, so projects must opt into it. To solve this Subscription is internal to .NET MAUI, and its usage made it possible to be a readonly struct. This was just an extra improvement. We made CA1815 a build error across the entire dotnet/maui repository. We implemented IEquatable<T> for all struct types. After these changes, we could no longer found Microsoft.Maui.WeakEventManager+Subscription in memory snapshots at all. Which saved ~21 MB of allocations in this sample application. If your own projects have usage of struct, it seems quite worthwhile to make CA1815 a build error. A smaller, targeted version of this change was backported to MAUI in .NET 7. See maui#13232 for details about this improvement. Fix performance issue in {AppThemeBinding} Profiling a .NET MAUI sample application from a customer, we noticed a lot of time spent in {AppThemeBinding} and WeakEventManager while scrolling 2.08s (17%) microsoft.maui.controls!Microsoft.Maui.Controls.AppThemeBinding.Apply(object,Microsoft.Maui.Controls.BindableObject,Micr... 2.05s (16%) microsoft.maui.controls!Microsoft.Maui.Controls.AppThemeBinding.AttachEvents() 2.04s (16%) microsoft.maui!Microsoft.Maui.WeakEventManager.RemoveEventHandler(System.EventHandler`1<TEventArgs_REF>,string) The following was happening in this application The standard .NET MAUI project template has lots of {AppThemeBinding} in the default Styles.xaml. This supports Light vs Dark theming. {AppThemeBinding} subscribes to Application.RequestedThemeChanged So, every MAUI view subscribe to this event — potentially multiple times. Subscribers are a Dictionary<string, List<Subscriber>>, where there is a dictionary lookup followed by a O(N) search for unsubscribe operations. There is potentially a usecase here to come up with a generalized “weak event” pattern for .NET. The implementation currently in .NET MAUI came over from Xamarin.Forms, but a generalized pattern could be useful for .NET developers using other UI frameworks. To make this scenario fast, for now, in .NET 8 Before For any {AppThemeBinding}, it calls both RequestedThemeChanged -= OnRequestedThemeChanged O(N) time RequestedThemeChanged += OnRequestedThemeChanged constant time Where the -= is notably slower, due to possibly 100s of subscribers. After Create an _attached boolean, so we know know the “state” if it is attached or not. New bindings only call +=, where -= will now only be called by {AppThemeBinding} in rare cases. Most .NET MAUI apps do not “unapply” bindings, but -= would only be used in that case. See the full details about this fix in maui#14625. See dotnet/runtime#61517 for how we could implement “weak events” in .NET in the future. Address CA1307 and CA1309 for performance Profiling a .NET MAUI sample application from a customer, we noticed time spent during “culture-aware” string operations 77.22ms microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor() 42.55ms System.Private.CoreLib!System.String.ToLower() This case, we can improve by simply calling ToLowerInvariant() instead. In some cases you might even consider using string.Equals() with StringComparer.Ordinal. In this case, our code was further reviewed and optimized in Reduce Java interop in MauiDrawable on Android. In .NET 7, we added CA1307 and CA1309 code analysis rules to catch cases like this, but it appears we missed some in Microsoft.Maui.Graphics.dll. These are likely useful rules to enable in your own .NET MAUI applications, as avoiding all culture-aware string operations can be quite impactful on mobile. See maui#14627 for details about this improvement. Address CA1311 for performance After addressing the CA1307 and CA1309 code analysis rules, we took things further and addressed CA1311. As mentioned in the turkish example, doing something like string text = something.ToUpper(); switch (text) { ... } Can actually cause unexpected behavior in Turkish locales, because in Turkish, the character I (Unicode 0049) is considered the upper case version of a different character ý (Unicode 0131), and i (Unicode 0069) is considered the lower case version of yet another character Ý (Unicode 0130). ToLowerInvariant() and ToUpperInvariant() are also better for performance as an invariant ToLower / ToUpper operation is slightly faster. Doing this also avoids loading the current culture, improving startup performance. There are cases where you would want the current culture, such as in a CaseConverter type in .NET MAUI. To do this, you simply have to be explicit in which culture you want to use return ConvertToUpper ? v.ToUpper(CultureInfo.CurrentCulture) v.ToLower(CultureInfo.CurrentCulture); The goal of this CaseConverter is to display upper or lowercase text to a user. So it makes sense to use the CurrentCulture for this. See maui#14773 for details about this improvement. Remove unused ViewAttachedToWindow event on Android Every Label in .NET MAUI was subscribing to public class MauiTextView AppCompatTextView { public MauiTextView(Context context) base(context) { this.ViewAttachedToWindow += MauiTextView_ViewAttachedToWindow; } private void MauiTextView_ViewAttachedToWindow(object? sender, ViewAttachedToWindowEventArgs e) { } //... This was leftover from refactoring, but appeared in dotnet-trace output as 278.55ms (2.4%) mono.android!Android.Views.View.add_ViewAttachedToWindow(System.EventHandler`1<Android.Views.View/ViewAttachedToWindowEv 30.55ms (0.26%) mono.android!Android.Views.View.IOnAttachStateChangeListenerInvoker.n_OnViewAttachedToWindow_Landroid_view_View__mm_wra Where the first is the subscription, and the second is the event firing from Java to C# — only to run an empty managed method. Simply removing this event subscription and empty method, resulted in only a few controls to subscribe to this event as needed 2.76ms (0.02%) mono.android!Android.Views.View.add_ViewAttachedToWindow(System.EventHandler`1<Android.Views.View/ViewAttachedToWindowEv See maui#14833 for details about this improvement. Remove unneeded System.Reflection for {Binding} All bindings in .NET MAUI commonly hit the code path if (property.CanWrite && property.SetMethod.IsPublic && !property.SetMethod.IsStatic) { part.LastSetter = property.SetMethod; var lastSetterParameters = part.LastSetter.GetParameters(); part.SetterType = lastSetterParameters[lastSetterParameters.Length - 1].ParameterType; //... Where ~53% of the time spent applying a binding appeared in dotnet-trace in the MethodInfo.GetParameters() method core.benchmarks!Microsoft.Maui.Benchmarks.BindingBenchmarker.BindName() ... microsoft.maui.controls!Microsoft.Maui.Controls.BindingExpression.SetupPart() System.Private.CoreLib.il!System.Reflection.RuntimeMethodInfo.GetParameters() The above C# is simply finding the property type. It is using a roundabout way of using the property setter’s first parameter, which can be simplified to part.SetterType = property.PropertyType; We could see the results of this change in a BenchmarkDotNet benchmark Method Mean Error StdDev Gen0 Gen1 Allocated –BindName 18.82 us 0.336 us 0.471 us 1.2817 1.2512 10.55 KB ++BindName 18.80 us 0.371 us 0.555 us 1.2512 1.2207 10.23 KB –BindChild 27.47 us 0.542 us 0.827 us 2.0142 1.9836 16.56 KB ++BindChild 26.71 us 0.516 us 0.652 us 1.9226 1.8921 15.94 KB –BindChildIndexer 58.39 us 1.113 us 1.143 us 3.1738 3.1128 26.17 KB ++BindChildIndexer 58.00 us 1.055 us 1.295 us 3.1128 3.0518 25.47 KB Where ++ denotes the new changes. See maui#14830 for further details about this improvement. Use StringComparer.Ordinal for Dictionary and HashSet Profiling a .NET MAUI sample application from a customer, we noticed 4% of the time while scrolling was spent doing dictionary lookups (4.0%) System.Private.CoreLib!System.Collections.Generic.Dictionary<TKey_REF,TValue_REF>.FindValue(TKey_REF) Observing the call stack, some of these were coming from culture-aware string lookups in .NET MAUI microsoft.maui!Microsoft.Maui.PropertyMapper.GetProperty(string) microsoft.maui!Microsoft.Maui.WeakEventManager.AddEventHandler(System.EventHandler<TEventArgs_REF>,string) microsoft.maui!Microsoft.Maui.CommandMapper.GetCommand(string) Which show up in dotnet-trace as a mixture of string comparers (0.98%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.GetHashCode(string) (0.71%) System.Private.CoreLib!System.String.GetNonRandomizedHashCode() (0.31%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.Equals(string,stri (0.01%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.GetStringComparer(object) In cases of Dictionary<string, TValue> or HashSet<string>, we can use StringComparer.Ordinal in many cases to get faster dictionary lookups. This should slightly improve the performance of handlers & all .NET MAUI controls on all platforms. See maui#14900 for details about this improvement. Reduce Java interop in MauiDrawable on Android Profiling a .NET MAUI customer sample while scrolling on a Pixel 5, we saw some interesting time being spent in (0.76%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv (0.54%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor() This sample has a <Border/> inside a <CollectionView/> and so you can see this work happening while scrolling. Specifically, we reviewed code in .NET MAUI, such as _borderPaint.StrokeWidth = _strokeThickness; _borderPaint.StrokeJoin = _strokeLineJoin; _borderPaint.StrokeCap = _strokeLineCap; _borderPaint.StrokeMiter = _strokeMiterLimit * 2; if (_borderPathEffect != null) _borderPaint.SetPathEffect(_borderPathEffect); This calls from C# to Java five times. Creating a new method in PlatformInterop.java allowed us to reduce it to a single time. We also improved the following method, which would perform many calls from C# to Java // C# void SetDefaultBackgroundColor() { using (var background = new TypedValue()) { if (_context == null || _context.Theme == null || _context.Resources == null) return; if (_context.Theme.ResolveAttribute(globalAndroid.Resource.Attribute.WindowBackground, background, true)) { var resource = _context.Resources.GetResourceTypeName(background.ResourceId); var type = resource?.ToLowerInvariant(); if (type == "color") { var color = new Android.Graphics.Color(ContextCompat.GetColor(_context, background.ResourceId)); _backgroundColor = color; } } } } To be more succinctly implemented in Java as // Java /** * Gets the value of android.R.attr.windowBackground from the given Context * @param context * @return the color or -1 if not found */ public static int getWindowBackgroundColor(Context context) { TypedValue value = new TypedValue(); if (!context.getTheme().resolveAttribute(android.R.attr.windowBackground, value, true) && isColorType(value)) { return value.data; } else { return -1; } } /** * Needed because TypedValue.isColorType() is only API Q+ * https//github.com/aosp-mirror/platform_frameworks_base/blob/1d896eeeb8744a1498128d62c09a3aa0a2a29a16/core/java/android/util/TypedValue.java#L266-L268 * @param value * @return true if the TypedValue is a Color */ private static boolean isColorType(TypedValue value) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return value.isColorType(); } else { // Implementation from AOSP return (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT); } } Which reduces our new implementation on the C# side to be a single Java call and creation of an Android.Graphics.Color struct void SetDefaultBackgroundColor() { var color = PlatformInterop.GetWindowBackgroundColor(_context); if (color != -1) { _backgroundColor = new Android.Graphics.Color(color); } } After these changes, we instead saw dotnet-trace output, such as (0.28%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv (0.04%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor() This improves the performance of any <Border/> (and other shapes) on Android, and drops about ~1% of the CPU usage while scrolling in this example. See maui#14933 for further details about this improvement. Improve layout performance of Label on Android Testing various .NET MAUI sample applications on Android, we noticed around 5.1% of time spent in PrepareForTextViewArrange() 1.01s (5.1%) microsoft.maui!Microsoft.Maui.ViewHandlerExtensions.PrepareForTextViewArrange(Microsoft.Maui.IViewHandler,Microsoft.Maui 635.99ms (3.2%) mono.android!Android.Views.View.get_Context() Most of the time is spent just calling Android.Views.View.Context to be able to then call into the extension method internal static int MakeMeasureSpecExact(this Context context, double size) { // Convert to a native size to create the spec for measuring var deviceSize = (int)context!.ToPixels(size); return MeasureSpecMode.Exactly.MakeMeasureSpec(deviceSize); } Calling the Context property can be expensive due the interop from C# to Java. Java returns a handle to the instance, then we have to look up any existing, managed C# objects for the Context. If all this work can simply be avoided, it can improve performance dramatically. In .NET 7, we made overloads to ToPixels() that allows you to get the same value with an Android.Views.View So we can instead do internal static int MakeMeasureSpecExact(this PlatformView view, double size) { // Convert to a native size to create the spec for measuring var deviceSize = (int)view.ToPixels(size); return MeasureSpecMode.Exactly.MakeMeasureSpec(deviceSize); } Not only did this change show improvements in dotnet-trace output, but we saw a noticeable difference in our LOLs per second test application from last year See maui#14980 for details about this improvement. Reduce Java interop calls for controls in .NET MAUI Reviewing the beautiful .NET MAUI “Surfing App” sample by @jsuarezruiz We noticed that a lot of time is spent doing Java interop while scrolling 1.76s (35%) Microsoft.Maui!Microsoft.Maui.Platform.WrapperView.DispatchDraw(Android.Graphics.Canvas) 1.76s (35%) Microsoft.Maui!Microsoft.Maui.Platform.ContentViewGroup.DispatchDraw(Android.Graphics.Canvas) These methods were deeply nested doing interop from Java -> C# -> Java many levels deep. In this case, moving some code from C# to Java could make it where less interop would occur; and in some cases no interop at all! So for example, previously DispatchDraw() was overridden in C# to implement clipping behavior // C# // ContentViewGroup is used internally by many .NET MAUI Controls class ContentViewGroup Android.Views.ViewGroup { protected override void DispatchDraw(Canvas? canvas) { if (Clip != null) ClipChild(canvas); base.DispatchDraw(canvas); } } By creating a PlatformContentViewGroup.java, we can do something like // Java /** * Set by C#, determining if we need to call getClipPath() * @param hasClip */ protected final void setHasClip(boolean hasClip) { this.hasClip = hasClip; postInvalidate(); } @Override protected void dispatchDraw(Canvas canvas) { // Only call into C# if there is a Clip if (hasClip) { Path path = getClipPath(canvas.getWidth(), canvas.getHeight()); if (path != null) { canvas.clipPath(path); } } super.dispatchDraw(canvas); } setHasClip() is called when clipping is enabled/disabled on any .NET MAUI control. This allowed the common path to not interop into C# at all, and only views that have opted into clipping would need to. This is very good because dispatchDraw() is called quite often during Android layout, scrolling, etc. This same treatment was also done to a few other internal .NET MAUI types like WrapperView improving the common case, making interop only occur when views have opted into clipping or drop shadows. For testing the impact of these changes, we used Google’s FrameMetricsAggregator that can be setup in any .NET MAUI application’s Platforms/Android/MainActivity.cs // How often in ms you'd like to print the statistics to the console const int Duration = 1000; FrameMetricsAggregator aggregator; Handler handler; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); handler = new Handler(Looper.MainLooper); // We were interested in the "Total" time, other metrics also available aggregator = new FrameMetricsAggregator(FrameMetricsAggregator.TotalDuration); aggregator.Add(this); handler.PostDelayed(OnFrame, Duration); } void OnFrame() { // We were interested in the "Total" time, other metrics also available var metrics = aggregator.GetMetrics()[FrameMetricsAggregator.TotalIndex]; int size = metrics.Size(); double sum = 0, count = 0, slow = 0; for (int i = 0; i < size; i++) { int value = metrics.Get(i); if (value != 0) { count += value; sum += i * value; if (i > 16) slow += value; Console.WriteLine($"Frame(s) that took ~{i}ms, count {value}"); } } if (sum > 0) { Console.WriteLine($"Average frame time {sum / count0.00}ms"); Console.WriteLine($"No. of slow frames {slow}"); Console.WriteLine("-----"); } handler.PostDelayed(OnFrame, Duration); } FrameMetricsAggregator‘s API is admittedly a bit odd, but the data we get out is quite useful. The result is basically a lookup table where the key is a duration in milliseconds, and the value is the number of “frames” that took that duration. The idea is any frame that takes longer than 16ms is considered “slow” or “janky” as the Android docs sometimes refer. An example of the .NET MAUI “Surfing App” running on a Pixel 5 Before Frame(s) that took ~4ms, count 1 Frame(s) that took ~5ms, count 6 Frame(s) that took ~6ms, count 10 Frame(s) that took ~7ms, count 12 Frame(s) that took ~8ms, count 10 Frame(s) that took ~9ms, count 6 Frame(s) that took ~10ms, count 1 Frame(s) that took ~11ms, count 2 Frame(s) that took ~12ms, count 4 Frame(s) that took ~13ms, count 2 Frame(s) that took ~15ms, count 1 Frame(s) that took ~16ms, count 1 Frame(s) that took ~18ms, count 2 Frame(s) that took ~19ms, count 1 Frame(s) that took ~20ms, count 5 Frame(s) that took ~21ms, count 2 Frame(s) that took ~22ms, count 1 Frame(s) that took ~25ms, count 1 Frame(s) that took ~32ms, count 1 Frame(s) that took ~34ms, count 1 Frame(s) that took ~60ms, count 1 Frame(s) that took ~62ms, count 1 Frame(s) that took ~63ms, count 1 Frame(s) that took ~64ms, count 2 Frame(s) that took ~66ms, count 1 Frame(s) that took ~67ms, count 1 Frame(s) that took ~68ms, count 1 Frame(s) that took ~69ms, count 2 Frame(s) that took ~70ms, count 2 Frame(s) that took ~71ms, count 2 Frame(s) that took ~72ms, count 1 Frame(s) that took ~73ms, count 2 Frame(s) that took ~74ms, count 2 Frame(s) that took ~75ms, count 1 Frame(s) that took ~76ms, count 1 Frame(s) that took ~77ms, count 2 Frame(s) that took ~78ms, count 3 Frame(s) that took ~79ms, count 1 Frame(s) that took ~80ms, count 1 Frame(s) that took ~81ms, count 1 Average frame time 28.67ms No. of slow frames 43 After the changes to ContentViewGroup and WrapperView were in place, we got a very nice improvement! Even in an app making heavy usage of clipping and shadows After Frame(s) that took ~5ms, count 3 Frame(s) that took ~6ms, count 5 Frame(s) that took ~7ms, count 7 Frame(s) that took ~8ms, count 7 Frame(s) that took ~9ms, count 4 Frame(s) that took ~10ms, count 2 Frame(s) that took ~11ms, count 6 Frame(s) that took ~12ms, count 2 Frame(s) that took ~13ms, count 3 Frame(s) that took ~14ms, count 4 Frame(s) that took ~15ms, count 1 Frame(s) that took ~16ms, count 1 Frame(s) that took ~17ms, count 1 Frame(s) that took ~18ms, count 2 Frame(s) that took ~19ms, count 1 Frame(s) that took ~20ms, count 3 Frame(s) that took ~21ms, count 2 Frame(s) that took ~22ms, count 2 Frame(s) that took ~27ms, count 2 Frame(s) that took ~29ms, count 2 Frame(s) that took ~32ms, count 1 Frame(s) that took ~34ms, count 1 Frame(s) that took ~35ms, count 1 Frame(s) that took ~64ms, count 1 Frame(s) that took ~67ms, count 1 Frame(s) that took ~68ms, count 2 Frame(s) that took ~69ms, count 1 Frame(s) that took ~72ms, count 3 Frame(s) that took ~74ms, count 3 Average frame time 21.99ms No. of slow frames 29 See maui#14275 for further detail about these changes. Improve performance of Entry.MaxLength on Android Investigating a .NET MAUI customer sample Navigating from a Shell flyout. To a new page with several Entry controls. There was a noticeable performance delay. When profiling on a Pixel 5, one “hot path” was Entry.MaxLength 18.52ms (0.22%) microsoft.maui!Microsoft.Maui.Platform.EditTextExtensions.UpdateMaxLength(Android.Widget.EditText,Microsoft.Maui.IEntry) 16.03ms (0.19%) microsoft.maui!Microsoft.Maui.Platform.EditTextExtensions.UpdateMaxLength(Android.Widget.EditText,int) 12.16ms (0.14%) microsoft.maui!Microsoft.Maui.Platform.EditTextExtensions.SetLengthFilter(Android.Widget.EditText,int) EditTextExtensions.UpdateMaxLength() calls EditText.Text getter and setter EditTextExtensions.SetLengthFilter() calls EditText.Get/SetFilters() What happens is we end up marshaling strings and IInputFilter[] back and forth between C# and Java for every Entry control. All Entry controls go through this code path (even ones with a default value for MaxLength), so it made sense to move some of this code from C# to Java instead. Our C# code before // C# public static void UpdateMaxLength(this EditText editText, int maxLength) { editText.SetLengthFilter(maxLength); var newText = editText.Text.TrimToMaxLength(maxLength); if (editText.Text != newText) editText.Text = newText; } public static void SetLengthFilter(this EditText editText, int maxLength) { if (maxLength == -1) maxLength = int.MaxValue; var currentFilters = new List<IInputFilter>(editText.GetFilters() ?? new IInputFilter[0]); var changed = false; for (var i = 0; i < currentFilters.Count; i++) { if (currentFilters[i] is InputFilterLengthFilter) { currentFilters.RemoveAt(i); changed = true; break; } } if (maxLength >= 0) { currentFilters.Add(new InputFilterLengthFilter(maxLength)); changed = true; } if (changed) editText.SetFilters(currentFilters.ToArray()); } Moved to Java (with identical behavior) instead // Java /** * Sets the maxLength of an EditText * @param editText * @param maxLength */ public static void updateMaxLength(@NonNull EditText editText, int maxLength) { setLengthFilter(editText, maxLength); if (maxLength < 0) return; Editable currentText = editText.getText(); if (currentText.length() > maxLength) { editText.setText(currentText.subSequence(0, maxLength)); } } /** * Updates the InputFilter[] of an EditText. Used for Entry and SearchBar. * @param editText * @param maxLength */ public static void setLengthFilter(@NonNull EditText editText, int maxLength) { if (maxLength == -1) maxLength = Integer.MAX_VALUE; List<InputFilter> currentFilters = new ArrayList<>(Arrays.asList(editText.getFilters())); boolean changed = false; for (int i = 0; i < currentFilters.size(); i++) { InputFilter filter = currentFilters.get(i); if (filter instanceof InputFilter.LengthFilter) { currentFilters.remove(i); changed = true; break; } } if (maxLength >= 0) { currentFilters.add(new InputFilter.LengthFilter(maxLength)); changed = true; } if (changed) { InputFilter[] newFilter = new InputFilter[currentFilters.size()]; editText.setFilters(currentFilters.toArray(newFilter)); } } This avoids marshaling (copying!) string and array values back and forth from C# to Java. With these changes in place, the calls to EditTextExtensions.UpdateMaxLength() are now so fast they are missing completely from dotnet-trace output, saving ~19ms when navigating to the page in the customer sample. See maui#15614 for details about this improvement. Improve memory usage of CollectionView on Windows We reviewed a .NET MAUI customer sample with a CollectionView of 150,000 data-bound rows. Debugging what happens at runtime, .NET MAUI was effectively doing _itemTemplateContexts = new List<ItemTemplateContext>(capacity 150_000); for (int n = 0; n < 150_000; n++) { _itemTemplateContexts.Add(null); } And then each item is created as it is scrolled into view if (_itemTemplateContexts[index] == null) { _itemTemplateContexts[index] = context = new ItemTemplateContext(...); } return _itemTemplateContexts[index]; This wasn’t the best approach, but to improve things use a Dictionary<int, T> instead, just let it size dynamically. use TryGetValue(..., out var context), so each call accesses the indexer one less time than before. use either the bound collection’s size or 64 (whichever is smaller) as a rough estimate of how many might fit on screen at a time Our code changes to if (!_itemTemplateContexts.TryGetValue(index, out var context)) { _itemTemplateContexts[index] = context = new ItemTemplateContext(...); } return context; With these changes in place, a memory snapshot of the app after startup Before Heap Size 82,899.54 KB After Heap Size 81,768.76 KB Which is saving about 1MB of memory on launch. In this case, it feels better to just let the Dictionary size itself with an estimate of what capacity will be. See maui#16838 for details about this improvement. Use UnmanagedCallersOnlyAttribute on Apple platforms When unmanaged code calls into managed code, such as invoking a callback from Objective-C, the [MonoPInvokeCallbackAttribute] was previously used in Xamarin.iOS, Xamarin.Mac, and .NET 6+ for this purpose. The [UnmanagedCallersOnlyAttribute] attribute came along as a modern replacement for this Mono feature, which is implemented in a way with performance in mind. Unfortunately, there are a few restrictions when using this new attribute Method must be marked static. Must not be called from managed code. Must only have blittable arguments. Must not have generic type parameters or be contained within a generic class. Not only did we have to refactor the “code generator” that produces many of the bindings for Apple APIs for AppKit, UIKit, etc., but we also had many manual bindings that would need the same treatment. The end result is that most callbacks from Objective-C to C# should be faster in .NET 8 than before. See xamarin-macios#10470 and xamarin-macios#15783 for details about these improvements. Faster Java interop for strings on Android When binding members which have parameter types or return types which are java.lang.CharSequence, the member is “overloaded” to replace CharSequence with System.String, and the “original” member has a Formatted suffix. For example, consider android.widget.TextView, which has getText() and setText() methods which have parameter types and return types which are java.lang.CharSequence // Java class TextView extends View { public CharSequence getText(); public final void setText(CharSequence text); } When bound, this results in two properties // C# class TextView View { public Java.Lang.ICharSequence? TextFormatted { get; set; } public string? Text { get; set; } } The “non-Formatted overload” works by creating a temporary String object to invoke the Formatted overload, so the actual implementation looks like partial class TextView { public string? Text { get => TextFormatted?.ToString (); set { var jls = value == null ? null new Java.Lang.String (value); TextFormatted = jls; jls?.Dispose (); } } } TextView.Text is much easer to understand & simpler to consume for .NET developers than TextView.TextFormatted. A problem with the this approach is performance creating a new Java.Lang.String instance requires Creating the managed peer (the Java.Lang.String instance), Creating the native peer (the java.lang.String instance), And registering the mapping between (1) and (2) And then immediately use and dispose the value… This is particularly noticeable with .NET MAUI apps. Consider a customer sample, which uses XAML to set data-bound Text values in a CollectionView, which eventually hit TextView.Text. Profiling shows 653.69ms (6.3%) mono.android!Android.Widget.TextView.set_Text(string) 198.05ms (1.9%) mono.android!Java.Lang.String..ctor(string) 121.57ms (1.2%) mono.android!Java.Lang.Object.Dispose() 6.3% of scrolling time is spent in the TextView.Text property setter! Partially optimize this case if the *Formatted member is (1) a property, and (2) not virtual, then we can directly call the Java setter method. This avoids the need to create a managed peer and to register a mapping between the peers partial class TextView { public string? Text { get => TextFormatted?.ToString (); // unchanged set { const string __id = "setText.(Ljava/lang/CharSequence;)V"; JniObjectReference native_value = JniEnvironment.Strings.NewString (value); try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (native_value); _members.InstanceMethods.InvokeNonvirtualVoidMethod (__id, this, __args); } finally { JniObjectReference.Dispose (ref native_value); } } } } With the result being Method Mean Error StdDev Allocated Before SetFinalText 6.632 us 0.0101 us 0.0079 us 112 B After SetFinalText 1.361 us 0.0022 us 0.0019 us – The TextView.Text property setter invocation time is reduced to 20% of the previous average invocation time. Note that the virtual case is problematic for other reasons, but luckily enough TextView.setText() is non-virtual and likely one of the more commonly used Android APIs. See java-interop#1101 for details about this improvement. Faster Java interop for C# events on Android Profiling a .NET MAUI customer sample while scrolling on a Pixel 5, We saw ~2.2% of the time spent in the IOnFocusChangeListenerImplementor constructor, due to a subscription to the View.FocusChange event (2.2%) mono.android!Android.Views.View.IOnFocusChangeListenerImplementor..ctor() MAUI subscribes to Android.Views.View.FocusChange for every view placed on the screen, which happens while scrolling in this sample. Reviewing the generated code for the IOnFocusChangeListenerImplementor constructor, we see it still uses outdated JNIEnv APIs public IOnFocusChangeListenerImplementor () base ( Android.Runtime.JNIEnv.StartCreateInstance ("mono/android/view/View_OnFocusChangeListenerImplementor", "()V"), JniHandleOwnership.TransferLocalRef ) { Android.Runtime.JNIEnv.FinishCreateInstance (((Java.Lang.Object) this).Handle, "()V"); } Which we can change to use the newer/faster Java.Interop APIs public unsafe IOnFocusChangeListenerImplementor () base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer) { const string __id = "()V"; if (((Java.Lang.Object) this).Handle != IntPtr.Zero) return; var h = JniPeerMembers.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null); SetHandle (h.Handle, JniHandleOwnership.TransferLocalRef); JniPeerMembers.InstanceMethods.FinishCreateInstance (__id, this, null); } These are better because the equivalent call to JNIEnv.FindClass() is cached, among other things. This was just one of the cases that was accidentally missed when we implemented the new Java.Interop APIs in the Xamarin timeframe. We simply needed to update our code generator to emit a better C# binding for this case. After these changes, we saw instead results in dotnet-trace (0.81%) mono.android!Android.Views.View.IOnFocusChangeListenerImplementor..ctor() This should improve the performance of all C# events that wrap Java listeners, a design-pattern commonly used in Java and Android applications. This includes the FocusedChanged event used by all .NET MAUI views on Android. See java-interop#1105 for details about this improvement. Use Function Pointers for JNI There is various machinery and generated code that makes Java interop possible from C#. Take, for example, the following instance method foo() in Java // Java object foo(object bar) { // returns some value } A C# method named CallObjectMethod is responsible for calling Java’s Native Interface (JNI) that calls into the JVM to actually invoke the Java method public static unsafe JniObjectReference CallObjectMethod (JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) { //... IntPtr thrown; var tmp = NativeMethods.java_interop_jnienv_call_object_method_a (JniEnvironment.EnvironmentPointer, out thrown, instance.Handle, method.ID, (IntPtr) args); Exception __e = JniEnvironment.GetExceptionForLastThrowable (thrown); if (__e != null) ExceptionDispatchInfo.Capture (__e).Throw (); JniEnvironment.LogCreateLocalRef (tmp); return new JniObjectReference (tmp, JniObjectReferenceType.Local); } In Xamarin.Android, .NET 6, and .NET 7 all calls into Java went through a java_interop_jnienv_call_object_method_a p/invoke, which signature looks like [DllImport (JavaInteropLib, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern unsafe jobject java_interop_jnienv_call_object_method_a (IntPtr jnienv, out IntPtr thrown, jobject instance, IntPtr method, IntPtr args); Which is implemented in C as JI_API jobject java_interop_jnienv_call_object_method_a (JNIEnv *env, jthrowable *_thrown, jobject instance, jmethodID method, jvalue* args) { *_thrown = 0; jobject _r_ = (*env)->CallObjectMethodA (env, instance, method, args); *_thrown = (*env)->ExceptionOccurred (env); return _r_; } C# 9 introduced function pointers that allowed us a way to simplify things slightly — and make them faster as a result. So instead of using p/invoke in .NET 8, we could instead call a new unsafe method named CallObjectMethodA // Before var tmp = NativeMethods.java_interop_jnienv_call_object_method_a (JniEnvironment.EnvironmentPointer, out thrown, instance.Handle, method.ID, (IntPtr) args); // After var tmp = JniNativeMethods.CallObjectMethodA (JniEnvironment.EnvironmentPointer, instance.Handle, method.ID, (IntPtr) args); Which calls a C# function pointer directly [System.Runtime.CompilerServices.MethodImpl (System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] internal static unsafe jobject CallObjectMethodA (IntPtr env, jobject instance, IntPtr method, IntPtr args) { return (*((JNIEnv**)env))->CallObjectMethodA (env, instance, method, args); } This function pointer declared using the new syntax introduced in C# 9 public delegate* unmanaged <IntPtr, jobject, IntPtr, IntPtr, jobject> CallObjectMethodA; Comparing the two implementations with a manual benchmark # JIPinvokeTiming timing 000001.6993644 # Average Invocation 0.00016993643999999998ms # JIFunctionPointersTiming timing 000001.6561349 # Average Invocation 0.00016561349ms With a Release build, the average invocation time for JIFunctionPointersTiming takes 97% of the time as JIPinvokeTiming, i.e. is 3% faster. Additionally, using C# 9 function pointers means we can get rid of all of the java_interop_jnienv_*() C functions, which shrinks libmonodroid.so by ~55KB for each architecture. See xamarin-android#8234 and java-interop#938 for details about this improvement. Removed Xamarin.AndroidX.Legacy.Support.V4 Reviewing .NET MAUI’s Android dependencies, we noticed a suspicious package Xamarin.AndroidX.Legacy.Support.V4 If you are familiar with the Android Support Libraries, these are a set of packages Google provides to “polyfill” APIs to past versions of Android. This gives them a way to bring new APIs to old OS versions, since the Android ecosystem (OEMs, etc.) are much slower to upgrade as compared to iOS, for example. This particular package, Legacy.Support.V4, is actually support for Android as far back as Android API 4! The minimum supported Android version in .NET is Android API 21, which was released in 2017. It turns out this dependency was brought over from Xamarin.Forms and was not actually needed. As expected from this change, lots of Java code was removed from .NET MAUI apps. So much, in fact, that .NET 8 MAUI applications are now under the multi-dex limit — all Dalvik bytecode can fix into a single classes.dex file. A detailed breakdown of the size changes using apkdiff > apkdiff -f com.companyname.maui_before-Signed.apk com.companyname.maui_after-Signed.apk Size difference in bytes ([*1] apk1 only, [*2] apk2 only) + 1,598,040 classes.dex - 6 META-INF/androidx.asynclayoutinflater_asynclayoutinflater.version *1 - 6 META-INF/androidx.legacy_legacy-support-core-ui.version *1 - 6 META-INF/androidx.legacy_legacy-support-v4.version *1 - 6 META-INF/androidx.media_media.version *1 - 455 assemblies/assemblies.blob - 564 res/layout/notification_media_action.xml *1 - 744 res/layout/notification_media_cancel_action.xml *1 - 1,292 res/layout/notification_template_media.xml *1 - 1,584 META-INF/BNDLTOOL.SF - 1,584 META-INF/MANIFEST.MF - 1,696 res/layout/notification_template_big_media.xml *1 - 1,824 res/layout/notification_template_big_media_narrow.xml *1 - 2,456 resources.arsc - 2,756 res/layout/notification_template_media_custom.xml *1 - 2,872 res/layout/notification_template_lines_media.xml *1 - 3,044 res/layout/notification_template_big_media_custom.xml *1 - 3,216 res/layout/notification_template_big_media_narrow_custom.xml *1 - 2,030,636 classes2.dex Summary - 24,111 Other entries -0.35% (of 6,880,759) - 432,596 Dalvik executables -3.46% (of 12,515,440) + 0 Shared libraries 0.00% (of 12,235,904) - 169,179 Package size difference -1.12% (of 15,123,185) See dotnet/maui#12232 for details about this improvement. Deduplication of generics on iOS and macOS In .NET 7, iOS applications experienced app size increases due to C# generics usage across multiple .NET assemblies. When the .NET 7 Mono AOT compiler encounters a generic instance that is not handled by generic sharing, it will emit code for the instance. If the same instance is encountered during AOT compilation in multiple assemblies, the code will be emitted multiple times, increasing code size. In .NET 8, new dedup-skip and dedup-include command-line options are passed to the Mono AOT compiler. A new aot-instances.dll assembly is created for sharing this information in one place throughout the application. The change was tested on MySingleView app and Monotouch tests in the xamarin/xamarin-macios codebase App Baseline size on disk .ipa (MB) Target size on disk .ipa (MB) Baseline size on disk .app (MB) Target size on disk .app (MB) Baseline build time (s) Target build time (s) .app diff (%) MySingleView Release iOS 5.4 5.4 29.2 15.2 29.2 16.8 47.9 MySingleView Release iOSSimulator-arm64 N/A N/A 469.5 341.8 468.0 330.0 27.2 Monotouch Release llvm iOS 49.0 38.8 209.6 157.4 115.0 130.0 24.9 See xamarin-macios#17766 for details about this improvement. Fix System.Linq.Expressions implementation on iOS-like platforms In .NET 7, codepaths in System.Linq.Expressions were controlled by various flags such as CanCompileToIL CanEmitObjectArrayDelegate CanCreateArbitraryDelegates These flags were controlling codepaths which are “AOT friendly” and those that are not. For desktop platforms, NativeAOT specifies the following configuration for AOT-compatible code <IlcArg Include="--featureSystem.Linq.Expressions.CanCompileToIL=false" /> <IlcArg Include="--featureSystem.Linq.Expressions.CanEmitObjectArrayDelegate=false" /> <IlcArg Include="--featureSystem.Linq.Expressions.CanCreateArbitraryDelegates=false" /> When it comes to iOS-like platforms, System.Linq.Expressions library was built with constant propagation enabled and control variables were removed. This further caused above-listed NativeAOT feature switches not to have any effect (fail to trim during app build), potentially causing the AOT compilation to follow unsupported code paths on these platforms. In .NET8, we have unified the build of System.Linq.Expressions.dll shipping the same assembly for all supported platforms and runtimes, and simplified these switches to respect IsDynamicCodeSupported so that the .NET trimmer can remove the appropriate IL in System.Linq.Expressions.dll at application build time. See dotnet/runtime#87924 and dotnet/runtime#89308 for details about this improvement. Set DynamicCodeSupport=false for iOS and Catalyst In .NET 8, the feature switch $(DynamicCodeSupport) is set to false for platforms Where it is not possible to publish without the AOT compiler. When interpreter is not enabled. Which boils down to applications running on iOS, tvOS, MacCatalyst, etc. DynamicCodeSupport=false enables the .NET trimmer to remove code paths depending on RuntimeFeature.IsDynamicCodeSupported such as this example in System.Linq.Expressions. Estimated size savings are dotnet new maui (ios) old SLE.dll new SLE.dll + DynamicCodeSupported=false diff (%) Size on disk (Mb) 40,53 38,78 -4,31% .pkg (Mb) 14,83 14,20 -4,21% When combined with the System.Linq.Expressions improvements on iOS-like platforms, this showed a nice overall improvement to application size See xamarin-macios#18555 for details about this improvement. Memory Leaks Memory Leaks and Quality Given that the major theme for .NET MAUI in .NET 8 is quality, memory-related issues became a focal point for this release. Some of the problems found existed even in the Xamarin.Forms codebase, so we are happy to work towards a framework that developers can rely on for their cross-platform .NET applications. For full details on the work completed in .NET 8, we’ve various PRs and Issues related to memory issues at Pull Requests Issues You can see that considerable progress was made in .NET 8 in this area. If we compare .NET 7 MAUI versus .NET 8 MAUI in a sample application running on Windows, displaying the results of GC.GetTotalMemory() on screen Then compare the sample application running on macOS, but with many more pages pushed onto the navigation stack See the sample code for this project on GitHub for further details. Diagnosing leaks in .NET MAUI The symptom of a memory leak in a .NET MAUI application, could be something like Navigate from the landing page to a sub page. Go back. Navigate to the sub page again. Repeat. Memory grows consistently until the OS closes the application due to lack of memory. In the case of Android, you may see log messages such as 07-07 185139.090 17079 17079 D Mono GC_MAJOR (user request) time 137.21ms, stw 140.60ms los size 10984K in use 3434K 07-07 185139.090 17079 17079 D Mono GC_MAJOR_SWEEP major size 116192K in use 108493K 07-07 185139.092 17079 17079 I monodroid-gc 46204 outstanding GREFs. Performing a full GC! In this example, a 116MB heap is quite large for a mobile application, as well as over 46,000 C# <-> Java wrapper objects! To truly determine if the sub page is leaking, we can make a couple modifications to a .NET MAUI application Add logging in a finalizer. For example ~MyPage() => Console.WriteLine("Finalizer for ~MyPage()"); While navigating through your app, you can find out if entire pages are living forever if the log message is never displayed. This is a common symptom of a leak, because any View holds .Parent.Parent.Parent, etc. all the way up to the Page object. Call GC.Collect() somewhere in the app, such as the sub page’s constructor public MyPage() { GC.Collect(); // For debugging purposes only, remove later InitializeComponent(); } This makes the GC more deterministic, in that we are forcing it to run more frequently. Each time we navigate to the sub page, we are more likely causing the old sub page’s to go away. If things are working properly, we should see the log message from the finalizer. Note GC.Collect() is for debugging purposes only. You should not need this in your app after investigation is complete, so be sure to remove it afterward. With these changes in place, test a Release build of your app. On iOS, Android, macOS, etc. you can watch console output of your app to determine what is actually happening at runtime. adb logcat, for example, is a way to view these logs on Android. If running on Windows, you can also use Debug > Windows > Diagnostic Tools inside Visual Studio to take memory snapshots inside Visual Studio. In the future, we would like Visual Studio’s diagnostic tooling to support .NET MAUI applications running on other platforms. See our memory leaks wiki page for more information related to memory leaks in .NET MAUI applications. Patterns that cause leaks C# events C# events, just like a field, property, etc. can create strong references between objects. Let’s look at a situation where things can go wrong. Take for example, the cross-platform Grid.ColumnDefinitions property public class Grid Layout, IGridLayout { public static readonly BindableProperty ColumnDefinitionsProperty = BindableProperty.Create("ColumnDefinitions", typeof(ColumnDefinitionCollection), typeof(Grid), null, validateValue (bindable, value) => value != null, propertyChanged UpdateSizeChangedHandlers, defaultValueCreator bindable => { var colDef = new ColumnDefinitionCollection(); colDef.ItemSizeChanged += ((Grid)bindable).DefinitionsChanged; return colDef; }); public ColumnDefinitionCollection ColumnDefinitions { get { return (ColumnDefinitionCollection)GetValue(ColumnDefinitionsProperty); } set { SetValue(ColumnDefinitionsProperty, value); } } Grid has a strong reference to its ColumnDefinitionCollection via the BindableProperty. ColumnDefinitionCollection has a strong reference to Grid via the ItemSizeChanged event. If you put a breakpoint on the line with ItemSizeChanged +=, you can see the event has an EventHandler object where the Target is a strong reference back to the Grid. In some cases, circular references like this are completely OK. The .NET runtime(s)’ garbage collectors know how to collect cycles of objects that point each other. When there is no “root” object holding them both, they can both go away. The problem comes in with object lifetimes what happens if the ColumnDefinitionCollection lives for the life of the entire application? Consider the following Style in Application.Resources or Resources/Styles/Styles.xaml <Style TargetType="Grid" xKey="GridStyleWithColumnDefinitions"> <Setter Property="ColumnDefinitions" Value="18,*"/> </Style> If you applied this Style to a Grid on a random Page Application‘s main ResourceDictionary holds the Style. The Style holds a ColumnDefinitionCollection. The ColumnDefinitionCollection holds the Grid. Grid unfortunately holds the Page via .Parent.Parent.Parent, etc. This situation could cause entire Page‘s to live forever! Note The issue with Grid is fixed in maui#16145, but is an excellent example of illustrating how C# events can go wrong. Circular references on Apple platforms Even since the early days of Xamarin.iOS, there has existed an issue with “circular references” even in a garbage-collected runtime like .NET. C# objects co-exist with a reference-counted world on Apple platforms, and so a C# object that subclasses NSObject can run into situations where they can accidentally live forever — a memory leak. This is not a .NET-specific problem, as you can just as easily create the same situation in Objective-C or Swift. Note that this does not occur on Android or Windows platforms. Take for example, the following circular reference class MyViewSubclass UIView { public UIView? Parent { get; set; } public void Add(MyViewSubclass subview) { subview.Parent = this; AddSubview(subview); } } //... var parent = new MyViewSubclass(); var view = new MyViewSubclass(); parent.Add(view); In this case parent -> view via Subviews view -> parent via the Parent property The reference count of both objects is non-zero. Both objects live forever. This problem isn’t limited to a field or property, you can create similar situations with C# events class MyView UIView { public MyView() { var picker = new UIDatePicker(); AddSubview(picker); picker.ValueChanged += OnValueChanged; } void OnValueChanged(object? sender, EventArgs e) { } // Use this instead and it doesn't leak! //static void OnValueChanged(object? sender, EventArgs e) { } } In this case MyView -> UIDatePicker via Subviews UIDatePicker -> MyView via ValueChanged and EventHandler.Target Both objects live forever. A solution for this example, is to make OnValueChanged method static, which would result in a null Target on the EventHandler instance. Another solution, would be to put OnValueChanged in a non-NSObject subclass class MyView UIView { readonly Proxy _proxy = new(); public MyView() { var picker = new UIDatePicker(); AddSubview(picker); picker.ValueChanged += _proxy.OnValueChanged; } class Proxy { public void OnValueChanged(object? sender, EventArgs e) { } } } This is the pattern we’ve used in most .NET MAUI handlers and other UIView subclasses. See the MemoryLeaksOniOS sample repo, if you would like to play with some of these scenarios in isolation in an iOS application without .NET MAUI. Roslyn analyzer for Apple platforms We also have an experimental Roslyn Analyzer that can detect these situations at build time. To add it to net7.0-ios, net8.0-ios, etc. projects, you can simply install a NuGet package <PackageReference Include="MemoryAnalyzers" Version="0.1.0-beta.3" PrivateAssets="all" /> Some examples of a warning would be public class MyView UIView { public event EventHandler MyEvent; } Event 'MyEvent' could could memory leaks in an NSObject subclass. Remove the event or add the [UnconditionalSuppressMessage("Memory", "MA0001")] attribute with a justification as to why the event will not leak. Note that the analyzer can warns if there might be an issue, so it can be quite noisy to enable in a large, existing codebase. Inspecting memory at runtime is the best way to determine if there is truly a memory leak. Tooling and Documentation Simplified dotnet-trace and dotnet-dsrouter In .NET 7, profiling a mobile application was a bit of a challenge. You had to run dotnet-dsrouter and dotnet-trace together and get all the settings right to be able to retrieve a .nettrace or speedscope file for performance investigations. There was also no built-in support for dotnet-gcdump to connect to dotnet-dsrouter to get memory snapshots of a running .NET MAUI application. In .NET 8, we’ve streamlined this scenario by making new commands for dotnet-dsrouter that simplifies the workflow. To verify you have the latest diagnostic tooling, you can install them via $ dotnet tool install -g dotnet-dsrouter You can invoke the tool using the following command dotnet-dsrouter Tool 'dotnet-dsrouter' was successfully installed. $ dotnet tool install -g dotnet-gcdump You can invoke the tool using the following command dotnet-gcdump Tool 'dotnet-gcdump' was successfully installed. $ dotnet tool install -g dotnet-trace You can invoke the tool using the following command dotnet-trace Tool 'dotnet-trace' was successfully installed. Verify you have at least 8.x versions of these tools $ dotnet tool list -g Package Id Version Commands -------------------------------------------------------------------------------------- dotnet-dsrouter 8.0.452401 dotnet-dsrouter dotnet-gcdump 8.0.452401 dotnet-gcdump dotnet-trace 8.0.452401 dotnet-trace To profile an Android application on an Android emulator, first build and install your application in Release mode such as $ dotnet build -f net8.0-android -tInstall -c Release -pAndroidEnableProfiler=true Build SUCCEEDED. 0 Warning(s) 0 Error(s) Next, open a terminal to run dotnet-dsrouter $ dotnet-dsrouter android-emu Start an application on android emulator with one of the following environment variables set DOTNET_DiagnosticPorts=10.0.2.29000,nosuspend,connect DOTNET_DiagnosticPorts=10.0.2.29000,suspend,connect Then in a second terminal window, we can set the debug.mono.profile Android system property, as the stand-in for $DOTNET_DiagnosticPorts $ adb shell setprop debug.mono.profile '10.0.2.29000,suspend,connect' $ dotnet-trace ps 3248 dotnet-dsrouter $ dotnet-trace collect -p 3248 --format speedscope ... [00000009] Recording trace 3.2522 (MB) Press <Enter> or <Ctrl+C> to exit... Note Android doesn’t have good support for environment variables like $DOTNET_DiagnosticPorts. You can create an AndroidEnvironment text file for setting environment variables, but Android system properties can be simpler as they would not require rebuilding the application to set them. Upon launching the Android application, it should be able to connect to dotnet-dsrouter -> dotnet-trace and record performance profiling information for investigation. The --format argument is optional and it defaults to .nettrace. However, .nettrace files can be viewed only with Perfview on Windows, while the speedscope JSON files can be viewed “on” macOS or Linux by uploading them to https//speedscope.app. Note When providing a process ID to dotnet-trace, it knows how to tell if a process ID is dotnet-dsrouter and connect through it appropriately. dotnet-dsrouter has the following new commands to simplify the workflow dotnet-dsrouter android Android devices dotnet-dsrouter android-emu Android emulators dotnet-dsrouter ios iOS devices dotnet-dsrouter ios-sim iOS simulators See the .NET MAUI wiki for more information about profiling .NET MAUI applications on each platform. dotnet-gcdump Support for Mobile In .NET 7, we had a somewhat complex method (see wiki) for getting a memory snapshot of an application on the Mono runtime (such as iOS or Android). You had to use a Mono-specific event provider such as dotnet-trace collect --diagnostic-port /tmp/maui-app --providers Microsoft-DotNETRuntimeMonoProfiler0xC9000014 And then we relied on Filip Navara’s mono-gcdump tool (thanks Filip!) to convert the .nettrace file to .gcdump to be opened in Visual Studio or PerfView. In .NET 8, we now have dotnet-gcdump support for mobile scenarios. If you want to get a memory snapshot of a running application, you can use dotnet-gcdump in a similar fashion as dotnet-trace $ dotnet-gcdump ps 3248 dotnet-dsrouter $ dotnet-gcdump collect -p 3248 Writing gcdump to '20231018_115631_29880.gcdump'... Note This requires the exact same setup as dotnet-trace, such as -pAndroidEnableProfiler=true, dotnet-dsrouter, adb commands, etc. This greatly streamlines our workflow for investigating memory leaks in .NET MAUI applications. See our memory leaks wiki page for more information. The post .NET 8 Performance Improvements in .NET MAUI appeared first on .NET Blog.


How to get current date in angular in HTML inline
Category: Research

In Angular, you can easily get the current date using the `Date` object. Here's how to do it</p ...


Views: 0 Likes: 33
Improvements & Changes in Android resource generation in .NET 8
Improvements & Changes in Android resource generat ...

With the release of .NET 8 we are introducing a new system for generating the C# code used to access Android Resources. The system which generated a Resource.designer.cs file in Xamarin.Android, .NET 6 and .NET 7 has been deprecated. The new system generates a single _Microsoft.Android.Resource.Designer assembly.  This will contain all the final resource classes for every assembly. What are Android Resources? All Android applications will have some sort of user interface resources in them. They often have the user interface layouts in the form of XML file, images and icons in the form of png or svg files and values which contain things like styles and theming. See the Google’s documentation for an in-depth look at Android resources. Part of the android build process is to compile these resources into a binary form, this is done by the android sdk tool aapt2. To access these resources android exposed an API which allows you to pass an integer id to retrieve the resource. SetContentView (2131492864); As part of the aapt2 build process the file R.txt is generated which contains a mapping from the “string” name of the resource to the id. For example layout/Main.xml might map to the id 2131492864. To access this data from C# we need a way to expose this in code. This is handled by a Resource class in the projects $(RootNamespace). We take the values from the R.txt and expose them in this class. In the system that shipped with .NET 7 and prior releases, this class was written to the Resource.designer.cs file. And it allowed users to write maintainable code by not hard coding ids. So the call from above would actually look like this SetContentView (Resource.Layout.Main); The Resource.Id.Main would map to the Id which was produced by aapt2. Why make this new system? The old system had some issues which impact both app size and startup performance. In the old system every Android assembly had its own set of Resource classes in it. So we effectively had duplicate code everywhere. So if you used AndroidX in your project every assembly which referenced AndroidX would have a Resource designer Id class like this public class Resource { public class Id { // aapt resource value 0x7F0A0005 public const int seekBar = 2131361797; // aapt resource value 0x7F0A0006 public const int menu = 2131361798; } } This code would be duplicated in each library. There are may other classes such as Layout/Menu/Style, all of which have these duplicate code in them. Also each of these Resource classes needed to be updated at runtime to have the correct values. This is because it is only when we build the final app and generate the R.txt file do we know the ids for each of these resources. So the application Resource classes are the only ones with the correct ids. The old system used a method called UpdateIdValues which was called on startup. This method would go through ALL the library projects and update the resource ids to match the ones in the application. Depending on the size of the application this can cause significant delays to startup. Here is an example of the code in this method public static void UpdateIdValues() { globalLibrary.Resource.Id.seekBar = globalFoo.Foo.Resource.Id.seekBar; globalLibrary.Resource.Id.menu = globalFoo.Foo.Resource.Id.menu; } Even worse because of the UpdateIdValues code the trimmer could not remove any of these classes. So even if the application only used one or two fields , all of them are preserved.  The new system reworks all of this to make it trimmer-friendly, almost ALL of the code shown above will no longer be produced. There is no need to even have an UpdateIdValues call at all. This will improve both app size and startup time.  How it works .NET 8 Android will have the MSBuild property $(AndroidUseDesignerAssembly) set to true by default. This will turn off the old system completely. Manually changing this property to false will re-enable the old system. The new system relies on parsing the R.txt file which aapt2 generates as part of the build process. Just before the call to run the C# compiler, the R.txt file will be parsed and generate a new assembly. The assembly will be saved in the the IntermediateOutputPath. It will also be automatically added to the list of References for the app or library.  For library projects we generate a reference assembly rather than a full assembly. This signals to the compiler that this assembly will be replaced at runtime. (A reference assembly is an assembly which contains an assembly-level ReferenceAssemblyAttribute.) For application projects we generate a full assembly as part of the UpdateAndroidResources target. This ensures that we are using the final values from the R.txt file. It is this final assembly which will be deployed with the final package. In addition to the assembly a source file will be generated. This will be __Microsoft.Android.Resource.Designer.cs, or __Microsoft.Android.Resource.Designer.fs if you use F# . This contains a class which will derive from the Resource class. It will exist in the projects’ $(RootNamespace). This is the glue which allows existing code to work. Because the namespace of the Resource class will not change. For application projects the Resource class in the project RootNamespace will be derived from the ResourceConstants class in the designer assembly. This is to maintain backward compatibility with the way the older Resource.designer.cs files worked for application projects. Tests show we can get about 8% improvement in startup time. And about a 2%-4% decrease in overall package size. Will my NuGet packages still work? Some of you might be worried that with this change your existing package references will stop working. Do not worry, the new system has introduced a Trimmer step which will upgrade assembly references which use the old system to use the new one. This will be done automatically as part of the build. This trimmer step analyses the IL in all the assemblies looking for places where the old Resource.designer fields were used. It will then update those to use the new Designer assembly properties. It will also completely remove the old Resource.designer in that assembly. So even if you use old packages you should still see the benefit of this new system. The Linker step should cover almost all of the code where the Resource.designer.cs fields are accessed. However if you come across a problem please open an issue at https//github.com/xamarin/xamarin-android/issues/new/choose. This will apply to any android assembly reference which is pre net8.0-android. Packages built with the new system cannot be used with previous versions of .NET Android. Please consider using multi targeting if you need to support .NET 7 or Classic Xamarin.Android. NuGet Package Authors Do you maintain a NuGet package which contains Android Resources? If so you will need to make some changes. Firstly there is no need to ship the new _Microsoft.Android.Resource.Designer.dll with your NuGet. It will be generated at build time by the application consuming the NuGet. The new system is incompatible with the Classic Pre .NET Xamarin.Android and .NET 6/7 Android Packages. So if you want to continue to support Classic Xamarin.Android as well as .NET 8 you will need to multitarget your assemblies. If you no longer need to support Class Xamarin.Android you can upgrade your project to the .NET Sdk Style project and use the following <TargetFrameworks>net7.0-android;net8.0-android</TargetFrameworks> Classic Xamarin.Android is going out of Support next year so this is probably the best option. If you need to support both systems, you can use Xamarin.Legacy.Sdk to both for both Xamarin.Android and net8.0-android. Xamarin.Legacy.Sdk is un-supported, so it will only be useful as a stop gap measure while your users upgrade to .NET 8. See the Xamarin.Legacy.Sdk GitHub site https//github.com/xamarin/Xamarin.Legacy.Sdk for details on how to use this package. Since .NET 6 android the AndroidResource, AndroidAsset, AndroidEnvironment, AndroidJavaLibrary, EmbeddedNativeLibrary and AndroidNativeLibrary items are no longer packaged inside the assembly. There is an .aar file generated at build time which contains this data and will be named the same as the assembly. For things to work correctly this .aar file will need to be shipped in the NuGet along side the assembly. If the .aar is not included it will result in missing resource errors at runtime, such as System.MissingMethodException 'Method not found int .Style.get_MyTheme()' The .aar will be included by default if you use dotnet pack on your project and specify your NuGet properties and settings in the csproj. However if you are using a .nuspec you will need to manually add the .aar file to your list of files to be included. The changes related to .aar files and embedded files are documented in OneDotNetEmbeddedResources.md Summary So the new system should result in slightly smaller package sizes, and fast start up times. The impact will be greater the more resources you are using in your app. The post Improvements & Changes in Android resource generation in .NET 8 appeared first on .NET Blog.


Html5 Code element is rendering HTML Elements with ...
Category: HTML5

Problem When appending HTML5 elements to the div using JQuery, a class attribute shows one apost ...


Views: 246 Likes: 87
10964 ExecStop=/usr/sbin/corosync-cfgtool -H --fo ...
Category: SERVERS

The error you encountered (`ExecStop=/usr/sbin/corosync-cfgtool -H --force (code=exited, status= ...


Views: 0 Likes: 45
SignalR Error in Dot Net Core 3.1 (.Net Core 3.1) ...
Category: .Net 7

Problems when implementing SignalR in Dot Net Core 3.1 (.Net Core 3.1) Error Failed to invoke 'H ...


Views: 2201 Likes: 100
private protected Functions used for Event Handler ...
Category: .Net 7

Question <span style="background-color #fbeeb8; ...


Views: 164 Likes: 57
Http 204 Status Code leads to Chrome downloading a ...
Category: Network

Question How do you solve the i ...


Views: 306 Likes: 93
How do I free up space on linux vm
Category: Research

Title Maximizing Linux Virtual Machine Performance Freeing Up Space and Optimizing Disk Usage< ...


Views: 0 Likes: 0
Developing Real-Time Collaborative Apps with Azure, Microsoft 365, Power Platform, and Github
Developing Real-Time Collaborative Apps with Azure ...

Learn Together Developing Real-Time Collaborative Apps Have you considered adding real-time collaboration into your apps? Do you want to learn how to collaborate more efficiently on code your team is writing? In today’s distributed work environment there are many new and exciting collaborative technologies available across Azure, Microsoft 365, Power Platform, and GitHub that you can tap into today. These technologies can be used to increase user productivity as well as developer productivity and take your applications to the next level! For example, you can Allow users to collaborate on data in real-time within your application using technologies such as the Fluid Framework or SignalR.Add real-time chat, audio, and video capabilities into your application using Azure Communication Services.Integrate business data into your app including user presence information by using Microsoft 365 and Azure.Integrate your app with collaboration hubs such as Microsoft Teams.Collaborate on code more efficiently using new technologies available in GitHub. Videos from the Developing Real-time Collaborative Apps event are now available to help you learn about implementing collaborative scenarios in your own apps! The videos cover What is collaboration-first development? – Dan Wahlin and April Dunnam discuss scenarios where real-time collaboration can be used in applications.Adding real-time data into your apps – Dan Wahlin and Dan Roney talk about the Fluid Framework and Azure Fluid Relay for real-time data in apps.Adding real-time communication into your apps – Piyali Dey and Reza Jooyandeh discuss Azure Communication Services and show how real-time chat and audio/video can be added to apps.Bringing your apps where your users work every day – Ayca Bas and Juma George Odhiambo talk about getting real-time data from Microsoft Graph into your applications to show user presence information. Technologies covered include Microsoft Graph, Power Platform, Microsoft Graph Toolkit, Azure Event Hub, Azure Functions, and Azure SignalR.Enhancing your development collaboration and productivity – Burke Holland and Brigit Murtaugh discuss Github extensions available in Visual Studio code as well as additional features such as Codespaces in Github, https//github.dev, and more. 1. What is collaboration-first development? 2. Adding real-time data into your apps 3. Adding real-time communication into your apps 4. Bringing your apps where your users work every day 5. Enhancing your development collaboration and productivity


Error Scaffolding Controllers: There was an error ...
Category: SQL

Error Scaffolding Controllers There was an error running the selected code generator Could not ...


Views: 1878 Likes: 98
 How to use configuration with C# 9 top-level programs
How to use configuration with C# 9 top-level prog ...

I’ve been working with top-level programs in C# 9 quite a bit lately. When writing simple console apps in .NET 5, it allows you to remove the ceremony of a namespace and a Main(string[] args) method. It’s very beginner-friendly and allows developers to get going without worrying about learning about namespaces, arrays, arguments, and so on. While I’m not a beginner—although I feel like it some days—I enjoy using top-level programs to prototype things quickly.With top-level programs, you can work with normal functions, use async and await, access command-line arguments, use local functions, and more. For example, here’s me working with some arbitrary strings and getting a random quote from the Ron Swanson Quotes APIusing System; using System.Net.Http; var name = "Dave Brock"; var weekdayHobby = "code"; var weekendHobby = "play guitar"; var quote = await new HttpClient().GetStringAsync("https//ron-swanson-quotes.herokuapp.com/v2/quotes"); Console.WriteLine($"Hey, I'm {name}!"); Console.WriteLine($"During the week, I like to {weekdayHobby} and on the weekends I like to {weekendHobby}."); Console.WriteLine($"A quote to live by {quote}"); Add configuration to a top-level programCan we work with configuration with top-level programs? (Yes, should we is a different conversation, of course.)To be clear, there are many, many ways to work with configuration in .NET. If you’re used to it in ASP.NET Core, for example, you’ve most likely done it from constructor dependency injection, wiring up a ServiceCollection in your middleware, or using the Options pattern—so you may think you won’t be able to do it with top-level programs.Don’t overthink it. Using the ConfigurationBuilder, you can easily use configuration with top-level programs.Let’s create an appsettings.json file to replace our hard-coded values with configuration values.{ "Name" "Dave Brock", "Hobbies" { "Weekday" "code", "Weekend" "play guitar" }, "SwansonApiUri" "https//ron-swanson-quotes.herokuapp.com/v2/quotes" } Then, make sure your project file has the following packages installed, and that the appSettings.json file is being copied to the output directory <ItemGroup> <PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> </ItemGroup> <ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> In your top-level program, create a ConfigurationBuilder with the appropriate valuesvar config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); With a config instance, you’re ready to simply read in your valuesvar name = config["Name"]; var weekdayHobby = config.GetSection("HobbiesWeekday"); var weekendHobby = config.GetSection("HobbiesWeekend"); var quote = await new HttpClient().GetStringAsync(config["SwansonApiUri"]); And here’s the entire top-level program in actionusing Microsoft.Extensions.Configuration; using System; using System.IO; using System.Net.Http; var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); var name = config["Name"]; var weekdayHobby = config.GetSection("HobbiesWeekdays"); var weekendHobby = config.GetSection("HobbiesWeekends"); var quote = await new HttpClient().GetStringAsync(config["SwansonApiUri"]); Console.WriteLine($"Hey, I'm {name}!"); Console.WriteLine($"During the week, I like to {weekdayHobby.Value}" + $" and on the weekends I like to {weekendHobby.Value}."); Console.WriteLine($"A quote to live by {quote}"); Review the generated codeWhen throwing this in the ILSpy decompilation tool, you can see there’s not a lot of magic here. The top-level program is merely wrapping the code in a Main(string[] args) method and replacing our implicit typingusing System; using System.IO; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; [CompilerGenerated] internal static class <Program>$ { private static async Task <Main>$(string[] args) { IConfigurationRoot config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build(); string name = config["Name"]; IConfigurationSection weekdayHobby = config.GetSection("HobbiesWeekday"); IConfigurationSection weekendHobby = config.GetSection("HobbiesWeekend"); string quote = await new HttpClient().GetStringAsync(config["SwansonApiUri"]); Console.WriteLine("Hey, I'm " + name + "!"); Console.WriteLine("During the week, I like to " + weekdayHobby.Value + " and on the weekends I like to " + weekendHobby.Value + "."); Console.WriteLine("A quote to live by " + quote); } } Wrap upIn this quick post, I showed you how to work with configuration in C# 9 top-level programs. We showed how to use a ConfigurationBuilder to read from an appsettings.json file, and we also reviewed the generated code.


Error 0xc0202009: Data Flow Task 1: SSIS Error Cod ...
Category: Servers

Question I came about this SQL Server ...


Views: 0 Likes: 44
RoutePatternException: The route template separato ...
Category: .Net 7

Question How do you resolve the error that says "#RoutePatternException The route template sep ...


Views: 0 Likes: 31
Tailing a log file or folder with Fluent Bit and Seq
Tailing a log file or folder with Fluent Bit and S ...

It's not always possible to ship logs directly from an application to a centralized log server like Seq. Many interesting systems, new and old, write text or JSON log files locally, and rely on a separate collector to read, parse, and ship them.This post shows how to tail a folder of log files, and send the contents to Seq for easy search and analysis, using Fluent Bit.Fluent BitFluent Bit is a vendor-neutral log shipper developed under the CNCF. It supports a wide range of input plug-ins, including files-on-disk, queues, and more complex sources such as the Windows Event Log.Once they're read by Fluent Bit, log events can be filtered and manipulated before being sent to one or more output plug-ins, including a few that are compatible with Seq like HTTP/S, GELF, or syslog.Installing and configuring Fluent BitOnce you've downloaded either the installer or binaries for your platform from the Fluent Bit website, you'll end up with a fluent-bit executable, a fluent-bit.conf file, and a parsers.conf file.fluent-bit/ bin/ fluent-bit[.exe] conf/ fluent-bit.conf parsers.conf On Windows you'll find these under C\Program Files\fluent-bit unless you customized the installation path.You can run fluent-bit with the default .conf files to check that everything's ready to go./bin/fluent-bit -c ./conf/fluent-bit.confThe two .conf files are where we'll add our configuration, so now's a good time to open those up in your editor of choice.Tailing a folderFor the purposes of this example, we'll assume you have a folder of log files, logs/*.log.Your log format could be just about anything, so for illustration I'll use a really simple format with an ISO-8601 timestamp, followed by a space, a message, and a newline.2022-10-18T054441.3830613Z Hello, world! 2022-10-18T054442.9375927Z Oven temperature 208 degreesIn the fluent-bit.conf file, you'll find an [INPUT] section. Replace everything there with[INPUT] Name tail Parser simple Path .\logs\*.log DB .\tail.dbHere's what each field meansName - this selects the input plug-in to use. We need tail for monitoring text files. The Fluent Bit docs list a whole range of other input plug-ins to try.Parser - simple is the name of the parser I've defined in parsers.conf that we'll check out in a moment. The parser's job is to break log lines down into individual fields like a timestamp and message.Path - this identifies one or more log files to tail, and supports the ** and * wildcards. It's a good idea to specify a fully-qualified path here for real-world deployment.DB - the tail plug-in keeps track of what files its already shipped, and its progress in each file, using a local SQLite database at this path.On Windows, the Path argument must use backslash \ separators in order to work with wildcards in Fluent Bit 1.9.Note that the tail plug-in will only ship new events by default, ignoring anything already in the log files when it's first started. To change this behavior and ship historical logs too (useful for testing), you can optionally add Read_from_Head onNow onto parsers.conf, the simple parser we're using can be added to the end of the file[PARSER] Name simple Format regex Regex ^(?<time>[^ ]+) (?<message>.+)$ Time_Key time Time_Format %Y-%m-%dT%H%M%S.%L%zThe fields areName - simple is the name we've given to this parser, refered to using the Parser field in the [INPUT] configuration.Format - this parser is defined using a regular expression; check out the Fluent Bit docs for other options including pre-built parsers for several common formats.Regex - super-simple, we'll treat all lines as log events, with the first token specifying a timestamp, and the rest of the line the message. The regular expression uses (?<name>...) named capture groups to give the extracted fields names, and these end up as properties on the resulting log events.Time_Key - selects which field extracted by the regular expression will be used as the log event's timestamp.Time_Format - shows Fluent Bit how to parse the extracted timestamp string as a correct timestamp.Shipping to SeqSeq can ingest newline-delimited JSON posted via HTTP. This is generally the easiest and best way to get events into Seq, since API keys are fully supported, and no additional input plug-ins are required.Seq recognizes a handful of "special" JSON payload properties defined in the CLEF format. We'll use a filter and the Fluent Bit HTTP output to get log events into the right shape.Back in fluent-bit.conf, we'll replace the remaining default [FILTER] and [OUTPUT] sections with[FILTER] Name modify Match * Rename message @m [OUTPUT] Name http Match * Host localhost Port 5341 URI /api/events/raw?clef Header X-Seq-ApiKey yQDwNvt1KdwM8N6SkgqR Format json_lines Json_date_key @t Json_date_format iso8601 Log_response_payload False[FILTER] is just doing one simple job, here our parser extracts the log message into a message field, while Seq expects this in @m. The filter renames the message so that everything is nice and readable in the Seq UI.Different Fluent Bit log inputs will have different fields attached, so each one will likely need its own [FILTER] to get things looking great.The output turns the Fluent Bit pipeline's view of an event into newline-delimited JSON for Seq to ingest, and ships this in batches using HTTP.Here's how the [OUTPUT] block is workingName - just like the tail plug-in we used for input, http identifies the HTTP output plug-in.Match - the pipeline we're using here just sends everything to Seq; the match option allows more sophisticated pipelines to be built.Host - the hostname of your Seq server, e.g. logs.example.com.Port - the port on the Seq server that will receive logs. This will most likely be 443, if you have Seq configured with HTTPS.URI - the path to the Seq ingestion endpoint. The ?clef parameter indicates the content type (we could alternatively have used a header for this).Header - the X-Seq-ApiKey header allows an API key to be specified. API keys provide fine-grained visibility and control over ingestion so this is highly recommended, but you can leave this out if you prefer unauthenticated ingestion.Format - the HTTP output plug-in supports a few options here; Seq needs newline-delimited JSON, which Fluent Bit calls json_lines.Json_date_key - CLEF uses @t to carry the timestamp.Json_date_format - CLEF expects ISO-8601 date/time formatting.Log_response_payload - turning this off saves some noise at the terminal when debugging; you can use Seq's Ingestion Log to get information about failed payloads, if any occur.Finally, if your Seq server uses HTTPS, you'll need to add one last field TLS onIf you think back to the original two log lines shown earlier, they'll reach Seq in an HTTP POST payload resembling{"@t""2022-10-18T054441.3830613Z","@m""Hello, world!"} {"@t""2022-10-18T054442.9375927Z","@m""Oven temperature 208 degrees"}Putting it all togetherWhen you run fluent-bit with your updated configuration files, if all is well, logs will start to flowRemember that in the default configuration, only new events will be written, so you might have to open your log file and paste in a few fake events to see some activity (or add the Read_from_Head option mentioned earlier).If things go wrong, the Fluent Bit docs and GitHub issues have a wealth of information to refer to. If you're still stumped, please drop us a line so we can help 🙂


Nginx.service: Control process exited, code=exited ...
Category: Server

Question How do you start Nginx when it fails to start with an error N ...


Views: 283 Likes: 91
How to Code a Windows Service that Updates the Dat ...
Category: .Net 7

Question How do you write C-Sharp Code that runs as a Windows Background Service to update the D ...


Views: 0 Likes: 25
Error 0xc0202009: Data Flow Task 1: SSIS Error Co ...
Category: SQL

Question How do you solve for this error?&nbsp; Error 0xc0202009 Data ...


Views: 0 Likes: 54
Debugging ASP.NET Core Applications: Accessing Kes ...
Category: .Net 7

How to Access and Debug an ASP.NET Core Application Running in Kestrel from Another Computer on t ...


Views: 2061 Likes: 110
How to Call a Soap Web Service from Asp.Net Core
Category: .Net 7

Question How do you call a web service that uses XML Soap Envelope as Payload B ...


Views: 725 Likes: 106
How to Sign Out a User when Session Expires in Asp ...
Category: .Net 7

Question Hello, am trying to implement code that Signs Out a Logged in User whe ...


Views: 0 Likes: 39
[Solved] Asp.Net Core C-Sharp Error: IOException: ...
Category: .Net 7

Question How do you solve for Process Locking File Access in Asp.Net Core "<str ...


Views: 424 Likes: 79
[Solved] cannot apply indexing with [] to an expre ...
Category: Technology

Questions Am trying to get the ...


Views: 1253 Likes: 106
Upgrading to .Net Core 3.1 Error Visual Studio 201 ...
Category: .Net 7

Problem When upgrading to Dot Net Core 3.1 from .Net Core 3.0 ...


Views: 671 Likes: 106
How to install pyenv on Linux 22.04
Category: SERVERS

<div class="js-voting-container d-flex jc-center fd-colum ...


Views: 0 Likes: 47
System.Net.Sockets.SocketException (10013): An att ...
Category: .Net 7

Question How do you solve the error that says "Unable to start Kestrel.&nbsp; &nbsp; &nbsp ...


Views: 0 Likes: 34
how to use speech in asp.net core
Category: .Net 7

Question How do you use Speech ...


Views: 268 Likes: 116
Asp.Net Core Error: InvalidOperationException: The ...
Category: .Net 7

Problem&nbsp;<span style="background-color #fff ...


Views: 341 Likes: 96
Structured logging from Node.js to Seq
Structured logging from Node.js to Seq

The benefits of centralized, structured logging should be available to all technology stacks, which is why there are Seq clients for many different programming languages, including Python, Java and JavaScript (Node.js). For Node.js developers there are several libraries that make it easier to send structured log events from Node.js applications to Seq, including bunyan-seq, pino-seq and most recently @datalust/winston-seq. winston is a logging library for Node.js. It's like Serilog, but for JavaScript. To provide different log event destinations Serilog has sinks, and winston has transports. Using different transports winston can log to the console, text files, AWS Cloudwatch, Seq and many other destinations. @datalust/winston-seq is the winston transport for sending log events to Seq. This post demonstrates how to configure winston and @datalust/winston-seq to send log events from a Node.js application to Seq.1. Install the winston and @datalust/winston-seq npm modulesnpm install winston @datalust/winston-seq NOTE The npm package name is @datalust/winston-seq. There is a different package called winston-seq that is built for an older version of winston and is not maintained by Datalust. 2. Create a winston logger within the application This example sends log events to the console and to Seq. Set the serverUrl to the ingestion endpoint of your Seq server. Set the apiKey to an API key with the Ingest permission.const winston = require('winston'); const { SeqTransport } = require('@datalust/winston-seq'); const logger = winston.createLogger({ level 'info', format winston.format.combine( winston.format.errors({ stack true }), winston.format.json(), ), transports [ new winston.transports.Console({ format winston.format.simple(), }), new SeqTransport({ serverUrl "https//your-seq-server", apiKey "your-api-key", onError (e => { console.error(e) }), handleExceptions true, handleRejections true, }) ] }); 3. Call log methods on the logger to send log eventsThere are different methods for different log levels. In the following example, {arch} and {speed} are log event properties. Their values are provided as properties on the object that is the second argument to logger.info. logger.info("My CPU is {arch} at {speed}GHz", { arch os.arch(), speed os.cpus()[0].speed/1000 }); logger.warn("I'm warning you"); logger.error("Something doesn't feel right"); 4. Watch the events appear in Seq5. Create signals, dashboards, and alerts Create signals, dashboards, and alerts that provide insight into the behavior of your Node.js application.FinallyIt is essential to understand what is happening within a running application, and to be able to diagnose issues when problems occur in production. winston and @datalust/winston-seq provide another way to bring this capability to the JavaScript development community. For more @datalust/winston-seq configuration scenarios refer to the @datalust/winston-seq website.


InvalidOperationException: No service for type Mic ...
Category: .Net 7

Question How do you solve for error that says "< ...


Views: 0 Likes: 45
What are the best way to write high performant C# ...
Category: Technology

Google Bard Response There are many ways to write high-performance C# code. ...


Views: 0 Likes: 46
Insert Data in Array In Style
Category: Databases

When I was reading about Stacks data structure, I found something interesting when trying to insert ...


Views: 294 Likes: 88
Exited with error code -532462766
Category: .Net 7

Question Every time I start an ...


Views: 0 Likes: 36
[Solved] Asp.Net Core 3.1 Visual Studio 2019 Erro ...
Category: .Net 7

Question How do you resolve the error "build failing with Error MSB6006 csc.ex ...


Views: 807 Likes: 122
Migrating a Local Node Script to Azure Functions using VS Code
Migrating a Local Node Script to Azure Functions u ...

I have a work project that uses GitHub APIs to access stats about specific repos (views, clones, forks, etc.). It was pretty straightforward to get the project running locally using GitHub’s Octokit REST package and with a little work I had a working Node script that could be run to retrieve the data and display it in the console. That was a good start, but the script functionality needed to be consumed by others in my organization as well as by services such as Power Automate. What to do? While I could easily convert the script into a Node/Express API and publish it to Azure App Service, I decided to go with Azure Functions since when you boil the script down to the basics, its job is to handle a request and return data. It doesn’t need to be constantly accessed so a consumption based model works well. Here’s the process I went through to convert my local script to an Azure Function. 1. Install the Azure Functions Extension for VS Code Creating a Function using VS Code and Extensions I wanted to develop the Azure Function locally and knew that the Azure Functions extension for VS Code could help with that. It allows you to do everything on your local machine and then publish to Azure Functions once you’re ready. To get started you can Open my project in VS Code. Install the extension (I already had it installed, but you’ll want it). Click on Azure in the VS Code sidebar. Locate the Workspace section and click the + icon. Select Create Function. Since I only had a simple Node project at this point, I received the following prompt Prompt to create an Azure Functions project. From there I selected the following Language TypeScript Trigger HTTP trigger Function Name getGitHubRepoStats Authorization level Anonymous I was prompted to overwrite my existing .gitignore and package.json files. I said “yes” since I only had @octokit/rest in the Node dependencies list. It finished creating the project and displayed the shiny new function in the editor. It added the following into my project (in addition to a few other items) Files added by the Azure Functions extension. Good progress! Time to get my existing code converted to an Azure Function. 2. Merge the Local Script Code into the Azure Function My initial script looked like the following const { Octokit } = require("@octokit/rest"); const { v4 uuidv4 } = require('uuid'); // Create personal access token (with repo --> public rights) at https//github.com/settings/tokens let octokit; let ownersRepos; let context; getStats(context); async function getStats(ctx) { context = ctx || { log console.log }; // Doing this to simulate what's it like in Azure Functions ownersRepos = getRepos(); context.log(ownersRepos); const stats = []; for (const repo of ownersRepos) { octokit = new Octokit({ auth repo.token }); const ownerRepo = { owner repo.owner, repo repo.repo } const clones = await getClones(ownerRepo); const forks = await getTotalForks(ownerRepo); const views = await getPageViews(ownerRepo); stats.push(getTodayRow(ownerRepo, clones, forks, views)); } context.log(stats); return stats; } function getRepos() { try { console.log(context); // Need to set env variable GITHUB_REPOS // export GITHUB_REPOS="[ { \"owner\" \"microsoft\", \"repo\" \"MicrosoftCloud\", \"token\" \"token_value\" } ]" const repos = JSON.parse(process.env['GITHUB_REPOS']); context.log('Repos', repos); return repos; } catch (e) { context.log(e); return []; } } function getTodayRow(ownerRepo, clones, forks, views) { const today = new Date(); const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1) .toISOString().split('T')[0] + 'T000000Z'; const todayClonesViewsForks ={ id uuidv4(), timestamp yesterday, owner ownerRepo.owner, repo ownerRepo.repo, clones 0, forks forks, views 0 }; const todayClones = clones.clones.find(c => c.timestamp === yesterday); const todayViews = views.views.find(v => v.timestamp === yesterday); if (todayClones) { todayClonesViewsForks.clones = todayClones.count; } if (todayViews) { todayClonesViewsForks.views = todayViews.count; } return todayClonesViewsForks; } async function getClones(ownerRepo) { try { // https//docs.github.com/en/rest/metrics/traffic#get-repository-clones const { data } = await octokit.rest.repos.getClones(ownerRepo); context.log(`${ownerRepo.owner}/${ownerRepo.repo} clones`, data.count); return data; } catch (e) { context.log(`Unable to get clones for ${ownerRepo.owner}/${ownerRepo.repo}. You probably don't have push access.`); } return 0; } async function getTotalForks(ownerRepo) { try { // https//docs.github.com/en/rest/repos/forks const { data } = await octokit.rest.repos.get(ownerRepo); const forksCount = (data) ? data.forks_count 0; context.log(`${ownerRepo.owner}/${ownerRepo.repo} forks`, forksCount); return forksCount } catch (e) { context.log(e); context.log(`Unable to get forks for ${ownerRepo.owner}/${ownerRepo.repo}. You probably don't have push access.`); } return 0; } async function getPageViews(ownerRepo) { try { // https//docs.github.com/en/rest/metrics/traffic#get-page-views const { data } = await await octokit.rest.repos.getViews(ownerRepo); context.log(`${ownerRepo.owner}/${ownerRepo.repo} visits`, data.count); return data; } catch (e) { context.log(`Unable to get page views for ${ownerRepo.owner}/${ownerRepo.repo}. You probably don't have push access.`); context.log(e); } return 0; } The next step was to merge my script into the new Azure Function. Since the Azure Functions extension (with my permission) overwrote my package.json file, I ran npm install @octokit/rest to get the package back into the dependencies list. At this point I had the following function code displayed in VS Code import { AzureFunction, Context, HttpRequest } from "@azure/functions" const httpTrigger AzureFunction = async function (context Context, req HttpRequest) Promise<void> { context.log('HTTP trigger function processed a request.'); const name = (req.query.name || (req.body && req.body.name)); const responseMessage = name ? "Hello, " + name + ". This HTTP triggered function executed successfully." "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."; context.res = { // status 200, /* Defaults to 200 */ body responseMessage }; }; export default httpTrigger; Now that I had the shell created for the function, I created a new getStats.ts script in the getGitHubRepoStats function folder, copied in my initial code, and changed require statements to import statements at the top of the file. It looked like the following after finishing a few “tweaks” import { Octokit } from '@octokit/rest'; import { v4 as uuidv4 } from 'uuid'; // Create personal access token (with repo --> public rights) at https//github.com/settings/tokens let octokit Octokit; let ownersRepos; let context; export async function getStats(ctx) { context = ctx || { log console.log }; ownersRepos = getRepos(); const stats = []; for (const repo of ownersRepos) { octokit = new Octokit({ auth repo.token }); const ownerRepo = { owner repo.owner, repo repo.repo } const clones = await getClones(ownerRepo); const forks = await getTotalForks(ownerRepo); const views = await getPageViews(ownerRepo); const yesterdayRow = getTodayRow(ownerRepo, clones, forks, views); stats.push(yesterdayRow); } return stats; } function getRepos() { try { const repos = JSON.parse(process.env['GITHUB_REPOS']); context.log('Repos', repos); return repos; } catch (e) { context.log(e); return []; } } function getTodayRow(ownerRepo, clones, forks, views) { const today = new Date(); const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1) .toISOString().split('T')[0] + 'T000000Z'; const todayClonesViewsForks ={ id uuidv4(), timestamp yesterday, owner ownerRepo.owner, repo ownerRepo.repo, clones 0, forks forks, views 0 }; const todayClones = clones.clones.find(c => c.timestamp === yesterday); const todayViews = views.views.find(v => v.timestamp === yesterday); if (todayClones) { todayClonesViewsForks.clones = todayClones.count; } if (todayViews) { todayClonesViewsForks.views = todayViews.count; } return todayClonesViewsForks; } async function getClones(ownerRepo) { try { // https//docs.github.com/en/rest/metrics/traffic#get-repository-clones const { data } = await octokit.rest.repos.getClones(ownerRepo); context.log(`${ownerRepo.owner}/${ownerRepo.repo} clones`, data.count); return data; } catch (e) { context.log(`Unable to get clones for ${ownerRepo.owner}/${ownerRepo.repo}. You probably don't have push access.`); } return 0; } async function getTotalForks(ownerRepo) { try { // https//docs.github.com/en/rest/repos/forks const { data } = await octokit.rest.repos.get(ownerRepo); const forksCount = (data) ? data.forks_count 0; context.log(`${ownerRepo.owner}/${ownerRepo.repo} forks`, forksCount); return forksCount } catch (e) { context.log(e); context.log(`Unable to get forks for ${ownerRepo.owner}/${ownerRepo.repo}. You probably don't have push access.`); } return 0; } async function getPageViews(ownerRepo) { try { // https//docs.github.com/en/rest/metrics/traffic#get-page-views const { data } = await await octokit.rest.repos.getViews(ownerRepo); context.log(`${ownerRepo.owner}/${ownerRepo.repo} visits`, data.count); return data; } catch (e) { context.log(`Unable to get page views for ${ownerRepo.owner}/${ownerRepo.repo}. You probably don't have push access.`); context.log(e); } return 0; } Next, I went into the getGitHubRepoStats/index.ts file, imported the getStats.ts script, and modified the body. Using this approach keeps the function nice and clean. import { AzureFunction, Context, HttpRequest } from '@azure/functions'; import { getStats } from './getStats'; const httpTrigger AzureFunction = async function (context Context, req HttpRequest) Promise<void> { context.log('HTTP trigger function processed a GitHub repo stats request.'); const stats = await getStats(context); context.log("The stats", stats); context.res = { body stats }; }; export default httpTrigger; I pressed F5 which then prompted me to install the “core” tools. After the installation completed, it showed several commands in the console, displayed the core tools version, built the code, and launched my new function locally. I hit the http//localhost7071/api/getGitHubRepoStats URL shown in the console and….drumroll please….it actually worked! Getting projects to work the first time is rare for me so it was nice to have a quick “win” for once. 3. Create a Function App in Azure Now that the function was working locally it was time to deploy it to Azure. I stopped my debugging session, went to the command pallet (shift+cmd+p on Mac), and selected Azure Functions Create Function App in Azure. Using the Azure Functions Create Function App in Azure Option in VS Code Once you select that option, you’ll be prompted for The Azure subscription to use The function name The runtime stack (I selected Node.js 16 LTS) The region 4. Deploy the Azure Function Code Once the Azure Function App is created you’ll see a message about viewing the details. The next step is to deploy the code. That can be done by going back to the command pallet in VS Code and selecting Azure Functions Deploy to Function App. You’ll be asked to select your subscription and Function App name. Once the function is created in Azure you can go to the Azure extension in VS Code, expand your subscription, expand your Function App, right-click on the function and select Browse Website. Add “/api/<your_function_app_name>” to the URL and if all of the planets align, you should see data returned from your function. Using the Azure VS Code extension to browser your Azure Functions website 5. Environment Variables and Key Vault You might have noticed that the function code relies on an environment variable named GITHUB_REPOS. I added that key and value into the Values property of the local.settings.json file which is used when running the function locally (that file isn’t checked into source control). { "IsEncrypted" false, "Values" { "AzureWebJobsStorage" "", "FUNCTIONS_WORKER_RUNTIME" "node", "GITHUB_REPOS" "[ { \"owner\" \"microsoft\", \"repo\" \"MicrosoftCloud\", \"token\" \"token-value\" }, { \"owner\" \"microsoft\", \"repo\" \"brainstorm-fluidframework-m365-azure\", \"token\" \"token-value\" } ]" } } I could deploy the function and have the GITHUB_REPOS value show up automatically in the Configuration –> Application Settings section of the Function App (you’ll see that section in the Azure Portal). In my case that wasn’t good enough though. The GITHUB_REPOS value has GitHub personal access tokens in it that are used to make the API calls. I needed a more secure solution when I ran the function in Azure. To handle that, I created a new Azure Key Vault secret that included the data required for the GITHUB_REPOS environment variable. I then went into Configuration –> Application Settings in the Function App and ensured that it had the following key/value pair [email protected](SecretUri=https//<your_key_vault_name>-vault.vault.azure.net/secrets/<your_secret_name>/) To get the Function App to successfully talk with Azure Key Vault and retrieve the secret, you’ll also need to create a managed identity. You can find details about that process here. Conclusion Migrating a custom script to Azure Functions is a fairly straightforward process especially if you’re able to reuse a lot of your original code. In my case, it allowed me to expose the local script functionality to anyone and any app. While this particular function is publicly accessible, it’s important to mention that you can also secure your functions as needed. Is that the end of the story? Not for me. I also needed to create a Power Automate flow to consume the data from the function and update a data store. That’s a subject for another post though. The code shown in this repo can be found here https//github.com/DanWahlin/github-repo-stats. What’s Next? The next post in this series titled Use Power Automate to Retrieve Data from an Azure Function for Reporting demonstrates how to automate calling the Azure Function and storing the data.


InvalidOperationException: Incorrect Content-Type
Category: MVC

Question How do you solve this error in Asp.net Core MVC Form Submission? The error says "An unh ...


Views: 0 Likes: 23
warning CS8632: The annotation for nullable refer ...
Category: .Net 7

Question What does Visual Studi ...


Views: 488 Likes: 70
Redis Docker Container Error ArgumentException: No ...
Category: Docker

Question How do you solve the error that comes when you are trying to cache an ...


Views: 331 Likes: 68
Linux service returns (code=exited, status=2)
Category: Linux

The message "exited, status=2" indicates that a Linux service has terminated with a status code ...


Views: 0 Likes: 10
Chat GPT Code in C#
Category: Other

<div class="react-scroll-to-bottom--css-dyfrr-79elbk h-full da ...


Views: 0 Likes: 8
How to get a correct file path manually in Visual ...
Category: Android

If you are trying to get a correct file path manually in cordova ionic app. Type the path and hit ...


Views: 455 Likes: 138
How to Insert two corresponding columns into a tem ...
Category: Other

Question How do you insert two columns corresponding to each other in a temp ta ...


Views: 0 Likes: 9
Video: Show a user’s emails in an ASP.NET Core app using Microsoft Graph
Video Show a user’s emails in an ASP.NET Core app ...

I’ve been working a lot with .NET Core and Microsoft Graph lately and decided to put together a short video based on a Microsoft Learn module covering how the technologies can be used together. If you haven’t used Microsoft Graph before, it provides a secure, unified API to access organizational data and intelligence (data stored in Microsoft 365 for example). So why would you ever want to access a signed in user’s emails and include them in your custom app? The simple answer is, “Bring organizational data where your users need it everyday!”. Instead of users switching from your app to find a relevant email, calendar event, Microsoft Teams chat (and more) by jumping between various productivity apps, you can pull that type of data directly into your custom app. This allows users to work more efficiently and make more informed decisions all while minimizing context shifts. In this video I’ll introduce you to The role of security in making Microsoft Graph calls.Microsoft Identity and Microsoft Graph Middleware configuration.The role of permissions/scopes and access tokens.The Microsoft Graph .NET Core SDK and how it can be used.How to create reusable classes that handle making Microsoft Graph calls.Dependency injection and how it can be used to access a GraphServiceClient object.How to retrieve a batch of email messages using the UserMessagesCollectionRequest class. Show a user’s emails in an ASP.NET Core app using Microsoft Graph


How do you perform Math in SQL
Category: Research

Math in SQL is an essential skill for anyone working with databases. It allows you to manipulate ...


Views: 0 Likes: 28
Firebase Notification API Code
Category: Technology

<pre class="default prettyprint prettyprinted" style="margin-bottom 1em; padding 5px; border-width ...


Views: 374 Likes: 61
Unable to attache to the process. Attaching a .Net ...
Category: Research

Debugging is an essential part of software development that helps developers identify and fix er ...


Views: 0 Likes: 27
Visual Studio Code cannot Detect Git Branches
Category: Git

When using Visual Studio code to develop an application that is in a local repository, sometimes VS ...


Views: 319 Likes: 104
How to Write to PDF using an Open Source Library c ...
Category: .Net 7

Question How do I use the #PDF Library called iText 7 to write to the pdf in C-sharp?<br / ...


Views: 400 Likes: 93
Nginx (Error) 503 Bad Gateway
Category: Servers

<span style="font-weight bold; textline underline; font-size lar ...


Views: 434 Likes: 93
How do you get an ID of recording getting inserted ...
Category: SQL

To get the identity value (the insertion_id) that corresponds to each record getting inserted int ...


Views: 0 Likes: 50
Solved: docker-compose build asp.net core 3.0 erro ...
Category: Docker

Problem The .NET Core frameworks can be found at <pre class="language-mar ...


Views: 2666 Likes: 150
[Bootstrap Padding Not working] pl-lg-4 not workin ...
Category: Bootstrap

Question I am trying to use Padding in Boostrap to only pad left when on the la ...


Views: 0 Likes: 40
How to Concatenate all the data in the SQL Table i ...
Category: SQL

To generate a fixed-length text from all columns in SQL Server and combine all columns and rows o ...


Views: 0 Likes: 14
The INSERT statement conflicted with the FOREIGN K ...
Category: SQL

Question How do you resolve the error that says&nbsp;The INSERT stateme ...


Views: 0 Likes: 30

Login to Continue, We will bring you back to this content 0



For peering opportunity Autonomouse System Number: AS401345 Custom Software Development at ErnesTech Email Address[email protected]