2014年5月8日星期四

ASP.NET Web API中的依赖注入 - 小白哥哥

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
ASP.NET Web API中的依赖注入 - 小白哥哥  阅读原文»

什么是依赖注入

依赖,就是一个对象需要的另一个对象,比如说,这是我们通常定义的一个用来处理数据访问的存储,让我们用一个例子来解释,首先,定义一个领域模型如下:

namespace Pattern.DI.MVC.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}

然后是一个用于实例的简单存储类:

namespace Pattern.DI.MVC.Models
{
public class ProductContext
{
public List<Product> Products { get; internal set; }


public ProductContext()
{
Products.Add(new Product() {Id = 1, Name = "苍阿姨", Price = 100});
Products.Add(new Product() {Id = 2, Name = "武藤奶奶", Price = 200});
Products.Add(new Product() {Id = 3, Name = "小泽姐姐", Price = 300});
}
}



public class ProductRepository
{
private ProductContext context=new ProductContext();


public IEnumerable<Product> GetAll()
{
return context.Products;
}

public Product GetById(int id)
{
return context.Products.FirstOrDefault(p => p.Id == id);
}
}
}

现在,我们定义一个ASP.NET Web API控制器来支持对Product实体集的GET请求:

namespace Pattern.DI.MVC.Controllers
{
public class ProductController : ApiController
{
private readonly ProductRepository productRepository=new ProductRepository();

public IEnumerable<Product> Get()
{
return productRepository.GetAll();
}

public Product Get(int id)
{
return productRepository.GetById(id);
}
}
}

现在注意到,这个控制器依赖了"ProductRepository"这个类,我们在类中实例化了ProductRepository,这就是设计的"坏味道"了,因为如下几个原因:

  • 假如你想要使用另外一个实现替换ProductRepository,你还要去修改ProductController类;
  • 假如ProductRepository存在依赖,你必须在ProductController中配置他们,对于一个拥有很多控制器的大项目来说,你就配置工作将深入到任何可能的地方;
  • 这是很难去做单元测试的因为控制器中硬编码了对数据库的查询,对于一个单元测试,你可以在没有确切设计之前,使用一个仿制的桩存储体。

我们可以使用注入一个ProductRepsoitory来解决这个问题,首先重构ProductRepository的方法到一个接口中:

namespace Pattern.DI.MVC.Models
{

public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product GetById(int id);
}


public class ProductRepository:IProductRepository
{
private ProductContext context = new ProductContext();


public IEnumerable<Product> GetAll()
{
return context.Products;
}

public Product GetById(int id)
{
return context.Products.FirstOrDefault(p => p.Id == id);
}
}
}
然后在ProductC0ntroller中使用参数传入IProductRepository:
namespace Pattern.DI.MVC.Controllers
{
public class ProductController : ApiController
{
private readonly IProductRepository productRepository;


public ProductController(IProductRepository productRepository)
{
this.productRepository = productRepository;
}

public IEnumerable<Product> Get()
{
return productRepository.GetAll();
}

public Product Get(int id)
{
return productRepository.GetById(id);
}
}
}

这个示例使用了构造器注入,你同样可以使用设置器注入的方式,ASP.NET Web API在为请求映射了路由之后创建控制器,而且现在他不知道任何关于IProductRepository的细节,这是通过API依赖器解析到的。

ASP.NET Web API依赖解析器

ASP.NET Web API定义了一个IDependencyResolever用来解析依赖项目,以下是这个接口的定义:

public interface IDependencyResolver : IDependencyScope, IDisposable
{
IDependencyScope BeginScope();
}

public interface IDependencyScope : IDisposable
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}

这个接口有两个方法

  • GetService为一个类型创建一个实例;
  • GetServices为一个特定的类型创建一个实例集合

这个接口继承自IDependencyScope并且添加了BeginScope方法,在这篇文章接下来将讨论这个方法。

当ASP.NET Web API创建一个controller实例的时候,它首先调用IDependencyResolver的GetService方法,传回一个Controller实例,你可以使用一个扩展的钩子去创建控制器并且解析依赖。假如GetService方法返回NULL,ASP.NET Web API将查找一个无参的构造函数。

使用Unity解析依赖

虽然你可以重头开始写一个IDenpendencyResolver的实现,但是这个接口已经设计了可以作为ASP.NET Web API和IoC工具的桥梁。

IoC容器是一个用来管理依赖项目的组建,你可以在其中注册类型,在使用的时候创建对象,IoC容易自动解析出依赖的关系,许多IoC容器允许你在对象的生命周期中进行控制。

首先在项目中使用NuGet Package Manage Console安装Unity,关于Unity的介绍可以点击这里查看详细。

Install-Package Unity

以下是一个使用Unity容器对IDependencyResolver的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Practices.Unity;
using System.Web.Http.Dependencies;

namespace Pattern.DI.MVC.Models
{
public class UnityResolver : IDependencyResolver
{
protected IUnityContainer container;

public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}

public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException)
C++的性能C#的产能?! - .Net Native 系列《三》:.NET Native部署测试方案及样例 - 小九的学堂  阅读原文»

  之前一文《c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥》 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方信息的翻译开始做起。

  此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题。本文是第三个主题:.NET Native部署测试及样例

  向导文链接:C++的性能C#的产能?! - .Net Native 系列:开发向导

  [小九的学堂,致力于以平凡的语言描述不平凡的技术。如要转载,请注明来源:小九的学堂cnblogs.com/xfuture]


    原文:.NET Native Getting Started

 .NET Native部署测试及样例

.NET Framework 4.5

    

Note 小贴士

这个主题依赖于预发行的.net native开发者预览版。下载地址: Microsoft Connect website. 友情提示需要注册..

  

   开发流程移步步骤《二 . NET Native开发流程详解》

  部署及测试:


  

  当你开发流程结束,配置成功.Net Native Tools并且更新了运行时指令文件, 你可以开始重建和部署测试你的应用。.Net Native 原生的二进制文件放置在项目属性配置的生成输出目录的子目录ILC.out中。若没有这个文件夹,则说明该项目并没有被.Net Native成功编译。

  .Net Native 目前支持x64和arm框架,所以你只能部署该应用程序到相应的设备中。之后您需要在该设备上进行测试和解决故障。

  如果你的应用程序不能正常运行(尤其是运行时抛出了MissingMetadataException或MissingInteropDataException异常),您需要按照下一块内容:手动解决缺少元数据异常。启动First-chance exception(程序产生了异常并被捕获,继续运行) 可以帮助您发现这些bugs。

  经过了测试和调试错误,您已经对上述的异常进行很好的处理后,应该测试一些性能优化情况。要做性能测试,必须要将项目构建从debug改成release,测其发行版本。

  常见的问题:


  .net native 最常见的问题就是遇到MissingMetadataException了。该异常会导致程序出现不可预测的行为甚至崩掉。本节来讨论如何通过在运行时添加指令来进行调试和解决此类型的异常。有关运行指令格式相关的信息,请参照:Runtime Directives (rd.xml) Configuration File Reference.当你添加好运行指令后,你可以部署和测试你的应用程序并且解决元数据的相关异常。

  注意: 添加指令并不应该更改底层代码,应该在较高层次集中处理。建议在命名空间或者类型初添加指令,而不是每一块代码段。添加指令后机器码项目编译时间也会增长。

  当处理元数据丢失的异常时,应考虑如下问题:

    1. 应用程序做了什么导致了这个异常? 例如它是否是数据绑定?还是序列化和反序列化拿到数据?或者是反射?

    2. 这个异常是否是普遍的?例如当序列化一个对象时产生了该异常,你就需要每一个序列化的部分都来检查测试一下了。

    3. 尽量少的用反射。重构代码,将反射部分尽可能的更换机制。

  测试中遇到问题的样例:

  


    Example: Handling Exceptions When Binding Data

    这个例子是一个.Net Native项目当绑定数据时遇到的MissingMetadataException异常。异常信息如下:

    

    This operation cannot be carried out as metadata for the following type was removed for performance reasons: App.ViewModels.MainPageVM

    下面是相关栈调用的信息:

Reflection::Execution::ReflectionDomainSetupImplementation.CreateNonInvokabilityException+0x238
Reflection::Core::ReflectionDomain.CreateNonInvokabilityException+0x2e
Reflection::Core::Execution::ExecutionEnvironment.+0x316
System::Reflection::Runtime::PropertyInfos::RuntimePropertyInfo.GetValue+0x1cb
System::Reflection::PropertyInfo.GetValue+0x22
System::Runtime::InteropServices::WindowsRuntime::CustomPropertyImpl.GetValue+0x42
App!$66_Interop::McgNative.Func_IInspectable_IInspectable+0x158
App!$66_Interop::McgNative::__vtable_Windows_UI_Xaml_Data__ICustomProperty.GetValue__STUB+0x46
Windows_UI_Xaml!DirectUI::PropertyProviderPropertyAccess::GetValue+0x3f
Windows_UI_Xaml!DirectUI::PropertyAccessPathStep::GetValue+0x31
Windows_UI_Xaml!DirectUI::PropertyPathListener::ConnectPathStep+0x113

    这个应用是WPF程序,Xaml中的空间元素(View)通过绑定和PropertyInfo.GetValue来通过类型拿到上下文数据载体(ViewModel)属性的值。但是这个属性的元数据丢失了,所以报出了元数据丢失的异常。

    解决方案:

    第一步设置该对象是可序列化的,保证其是可访问的:

    

<Type Name="App.ViewModels.MainPageVM" Serialize="Required Public" />

    第二步考虑其是否为普遍问题。在本例中所有viewmodel都存在该问题,所以不应一次只修改一个地方后就继续进行调试。由于所有viewmodel都在app.viewmodels里所以可以进行如下设置:

    

<Namespace Name="App.ViewModels " Serialize="Required Public" />

    由于数据绑是基于反射的,所以无法通过更改代码方式来避免反射。但有一些方法可以做到在编译时就将view和viewmodel关联起来,不依赖运行时来保存元数据。例如可以设置Windows.UI.Xaml.Data.BindableAttribute 属性,它会让编译器在编译时关联上下文,避免使用运行时Default.rd.xml序列化来拿元数据。

    有关rd.xml可以参阅Runtime Directives (rd.xml) Configuration File Reference.


    在开发中可能会遇到更多的问题,可以MSDN论坛来进行发问。

    若想查看该系列前几篇可以点击: 向导

    

没有评论:

发表评论