eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsWrite-once
Stockage
Edit this page
5 min read
Une signature par-dessus le magasin natif propre à chaque plateforme. Une application écrit le même C# partout, et la valeur atterrit là où cette plateforme garde déjà ce genre de chose, si bien que sa sauvegarde, sa migration et sa désinstallation se comportent comme un utilisateur s'y attend, sans que le framework invente ou embarque quoi que ce soit.
Depuis 0.2.0-preview.10
Il y a deux interfaces, et laquelle vous prenez est une décision, pas un paramètre.
IAppStorage
ISecretStore
Web
localStorage
aucun, se résout à null
macOS / iOS
NSUserDefaults
Keychain
Android
SharedPreferences
AES/GCM adossé au KeyStore
Les deux se prennent par constructeur, comme toute autre capacité :
1
2
3
4
public sealed class SettingsShell(IAppStorage storage) : StatefulComponent
{
private void Remember(string language) => storage.Set("language", language);
}
CHAQUE CIBLE En natif, le conteneur .NET le résout ; dans le navigateur, le constructeur transpilé le résout par le nom de l'interface ; pendant le SSR il se résout à un magasin qui ne contient rien et le dit : les lectures répondent null, les écritures ne vont nulle part, parce que le stockage que ceci représente vit sur la machine de la visiteuse. Donc une page lit sa valeur stockée au premier rendu du client, et le balisage rendu côté serveur montre la même chose qu'une toute première visite. Voir Capacités pour le tableau complet.
Lisez-le dans OnMount, pas dans le constructeur : le réconciliateur construit des instances neuves à chaque passe et garde celle qui est retenue, donc un constructeur qui lit le stockage le relit pour une instance qui est ensuite jetée. (Composants write-once)
Pourquoi les secrets sont un type différent
Mettre un jeton dans localStorage est l'erreur de sécurité la plus commise sur le web, et une API où la sûreté est un argument secure: true est une API où la sûreté est à un argument oublié près. Ici le choix ne peut pas se faire par omission : un secret demande un service différent, et le demander est quelque chose que quelqu'un a fait exprès.
Le navigateur n'a pas de coffre, donc sur le web ISecretStore se résout à null, de la même façon que le framework rapporte chaque capacité qu'un hôte n'a pas, plutôt que de faire semblant avec un magasin que n'importe quel script de l'origine peut lire. Une application qui le cherche sur le web doit répondre à la question qu'elle évitait : garder le secret sur le serveur et remettre au navigateur une session qu'il ne peut que dépenser, ou accepter que ce qu'elle stockait n'a jamais vraiment été un secret.
En natif, c'est le vrai coffre de la plateforme. Apple : un élément kSecClassGenericPassword par clé sous l'identifiant de bundle de l'application, AfterFirstUnlockThisDeviceOnly pour qu'un rafraîchissement en arrière-plan puisse le lire tandis qu'un appareil non déverrouillé depuis le démarrage ne peut pas, et jamais synchronisé vers iCloud. Android : une clé AES adossée au matériel dans AndroidKeyStore (le matériau de la clé est illisible même pour ce processus) avec un chiffré AES/GCM dans un fichier de préférences. C'est ce que fait EncryptedSharedPreferences d'AndroidX, écrit directement parce que la plateforme livre chaque pièce et que le framework ne prend pas de dépendance pour quelque chose que l'OS fournit déjà.
Des chaînes seulement
Chaque magasin ici est une carte de chaînes en dessous. Une surface typée serait trois sérialiseurs à maintenir identiques, et leur premier désaccord est une valeur qui s'écrit sur une plateforme et ne se lit pas sur une autre. Sérialisez dans votre application, là où le format est le vôtre.
Le stockage est une capacité CLIENT
Il n'y a pas de réalisation serveur. Pendant le SSR le service se résout à null et les lectures ne répondent rien.
Cela compte plus qu'il n'y paraît. Une page qui branche son arbre sur un état stocké rend une chose sur le serveur et une autre dans le navigateur, et le réconciliateur réconciliera fidèlement la différence sous les yeux de la lectrice. Lisez le stockage dans un gestionnaire d'événement ou après le mount, jamais pendant la construction.
C'est aussi pourquoi le stockage seul ne peut pas se souvenir d'un thème sans un clignotement : le serveur peint d'abord et localStorage n'est pas quelque chose qu'il peut voir. Ce problème précis veut un cookie, parce que l'exigence n'est pas "se souvenir" mais "dire au serveur", et le framework s'en occupe déjà, donc une application n'a pas à le faire. Voir ServerIntegration.
Les échecs répondent, ils ne lèvent jamais
•
localStorage est l'API du DOM la plus susceptible de lever plutôt que d'échouer discrètement. Le mode privé de Safari a levé à l'écriture, un navigateur à son quota lève, et le lire tout court lève quand l'origine a le stockage bloqué. Une préférence qui n'arrive pas à s'enregistrer ne doit pas faire tomber une page, donc une écriture qui ne peut pas avoir lieu n'a simplement pas lieu, et une lecture qui ne peut pas avoir lieu répond null.
•
Un élément de Keychain ne survit pas à toute restauration, et une clé de KeyStore est invalidée par un nouvel écran de verrouillage. Les deux rendent chaque valeur sous elles définitivement illisible.
Traitez donc une lecture null comme un "redemande" (reconnecte-toi, retombe sur la valeur par défaut) et jamais comme une erreur à rapporter. C'est la même réponse que donne une clé qui n'a jamais été écrite, et l'appelant gère déjà celle-là.
Apporter le vôtre
Chaque capacité est enregistrée avec TryAdd, donc enregistrer le vôtre gagne :
1
builder.Services.AddSingleton<IAppStorage, MyOwnStorage>();
C'est l'échappatoire pour un magasin à la durabilité différente, un fichier chiffré ou un double de test.