Astrol.JSInterop.Base: browser storage, clipboard, files and browser data from C#.
Astrol.JSInterop.Base comes with Astrol.Component.Basic and gathers the browser calls almost every app needs, so you don't write JavaScript. Its JS module is downloaded the first time it is used. AddAstrolBasic() already registers AstrolJSInterop; storage is a set of extension methods of IJSRuntime.
@* Program.cs: builder.Services.AddAstrolBasic(); (registers AstrolJSInterop) *@@using Astrol.JSInterop.Base@inject AstrolJSInterop jsI @* clipboard, files, browser, idle timer *@@inject IJSRuntime js @* storage: extension methods of IJSRuntime *@Store preferences, filters, a wizard's last step or a draft with its C# type: the value becomes JSON when saved and goes back to its type when read; if it doesn't exist you get default. localStorage survives closing the browser and sessionStorage lasts only as long as the tab.
By default (useEncrypt = true) the value is stored obfuscated: it can't be read at a glance in the browser tools, which adds a layer of discretion. It is not security-grade encryption: don't store passwords, tokens or sensitive data in the browser. With useEncrypt: false readable JSON is stored (e.g. to share it with JavaScript code). Always read with the same useEncrypt value you saved with.
Try it: save with and without useEncrypt and compare the stored value.
—public record Preferences(string Theme, int PageSize); // Save any serializable value (stored as JSON; obfuscated by default)await js.SetInLocalStorage("prefs", new Preferences("dark", 20)); // Read it back with its type (default if it does not exist)var prefs = await js.GetFromLocalStorage<Preferences>("prefs"); bool exists = await js.ExistsInLocalStorage("prefs");await js.RemoveFromLocalStorage("prefs");await js.ClearLocalStorage(); // Same API for sessionStorage (cleared when the tab is closed)await js.SetInSessionStorage("wizard-step", 3);int step = await js.GetFromSessionStorage<int>("wizard-step"); // Plain, readable JSON (e.g. to share it with JavaScript code)await js.SetInLocalStorage("theme", "dark", useEncrypt: false);CopyToClipboard copies a text (returns false if the browser doesn't allow it). SaveAs downloads a file generated in the app from its bytes (a CSV, a PDF, an Excel...) and DownloadFile one that is already at a URL, with the name you choose.
bool copied = await jsI.CopyToClipboard("dotnet add package Astrol.Component.Basic"); // A file generated in the app (CSV, PDF, Excel... as byte[])await jsI.SaveAs("clients.csv", Encoding.UTF8.GetBytes(csv)); // A file that is already at a URLawait jsI.DownloadFile("files/report.pdf", "report-2026.pdf");The user's time zone and language (for dates and texts), MatchesMedia for any CSS media query (mobile, dark mode, print...), opening a new tab safely and scrolling to an element or to the top of the page (also inside the AstrolLayoutMenu body).
string timeZone = await jsI.GetTimeZone(); // "America/Tijuana"string language = await jsI.GetBrowserLanguage(); // "es-MX"bool isMobile = await jsI.MatchesMedia("(max-width: 768px)");bool prefersDark = await jsI.MatchesMedia("(prefers-color-scheme: dark)"); await jsI.OpenInNewTab("https://astrolui.com");await jsI.ScrollToElement("#results");await jsI.ScrollToTop();IdleTimer calls your [JSInvokable] Logout() method when the user hasn't moved the mouse, typed, scrolled or touched the screen for a while. The last activity is shared between tabs: working in one keeps the session alive in the others.
// MainLayout.razor.cs: log out after 30 minutes without activityprivate DotNetObjectReference<MainLayout>? self; protected override async Task OnAfterRenderAsync(bool firstRender){ if (firstRender) { self = DotNetObjectReference.Create(this); await jsI.IdleTimer(self, storageKey: "myapp", time: 30 * 60 * 1000); }} [JSInvokable]public async Task Logout(){ await AuthService.LogoutAsync(); Navigation.NavigateTo("/login");}Storage (IJSRuntime extensions, useEncrypt defaults to true)
AstrolJSInterop