65.9K
CodeProject 正在变化。 阅读更多。
Home

实例存储 - 应用程序之间共享配置数据的简单方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (7投票s)

2017年10月5日

CPOL

3分钟阅读

viewsIcon

13892

downloadIcon

140

我们都知道数据库连接字符串等等不应该被硬编码 - 但问题在于当你发布软件时,如何将正确的字符串放入正确的 PC 中。

引言

我们都知道硬编码配置数据是个坏主意:我们经常这样告诉新手,但我们仍然看到这样的代码

using(SqlConnection conn = new SqlConnection("Server=[server_name];
      Database=[database_name]; password=[password_name];user = [user_name]")) 
    {
    con.Open();
    ...
    }

虽然很容易创建一个配置文件条目来保存数据,但这仍然给我们带来了一个问题:多个应用程序需要多个配置字符串。当我开始向 WookieTab 添加应用程序时,我真的注意到了这一点,当我更改了我的 PC 的服务器名称时也是如此:突然加载了很多应用程序失败,我需要编辑所有连接字符串。

这很糟糕,而且容易出错 - 所以让我们看看我们是否可以做些什么。

背景

想想看,同一台 PC 上的大多数应用程序都将使用相同的用户名和密码组合访问相同的数据库系统 - 只有数据库名称会发生变化。那么为什么不建立一个系统,让一个特定的类负责存储、更新和检索“公共”配置信息呢?

所以我写了一个。

在深入细节之前,让我们快速看看如何在你的代码中使用它 - 这非常简单。

像往常一样添加对程序集的引用,然后添加一个 using

using SMDBSupport;

然后告诉它你要访问哪个数据库

strConnection = SMInstanceStorage.GetInstanceConnectionString("SMStandardReplies");

你知道,如果它适用于应用程序 A,那么当你发布应用程序 B 时,它也会在没有任何努力或更改的情况下工作。

那么,它是如何工作的?

这并不复杂!事实上,我认为我花在想出我需要这个想法的时间比编写代码的时间更长 - 有时我可能是一个慢速学习者...

存储在“公共配置”中的 string 是一种适合 String.Format 的格式 - 这也是 GetInstanceConnectionString 方法使用的。

"Data Source=GRIFF-DESKTOP\SQLEXPRESS;Initial Catalog={0};Integrated Security=True"
        /// <summary>
        /// Fetch the correct connection string for this instance and database
        /// </summary>
        /// <param name="databaseName"></param>
        /// <returns></returns>
        public static string GetInstanceConnectionString(string databaseName)
            {
            return string.Format(strConnectFormat, databaseName);
            }

format string 存储在“Local Application Data”文件夹中,位于一个 GUID 文件夹名称下,该文件夹名称分配给控制 SMInstanceStorage 类的程序集构建过程的项目 - 这意味着文件位置在系统之间是可变的,但该文件夹对用户登录空间中的每个应用程序都可用,并且同一台机器上的不同用户可以拥有不同的数据库登录信息。 当第一次使用该类时,所有这些都是由一个 static 构造函数获取的。

/// <summary>
/// Default constructor
/// </summary>
static SMInstanceStorage()
    {
    assemblyGuid = AssemblyGuid.ToString("B").ToUpper();
    strConnectFormat = "None assigned";
    string folderBase = Path.Combine(Environment.GetFolderPath
             (Environment.SpecialFolder.LocalApplicationData), assemblyGuid);
    strDataPath = string.Format(@"{0}\InstanceStorage.dat", folderBase);
    if (!Directory.Exists(folderBase))
        {
        Directory.CreateDirectory(folderBase);
        }
    if (!File.Exists(strDataPath))
        {
        File.WriteAllText(strDataPath, "");
        }
    string[] lines = File.ReadAllLines(strDataPath);
    foreach (string line in lines)
        {
        string[] parts = GetParts(line);
        if (parts.Length == 2)
            {
            switch (parts[0])
                {
                case scConnect: strConnectFormat = parts[1]; break;
                }
            }
        }
    }

在我的系统上,文件夹看起来像这样

C:\Users\PaulG\AppData\Local\{97BCDB46-E0BA-4DEB-B23A-07227BAB5354}

如果它不存在,就会被创建,文件也一样。

然后使用每个行的简单辅助方法进行处理

        /// <summary>
        /// Like string.Split, but only on the first instance.
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        private static string[] GetParts(string line)
            {
            int index = line.IndexOf('=');
            if (index < 0) return new string[0];
            return new string[] { line.Substring(0, index), line.Substring(index + 1) };
            }

你不能直接使用 string.Split 来做到这一点,因为它会分割所有 "=" 字符,而数据库连接 strings 也使用 equals。"将各个位重新拼凑起来"效率不高。

目前,static 构造函数只知道 "Connection" 对象。

/// <summary>
/// Connection storage class
/// </summary>
private const string scConnect = "Connection";

但是该类包含其他代码来支持多个应用程序通用但并非全部的常见“用户”存储

// <summary>
/// Read a storage class from the instance store
/// </summary>
/// <param name="storageClass"></param>
/// <returns>null if no such class found</returns>
public static string ReadInstanceData(string storageClass)
    {
    string result = null;
    string[] lines = File.ReadAllLines(strDataPath);
    foreach (string line in lines)
        {
        string[] parts = GetParts(line);
        if (parts.Length == 2 && parts[0] == storageClass)
            {
            result = parts[1];
            break;
            }
        }
    return result;
    }
/// <summary>
/// Read storage classes from the instance store
/// </summary>
/// <returns>null if no such class found</returns>
public static Dictionary<string, string> ReadAllInstanceData()
    {
    Dictionary<string, string> results = new Dictionary<string, string>();
    string[] lines = File.ReadAllLines(strDataPath);
    foreach (string line in lines)
        {
        string[] parts = GetParts(line);
        if (parts.Length == 2) results.Add(parts[0], parts[1]);
        }
    return results;
    }
/// <summary>
/// Read storage classes from the instance store
/// </summary>
/// <param name="storageClasses"></param>
/// <returns></returns>
public static Dictionary<string, string> ReadInstanceData(params string[] storageClasses)
    {
    Dictionary<string, string> results = new Dictionary<string, string>();
    string[] lines = File.ReadAllLines(strDataPath);
    if (storageClasses == null || storageClasses.Length == 0)
        {
        foreach (string line in lines)
            {
            string[] parts = GetParts(line);
            if (parts.Length == 2) results.Add(parts[0], parts[1]);
            }
        }
    else
        {
        foreach (string line in lines)
            {
            string[] parts = GetParts(line);
            if (parts.Length == 2)
                {
                int index = Array.IndexOf(storageClasses, (parts[0]));
                if (index >= 0) results.Add(parts[0], parts[1]);
                }
            }
        }
    return results;
    }
/// <summary>
/// Save a new instance storage value
/// </summary>
/// <param name="storeageClass"></param>
/// <param name="value"></param>
public static void SaveInstanceData(string storeageClass, string value)
    {
    string[] lines = File.ReadAllLines(strDataPath);
    for (int i = 0; i < lines.Length; i++)
        {
        string line = lines[i];
        string[] parts = GetParts(line);
        if (parts.Length == 2 && parts[0] == storeageClass)
            {
            lines[i] = string.Join("=", parts[0], value);
            break;
            }
        }
    File.WriteAllLines(strDataPath, lines);
    }
/// <summary>
/// Save all storage classes to the instance store.
/// This overwrites all existing data.
/// </summary>
/// <param name="storageData"></param>
/// <returns>null if no such class found</returns>
public static void SaveAllInstanceData(Dictionary<string, string> storageData)
    {
    List<string> lines = new List<string>();
    foreach (string key in storageData.Keys)
        {
        lines.Add(string.Join("=", key, storageData[key]));
        }
    File.WriteAllLines(strDataPath, lines);
    }
/// <summary>
/// Updates storage classes to the instance store
/// </summary>
/// <param name="storageData"></param>
/// <returns>null if no such class found</returns>
public static void UpdateInstanceData(Dictionary<string, string> storageData)
    {
    string[] lines = File.ReadAllLines(strDataPath);
    int changes = 0;
    for (int i = 0; i < lines.Length; i++)
        {
        string line = lines[i];
        string[] parts = GetParts(line);
        if (parts.Length == 2)
            {
            if (storageData.ContainsKey(parts[0]))
                {
                string newLine = string.Join("=", parts[0], storageData[parts[0]]);
                if (line != newLine)
                    {
                    line = newLine;
                    changes++;
                    }
                }
            }
        }
    if (changes > 0) File.WriteAllLines(strDataPath, lines);
    }

剩下的就是一个简单的方法来更新连接字符串

/// <summary>
/// Save the correct connection string for this instance and database
/// </summary>
/// <param name="connectionString"></param>
/// <param name="databaseName"></param>
/// <returns></returns>
public static void SetInstanceConnectionString(string connectionString, string databaseName)
    {
    strConnectFormat = connectionString.Replace(databaseName, "{0}");
    SaveInstanceData(scConnect, strConnectFormat);
    }

这个应用程序不加密数据,所以用户名和密码信息会被暴露 - 但如果你的环境需要,添加加密并不难。麻烦在于找到一种可靠的方法来保持加密密钥的安全,当多个应用程序需要提供它时……这就是为什么它目前不支持加密的原因。

你还需要什么才能使用它? 你想要一个支持程序来帮助你编辑 strings?我对你太好了,我真的 - 下载内容中有一个:StorageClassMaintenance 是一个简单的 WinForms 应用程序来处理文件。

历史

  • 2017-09-01:第一个版本
© . All rights reserved.