管理动态网站设置保留在数据库中

时间:2012-04-04 17:36:46

标签: c# asp.net architecture

我正在尝试创建一些东西,以便在我们的ASP.NET网站中保存全局站点范围的设置 - 诸如站点名称,谷歌分析帐号,facebook URL等等...该站点可以有多个'品牌'或子 - 与之相关的站点,因此是sitguid列,我们还希望可以选择将它们分组,因此组列 - 例如测试和生产(通过web.config appsetting设置)。

我不希望任何KEY在任何地方被硬编码,但我希望能够在代码中尽可能简单地引用它们,例如SiteSetting.getSetting(“SiteName”)(这些可能用于更多初级开发人员将创建的模板(masterpages等))

我还希望能够管理管理控制台中的现有设置,并能够创建新设置。

数据类型列用于编辑表单,以便可以使用正确的输入元素,例如位类型的复选框,varchar的文本框等...

目前

SiteSettings数据库表:

  [sts_sitGuid] [uniqueidentifier] NOT NULL, -- tells us which site the setting is for
  [sts_group] [nvarchar](50) NOT NULL, -- used to group settings e.g. test/live
  [sts_name] [nvarchar](max) NULL, -- the display name of the setting, for edit forms
  [sts_alias] [nvarchar](50) NOT NULL, -- the name for the setting
  [sts_value] [nvarchar](max) NOT NULL, -- the settings value
  [sts_dataType] [nvarchar](50) NULL, -- indicates the control to render on edit form
  [sts_ord] [tinyint] NULL,  -- The order they will appear in on the admin form

我现在已经完成了这项工作的一部分,但我对我的工作方式并不满意,并且希望此处有任何建议可以帮助找到“正确”的解决方案!我相信人们已经在我面前这样做了。 (我会分享到目前为止我所拥有的,但不想以任何特定的方式扭曲答案)我所寻找的是一个概述如何做得最好,而不是寻找任何人为我写的; )

我已经在这里和谷歌进行了很多搜索,并没有真正找到我正在寻找的东西,特别是能够添加新的设置'定义'以及编辑现有的设置。

系统使用webforms在ASP.NET上运行,它全部用c#编写并使用MSSQL 2008。

与往常一样,非常感谢任何帮助!

编辑:澄清一下我将解释到目前为止我所建的内容。由于我们不希望web.config或其他xml文件或其他数据库浮出水面,所以我在将这个应用程序推广到其他客户时会给我们更多的事情。

到目前为止,我有一个SiteSettings类,它有一个GetSetting方法,我可以用GetSetting(“SettingAlias”)调用它来获取DB中“SettingAlias”的值。此类的构造函数从数据库中获取当前站点的所有设置,并将这些设置存储在字典中,GetSetting从该字典中读取。到目前为止,我很满意这一切。

我正在努力的部分是生成编辑表单。之前的版本使用了web服务来获取/设置设置,我试图继续使用类似的东西来保存工作,但它们都在代码中定义,例如GoogleAnalyticsID,Sitename等...并且每个都有一个列在数据库中,我所做的更改是将这些设置存储为ROWS(从那时起,它更容易添加更多,无需更改架构和所有sitesettings类)目前我的SiteSettings类有一个SiteSettingsEditForm方法可以抓取来自db的所有信息,为表单元素创建一组控件,将其放在临时页面中并执行该控件,然后通过ajax将生成的HTML传递给我们的管理系统。这感觉不对,有点笨拙,并且是在这里发布它的原因,我无法弄清楚如何通过webservice保存这些东西,但更重要的是通过执行包含负载的页面生成一堆HTML表单控件只是感觉像是错误的方式。

总而言之,我(我想)想写一个能够缓存的类。从数据库表中读取少量行,并根据同一数据库表的内容(例如,我的类型列为'位),给我一个编辑表单(或给其他人生成表单的数据) '我想要一个复选框,它是'文本',我想要一个文本输入)

5 个答案:

答案 0 :(得分:3)

如果您从数据模型开始,有时这种问题更容易可视化。如果你想要每行设置,那么两个表可能是存储它的最佳方式:

网站:

SiteId    SiteKey     SiteName
1         XYGZ4345    Client Site 1
2         AZT43752    Client Site 2

这将定义您配置的网站列表。我会使用SiteKey,因为你将它放在你的web.config中,最好把它抽象成一个随机字符串或GUID(为了让它更难以意外加载其他人的配置),客户端可以改变它们的名字和你不会在将来感到困惑,因为你没有使用他们的旧名称作为钥匙等等。

如果我们将每个设置视为字符串,配置表本身也很简单:

SiteSetting:

SettingId    SiteId    SettingName    SettingValue
1            1         Brand          KrustyBrand
2            1         GoogleId       MSFTSUX0R
3            2         Brand          ConfigMasters(TM)

然后您可以非常简单地加载所有配置:

SELECT * FROM SiteSetting INNER JOIN Site ON (SiteSetting.SiteId = Site.SiteId) WHERE Site.SiteKey = 'XYGZ4345'

现在我们有一个键值对列表,您可以将它们存储在类中:

public class SiteSetting
{
    public Site Site {
        get; set; //Site would just be a simple class consisiting of Id, Key and Name to match the database table
    }

    protected Dictionary<String, String> Settings { get; set; } //Simple key value pairs

}

所以这是一个非常简单的解决方案。但是,我们可以更进一步 - 需要考虑的事项:

1)我们可以在某处添加一个环境吗?

我们可以为每个环境添加一个网站

OR

向SiteSetting表添加环境。这样做的好处是你可以定义enironment ='ALL'以避免重复。

OR

加载配置的数据库定义环境;所以你在app config中更改配置连接字符串。当然,要连接到不同的环境,您必须更改app.config,但无论如何您都可能需要更改客户端密钥和/或环境。

2)添加用户可定义设置的概念 - 您想要更改的一些设置,一些您想要锁定的设置。包含“UserDefinable”的位列将允许您对此进行排序

3)键入设置。

这稍微困难一些。你可能有类似的东西:

PropertyId    PropertyName        PropertyType    Format          UserDefined
1             Brand               String          NULL            1
2             DatePresentation    DateTime        "yyyy-MM-dd"    1

然后,Settings表只定义一个值和一个PropertyId。这样做的好处是,您可以开始增加有关您存储的每个设置的信息,并在设计更加规范化时重复使用此信息。然后,Settings类会像这样改变:

public List<PropertyValue> { get; set; } //replacing the dictionary

PropertyValue看起来像:

public class PropertyValue
{
    int Id { get; set; }

    public string Name { get; set; }

    public string Value { get; set; }

    public string PVType { get; set; } //Could be an enum

    public string DisplayFormat { get; set;

    private string _RawValue;

    public string Value{
        get{
          switch(PVType){
            case "DateTime":
                    return Convert.ToDateTime(_RawValue).ToString(DisplayFormat);
            break;
            case "Double":
                    return Convert.ToDouble(_RawValue).ToString(DisplayFormat);
            break;
            default:
                    return _RawValue;
          }
        }
        set{
            _RawValue = value;
        }
    }

}

需要改进Value方法以支持强大的错误处理(您还可以调查使用Convert.ChangeType来简化切换块)

此主题与您选择的主题一样简单或复杂;-)

<强>修改

关于维持它们;一个非常简单的GUI将允许用户以表格格式查看其所有属性。您可以考虑将UserDefinable = 0的行设置为readonly,否则用户可以编辑和添加行。您需要进行验证,尤其是对于重复的设置名称,例如。

最简单的方法是使用DataGrid;一个简单的模型可能看起来像这样:

Mockup of Grid Edit

更复杂的方法可能看起来像this

生成表单非常简单,只需将PropertyValue对象集合数据绑定到所选的网格解决方案即可。

答案 1 :(得分:1)

正如您可能发现的那样,您可以通过多种方式执行此操作,从Microsoft推荐到100%自行滚动。你现在正处于自己的频谱端,我认为无论如何都会更好。

无论如何,基本上你要找的是StateBag。松散类型和高度可配置的东西,它并不能很好地适用于RDBMS,但如果您已经安装了SQL Server,那么使用它可能是最简单的。

选项1:Redis

但是,另一种选择是为站点配置运行Redis之类的东西。您可以将它存储为Redis对象中的键/值对的集合,将其拉入JSON对象,然后将其转换为Hashtable,其中查找速度非常快,或者其他一些可散列集合。围绕集合的简单包装类就足够了。

Redis非常轻巧,在您的情况下不需要进行大量配置或维护。

选项2:SQL Server

你提到的方式实际上是一种非常好的方式,但不是每次都查询数据库,而是在缓存中放置一个强类型集合或Hashtable,让它每隔几个小时就会过期。

如果你基本上使用现有的东西,你可以像这样存储它:

Setting
    SettingId Int PK,
    Key NVarchar(50),
    Name NVarchar(50),
    Description NVarchar(1000),
    Type NVarchar(50),
    Priority Int

Configuration
    SiteId Int,
    SettingId Int FK(Setting),
    SettingValue NVarchar(1000)

不要将live / test / dev之类的东西存储在数据库中。您需要拥有完全独立的数据库和服务器来区分live / test / dev / stage / QA等。

答案 2 :(得分:1)

我现在通过创建一个具有所有设置的类来实现这一点,如

class Setting
{
GUID siteGuid{get; set;}
//other settings
}

然后我创建了一个像这样的静态类SettingManager

public static class SettingManager

{
private ConcurrentDictionary<GUID,Setting> settings= new ConcurrentDictionary<GUID,Setting>;
GetSetting(Guid siteGUID)
{
settings.TryGet(siteGuid);
Lastrefreshed = DateTime.Now;
//other code
}
Private DateTime LastRefreshedOn = DateTime.Now;
public void PopulateSetingsDic()
{
//populate the settings dictionary by getting the values from the database
}
}


现在代码中的任何地方都包含命名空间并使用设置 您可以使用application_start变量在lastRefreshedOn中填充一次或每个时间间隔的设置 它会很快,因为你拥有记忆中的所有设置。

如果你想要你应该能够动态添加设置那么你可以使用ExpandoObject并使用数据库中的列名动态添加设置或只是映射
那么你将能够通过将设置ExpandoObject转换为IDictionary<string,object>来使用设置这里的字符串可以是转换后的字符串GUID

编辑: - http://zippedtech.blogspot.in/2012/04/dynaminism-in-net-40.html检查链接..我添加了一个新帖子来解决此类问题。

答案 3 :(得分:0)

如果我正确理解您的问题,那么您正在寻找集中式配置框架。对于配置&amp;服务器管理我通常会建议ChefPuppet但是对于ASP.NET,我做了一些快速的Google搜索,看起来基于WCF的Configuration Service可能会为你做这个技巧。我链接的文档是.NET StockTrader 5 Sample Application中使用的配置服务的分步教程。

答案 4 :(得分:0)

我会使用xml,创建一个可以将xpath作为“键”的类     Ex.     MySett.get("//level1/mysetting")     or even     MySett.get("//mysetting")

每个人都可以返回一个集合,只是第一个集合,或者你想要的东西。 你甚至可以超载。 我喜欢xml,因为它具有很好的灵活性,而且为了减少代码,只需编写一个类。 缺点,您需要在应用程序启动时加载文档并在关闭时保存。 这是vb代码中的示例类。 (c代码仍然非常相似,我只是使用了vb,因为它在时间上升了

Imports System.Xml
Public Class XSett
Public xml As XmlDocument
Public Overloads Function gett(ByVal xp As String)
    Return CType(xml.SelectSingleNode(xp), XmlElement).InnerXml
    'by using inner xml, you can have either text setting 
    'or more markup that you might need for another function
    'your choice.  you could even cast it into another instance
    'of this class
End Function
Public Overloads Function gett(ByVal xp As String, ByVal sel As Integer)
    Return CType(xml.SelectNodes(xp)(sel), XmlElement).InnerXml
    'here, you can have multiple and choose the one you want
End Function
Public Overloads Sub gett(ByVal xp As String, ByRef col As Collection)
    Dim i As Integer
    Dim nds = xml.SelectNodes(xp)
    For i = 0 To nds.Count - 1
        col.Add(CType(nds(i), XmlElement).InnerXml)
    Next
    'Creted an entire collection of elemens.
    'i used vb's "collection" object, but any collection would do
End Sub
Public Overloads Sub sett(ByVal ap As String, ByVal name As String, ByVal data As String)
    'assume add here.
    'ap asks for existing parent element. eg: //guids
    'name ask for name of setting element
    Dim ts = xml.CreateElement(name)
    ts.InnerXml = data
    If ap = "" Then 'we assume document element
        xml.DocumentElement.AppendChild(ts)
    Else
        Dim p = CType(xml.SelectSingleNode(ap), XmlElement)
        p.AppendChild(ts)
    End If
End Sub
Public Overloads Sub sett(ByVal xp As String, ByVal sel As Integer, ByVal data As String)
    'just change existing setting
    CType(xml.SelectNodes(xp)(sel), XmlElement).InnerXml = data
End Sub
'naturally you can expand infinitely if needed
End Class