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

Wi-Fi Releases

Feature
New Shiny.Net.Wifi package — cross-platform Wi-Fi for Android, iOS, Mac Catalyst, macOS and Windows, with Shiny.Net.Wifi.Linux covering Linux through NetworkManager. Register services.AddWifi() and inject IWifiManager to scan for access points, join and leave networks, manage the networks the device has saved, read the current network, and power the radio. Backed by WifiManager + ConnectivityManager on Android, NEHotspotConfiguration + CaptiveNetwork on iOS/Mac Catalyst, CoreWLAN on macOS, the WiFiAdapter WinRT API on Windows, and NetworkManager over D-Bus on Linux.
Feature
IWifiManager.Capabilities publishes a WifiCapabilities flags value describing what the current platform can actually do, and anything unavailable throws WifiNotSupportedException naming the specific limit. Wi-Fi is the most unevenly exposed capability across these platforms — iOS has no scanning API, neither phone OS will show an app the networks the user saved, only three platforms can raise a hotspot — and the API states that rather than returning empty results that are indistinguishable from a quiet neighbourhood.
Feature
Scan(...) returns one WifiNetwork per BSSID — SSID, WifiSecurity (Open/WEP/WPA/WPA2/WPA3/Enterprise/OWE), signal in both dBm and a 0-100 percentage, frequency, and derived Band and Channel. Results are strongest-first. Networks in a WPA2/WPA3 or OWE transition mode report the stronger scheme, so a caller never sees a network as weaker than it is.
Feature
Connect(new WifiConnectionRequest(ssid) { Passphrase = ... }) joins a network and returns only once an address has been assigned, not merely on association — a result with no IP on it is not useful to the caller. Disconnect() leaves. WifiConnectionException carries the reason a join failed: wrong passphrase, out of range, user declined the system prompt, or DHCP timeout.
Feature
Known networks — GetKnownNetworks() lists what the device has saved as KnownWifiNetwork (an opaque platform-issued Id, plus SSID, security, hidden flag and AddedByThisApp), Forget(id) deletes one, and Connect(id) rejoins one without the passphrase being handed over again. The three sit behind the KnownNetworks, ForgetNetwork and ConnectKnownNetwork capability flags. Id is the platform’s own handle — a NetworkManager connection UUID on Linux, a numeric network id on Android below API 29, the SSID everywhere else — so round-trip it rather than parsing it.
Feature
What counts as “known” is scoped differently per platform and the API says which rather than papering over it: iOS, Mac Catalyst and Android disclose only the entries your own app created, while Windows, macOS and Linux hand back every profile on the machine. Neither phone OS will show an app the networks the user saved themselves, so AddedByThisApp exists to tell the two cases apart and a “manage all my Wi-Fi networks” screen is a desktop-only idea.
FeatureiOS
Saved networks on iOS and Mac Catalyst map onto NEHotspotConfigurationManagergetConfiguredSSIDs for the listing and removeConfiguration(forSSID:) for Forget. Only names come back; a stored configuration carries no security type or hidden flag. Connect(id) is not available: a configuration is a standing instruction iOS acts on when the network is in range, and there is no call to force the join.
FeatureAndroid
Remember = true now does something on Android 11+. The specifier join is still what gets the device onto the network, but a WifiNetworkSuggestion is registered alongside it so the OS can rejoin later — which is also what makes the network visible to GetKnownNetworks(). The suggestion only takes effect once the user approves the notification Android raises, and a failure to register one never fails the join. Below API 29 the legacy WifiConfiguration list is used instead, and its numeric network id is a real handle, so Connect(id) works there.
FeatureWindows
Saved profiles are outside WinRT entirely — WiFiAdapter cannot list, delete or join one — so GetKnownNetworks(), Forget() and Connect(id) P/Invoke wlanapi.dll directly (WlanGetProfileList, WlanGetProfile, WlanDeleteProfile, WlanConnect) rather than shelling out to netsh and parsing localised console output. Security and the hidden flag are read out of each profile’s XML; one that cannot be parsed is still reported, as unknown.
Feature
macOS lists the machine’s preferred-network list from CWConfiguration.networkProfiles and rejoins from the login keychain, so your app never sees the passphrase. Forget() there means committing a whole CWConfiguration, which macOS gates behind an SFAuthorization a normal app cannot raise — it throws WifiPermissionException explaining that. Linux reads, activates and deletes NetworkManager’s saved connections directly, with deletion gated on the polkit action org.freedesktop.NetworkManager.settings.modify.system.
Feature
IWifiManager.GetCurrentNetwork(ct) reports the joined network as a WifiNetworkInfo — SSID, BSSID, security, signal and frequency alongside every IP address, DNS resolver, gateway and subnet mask, with IPv4Address/IPv6Address shortcuts. The Changed event fires with the new WifiNetworkInfo? (null when the device drops off Wi-Fi) and is de-duplicated: the native watchers behind it all fire several times per real change. Subscribing now delivers the current network once up front, so a handler does not sit blind until the network next moves. WifiNetworkInfo compares its address lists by value rather than by reference, so snapshots can be diffed directly. The addressing fields are best-effort individually: a platform is allowed not to implement one — GatewayAddresses is unsupported on Android, and the sandboxed platforms vary on the rest — and a refused field comes back null or empty rather than throwing, so one missing field never costs the caller the whole read.
FixiOS
The SSID and BSSID of the joined network came back null on iOS 14 and later. CNCopyCurrentNetworkInfo was the source, and since iOS 14 it returns nothing unless the calling app itself configured the network being asked about — it succeeds and hands back an empty dictionary otherwise, so nothing failed and the name was simply missing. Reading now goes through NEHotspotNetwork.fetchCurrent, the iOS 14 replacement, which needs only the Access WiFi Information capability and location — not the case-by-case NEHotspotHelper entitlement its instance-side API once did. It reports more than CaptiveNetwork ever could, so Security and SignalStrengthPercent are now populated on iOS and Mac Catalyst instead of sitting at their defaults.
FixAndroid
The SSID and BSSID of the joined network came back null on Android 12 (API 31) and later even with ACCESS_FINE_LOCATION granted. API 31 redacts both out of every pull-style read — WifiManager.getConnectionInfo and the WifiInfo hanging off getNetworkCapabilities alike — regardless of permissions, and hands them out only through a NetworkCallback registered with FLAG_INCLUDE_LOCATION_INFO. Shiny now registers its watcher with that flag and serves reads from what it delivers, falling back to a short-lived callback (with a timeout, so a device that is off Wi-Fi returns null rather than hanging) when no watcher is running. Below API 31 the legacy getConnectionInfo path is unchanged — the redaction is what arrived in 31, not the deprecation.
BREAKINGFeature
IWifiManager.CurrentNetwork is replaced by Task<WifiNetworkInfo?> GetCurrentNetwork(CancellationToken ct = default). The synchronous property could not be made correct: the two platforms the module exists for both stopped answering synchronously, and a property that silently reports a null SSID is worse than one that makes the cost visible. Replace wifi.CurrentNetwork with await wifi.GetCurrentNetwork(); a Changed handler needs no change at all, since the new network already arrives in the event argument. On macOS, Windows, Linux and plain .NET the read is still synchronous underneath and the Task completes immediately.
Enhancement
New WifiSecurity.Psk — a pre-shared key whose WPA generation the platform did not name. iOS reports “personal” and nothing finer, and reporting that as Unknown would throw away what iOS does know while picking one of WPA/WPA2/WPA3 would be a guess. A switch over WifiSecurity that only handles Wpa2Psk/Wpa3Psk needs a case for this.
Feature
New IWifiHotspot (services.AddWifiHotspot()) raises an access point. Start(...) returns an IHotspotSession carrying the SSID and passphrase actually in use; disposing it brings the access point down. GetClients() lists joined devices with MAC address, IP and host name.
FeatureAndroid
The Android hotspot is a local-only hotspot — clients reach the device but get no route to the internet, because real tethering sits behind the signature permission TETHER_PRIVILEGED. The OS also generates the SSID and passphrase and offers no supported way to choose them (SoftApConfiguration.Builder exposes only the channel to non-system apps), so read them back off IHotspotSession.Info and show them to the user. GetClients() throws — Android has never exposed a client list and blocked the /proc/net ARP table apps read instead in Android 10.
FeatureWindows
Windows hotspot support goes through NetworkOperatorTetheringManager — real tethering that shares the machine’s current internet connection, honours the SSID, passphrase and band you supply, and can enumerate connected clients. Starting one with the machine offline fails, since there is no connection to share.
Feature
Linux hotspot support runs NetworkManager AP mode with ipv4.method=shared for DHCP and NAT, honours the full HotspotConfiguration, and lists clients from the kernel neighbour table filtered to the hotspot interface.
FeatureiOS
iOS and Mac Catalyst support joining, leaving and reading the current network, and nothing else. There is no scanning API — the only one that lists nearby networks lives inside a NEHotspotHelper, an entitlement Apple grants case by case — no hotspot API and no radio toggle. Requires the Hotspot Configuration and Access WiFi Information App ID capabilities plus NSLocationWhenInUseUsageDescription; without the location grant the SSID and BSSID come back null rather than failing, which is why RequestAccess() asks for it.
FeatureAndroid
Android needs ACCESS_WIFI_STATE, CHANGE_WIFI_STATE and ACCESS_FINE_LOCATION, plus NEARBY_WIFI_DEVICES from API 33. Joining uses a WifiNetworkSpecifier from API 29 (a system dialog, and the join itself is never persisted) and falls back to the legacy WifiConfiguration path below it. The radio toggle drops out of Capabilities from API 29, where setWifiEnabled was revoked for third-party apps.
Feature
macOS is backed by CoreWLAN and supports the full station API — scanning, associating, disassociating and powering the interface. Needs NSLocationWhenInUseUsageDescription, since macOS 14 gates scan results and the SSID on location the way iOS does. Windows needs the wiFiControl and radios capabilities; Linux gates the mutating calls behind the polkit actions org.freedesktop.NetworkManager.network-control and …enable-disable-wifi.
Enhancement
A scan that comes back empty because a permission was refused throws WifiPermissionException naming what is missing, rather than looking like an empty neighbourhood — Android returns an empty list instead of an error in that case. Android’s <unknown ssid> placeholder and the all-zero BSSID it substitutes without location permission are reported as null rather than passed through as literal strings.
Enhancement
WaitForAddress — the poll every connect path runs so a join returns with an address on it rather than on bare association — moved onto AbstractWifiManager instead of being copied into the Android, macOS, Windows and Linux managers. Linux also dropped the cache it kept purely to satisfy the old synchronous property, along with the blocking Task.Run(...).Wait(5s) prime behind it, and AndroidWifiManager.Dispose no longer blocks on its own Disconnect task.
FixAndroid
The internal receiver that picks up scan results below API 30 no longer carries a [BroadcastReceiver] attribute. It is registered from code — it has to be, since SCAN_RESULTS_AVAILABLE_ACTION is an implicit broadcast a manifest-declared receiver cannot get on API 26+ — but the attribute still put a <receiver> node in the generated manifest, and .NET for Android then failed the build of any app referencing the package with XA4213: The type 'Shiny.Net.Wifi.ScanReceiver' must provide a public default constructor.
Chore
The plain .NET target of the base package is a deliberate stub rather than a fallback: GetCurrentNetwork() still reports the IP, DNS, gateway and mask of the wireless interface and Changed still fires off NetworkChange, because that comes from the managed network stack — but every Wi-Fi-specific call throws, pointing at Shiny.Net.Wifi.Linux where relevant. Capabilities reports None.