2013-03-24 35 views
0

创建用户配置文件的控制器正在使用配置文件视图模型,该模型的一部分是默认徽标(PNG)将显示和使用哪些内容,如果用户不选择上传他自己的。ASP.NET MVC放置常量的最佳位置(从TDD角度来看)

我创建了public static class Configuration,它保留了项目的所有常量。其中一个常量是将用于默认徽标的文件名和位置。

我把实际的文件名在Web.Config文件,而不是硬编码的常数以下几个原因:1。 文件名可以更改 2.文件位置可以改变 3.讨厌硬编码任何东西,当我知道它可能会改变

首先尝试通过调用ConfigurationManager.AppSettings来测试访问Web.Config的常量类上的控制器失败。这里的人们(非常公正地告诉我)这是另一个应该避免的依赖。另一方面,它仅仅是一个外部常量,我不喜欢像这样保持硬编码File f = new File("..\\Images\\Profile\\DefaultLogo.PNG");。没有人做这些事情,它应该是可配置的(意思是Web.Config - 请纠正我,如果我错了)。

你会如何处理这种情况?

编辑问题 让我重申这个问题: 它不是使用什么格式,而是如何架构处理控制器具有TDD可验证的困境,并保持了它的常量。 (目前我使用静态类,所以我必须通过所有常量控制器需要用手在构造函数中,它看起来并不像最好的选择)

  • 硬编码的常数(讨厌做)
  • 从控制器转移到视图模型(我不认为会解决TDD问题)
  • 必须在控制器中的另一个抽象接口(IConfiguration)与IRepository沿(相貌丑陋我) - 而且它不会工作因为配置类是静态的,不能实现接口
  • 我没有想到的东西,因此寻求好的想法。

非常感谢您

+0

如果将配置常量值作为XML文件存储,或者作为解决方案的一部分或在外部存储(但仍然可以在程序中仍可访问的位置),该怎么办?这样,您可以在XML文件中编辑配置值,而无需更改代码(避免对值进行硬编码)。 – 2013-03-24 12:52:05

+0

感谢您的回答。 XML是一个好主意,但是你知道,Web.Config也是XML。它不是使用什么格式,而是如何实现常量,使它们在代码外部,但仍然保持控制器可测试性(因为它们是外部的,它们引入依赖性) - 我正在为这个确切的困境而挣扎。 – 2013-03-24 12:55:21

回答

2

在控制器 另一个抽象接口(IConfiguration)与IRepository沿(相貌丑陋的我) - 而且它不会工作 因为配置类是静态的并且不能实现接口

大胆的声明并不完全正确。例如:

public interface IManageConfigurations 
{ 
    string DefaultFileLocation { get; } 
} 

public class ConfigurationManagerImpl : IManageConfigurations 
{ 
    public string DefaultFileLocation 
    { 
     get { return ConfigurationManager.AppSettings["DefaultFileLocation"]; } 
    } 
} 

public class MyController : Controller 
{ 
    private readonly IRepository _repository; 
    private readonly IManageConfigurations _config; 

    public MyController(IRepository repository, IManageConfigurations config) 
    { 
     _repository = repository; 
     _config = config; 
    } 
} 

我不认为这是一个丑陋的解决方案,但这主要是一种美学观点,这是主观的。在大多数情况下,我不认为一个类要揭示超注射反模式,直到它有大约5个或更多的接口依赖。此外,这几乎是单元可测试代码中依赖性解析的标准解决方案。您可以模拟出配置界面,就像您嘲笑仓库一样。以上是我如何使用web.config中保存的值解决依赖关系。

另一种解决方案是在控制器中对ConfigurationManager.AppSettings["key"]调用进行硬编码。然后,在您的单元测试项目中,您可以在app.config文件中设置相同的值。然后,单元测试运行器将查找app.config中的值,而Web服务器将在web.config中查找它们。

public class MyController : Controller 
{ 
    public ActionResult Index() 
    { 
     // the below will get the value from the unit test project's app.config 
     // when run as a unit test, but will get the value from web.config in server 
     var fileLocation = ConfigurationManager.AppSettings["DefaultFileLocation"]; 
    } 
} 

更新

如果你真的想保持字符串静态类,并有单元测试代码,你可以这样做:

public static class ConfigSettings 
{ 
    public static string DefaultFileLocation 
    { 
     get { return ConfigurationManager.AppSettings["DefaultFileLocation"]; } 
    } 
} 

然后,您可以使用静态从Controller内免费上课。但是,为了提供作为单元测试运行时的值,如上所述,您必须在单元测试项目的app.config文件中放置一个appSettings节点,这非常类似于您的web项目的web.config文件中的appSettings节点。单元测试运行器将从app.config中提取值,Web服务器将从web.config中提取值。

UnitTestProject \ App.config中

<configuration> 
    <appSettings> 
     <!-- use this value when executed in unit test runner --> 
     <add key="DefaultFileLocation" 
      value="C:\Users\me\test_files\test_file.png" /> 
    </appSettings> 
</configuration> 

MvcProject \ web.config文件(默认)

<configuration> 
    <appSettings> 
     <!-- use this value when executed locally in IIS Express --> 
     <add key="DefaultFileLocation" 
      value="C:\Users\me\Documents\Visual Studio 20xx\Projects\MyProj\App_Data\default_files\default_logo.png" /> 
    </appSettings> 
</configuration> 

MvcProject \ web.Release.config(变换)

<configuration> 
    <appSettings> 
     <!-- use this value when executed on live IIS production server --> 
     <add key="DefaultFileLocation" 
      value="E:\approot\siteroot\App_Data\default_files\default_logo.png" 
      xdt:Transform="SetAttributes" xdt:Locator="Match(key)" /> 
    </appSettings> 
</configuration> 

如果这没有回答你的问题,也许我还没有完全理解你以后的事情。

更新

我唯一缺少的解决方案中的一个部分 - 如何使用从控制器内的公共 静态类ConfigSettings?我应该将 这个静态类作为参数传递给构造函数吗?

不,你不需要做任何特别的事情。只要使用它作为一个类,就像你会使用静态ConfigurationManager

public ActionResult MyAction() 
{ 
    var customFilePath = GetCustomFilePath(); // may not be set 
    if (string.IsNullOrWhiteSpace(customFilePath)) // fall back to default 
     customFilePath = ConfigSettings.DefaultFilePath; 
} 

所以你的控制器行动正在对静态ConfigSettings类的依赖,但它不是在这种情况下,注射/热插拔依赖。当单元测试运行器调用此方法并且customFilePath为null/empty/whitespace时,它将进入ConfigSettings get方法,该方法将调用ConfigurationManager.AppSettings,该方法将在单元测试项目的app.config/appSettings中查找该值。

+0

谢谢!我必须指出,压力是在类是静态**意味着它不能实现接口。 *不*有这种类别的静态杀死它的目的 - 如果每次我需要接触常量,我需要创建一个类的实例?只是内联常量的开销太多了。思考? – 2013-03-24 14:13:12

+0

我最重要的想法是关于你的问题的语义,尤其是“常量”这个词。当我想到.NET和C#代码讨论中的常量时,我​​想到了'const'关键字。对我而言,一个常数是***从不改变***的东西。另一方面,如果在单元测试之间或跨不同部署(调试,QA,产品)之间存在可能更改的值,则它是配置设置,而不是常量。我会根据您的评论更新我的答案。 – danludwig 2013-03-24 14:19:18

+0

非常好的解决方案。非常感谢你。你做对了。我只在你的解决方案中缺少一块 - 如何从控制器内部使用'public static class ConfigSettings'? *我应该将这个静态类作为参数传递给构造函数*吗?你看我从哪里来?如果只是运行代码 - 它会选择正确的位置和文件,但是如果我运行测试,我怎么让我的控制器知道测试'ConfigSettings'静态类(这正是我的问题所在)。谢谢! – 2013-03-24 15:44:51

相关问题