Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
164868 stories
·
33 followers

Where do I govern my Copilot Studio agents?

1 Share

Copilot Studio agents are becoming more action-oriented and autonomous, and taking on more complex work. With agent user accounts, supported Copilot Studio agents can have their own Microsoft 365 account, directory profile, and org-chart relationship, and join Teams and Outlook with scoped permissions. Makers see a digital teammate; admins see a new actor whose access, data use, cost, and behavior must be governed.

Microsoft reports that 29% of employees have used unsanctioned AI agents for work, and 32% of surveyed organizations' data security incidents involve generative AI tools. How can admins enable agents safely without slowing makers down?

For IT teams, this creates a series of connected governance outcomes they need to achieve:

  • Enable makers safely and at scale
  • Configure agent identity and resource access
  • Protect the information agents use and produce
  • Maintain estate-wide observability and prove business value
  • Detect and respond to threats

 

Each outcome has a lead, but no outcome stops where the next begins. Together, they help IT leaders organize their teams and give practitioners a clear view of the outcomes and controls they own. This blog walks through each governance outcome: who owns it, the challenges involved, and the Microsoft products that support end-to-end, layered governance.

Enable makers safely and at scale

Organizations need room for makers to experiment without allowing agents to sprawl. A single shared environment is difficult to govern consistently. On the other hand, requiring a separate approval for every early idea can slow adoption. The first governance job is to establish a path from personal development to broader deployment.

The Power Platform admin leads the environment strategy. The desired outcome is an administrated place to build, with consistent controls and clear requirements for moving an agent into team or production use.

In the Power Platform admin center, environment routing can give makers personal developer environments instead of placing everyone in the default environment. Environment groups and rules apply settings consistently across those environments, including advanced connector policies that allow only approved connectors and actions and block everything else by default. Sharing controls limit how agents are distributed and contain agent user account risk: an agent can only act through what its environment allows, so a least-privilege allowlist limits its reach even if its permissions expand. Maker-facing guidance makes these boundaries clear at the point of creation, and these controls continue to apply as agents move into wider use.

 

Directing makers with environment routing in the Power Platform admin center

Configure agent identity and resource access

As agents connect to systems and take actions, IT needs to know which identity is being used and what that identity can access. Permissions appropriate for an initial use case may become excessive as an agent gains new capabilities or connections. Agent user accounts make this question more visible, but identity and access governance applies more broadly.

The identity and access admin leads this job in Microsoft Entra. The desired outcome is that agent identities are identifiable, resource access is appropriately scoped, and access conditions and permissions can be reviewed as an agent’s use changes.

Microsoft Entra Agent ID provides capabilities to manage agent identities. Conditional Access for agents helps apply access conditions to supported agent access patterns. Identity admins need to distinguish between an agent identity, an agent acting on behalf of a user, and an agent user account: these patterns do not use or govern access in identical ways. Entra’s identity controls complement the environment and connector boundaries established in the Power Platform admin center.

 

Configuring access packages for agents in Microsoft Entra

Protect the information agents use and produce

An agent may have legitimate access to information without every use or disclosure of that information being appropriate. As agents connect to more sources and produce outputs for more people, IT needs to understand where sensitive data is exposed and whether existing protections cover the agent’s interactions.

The compliance admin and data security team lead this job in Microsoft Purview. Their desired outcome is to identify data risks, apply appropriate protections, and maintain visibility into how sensitive information is used across supported agent interactions. Those protections need to be reassessed as an agent’s sources, audience, or capabilities change.

Microsoft Purview Data Security Posture Management helps identify AI-related data risks. Classification, sensitivity labels, data loss prevention, and audit provide additional protection and visibility for supported scenarios. Coverage differs by agent and interaction type, so admins should verify which controls apply and include agent instances in relevant policies where required. Copilot Studio data policies remain a complementary platform control over connections and capabilities; they do not replace information protection.

This collective protection matters most for agents with user accounts. They don’t just act on behalf of a user or inherit delegated access; they have their own access and their own knowledge. AI agents can discover vulnerabilities faster than humans, making protection of underlying data more critical than ever. With their own access and permissions, agents with agent user accounts can introduce new risks of data oversharing and exfiltration.

 

Setting and managing sensitivity labels in Microsoft Purview

Maintain estate-wide observability and prove business value

As agents spread across teams and platforms, IT needs an estate-wide view of what exists, who owns it, how it is used, which agents need attention, and whether spend is tied to value.

 The AI admin coordinates this view in Microsoft Agent 365, the observability platform for AI that provides a view of agents across the organization. The desired outcome is an agent estate IT can continuously observe and improve. Power Platform admins continue to manage environments for Copilot Studio and platform-specific controls.

Copilot Studio agents appear in the agent registry in the Microsoft 365 admin center, alongside Microsoft and non-Microsoft agents, with usage insights on by default. The agent map shows how each agent connects to users and tools, so IT knows where to look first.

From an agent’s details page, the AI admin reviews ownership, usage, and security signals, then routes findings to the right admin in Microsoft Entra, Microsoft Purview, or Microsoft Defender. Each admin acts in their own portal, with their existing permissions.

With tool governance, IT controls which plugins, skills, and connectors are available, and to whom, while cost management tracks Copilot Credit spend, with spending policies and alerts.

Agent 365 complements, rather than replaces, the controls in those products and the Power Platform admin center, while extending governance beyond Copilot Studio agents. Available information and actions vary by agent, publishing state, and tool type.

 

The agent map in Microsoft Agent 365 shows how an agent connects to users and tools.

Detect and respond to threats

Preventive guardrails cannot resolve every risk before an agent is deployed. Once agents are running, they face threats that look different from traditional attacks. A jailbreak prompt can trick an agent into ignoring its instructions. A prompt injection hidden in an email, document, or web page can redirect an agent to leak data or take action nobody intended. A compromised or overly broad connected tool can be misused to reach systems the agent was never meant to touch, and an unusual spike in an agent’s activity can signal that it is being abused. IT needs to identify the affected agent, determine what happened, and act across the relevant controls.

The security admin and Security Operations Center (SOC) analysts lead threat investigation and response, but they cannot do it alone. SOC analysts see the alert; IT knows the agent. When a jailbreak attempt or suspicious tool call is flagged, the AI admin connects the signal to the agent and its owner, so analysts know what the agent is for, what it can reach, and who to contact.

From there, the teams investigate together: the SOC reconstructs what happened, the identity admin checks or restricts the agent’s permissions, the compliance admin assesses whether sensitive data was exposed, and the Power Platform admin changes connectors, sharing, or environment settings, or disables the agent. The desired outcome is a response path that does not depend on one admin having every tool or every form of expertise.

Microsoft Defender provides agent security posture and threat investigation capabilities, with real-time protection for supported and configured scenarios. A finding in a broader agent-management view can prioritize attention, but specialist investigation and response remain with the security team. Findings should also inform future access, data-protection, and platform decisions.

Investigate agent-related security incidents in Microsoft Defender

Learn more about enabling Defender security for AI agents.

Put the governance framework to work

For IT leaders, this guide is a starting point for organizing Copilot Studio governance across teams. Assign a lead for each outcome, then agree on the decisions that need input from other admins (for example: when an agent moves into production, gains access to a new resource, or reaches a wider audience).

A regular review of agent ownership, access, data protection, usage, and risk can help teams identify gaps before they become operational issues. Working across several admin surfaces is intentional; each team gets the tools built for its role, and shared context and clear handoffs keep everyone moving toward the same outcome.

When a governance question arises, begin with the outcome you need rather than the admin center you expect to use. An agent that appears in the inventory but has no clear owner calls for a different response from one whose permissions have expanded or whose outputs may contain sensitive information. With a strong governance framework, clear responsibilities, and effective delegation across admins, organizations can confidently prepare for a future where agents have a place on the org chart.

Get started

Ready to put layered governance in place? Start with the two admin experiences that anchor Copilot Studio’s layered governance :

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Native badges for Shell tabs in .NET 11

1 Share

Badges are a small but useful part of many mobile applications. Whether you’re displaying unread messages, pending notifications, or simply indicating that something needs attention, they’re a familiar way to communicate information without requiring the user to navigate away from their current page.

Until now, adding badges to Shell tabs in .NET MAUI typically meant writing platform-specific code or introducing a third-party solution. With .NET MAUI 11, the team introduced native badge support for Shell tabs, making the process considerably simpler.

The feature is available across Android, iOS, Mac Catalyst, and Windows, with support for badge text, background colours, and text colours.

Adding a badge to a Shell tab

Three new bindable properties have been added to BaseShellItem:

  • BadgeText defines the text or number displayed.
  • BadgeColor sets the background colour.
  • BadgeTextColor sets the text colour.

To use them, add the properties to a ShellContent or Tab within your Shell definition.

<TabBar>
    <ShellContent
        Title="Home"
        Icon="home.png"
        ContentTemplate="{DataTemplate views:HomePage}" />

    <ShellContent
        Title="Notifications"
        Icon="notifications.png"
        BadgeText="5"
        BadgeColor="Red"
        BadgeTextColor="White"
        ContentTemplate="{DataTemplate views:NotificationsPage}" />
</TabBar>

That’s all that’s required! The Notifications tab now displays a native badge containing the number 5, using the appropriate platform implementation.

Because these are bindable properties, you can also connect them to your application’s state. For example, if you’re tracking unread notifications in a view model, you can bind the badge directly to a property:

<ShellContent
    Title="Notifications"
    Icon="notifications.png"
    BadgeText="{Binding UnreadBadgeText}"
    ContentTemplate="{DataTemplate views:NotificationsPage}" />

As long as your binding source raises property change notifications, updates to UnreadBadgeText are reflected in the badge without additional platform code.

More than just numbers

One useful detail is how BadgeText handles different values. The team has implemented three distinct behaviours:

Value Behaviour
"5" Displays a badge containing 5
"" Displays a dot indicator
null Hides the badge

This makes it possible to use the same API for both notification counts and simple status indicators.

For example, you might want to display a dot when new content is available without exposing the number of items. Setting BadgeText to an empty string does exactly that.

You can also leave BadgeColor and BadgeTextColor unset to use the platform defaults, allowing the badge to follow the native appearance.

How does it work?

The implementation is worth a closer look. In PR #34659, Gerald Versluis introduced the new properties and connected them to the native badge implementations on each platform.

Rather than drawing a custom badge, the team uses the capabilities already provided by the operating system.

On Android, badges use Material’s BadgeDrawable, obtained through BottomNavigationView.GetOrCreateBadge(). The implementation can display a numeric badge or call ClearNumber() to switch to a dot indicator.

On iOS and Mac Catalyst, the implementation uses UITabBarItem.BadgeValue, with additional APIs for configuring the badge colours.

Windows takes a slightly different approach, using the WinUI InfoBadge control within the navigation item template. Numeric values are displayed as counts, while non-numeric values are represented by a dot.

This is an important distinction if you’re planning to display arbitrary text. Although the public API accepts a string, the visual result isn’t identical across platforms.

It’s also interesting that these properties live on BaseShellItem, rather than being implemented separately for each navigation element. This gives the different Shell navigation items a common API, while their respective platform implementations decide how the badges are displayed. The initial implementation focuses on bottom tabs, rather than flyout items or top tabs.

Wrapping up

Native Shell badges are a welcome addition to .NET MAUI 11. They’re straightforward to configure, support data binding, and remove the need to maintain platform-specific implementations for a common UI feature.

I particularly like the distinction between an empty string and null. It’s a small API decision, but it makes switching between a notification count, a simple dot indicator, and no badge at all surprisingly easy.

You can explore the feature in the Shell tab documentation, or take a look at the original implementation on GitHub, which also includes unit tests, UI tests, and an interactive sample.

Credit to Gerald Versluis and the .NET MAUI team for bringing this into the framework 🙌

Read the whole story
alvinashcraft
22 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

A closer look at XAML Incremental Hot Reload in MAUI 11

1 Share

Hot Reload has been part of .NET MAUI for some time, allowing developers to make changes to XAML without rebuilding and redeploying their applications. It’s a useful part of the development workflow, although larger changes can sometimes mean losing UI state or having to recreate a page.

With .NET MAUI 11, the team has introduced a new XAML Incremental Hot Reload engine that takes a different approach to updating the UI. Rather than relying on recreating elements or pages, it generates targeted updates that can be applied to existing controls.

Let’s take a look at how it works.

What’s changed?

Introduced in .NET 11 Preview 7, the new engine uses a Roslyn source generator alongside .NET’s MetadataUpdateHandler infrastructure. When you modify a XAML file, the generator compares the changes and produces C# code to update the affected elements.

This supports changes to properties, bindings, resource dictionaries and even structural changes such as adding, removing or reordering child elements.

More importantly, these updates can be applied to existing page instances without navigating away or recreating the page.

A look at the source code

The implementation is particularly interesting. If we look at the team’s UpdateComponentCodeWriter, we can see how the generator locates existing controls before applying property changes.

Here’s a small extract:

if (templateNodeIds?.Contains(nodeDiff.NodeId) == true)
    codeWriter.WriteLine(
        $"foreach (var {varName} in global::Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetTemplateComponents(this, \"{nodeDiff.NodeId}\"))");
else
    codeWriter.WriteLine(
        $"if (global::Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.TryGet(this, \"{nodeDiff.NodeId}\", out var {varName}))");

The code is part of the source generator, so it’s generating C# rather than directly updating the UI.

What’s interesting here is the use of XamlComponentRegistry. The registry tracks existing controls, allowing the generated update method to find the correct instance and apply changes to it. Notice the separate handling for template components, where multiple instances of the same XAML element might exist.

This is particularly relevant for controls such as CollectionView, where an item template can be displayed many times.

What about CollectionView?

The team has continued improving the engine throughout the .NET 11 previews. In RC1, changes to properties within a direct CollectionView.ItemTemplate can update existing, realised cells without replacing the entire template.

That means changes such as adjusting a label’s font size or colour can be applied while preserving the collection’s scroll position, selection and focus.

It’s a small detail, but one that can make a considerable difference when working on more complex pages. Nobody wants to scroll through a long collection again every time they make a minor XAML adjustment!

Getting started

XAML Incremental Hot Reload is enabled by default for Debug builds targeting .NET MAUI 11, so there’s nothing additional to configure for a typical project. It also works with dotnet watch, bringing the new update mechanism to command-line development workflows.

The engine is still a preview feature, and not every edit can be applied incrementally. Changes such as introducing new code-behind fields or modifying the root element type can still require a rebuild.

If you encounter problems, you can switch back to the existing Hot Reload engine by adding the following to your project file:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <EnableMauiIncrementalHotReload>false</EnableMauiIncrementalHotReload>
</PropertyGroup>

Final thoughts

This is one of those improvements that might not immediately stand out in the .NET 11 release notes, but could have a meaningful impact on everyday development.

The move towards source-generated, targeted updates is particularly interesting, especially as the team continues improving XAML source generation across MAUI.

I’m looking forward to seeing how it performs with larger, more complex applications.

References and further reading

Read the whole story
alvinashcraft
29 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Long Press Gestures Are Finally Built Into .NET MAUI 11

1 Share

Sometimes it’s the smaller additions to .NET MAUI that make the biggest difference to everyday development. One such improvement in .NET 11 is the introduction of LongPressGestureRecognizer, bringing native support for press-and-hold interactions directly into the framework.

Until now, implementing a long press in MAUI generally meant reaching for something like the .NET MAUI Community Toolkit’s TouchBehavior, or writing your own platform-specific implementation. With .NET 11, long press joins the existing family of gesture recognizers, alongside tap, swipe, pan and pinch.

Getting started

The new recognizer works much like the existing TapGestureRecognizer. You attach it to a view through its GestureRecognizers collection and configure what happens when a long press is detected.

For example, suppose you have an image representing a property and want to display additional actions when the user presses and holds it.

<Image Source="property.jpg">
    <Image.GestureRecognizers>
        <LongPressGestureRecognizer
            MinimumPressDuration="750"
            AllowableMovement="10"
            Command="{Binding ShowActionsCommand}"
            CommandParameter="{Binding .}" />
    </Image.GestureRecognizers>
</Image>

Here, MinimumPressDuration specifies how long the user must hold their finger down, in milliseconds, while AllowableMovement determines how far their finger can move before the gesture is cancelled. The recognizer also supports Command and CommandParameter, making it straightforward to integrate with an MVVM application.

If you’d rather handle the interaction in code-behind, you can use the LongPressed event instead.

private void OnLongPressed(
    object sender,
    LongPressedEventArgs e)
{
    var position = e.GetPosition((View)sender);

    // Handle the long press.
}

The GetPosition method is particularly useful when you need to know where the user pressed, perhaps to display a contextual menu near their finger.

A look under the hood

Digging into the MAUI source code, there’s an interesting detail in how the recognizer handles commands and events.

The following is a shortened extract from LongPressGestureRecognizer.SendLongPressed:

var cmd = Command;

if (cmd != null && cmd.CanExecute(CommandParameter))
    cmd.Execute(CommandParameter);

LongPressed?.Invoke(
    sender,
    new LongPressedEventArgs(CommandParameter, getPosition));

Rather than requiring developers to choose between commands and events, the implementation supports both. When a successful long press completes, MAUI checks whether the bound command can execute, invokes it if permitted, and then raises the LongPressed event.

There’s also a separate LongPressing event for monitoring gesture state changes, including Started, Running, Completed and Canceled. This provides opportunities for more interactive experiences, such as showing visual feedback while the user continues holding their finger down.

A few platform differences

Although the API is cross-platform, the underlying implementations aren’t identical. On iOS and Mac Catalyst, MAUI uses the native UILongPressGestureRecognizer, while Android uses its native GestureDetector and Windows uses a timer-based implementation.

One important difference is that Android ignores MinimumPressDuration and instead uses the system’s default long-press timeout, typically around 400 milliseconds. Additionally, continuous gesture state updates are available on iOS and Mac Catalyst, while Android and Windows provide completion and cancellation updates.

These differences are worth considering if you’re building an interaction that depends on precise timing or continuous feedback across platforms.

Wrapping up

LongPressGestureRecognizer isn’t a groundbreaking addition to .NET MAUI, but it’s a welcome one. Bringing commonly used interactions into the framework means fewer dependencies, less platform-specific code and a more consistent development experience.

It’s also encouraging to see improvements like this continuing alongside the larger architectural and performance changes coming in .NET 11.

If you’re interested in exploring the implementation further, Gerald Versluis’s pull request #33432 is worth a look. You can also find usage examples and platform-specific guidance in the official Microsoft documentation.

Read the whole story
alvinashcraft
36 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How inline C# in XAML works behind the scenes in .NET MAUI

1 Share

One of the more interesting additions to .NET MAUI 11 is the ability to write C# directly inside XAML files using the new x:Code directive.

I’ve previously written about C# expressions in XAML, which allow us to replace simple converters, calculated properties and some event handlers with inline expressions. The new x:Code directive takes this a little further, allowing us to define methods, fields and properties without needing to move into a separate code-behind file.

It’s an interesting addition to the XAML source generator, so let’s have a look at how it works, where it might be useful, and how the team has implemented it.

Getting started

First, we need to enable XAML source generation and preview features in our project file:

<PropertyGroup>
    <MauiXamlInflator>SourceGen</MauiXamlInflator>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>

With that in place, we can use x:Code directly inside our XAML pages.

Example 1: A simple counter

Let’s start with a button that increments a counter whenever it’s clicked.

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="MyApp.MainPage">

    <x:Code><![CDATA[
        private int _count;

        void OnCounterClicked(object sender, EventArgs e)
        {
            _count++;
            CounterLabel.Text = $"Clicked {_count} times";
        }
    ]]></x:Code>

    <VerticalStackLayout
        Padding="20"
        Spacing="12">

        <Label
            x:Name="CounterLabel"
            Text="Clicked 0 times" />

        <Button
            Text="Increment"
            Clicked="OnCounterClicked" />

    </VerticalStackLayout>
</ContentPage>

Nothing particularly complicated here. We’re declaring a private field and an event handler directly inside our XAML, then referencing the handler from the button.

Previously, we’d have placed these members in MainPage.xaml.cs. With x:Code, everything needed for this simple interaction lives in the same file.

Example 2: Handling CollectionView selection

For something a little more practical, let’s use x:Code to handle an item being selected from a CollectionView.

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="MyApp.PropertiesPage">

    <x:Code><![CDATA[
        async void OnSelectionChanged(
            object sender,
            SelectionChangedEventArgs e)
        {
            if (e.CurrentSelection.FirstOrDefault()
                is not Property property)
                return;

            await Shell.Current.GoToAsync(
                $"property/{property.Id}");
        }
    ]]></x:Code>

    <CollectionView
        ItemsSource="{Binding Properties}"
        SelectionMode="Single"
        SelectionChanged="OnSelectionChanged">

        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="local:Property">
                <Label
                    Text="{Binding Name}"
                    Padding="12" />
            </DataTemplate>
        </CollectionView.ItemTemplate>

    </CollectionView>
</ContentPage>

This example assumes a Property model, a Properties binding and a registered Shell route. The usual namespace declaration for local is also required.

Here, the event handler belongs specifically to the page and performs a simple navigation action. Keeping it alongside the XAML makes it easier to see how the selection event is handled without switching between files.

For more complicated navigation or business logic, I’d still favour a ViewModel or separate C# implementation. The value here is having the option to keep small amounts of view-specific code close to where they’re used.

How does it work?

The implementation was introduced by Stéphane Delcroix in PR #34715, which adds a third pipeline to the XAML source generator.

Previously, the generator handled code-behind declarations and InitializeComponent generation. The new implementation adds a step between those two operations:

XAML
  |
  +-- CodeBehind generation
  |
  +-- x:Code generation
  |
  +-- InitializeComponent generation

This order is important. By generating the inline C# before InitializeComponent, the compiler can resolve methods referenced by XAML event handlers.

Looking at the source, two components are particularly relevant:

  • ComputeXCodeSource() extracts the inline code from the parsed XAML.
  • XCodeCodeWriter generates a separate C# partial class containing the extracted members.

The generator produces a file with a name following this pattern:

MainPage.xaml.xcode.cs

For our counter example, the generated code is conceptually equivalent to:

namespace MyApp
{
    partial class MainPage
    {
        private int _count;

        void OnCounterClicked(object sender, EventArgs e)
        {
            _count++;
            CounterLabel.Text = $"Clicked {_count} times";
        }
    }
}

This is a simplified representation of the generated output, rather than a verbatim extract.

Because the generated class is partial, its members are available alongside those in the usual code-behind file. We can even reference other methods and properties declared on the page.

There’s another nice implementation detail here. The generator automatically moves using directives out of the inline block and places them at the top of the generated C# file, removing duplicates along the way.

That means the following is also valid:

<x:Code><![CDATA[
    using System.Diagnostics;

    private int _clickCount;

    void OnButtonClicked(object sender, EventArgs e)
    {
        _clickCount++;
        Debug.WriteLine($"Clicked {_clickCount} times");
    }
]]></x:Code>

The source generator takes care of arranging the code into a valid C# file, while the normal C# compiler remains responsible for checking the method bodies.

What about the visual tree?

One detail I particularly like is that x:Code doesn’t become part of the visual tree.

The implementation explicitly excludes these elements from the visitors responsible for generating controls, setting properties and registering names. The directive exists purely for code generation, so there’s no additional runtime control or corresponding visual element.

There are a few restrictions. The directive must be a direct child of the root XAML element, and the root must declare x:Class.

The team has also introduced diagnostics for incorrect usage, including MAUIX2015 when the directive isn’t a direct child of the root and MAUIX2016 when x:Class is missing.

Should we use it?

I can see this being useful for small, view-specific event handlers and helper methods. Keeping a few lines of presentation logic alongside the XAML they belong to could make a page easier to understand, particularly when prototyping or building relatively simple views.

That doesn’t mean we should start moving large amounts of code-behind into XAML. For anything more complicated, a separate C# file is probably still easier to read, maintain and test.

For me, the more interesting story is what’s happening underneath. The team continues to expand what the XAML source generator can do, moving more work into the build process while giving developers additional ways to structure their applications.

It’s worth experimenting with, particularly alongside the new C# expressions feature. Just remember that both are still preview features in .NET 11.

References and further reading

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Testing your MAUI code on real devices comes to .NET 11

1 Share

Testing .NET MAUI applications has always presented a few challenges, particularly when the code you’re testing depends on platform-specific functionality. Traditional unit tests are great for testing business logic, but what happens when you need to test something that interacts directly with Android or iOS?

With .NET 11, the team has introduced native device test projects, bringing the familiar dotnet test workflow to Android, iOS, macOS, Mac Catalyst and tvOS.

This is a useful addition for MAUI developers, allowing tests to run inside a native application process on a physical device or emulator, rather than relying entirely on mocks or desktop-based tests.

Let’s take a closer look.

Getting started

.NET 11 RC1 introduces five new project templates, each configured to use MSTest and Microsoft.Testing.Platform.

Template Platform
androidtest Android
iostest iOS
tvostest tvOS
macostest macOS
maccatalysttest Mac Catalyst

Creating an Android test project is straightforward. With the .NET 11 SDK and Android workload installed, run:

dotnet new androidtest -n MyAndroidTests
cd MyAndroidTests
dotnet test

With an Android emulator running or a compatible physical device connected, the tooling builds and installs the test application before executing the tests on the device.

You can write tests using the familiar MSTest attributes, just as you would in a conventional .NET test project.

For example, let’s check whether Android’s connectivity service is available:

using Android.App;
using Android.Content;
using Android.Net;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ConnectivityTests
{
    [TestMethod]
    public void ConnectivityService_IsAvailable()
    {
        var context = Application.Context;

        var connectivity = context.GetSystemService(
            Context.ConnectivityService);

        Assert.IsInstanceOfType<ConnectivityManager>(
            connectivity);
    }
}

Unlike a conventional unit test running on your development machine, this test executes inside an Android application process and interacts directly with the platform’s APIs.

It’s a simple example, but the same approach can be used to test platform integrations that are difficult to exercise in an ordinary .NET test project.

A look behind the scenes

One of the interesting parts of this feature is how the team has integrated device testing into the existing .NET tooling.

If we look at dotnet/android PR #11130, we can see the introduction of AndroidTestAdapter, which connects Android’s instrumentation system with Microsoft.Testing.Platform.

Android already provides a mechanism for executing tests through its instrumentation framework. For example, the following command executes instrumentation on a connected device:

adb shell am instrument -w <package>/<runner>

The new adapter builds on this existing functionality. When you run dotnet test, the tooling launches Android instrumentation, retrieves the generated TRX results file and reports the results through Microsoft.Testing.Platform.

This means developers can use the same dotnet test command they’re already familiar with, without needing to manually manage the Android instrumentation process.

The implementation also integrates with the .NET SDK’s device discovery capabilities. If multiple devices are available, you can select a particular device using the --device option, or list available devices with --list-devices.

Where does this fit with MAUI testing?

It’s worth distinguishing native device tests from UI automation.

Tools such as Appium and the MAUI testing ecosystem remain important for testing complete user journeys, including navigation, button interactions and validating what’s displayed on screen.

Native device test projects solve a different problem. They allow developers to run tests against platform functionality inside a real Android or Apple application process, without requiring a complete UI automation framework.

For MAUI applications that use native APIs, this opens up some useful possibilities. Platform-specific services, permissions, device capabilities and native integrations are all potential candidates, provided the tests account for the device configuration and permissions they require.

It also means there’s a clearer path towards including these tests in automated build pipelines, although your build environment will still need access to suitable devices or emulators.

Final thoughts

This is another of those .NET 11 improvements that could easily be overlooked among the more visible MAUI features.

Bringing native device testing into dotnet test removes some of the additional tooling and configuration previously required to execute tests on mobile platforms. More importantly, it makes testing platform-specific functionality feel like a natural extension of the existing .NET development workflow.

I’m particularly interested in how this could help test native integrations in larger MAUI applications, where relying entirely on mocks doesn’t always give you confidence that the platform implementation behaves as expected.

References and further reading

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories