用于后台运行和设备硬件服务的 .NET Framework (iOS、MacCatalyst、Android 和 Windows)
Shiny is a cross-platform framework designed to make working with device services and background processes easy, testable, and consistent while bringing things like dependency injection & logging in a structured way to your code!
BGTaskScheduler (iOS/tvOS), WorkManager (Android), COM-activated background tasks (Windows), and an in-process managed runner for Linux/macOS/Blazor WASMNSURLSession (iOS/tvOS), an HttpClient-driven managed loop with Range-based resume (Android, Windows, Linux, macOS, .NET base), and Service Worker Background Sync (Blazor WASM). Pause/resume support - pause stops a transfer without cancelling it (downloads continue from where they left off, uploads restart). First-class Azure Blob Storage and AWS S3 (SigV4) request builders included. AddTransferProgress() puts progress in front of the user - an iOS Live Activity, or the Android foreground-service notification promoted to an Android 16 live update - from one manager, with no code in your transfer delegateNSURLSession (iOS/tvOS/Mac Catalyst), Foreground Service + HttpClient (Android), HttpClient + connectivity loop (Windows/Linux/macOS), and LocalStorage-backed HttpClient (Blazor WASM). Includes batched outbox, tombstones, conflict resolution, retry with exponential backoff, and AOT-safe serialization through Shiny.JsonNSNetService on iOS/tvOS/Mac Catalyst/macOS (so no com.apple.developer.networking.multicast entitlement is needed), NsdManager on Android (no multicast lock), and a dependency-free managed responder on UDP 5353 for Windows, Linux, macOS console, and server .NET. await foreach (var r in mdns.Browse("_http._tcp", ct)) for live discovery, BrowseOnce(...) for a one-shot scan, and Publish(...) to advertise your own service with TXT metadatassdp.SearchAll() for a one-shot sweep, Browse(...) for a live list keyed on UDN with ssdp:alive/byebye tracking and BOOTID-aware expiry, GetDescription(...) to fetch and parse the device description (friendly name, model, icons, service list), and Publish(...) to advertise your own root device. Discovery and description only - no SOAP action invocation, no GENA eventingTypes, RFC 3986 segment-prefix scope matching, and ProbeOnvifCameras(...) as a shortcutcom.apple.developer.networking.multicast entitlement, Android needs CHANGE_WIFI_MULTICAST_STATE (the multicast lock is acquired for you) plus ACCESS_LOCAL_NETWORK from Android 17. Sandboxed hosts still need their usual network permission - com.apple.security.network.client/.server on Mac Catalyst & macOS, privateNetworkClientServer for packaged Windows apps. A missing one throws DiscoveryPermissionException naming exactly what to add, rather than silently finding nothingWifiCapabilities flags property; anything unavailable throws WifiNotSupportedException naming the specific limit (an Apple entitlement, an Android API level that revoked the call, a platform with no such concept). Check the flag to branch, catch the exception as a backstopIWifiManager - Scan(ct) returns one WifiNetwork per BSSID with SSID, security scheme, dBm + 0-100 signal, frequency, band and channel. Connect(new WifiConnectionRequest(ssid) { Passphrase = ... }) joins and waits for DHCP rather than returning on association, and with Remember left on also persists the network for later (a WifiNetworkSuggestion on Android 11+, an ordinary profile elsewhere). await GetCurrentNetwork(ct) reports the joined network - SSID, BSSID, security, signal, plus every IP, DNS resolver, gateway and mask - and Changed fires with the new WifiNetworkInfo? (null when Wi-Fi drops), de-duplicated so the chatty native watchers behind it do not leak through, delivering the current network once on subscribe. Reading the SSID is asynchronous because both phone platforms stopped answering synchronously: iOS 14 replaced CNCopyCurrentNetworkInfo with NEHotspotNetwork.fetchCurrent, and Android 12 redacts the SSID and BSSID out of every pull-style read no matter what permissions are held, handing them out only through a NetworkCallback registered with FLAG_INCLUDE_LOCATION_INFOGetKnownNetworks() lists what the device has saved as KnownWifiNetwork (opaque platform Id, SSID, security, hidden), Forget(id) deletes one, and Connect(id) rejoins one without handing the passphrase over again. The scope differs and the API says which: iOS/Mac Catalyst and Android only ever disclose your own app's entries (NEHotspotConfigurationManager.getConfiguredSSIDs, network suggestions), while Windows, macOS and Linux hand back every profile on the machine. Connect(id) works on Windows, macOS, Linux and Android below API 29 - iOS and modern Android treat a saved network as a standing hint the OS acts on, with no call to force it. The Id is the platform's own handle: a NetworkManager connection UUID on Linux, a numeric network id on legacy Android, the SSID everywhere elseIWifiHotspot - Start(...) returns an IHotspotSession carrying the SSID and passphrase actually in use; dispose it to bring the access point down. GetClients() lists joined devices with MAC and address on Windows and Linux. Android raises a local-only hotspot (clients reach the device, not the internet) and picks the SSID/passphrase itself, Windows shares the machine's internet connection, Linux runs NetworkManager AP mode with DHCP + NAT, and iOS/macOS have no hotspot API at allWifiManager + ConnectivityManager on Android (specifier-based joins from API 29, legacy WifiConfiguration below), NEHotspotConfiguration + CaptiveNetwork on iOS/Mac Catalyst, CoreWLAN on macOS, WiFiAdapter + NetworkOperatorTetheringManager + Radio on Windows, and NetworkManager over D-Bus on Linux (separate Shiny.Net.Wifi.Linux package). Saved profiles are outside WinRT entirely, so Windows reaches wlanapi.dll directly for those three calls. The plain .NET target still reports IP/DNS off the wireless interface and raises Changed; the Wi-Fi-specific calls throwACCESS_WIFI_STATE, CHANGE_WIFI_STATE, ACCESS_FINE_LOCATION and NEARBY_WIFI_DEVICES (API 33+); iOS needs the Hotspot Configuration and Access WiFi Information capabilities plus NSLocationWhenInUseUsageDescription; macOS needs the location usage description; Windows needs wiFiControl and radios; Linux gates the mutating calls behind polkit. A scan that comes back empty because location was refused throws WifiPermissionException instead of looking like an empty neighbourhoodScreenRecorderCapabilities flags property; anything unavailable throws ScreenRecorderNotSupportedException naming the specific limit. The flags are read off the instance because they differ within a platform: macOS 15 gains microphone capture and loses pause (SCRecordingOutput writes the file itself and cannot be detached mid-recording) where macOS 12.3-14 has it the other way roundIScreenRecorder - await recorder.Start(new ScreenRecordingRequest { IncludeMicrophone = true, MaxWidth = 1280 }) returns an IScreenRecording that does not come back until frames are genuinely being written - so a consent dialog, a compositor picker or an Android foreground-service promotion all complete first. Pause()/Resume() close the gap in the timeline rather than leaving a frozen stretch, Stop() returns a ScreenRecordingResult with the path, duration, size and the MimeType actually produced (browsers disagree - Safari and Chrome give MP4, Firefox gives WebM), and disposing without stopping cancels and deletes the partial file. A request asking for something outside Capabilities throws before any native call, because a recording that silently came out without the microphone is worse than one that refused to startGetTargets() lists displays, windows and applications on macOS and Windows, so an app can offer its own picker. Linux and the browser hand selection to the compositor and throw here - their picker appears during Start insteadIScreenRecording.Faulted fires with a reason - the user hit Android's cast notification or the browser's "Stop sharing" bar, iOS took the screen for an incoming call, a monitor was unplugged, the encoder failed - and carries whatever was salvaged, so a recording cut short still yields a playable file rather than a silent truncation. MaxDuration stops cleanly and reports the same waystartCapture into an AVAssetWriter on iOS/tvOS/Mac Catalyst (startRecording only surrenders its file through a share sheet, which is no use to a library), ScreenCaptureKit on macOS - SCRecordingOutput on 15+ and the same AVAssetWriter below it - Windows.Graphics.Capture into a MediaStreamSource transcoded by Media Foundation on Windows, and the xdg-desktop-portal ScreenCast API driving gst-launch-1.0 or ffmpeg on Linux (separate Shiny.ScreenRecorder.Linux package). Blazor WebAssembly uses getDisplayMedia + MediaRecorder (separate Shiny.ScreenRecorder.Blazor package) and is the only platform where pause is native and the result has no file path - there is no filesystem, so OpenRead() is the portable accessorFOREGROUND_SERVICE, FOREGROUND_SERVICE_MEDIA_PROJECTION and RECORD_AUDIO for audio, plus a per-recording consent dialog that cannot be pre-granted; iOS needs `NSMicrophoneUsageDescript暂无开放 Issues,或尚未同步最近议题。