.Net6 独立存储详解

介绍

IsolatedStorageFile 对于桌面应用,独立存储是一种数据存储机制,它定义了将代码与保存的数据关联的标准化方式,从而提供隔离性和安全性。 同时,标准化也提供了其他好处。 管理员可以使用旨在操作独立存储的工具来支持配置文件存储空间设置安全策略及删除未使用的数据。 通过独立存储,代码不再需要使用唯一的路径来指定文件系统中的安全位置,同时可以保护数据免遭只具有独立存储访问权限的其他应用程序的损坏。 不再需要指示应用程序的存储区域位置的硬编码信息。

独立存储优点

  1. 存储隔离
  2. 不必关心数据存储的文件路径
  3. 可操作应用数据的存储空间

独立存储方案

总结下来一共有9种存储方案,可参考如下表1。

表1: 隔离类型表

UserUser-Roaming(用户漫游)Machine
AssemblyUser-AssemblyUser-Roaming-AssemblyMachine-Assembly
DomainUser-Asembly-DomainUser-Roaming-Assembly-DomainMachine-Assembly-Domain
ApplicationUser-ApplicationUser-Roaming-ApplicationMachine-Application

存储区

  1. %LOCALAPPDATA%\IsolatedStorage\:例如,User 范围的 C:\Users\<username>\AppData\Local\IsolatedStorage\
  2. %APPDATA%\IsolatedStorage\:例如,User|Roaming 范围的 C:\Users\<username>\AppData\Roaming\IsolatedStorage\
  3. %PROGRAMDATA%\IsolatedStorage\:例如,Machine 范围的 C:\ProgramData\IsolatedStorage\

每个用户的前两个位置都是独立的。 Windows 可确保同一计算机上的不同用户帐户无法访问彼此的用户配置文件文件夹。 使用 User 或 User|Roaming 存储区的两个不同用户帐户不会看到彼此的数据,并且不会干扰彼此的数据。

第三个位置在计算机上的所有用户帐户之间共享。 不同的帐户可以从此位置读取数据以及将数据写入此位置,并且可以查看彼此的数据。

隔离类型

独立存储访问包括(应用的域、程序集标识、应用程序标识)关联。 在运行时通过以下方式获取这些标识。

  • 域标识表示证明应用的证据,对于 Web 应用,这可能就是完整 URL。 对于 shell 托管代码,域标识可能基于应用目录路径。 例如,如果通过路径 C:\Office\MyApp.exe 运行可执行文件,域标识为 C:\Office\MyApp.exe。

  • 程序集标识是证明程序集的证据。 这可能来自加密数字签名,可以是程序集的强名称、程序集的软件发行者或程序集的 URL 标识。 如果程序集同时包含强名称和软件发行者标识,使用的是软件发行者标识。 如果程序集来自 Internet 且未签名,使用的是 URL 标识。 若要详细了解程序集和强名称,请参阅使用程序集编程。

  • 应用程序标识 通过GetEntryAssembly方法获取应用程序标识。

通过将用户、域和程序集标识这些概念相结合,独立存储可以通过下列方式隔离数据,每种方式都有自己的使用方案:
隔离类型

按程序集隔离

如果需要从任何应用的域都可以访问程序集使用的数据存储,按用户和程序集隔离更为合适。 在这种情况下,独立存储通常用于存储跨多个应用的数据,而不是与任何特定应用绑定的数据,如用户名或许可证信息。

// 按用户、程序集隔离
IsolatedStorageFile isoFile =
    IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
    IsolatedStorageScope.Assembly, null, null);
 // 快捷方式
IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForAssembly();

按域隔离

如果应用的程序集需要专用的数据存储,可以使用独立存储来存储专用数据。按用户、域、程序集隔离确保,仅当使用程序集的应用在程序集运行时创建独立存储,只有给定程序集中的代码才能够访问数据。按用户、域和程序集隔离可防止第三方程序集将数据泄漏给其他应用。 如果确定要使用独立存储,但不确定要使用哪种类型的隔离,此隔离类型应为默认选择

// 按用户、域、程序集隔离
IsolatedStorageFile isoFile =
    IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
        IsolatedStorageScope.Domain |
        IsolatedStorageScope.Assembly, null, null);
 // 快捷方式
 IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForDomain();

按应用程序隔离

此方法与按程序集隔离相同,通过不同的目录区分而已。参考IsolatedStorageFile构造方法的InitStore方法(基于.Net6.0)

源码解读(可跳过看总结)

  1. 获取IsolatedStorageFile实例:

    1. 调用GetUserStoreForXXX()GetMachineStoreForXXX()
    2. 调用GetStore(ScoreFlags)
      以上两种方式最终都会对调用
    private static IsolatedStorageFile GetStore(IsolatedStorageScope scope)
    {
    	return new IsolatedStorageFile(scope);
    }
    
  2. 生成独立存储路径解析
    IsolatedStorageFile只有唯一一个内部的构造方法如下:

    internal IsolatedStorageFile(IsolatedStorageScope scope)
    {
    	InitStore(scope, null, null);
    	StringBuilder stringBuilder = new StringBuilder(Helper.GetRootDirectory(scope)); // 存储区路径
    	stringBuilder.Append(SeparatorExternal);	// 分隔符
    	stringBuilder.Append(base.IdentityHash);	// 通过InitStore初始化 IdentityHash
    	stringBuilder.Append(SeparatorExternal);
    	if (Helper.IsApplication(scope))
    	{
    		stringBuilder.Append("AppFiles");
    	}
    	else if (Helper.IsDomain(scope))
    	{
    		stringBuilder.Append("Files");
    	}
    	else
    	{
    		stringBuilder.Append("AssemFiles");
    	}
    	stringBuilder.Append(SeparatorExternal);
    	_rootDirectory = stringBuilder.ToString();
    	Helper.CreateDirectory(_rootDirectory, scope);
    }
    

    如上的代码配合注释可以就能到了解到路径都是和Scope挂钩的。 由获取Scope存储区的路径Helper.GetRootDirectory(Scope)+目录分隔符+IdentityHash+目录分隔符+存储类型目录(AppFiles,Files, AssemFiles)。那剩下的只需要解析GetRootDirectory(Scope)IdentityHash就万事大吉了。
    Helper.GetRootDirectory(Scope)方法如下:

    internal static string GetRootDirectory(IsolatedStorageScope scope)
    {
    	if (IsRoaming(scope))
    	{
    		if (string.IsNullOrEmpty(s_roamingUserRootDirectory))
    		{
    			s_roamingUserRootDirectory = GetDataDirectory(scope);
    		}
    		return s_roamingUserRootDirectory;
    	}
    	if (IsMachine(scope))
    	{
    		if (string.IsNullOrEmpty(s_machineRootDirectory))
    		{
    			s_machineRootDirectory = GetRandomDirectory(GetDataDirectory(scope), scope);
    		}
    		return s_machineRootDirectory;
    	}
    	if (string.IsNullOrEmpty(s_userRootDirectory))
    	{
    		s_userRootDirectory = GetRandomDirectory(GetDataDirectory(scope), scope);
    	}
    	return s_userRootDirectory;
    }
    
    internal static string GetDataDirectory(IsolatedStorageScope scope)
    {
    	Environment.SpecialFolder folder = IsMachine(scope) ? Environment.SpecialFolder.CommonApplicationData : (IsRoaming(scope) ? Environment.SpecialFolder.ApplicationData : Environment.SpecialFolder.LocalApplicationData);
    	string folderPath = Environment.GetFolderPath(folder, Environment.SpecialFolderOption.Create);
    	return Path.Combine(folderPath, "IsolatedStorage");
    }
    
    internal static string GetRandomDirectory(string rootDirectory, IsolatedStorageScope scope)
    {
    	// some code...
    	// 这里代码做了精简,最终目的就是创建两层12位名称随机目录
    	return Path.Combine(rootDirectory, Path.GetRandomFileName(), Path.GetRandomFileName());  
    }
    

    如上代码所述: 根据Scope(Roaming,Machine,User)这三种类型映射到对应的Environmen.SpecialFolder目录+IsolatedStorage+随机目录名+随机目录名。

    剩下IdentityHash是怎么计算的。我们可以通过InitStore一探究竟。

    protected unsafe void InitStore(IsolatedStorageScope scope, Type domainEvidenceType, Type assemblyEvidenceType)
    {
       // some code...
       Scope = scope;
       Helper.GetDefaultIdentityAndHash(out object identity, out string hash, SeparatorInternal);
       if (Helper.IsApplication(scope))
       {
       	_applicationIdentity = identity;
       }
       else
       {
       	// some code...
       	_assemblyIdentity = identity;
       }
       IdentityHash = hash;  // 计算的Hash
    }
    
    internal static void GetDefaultIdentityAndHash(out object identity, out string hash, char separator)
    {
       Assembly entryAssembly = Assembly.GetEntryAssembly();  // 获取入口程序集信息
       string text = null;
       if (entryAssembly != (Assembly)null)
       {
       	AssemblyName name = entryAssembly.GetName();
       	hash = IdentityHelper.GetNormalizedStrongNameHash(name);
       	if (hash != null)  
       	{
       		// 根据程序集强命名得到Hash
       		hash = "StrongName" + separator.ToString() + hash;
       		identity = name;
       		return;
       	}
       	// 如果程序集没有进行强命名则使用程序集的文件路径
       	text = entryAssembly.Location;  
       }
       if (string.IsNullOrEmpty(text))
       {
       	// 如果程序集文件路径获取失败则获取进程路径
       	text = Environment.ProcessPath;
       }
       if (string.IsNullOrEmpty(text))
       {
       	throw new IsolatedStorageException(System.SR.IsolatedStorage_Init);
       }
       Uri uri = new Uri(text);
       hash = "Url" + separator.ToString() + IdentityHelper.GetNormalizedUriHash(uri); // 问程序集强命名Key或程序集路径使用SHA1生成Hash
       identity = uri;
    }
    
    

    配合代码和注释,可以看到程序的Hash通过程序集的强命名Key或程序集的文件路径再使用SHA1加密获得。

    最终路径如下:

    Environment.GetFolderPath(Scope)\IsolatedStorage\12位随机目录\12位随机目录\(SHA1.Hash(程序集信息))\AppFiles
    

总结

  1. 独立存储的储存路径: 存储区路径+IsolatedStorage+(12位随机目录名)+(12位随机目录名)+隔离类型标识Hash目录+隔离类型目录名。

    -UserUser-Roaming(用户漫游)Machine
    AssemblyHash\AssemFilesHash\AssemFilesHash\AssemFiles
    DomainHash\Hash\FilesHash\Hash\FilesHash\Hash\Files
    ApplicationHash\AppFilesHash\AppFilesHash\AppFiles

    举个例子: 由 User 与 Assembly 组成最终存储路径如下:
    C:\Users\<username>\AppData\Local\IsolatedStorage\\(12位随机目录名)\\(12位随机目录名)\\Url.(应用程序路径Hash)\\AssemFiles

  2. 不同的隔离区或不同的隔离类型之间存储不会互相干扰!

  3. 当程序路径变更后,也将用户的配置也将失效(除非程序集强命名

参考

Microsoft Docs - 独立存储
Microsoft Docs - 隔离的类型

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一种与线程绑定的变量,它可以解决多线程并发访问的问题。与Synchronized不同,ThreadLocal为每个线程提供了一个独立的变量副本,每个线程都可以独立地修改自己的副本,而不会影响其他线程的副本。 ThreadLocal的使用方法比较简单。我们可以通过ThreadLocal类的set()方法来设置当前线程所关联的变量的值,通过get()方法来获取当前线程所关联的变量的值。在使用完ThreadLocal后,如果不再需要这个变量,应该调用remove()方法来清除当前线程的关联变量,避免内存泄漏的问题。 ThreadLocal的原理是通过每个线程都拥有一个独立的ThreadLocalMap对象来实现的。ThreadLocalMap内部使用一个Entry数组来存储键值对,键为ThreadLocal对象,值为对应的变量副本。在获取当前线程所关联的变量时,会根据ThreadLocal对象找到对应的变量副本并返回。ThreadLocal与Thread、ThreadLocalMap之间的关系是,每个线程都有一个ThreadLocalMap对象,其中存储了与该线程关联的所有ThreadLocal对象及其对应的变量副本。 ThreadLocal的常见使用场景包括但不限于: - 解决线程安全问题:可以将需要在多个线程中共享的数据存储在ThreadLocal变量中,每个线程访问自己的变量副本,避免了线程安全问题。 - 传递上下文信息:可以将一些需要在多个方法中共享的上下文信息存储在ThreadLocal变量中,在方法调用链中方便地获取这些上下文信息。 - 数据库连接管理:可以将数据库连接存储在ThreadLocal变量中,在每个线程中独立管理数据库连接,避免了线程间的冲突。 总之,ThreadLocal提供了一种方便的方式来实现线程间的数据隔离和传递,能有效地解决多线程并发访问的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [史上最全ThreadLocal 详解](https://blog.csdn.net/qq_43842093/article/details/126715922)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [ThreadLocal详解](https://blog.csdn.net/m0_49508485/article/details/123234587)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值