Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Scanning, Connecting & the Current Network

IWifiManager covers the station side of the radio: what is in range, what you are joined to, and moving between the two.

var access = await wifi.RequestAccess(ct);
if (access != AccessState.Available)
return;
var networks = await wifi.Scan(ct);

Results come back strongest first, one entry per BSSID — not per SSID. A multi-band router or a mesh network answers on several radios, so the same name appears more than once. Group them if you are building a picker:

var forDisplay = networks
.GroupBy(x => x.Ssid)
.Select(g => g.MaxBy(x => x.SignalStrengthPercent)!)
.Where(x => !x.IsHidden)
.ToList();
Member Notes
Ssid Empty for a hidden network that did not broadcast one
Bssid The MAC of the radio that answered; null where the platform withholds it
Security Open, Wep, WpaPsk, Wpa2Psk, Wpa3Psk, Enterprise, Owe, Unknown
SignalStrengthDbm Typically -30 (excellent) to -90 (unusable). Null on Linux, which reports only a quality percentage
SignalStrengthPercent 0-100, populated on every platform — the safe one to display
FrequencyMhz Centre frequency
Band / Channel Derived from the frequency
IsHidden The access point does not broadcast its SSID
IsOpen No passphrase needed. WEP counts as secured here, but treat it as open in practice

There is no public API. The only thing that lists nearby networks is NEHotspotHelper, whose entitlement Apple grants case by case to captive-network-assistant apps. Scan() throws WifiNotSupportedException on iOS and Mac Catalyst — check Capabilities.HasFlag(WifiCapabilities.Scan) and offer a “join by name” field instead.

Android throttles startScan from API 29 (roughly four scans per two minutes in the foreground) and NetworkManager refuses a scan requested within about ten seconds of the last one. Both serve cached results instead of failing, so a rapid second scan returns the previous sweep rather than an error. Do not poll.

var request = new WifiConnectionRequest("Kitchen")
{
Passphrase = "hunter2hunter2",
Remember = true,
Timeout = TimeSpan.FromSeconds(20)
};
try
{
var joined = await wifi.Connect(request, ct);
}
catch (WifiConnectionException ex)
{
// wrong passphrase, out of range, user declined the prompt, or DHCP never answered
}

Connect returns only once an address has been assigned. Association completes well before DHCP does, and a WifiNetworkInfo with no IP on it is not much use to the caller.

Field Android iOS / Catalyst macOS Windows Linux
Ssid, Passphrase
Security ✅ (picks WPA2 vs WPA3 key mgmt) WEP flag only read from beacon read from beacon
Bssid ignored
IsHidden
Remember ✅ API 30+ (adds a suggestion) ✅ (JoinOnce inverted)

Leave Security as Unknown unless the network is hidden. The platform reads the scheme off the beacon; a hidden network has no beacon to read, so it has to be told.

Remember saves the network so it can be rejoined later, and is what puts it in GetKnownNetworks(). Windows, macOS and Linux write an ordinary profile; iOS keeps the hotspot configuration; Android 11+ registers a WifiNetworkSuggestion next to the join, because the specifier-based join itself is never persisted.

await wifi.Disconnect(ct);

What this means varies more than the name suggests:

  • Android 10+ and iOS drop the network your app asked for. The OS is then free to rejoin one the user had already saved, so the device may not end up offline at all.
  • Windows, macOS and Linux disassociate the adapter outright.
var current = wifi.CurrentNetwork;
if (current != null)
{
Console.WriteLine(current.Ssid);
Console.WriteLine(current.IPv4Address);
Console.WriteLine(current.Gateway);
Console.WriteLine(String.Join(", ", current.DnsAddresses));
}

WifiNetworkInfo carries Ssid, Bssid, Security, SignalStrengthDbm, SignalStrengthPercent, FrequencyMhz, Band, Channel, InterfaceName, IpAddresses, DnsAddresses, Gateway, SubnetMask, and the IPv4Address / IPv6Address shortcuts.

CurrentNetwork is read live off the OS on every access, so it is always current but is not free — hold the result rather than re-reading it in a loop. The one exception is Linux, where D-Bus is asynchronous: the value is cached and refreshed from the NetworkManager watcher, and only the first read pays for a blocking round trip.

public sealed class NetworkWatcher(IWifiManager wifi) : IDisposable
{
public void Start() => wifi.Changed += this.OnChanged;
public void Dispose() => wifi.Changed -= this.OnChanged;
void OnChanged(object? sender, WifiNetworkInfo? network)
=> this.status = network == null
? "Offline"
: $"{network.Ssid} ({network.SignalStrengthPercent}%)";
}

Changed fires with the new network, or null when the device drops off Wi-Fi entirely.

  • It is de-duplicated. The native watchers behind it — Android’s NetworkCallback, Apple’s NWPathMonitor, NetworkManager’s PropertiesChanged — all fire several times for one real change. Only genuine differences are raised.
  • WifiNetworkInfo compares its address lists by value, not by reference, so diffing snapshots yourself works too. The record’s synthesized equality would have compared the arrays by reference and made every poll look like a change.
  • Unsubscribe. The native watcher is created on the first subscription and torn down on the last, so a leaked handler keeps a radio callback alive for the life of the process.
if (wifi.Capabilities.HasFlag(WifiCapabilities.RadioToggle))
await wifi.SetRadioEnabled(true, ct);
var isOn = await wifi.GetRadioEnabled(ct);

Android revoked setWifiEnabled for third-party apps in API 29 — the capability flag is only set below that, and above it you should send the user to Settings.Panel.ACTION_WIFI. iOS never allowed it and does not report the state either. Windows, macOS and Linux support both.

Networks the device has already saved are covered on their own page — Known Networks — including the important caveat that iOS and Android only ever disclose the entries your own app created.

Exception Means Recoverable
WifiNotSupportedException The OS has no API for this. The message names the limit. No — branch on Capabilities
WifiPermissionException A permission, entitlement or manifest entry is missing. The message names it. Yes
WifiConnectionException The join failed or timed out Yes
WifiException The base type for all of the above Depends