我的应用程序的app.config文件中有一些键,用于存放电子邮件设置。我目前正在开发的应用程序有几个地方我需要发送一封电子邮件(主要是在错误检查中让我们知道发生了一些不好的事情)。为了更容易在我需要的地方获取该设置(而不是回到app.config查找要在ConfigurationManager.AppSettings["SomeKey"].ToString()
中使用的文本字符串,我创建了一个简单的静态类来存放一些返回的只读属性我需要的价值。这是基本结构
internal static Class myClass
{
internal static string setting1
{
get { return ConfigurationManager.AppSettings["SomeKey"].ToString(); }
}
internal static string setting2
{
get { return ConfigurationManager.AppSettings["SomeOtherKey"].ToString(); }
}
}
我这样做是否可以接受?我找不到这个问题,但不禁想到我错过了什么。
其他信息:
我知道简单地使用Settings.Settings文件的方法。 我不能使用Settings.Settings文件,因为我工作的地方有代码策略,这会让我不必要这样做。
编辑: “可以接受”是指它通常被认为是一个好主意吗?我意识到整个社区都不能推测我工作的可接受性。
答案 0 :(得分:12)
它不错,但通过小调整可能会更好。 (好吧,也许不是很小。)
如果您的类依赖于静态类,并且该类依赖于AppSettings
,那么您的类仍然会耦合到AppSettings
。换句话说,他们没有其他办法可以获得他们的设置。如果你想对你的班级进行单元测试,这将是一个挑战。这意味着您的单元测试项目必须具有相同的<appSettings>
部分。但是,如果您想要两个使用两个不同值进行设置的测试呢?这是不可能的。或者,如果您有一个需要设置的类,并且在几年内您想在ASP.NET Core应用程序中使用它并且没有web.config,该怎么办?然后它根本不会工作。
为避免这样做,你可以这样做:
public interface IMySettings
{
string Setting1 {get;}
string Setting2 {get;}
}
public class MyConfigurationSettings : IMySettings
{
public string Setting1
{
get { return ConfigurationManager.AppSettings["SomeKey"].ToString(); }
}
public string Setting2
{
get { return ConfigurationManager.AppSettings["SomeOtherKey"].ToString(); }
}
}
然后,在需要设置的类中:
public class ClassThatNeedsSettings
{
private readonly IMySettings _settings;
public ClassThatNeedsSettings(IMySettings settings)
{
_settings = settings;
}
}
然后,当你创建ClassThatNeedsSettings
的实例时,你传递了一个实现IMySettings
的类的实例,该类将使用它来检索设置。当您的应用程序运行时,您会传递MyConfigurationSettings
,以便您的值来自AppSettings
。但是ClassThatNeedsSettings
永远都不知道。它只知道它使用的是IMySettings
。
这被称为&#34;依赖注入。&#34; ClassThatNeedsSettings
取决于IMySettings
,因此您需要注入&#34;它进入构造函数。那样ClassThatNeedsSettings
可以获得它所需要的东西。它不负责创建它。
如果你想进行单元测试,你可以&#34;模拟&#34; IMySettings
。也就是说,您可以创建实现该接口的其他类,并使用它们传递您要测试的任何值。甚至有像Moq这样的工具可以帮助你创建这些类。
通常,如果您使用依赖注入,您还将使用Windsor,Unity,Autofac或其他框架来管理为您创建对象。感觉有点像诱饵和开关在最后介绍它因为它需要更多的学习并且可能改变应用程序的配置方式。但这就是我们使用它的原因,以防止一个类对另一个类具有绝对依赖性,这使得它不那么灵活,难以测试。
答案 1 :(得分:3)
这是一个可测试代码的示例,用于解决评论者的问题。顾虑:
ShipToAddress
如果您将设置存储在app.config中,请使用:
interface IEmailSettings {
string Server { get; }
string Sender { get; }
string[] Recipients { get; }
}
如果您将设置存储在数据库中,请使用以下命令:
class AppConfigEmailSettings : IEmailSettings {
public AppConfigEmailSettings() {
this.Server = ConfigurationManager.AppSettings["server"];
this.Sender = ConfigurationManager.AppSettings["sender"];
this.Recipients = ConfigurationManager.AppSettings["recipients"].Split(';');
}
public string Server { get; private set; }
public string Sender { get; private set; }
public string[] Recipients { get; private set; }
}
对于测试,您可以使用以下内容:
class DatabaseEmailSettings : IEmailSettings {
public DatabaseEmailSettings(string connectionString) {
//code to connect to database and retrieve settings
}
//you can use the same fields and properties from AppConfigEmailSettings
}
你明白了。此代码比您的代码更容易测试。此外,如果您将class MockSettings : IEmailSettings {
public string Server { get { return "localhost"; } }
public string Sender { get { return "sender@example.com" } }
public string[] Recipients { get { return new string[] { "r1@example.com" }; } }
}
注入发送电子邮件的代码中,您可以通过更改整个应用程序中的一行代码轻松更改存储电子邮件设置的方式。这是将IEmailSettings
对象实例化为IEmailSettings
或AppConfigEmailSettings
或其他内容的行。
答案 2 :(得分:1)
这里没有人可以真正定义什么是“可接受”或不符合你的情况。我认为你真正要问的是这是一个好主意还是它可能是一个问题。
简短的回答是可能可以接受一个简单的解决方案,但对于任何复杂的问题,这都是一个坏主意。静态类使单元测试非常困难(或不可能)。相反,正如您的评论中所提到的,更好的选择是定义一个具有GetSettings方法的接口。您可以使用类(非静态)实现它,该类只包含对DavidManager L在评论中提到的对ConfigurationManager.AppSettings的调用。
任何需要访问设置的类都应该将此接口传递给构造函数。您可以使用IoC容器或创建类的新实例以传入(再次依赖于复杂性),但更重要的是,您现在可以编写单元测试并传入一个具有自定义设置的类进行单元测试。