2017-08-07 54 views
2

我一直在努力将我的紧密耦合方法转换为可以进行单元测试的方法,并在此处寻求一些建议。我现在有我的方法通过它的单元测试感谢一些建议 - 但是我现在发现我无法从我的应用程序调用该方法。我以前从控制器访问我GetAllProductsFromCSV()方法如下:通过依赖注入执行我的类函数

public ActionResult Index() 
    { 
     var products = new ProductsCSV(); 
     List<ProductItem> allProducts = products.GetAllProductsFromCSV(); 

     foreach (var product in allProducts) 
     { 
      if (string.IsNullOrEmpty(product.ImagePath)) 
      { 
       product.ImagePath = "blank.jpg"; 
      } 
     } 

     return View(allProducts); 
    } 

的方法如下:

public class ProductsCSV 
{ 
    public List<ProductItem> GetAllProductsFromCSV() 
    { 
     var productFilePath = HttpContext.Current.Server.MapPath(@"~/CSV/products.csv"); 

     String[] csvData = File.ReadAllLines(productFilePath); 

     List<ProductItem> result = new List<ProductItem>(); 

     foreach (string csvrow in csvData) 
     { 
      var fields = csvrow.Split(','); 
      ProductItem prod = new ProductItem() 
      { 
       ID = Convert.ToInt32(fields[0]), 
       Description = fields[1], 
       Item = fields[2][0], 
       Price = Convert.ToDecimal(fields[3]), 
       ImagePath = fields[4], 
       Barcode = fields[5] 
      }; 
      result.Add(prod); 
     } 
     return result; 
    } 
} 

我现在做的ProductCSV类以下变化:

public class ProductsCSV 
{ 
    private readonly IProductsCsvReader reader; 

    public ProductsCSV(IProductsCsvReader reader = null) 
    { 
     this.reader = reader; 
    } 

    public List<ProductItem> GetAllProductsFromCSV() 
    { 
     var productFilePath = @"~/CSV/products.csv"; 
     var csvData = reader.ReadAllLines(productFilePath); 
     var result = parseProducts(csvData); 
     return result; 
    } 


    private List<ProductItem> parseProducts(String[] csvData) 
    { 
     List<ProductItem> result = new List<ProductItem>(); 

     foreach (string csvrow in csvData) 
     { 
      var fields = csvrow.Split(','); 
      ProductItem prod = new ProductItem() 
      { 
       ID = Convert.ToInt32(fields[0]), 
       Description = fields[1], 
       Item = fields[2][0], 
       Price = Convert.ToDecimal(fields[3]), 
       ImagePath = fields[4], 
       Barcode = fields[5] 
      }; 
      result.Add(prod); 
     } 
     return result; 
    } 

随着下面的类&接口:

public class DefaultProductsCsvReader : IProductsCsvReader 
{ 
    public string[] ReadAllLines(string virtualPath) 
    { 
     var productFilePath = HostingEnvironment.MapPath(virtualPath); 
     String[] csvData = File.ReadAllLines(productFilePath); 
     return csvData; 
    } 
} 

public interface IProductsCsvReader 
{ 
    string[] ReadAllLines(string virtualPath); 
} 

正如我所说,单元测试方法上成功GetAllProductsFromCSV现在完成但是当我尝试从我的访问控制器的方法,我得到一个NullReferenceException在reader.ReadAllLines内GetAllProductsFromCSV调用。当我尝试从控制器内创建ProductsCSV实例时,我感觉很有意义 - 我没有传入任何参数...但是该类的构造函数请求一个IProductsCsvReader。我无法弄清楚的是,我现在如何实际调用该方法?我希望这是明确的?

+1

你在哪里注册你的接口和具体实现?看起来那部分不起作用 – Shyju

回答

5

首先让更新ProductsCSV有一个背衬接口

public interface IProductsCSV { 
    List<ProductItem> GetAllProductsFromCSV(); 
} 

public class ProductsCSV : IProductsCSV { 
    //...other code removed for brevity 
} 

控制器伤口现在依赖于上面介绍的抽象,从原来的控制器的具体实施解耦它。虽然是一个简化的例子,但它可以让控制器更容易维护并单独进行单元测试。

public class ProductsController : Controller { 
    private readonly IProductsCSV products; 

    public ProductsController(IProductsCSV products) { 
     this.products = products; 
    } 

    public ActionResult Index() { 
     List<ProductItem> allProducts = products.GetAllProductsFromCSV();  
     foreach (var product in allProducts) { 
      if (string.IsNullOrEmpty(product.ImagePath)) { 
       product.ImagePath = "blank.jpg"; 
      } 
     }  
     return View(allProducts); 
    } 
} 

请注意,除了如何创建products之外,动作与之前的动作完全匹配。

最后,现在控制器已经重构为依赖性反转,需要将框架配置为能够在请求时将相关性注入到控制器中。

最后,您可以使用您所选择的库,但在这个例子中,我使用它们在文档中使用了什么

ASP.NET MVC 4 Dependency Injection

没关系的版本。实施是可以转让的。

在上面的文档中,他们使用Unity为他们的IoC容器。有许多容器库可供使用,以便搜索您喜欢的并使用它的那个。

public static class BootStrapper { 

    private static IUnityContainer BuildUnityContainer() { 
     var container = new UnityContainer(); 

     //Register types with Unity 
     container.RegisterType<IProductsCSV , ProductsCSV>(); 
     container.RegisterType<IProductsCsvReader, DefaultProductsCsvReader>(); 

     return container; 
    } 

    public static void Initialise() { 
     //create container 
     var container = BuildUnityContainer(); 
     //grab the current resolver 
     IDependencyResolver resolver = DependencyResolver.Current; 
     //create the new resolver that will be used to replace the current one 
     IDependencyResolver newResolver = new UnityDependencyResolver(container, resolver); 
     //assign the new resolver. 
     DependencyResolver.SetResolver(newResolver); 
    } 
} 

public class UnityDependencyResolver : IDependencyResolver { 
    private IUnityContainer container; 
    private IDependencyResolver resolver; 

    public UnityDependencyResolver(IUnityContainer container, IDependencyResolver resolver) { 
     this.container = container; 
     this.resolver = resolver; 
    } 

    public object GetService(Type serviceType) { 
     try { 
      return this.container.Resolve(serviceType); 
     } catch { 
      return this.resolver.GetService(serviceType); 
     } 
    } 

    public IEnumerable<object> GetServices(Type serviceType) { 
     try { 
      return this.container.ResolveAll(serviceType); 
     } catch { 
      return this.resolver.GetServices(serviceType); 
     } 
    } 
} 

你会叫在启动时的代码上面的引导程序。

例如

protected void Application_Start() { 
    AreaRegistration.RegisterAllAreas();  
    WebApiConfig.Register(GlobalConfiguration.Configuration); 
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 
    RouteConfig.RegisterRoutes(RouteTable.Routes); 
    BundleConfig.RegisterBundles(BundleTable.Bundles); 

    Bootstrapper.Initialise(); //<-- configure DI 

    AppConfig.Configure(); 
} 

当过框架具有创建ProductsController它会知道如何初始化和注入控制器的依赖所以现在。