0%

AutoMapper小结 - 呆河马 - 博客园

Excerpt

先来看看我所虚拟的领域模型。这一次我定义了一个书店(BookStore): 书店有自己的地址(Address): 同时书店里放了N本书(Book): 每本书都有出版商信息(Publisher): 每本书可以有最多2个作者的信息(Author): 每个作者都有自己的联系方式(ContactInfo):


  呆河马  阅读(43023)  评论()  编辑  收藏  举报

1
<span>一些orm框架,在用到Entity的时候有一些开源代码用到了automapper(如:nopcommence),将数据对象转成DTO。比如在ORM中,与数据库交互用的Model模型是具有很多属性变量方法神马的。而当我们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各种考虑),我们不希望直接将这个Model模型传递给它们,这时我们会创建一个贫血模型来保存数据并传递。什么是贫血模型?贫血模型(DTO,Data Transfer Object)就是说只包含属性什么的,只能保存必须的数据,没有其它任何的多余的方法数据什么的,专门用于数据传递用的类型对象。在这个创建的过程中,如果我们手动来进行,就会看到这样的代码:</span>
1
<span>A a=new A();</span><br><span>a.X1=b.X1;</span><br><span>a.X2=b.X2;</span><br><span>...</span><br><span>...</span><br><span>...</span><br><span>return a;</span>
1
<span>此时,AutoMapper可以发挥的作用就是根据A的模型和B的模型中的定义,自动将A模型映射为一个全新的B模型。基于访问性的控制或从模型本身上考虑。对外开放的原则是,尽量降低系统耦合度,否则内部一旦变更外部所有的接口都要跟随发生变更;另外,系统内部的一些数据或方法并不希望外部能看到或调用。类似的考虑很多,只是举个例子。系统设计的原则是高内聚低耦合,尽量依赖抽象而不依赖于具体。这里感觉automapper就是使数据库实体对一个外部调用实体的转换更简便(不用一个属性一个属性的赋值)。</span>
1
<span>例如1:数据库里面有用户信息表,供别的系统调用,提供了数据接口。如果直接暴露了数据库层的表结构的话,会对系统本身产生依赖。具体表现在,假定现在因为某种需要,为用户信息增加了十个字段的信息,那么,如果不进行类型映射的话,会导致所有基于此用户数据结构的模块集体挂掉(接口约定变更)。而如果使用了映射的话,我们可以在内部进行转换,保持原有接口不变并提供新的更全面的接口,这是保证系统的可维护性和可迁移性。</span><br><br><span>例如2:一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中。相反,当用户请求数据时,我们又需要做相反的工作:将从数据库中查询出来的领域模型以相反的方式转换成Dto再呈现给用户。使用AutoMapper(一个强大的Object-Object Mapping工具),来实现这个转换。</span>
一 ,应用场景

 先来看看我所虚拟的领域模型。这一次我定义了一个书店(BookStore): 

复制代码

1
2
3
4
5
6
<span>public</span> <span>class</span><span> BookStore  
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> List&lt;Book&gt; Books { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> Address Address { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

书店有自己的地址(Address): 

复制代码

1
2
3
4
5
6
7
<span>public</span> <span>class</span><span> Address  
{
</span><span>public</span> <span>string</span> Country { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> City { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Street { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> PostCode { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

同时书店里放了N本书(Book): 

复制代码

1
2
3
4
5
6
7
8
9
10
11
<span>public</span> <span>class</span><span> Book  
{
</span><span>public</span> <span>string</span> Title { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Description { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Language { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>decimal</span> Price { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> List&lt;Author&gt; Authors { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> DateTime? PublishDate { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> Publisher Publisher { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>int</span>? Paperback { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

每本书都有出版商信息(Publisher): 

1
2
3
4
<span>public</span> <span>class</span><span> Publisher  
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
} </span>

每本书可以有最多2个作者的信息(Author): 

复制代码

1
2
3
4
5
6
<span>public</span> <span>class</span><span> Author  
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Description { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> ContactInfo ContactInfo { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

每个作者都有自己的联系方式(ContactInfo): 

复制代码

1
2
3
4
5
6
<span>public</span> <span>class</span><span> ContactInfo  
{
</span><span>public</span> <span>string</span> Email { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Blog { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Twitter { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

差不多就是这样了,一个有着层级结构的领域模型。 再来看看我们的Dto结构。 在Dto中我们有与BookStore对应的BookStoreDto: 

复制代码

1
2
3
4
5
6
 <span>public</span> <span>class</span><span> BookStoreDto  
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> List&lt;BookDto&gt; Books { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> AddressDto Address { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

其中包含与Address对应的AddressDto: 

复制代码

1
2
3
4
5
6
7
<span>public</span> <span>class</span><span> AddressDto  
{
</span><span>public</span> <span>string</span> Country { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> City { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Street { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> PostCode { <span>get</span>; <span>set</span><span>; }
}</span>

复制代码

以及与Book相对应的BookDto: 

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<span>public</span> <span>class</span><span> BookDto  
{
</span><span>public</span> <span>string</span> Title { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Description { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Language { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>decimal</span> Price { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> DateTime? PublishDate { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Publisher { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>int</span>? Paperback { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> FirstAuthorName { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> FirstAuthorDescription { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> FirstAuthorEmail { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> FirstAuthorBlog { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> FirstAuthorTwitter { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> SecondAuthorName { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> SecondAuthorDescription { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> SecondAuthorEmail { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> SecondAuthorBlog { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> SecondAuthorTwitter { <span>get</span>; <span>set</span><span>; }
} </span>

复制代码

注意到我们的BookDto”拉平了“整个Book的层级结构,一个BookDto里携带了Book及其所有Author、Publisher等所有模式的数据。正好我们来看一下Dto到Model的映射规则。 

(1)BookStoreDto –> BookStore

BookStoreDto中的字段BookStore中的字段
NameName
BooksBooks
AddressAddress

(2)AddressDto –> Address

AddressDto中的字段Address中的字段
CountryCountry
CityCity
StreetStreet
PostCodePostCode

(3)BookDto -> Book。 BookDto中的一些基本字段可以直接对应到Book中的字段。

BookDto中的字段Book中的字段
TitleTitle
DescriptionDescription
LanguageLanguage
PricePrice
PublishDatePublishDate
PaperbackPaperback

每本书至多有2个作者,在BookDto中分别使用”First“前缀和”Second“前缀的字段来表示。因此,所有FirstXXX字段都将映射成Book的Authors中的第1个Author对象,而所有SecondXXX字段则将映射成Authors中的第2个Author对象。

BookDto中的字段Book中的Authors中的第1个Author对象中的字段
FirstAuthorNameName
FirstAuthorDescriptionDescription
FirstAuthorEmailContactInfo.Email
FirstAuthorBlogContactInfo.Blog
FirstAuthorTwitterContactInfo.Twitter

注意上表中的ContactInfo.Email表示对应到Author对象的ContactInfo的Email字段,依次类推。类似的我们有:

BookDto中的字段Book中的Authors中的第2个Author对象中的字段
SecondAuthorNameName
SecondAuthorDescriptionDescription
SecondAuthorEmailContactInfo.Email
SecondAuthorBlogContactInfo.Blog
SecondAuthorTwitterContactInfo.Twitter

最后还有Publisher字段,它将对应到一个独立的Publisher对象。

BookDto中的字段Publisher中的字段
PublisherName

差不多就是这样了,我们的需求是要实现这一大坨Dto到另一大坨的Model之间的数据转换。

二,以Convention方式实现零配置的对象映射

在上一篇文章中我们构造出了完整的应用场景,包括我们的Model、Dto以及它们之间的转换规则。下面开始我们的AutoMapper之旅了。

引用:AutoMapper uses a convention-based matching algorithm to match up source to destination values.

我们要做的只是将要映射的两个类型告诉AutoMapper(调用Mapper类的Static方法CreateMap并传入要映射的类型): 

1
Mapper.CreateMap&lt;AddressDto, Address&gt;();  

然后就可以交给AutoMapper帮我们搞定一切了: 

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
AddressDto dto = <span>new</span><span> AddressDto  
{
Country </span>= <span>"</span><span>China</span><span>"</span><span>,
City </span>= <span>"</span><span>Beijing</span><span>"</span><span>,
Street </span>= <span>"</span><span>Dongzhimen Street</span><span>"</span><span>,
PostCode </span>= <span>"</span><span>100001</span><span>"</span><span>
};
Address address </span>= Mapper.Map&lt;AddressDto,Address&gt;<span>(Dto);
address.Country.ShouldEqual(</span><span>"</span><span>China</span><span>"</span><span>);
address.City.ShouldEqual(</span><span>"</span><span>Beijing</span><span>"</span><span>);
address.Street.ShouldEqual(</span><span>"</span><span>Dongzhimen Street</span><span>"</span><span>);
address.PostCode.ShouldEqual(</span><span>"</span><span>100001</span><span>"</span>);

复制代码

如果AddressDto中有值为空的属性,AutoMapper在映射的时候会把Address中的相应属性也置为空: 

复制代码

1
2
3
4
5
6
7
Address address = Mapper.Map&lt;AddressDto,Address&gt;(<span>new</span><span> AddressDto  
{
Country </span>= <span>"</span><span>China</span><span>"</span><span>
});
address.City.ShouldBeNull();
address.Street.ShouldBeNull();
address.PostCode.ShouldBeNull();</span>

复制代码

甚至如果传入一个空的AddressDto,AutoMapper也会帮我们得到一个空的Address对象。 

1
2
Address address = Mapper.Map&lt;AddressDto,Address&gt;(<span>null</span><span>);  
address.ShouldBeNull(); </span>

千万不要把这种Convention的映射方式当成“玩具”,它在映射具有相同字段名的复杂类型的时候还是具有相当大的威力的。 
例如,考虑我们的BookStoreDto到BookStore的映射,两者的字段名称完全相同,只是字段的类型不一致。如果我们定义好了BookDto到Book的映射规则,再加上上述Convention方式的AddressDto到Address的映射,就可以用“零配置”实现BookStoreDto到BookStore的映射了: 

1
2
3
4
IMappingExpression&lt;BookDto, Book&gt; expression = Mapper.CreateMap&lt;BookDto,Book&gt;<span>();  
</span><span>//</span><span> Define mapping rules from BookDto to Book here </span>
Mapper.CreateMap&lt;AddressDto, Address&gt;<span>();
Mapper.CreateMap</span>&lt;BookStoreDto, BookStore&gt;();

然后我们就可以直接转换BookStoreDto了: 

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BookStoreDto dto = <span>new</span><span> BookStoreDto  
{
Name </span>= <span>"</span><span>My Store</span><span>"</span><span>,
Address </span>= <span>new</span><span> AddressDto
{
City </span>= <span>"</span><span>Beijing</span><span>"</span><span>
},
Books </span>= <span>new</span> List&lt;BookDto&gt;<span>
{
</span><span>new</span> BookDto {Title = <span>"</span><span>RESTful Web Service</span><span>"</span><span>},
</span><span>new</span> BookDto {Title = <span>"</span><span>Ruby for Rails</span><span>"</span><span>},
}
};
BookStore bookStore </span>= Mapper.Map&lt;BookStoreDto,BookStore&gt;<span>(dto);
bookStore.Name.ShouldEqual(</span><span>"</span><span>My Store</span><span>"</span><span>);
bookStore.Address.City.ShouldEqual(</span><span>"</span><span>Beijing</span><span>"</span><span>);
bookStore.Books.Count.ShouldEqual(</span><span>2</span><span>);
bookStore.Books.First().Title.ShouldEqual(</span><span>"</span><span>RESTful Web Service</span><span>"</span><span>);
bookStore.Books.Last().Title.ShouldEqual(</span><span>"</span><span>Ruby for Rails</span><span>"</span>);

复制代码

实现BookDto到Book之间的转换(他嵌套了相应的子类型如:Publisher ->ContactInfo,Author):

1
2
3
<span>var</span> exp = Mapper.CreateMap&lt;BookDto, Book&gt;<span>();
exp.ForMember(bok</span>=&gt; bok.Publisher<span>/*</span><span>(变量)</span><span>*/</span><span>,
(map) </span>=&gt; map.MapFrom(dto=&gt;<span>new</span> Publisher(){Name= dto.Publisher<span>/*</span><span>(DTO的变量)</span><span>*/</span>}));
1
<span>一般在我们写完规则之后通常会调用,该方法主要用来检查还有那些规则没有写完。</span>
1
Mapper.AssertConfigurationIsValid();

参见:http://stackoverflow.com/questions/4928487/how-to-automap-thismapping-sub-members

其它的就以此类推。

如果要完成 BookStore 到 BookStoreDto 具体的应该如何映射呢?
相同的类型与名字就不说了,如BookStore.Name->BookStoreDto.Name AutoMapper会自动去找。而对于List与List者我们必须在配置下面代码之前

1
2
<span>var</span> exp = Mapper.CreateMap&lt;BookStore, BookStoreDto&gt;<span>();
exp.ForMember(dto </span>=&gt; dto.Books, (map) =&gt; map.MapFrom(m =&gt; m.Books));

告诉AutoMapper,Book与BookDto的映射,最后效果为:

1
2
3
Mapper.CreateMap&lt;Book, BookDto&gt;<span>();
</span><span>var</span> exp = Mapper.CreateMap&lt;BookStore, BookStoreDto&gt;<span>();
exp.ForMember(dto </span>=&gt; dto.Books, (map) =&gt; map.MapFrom(m =&gt; m.Books));

Address同理。如果要完成不同类型之间的转换用AutoMapper,如string到int,string->DateTime,以及A->B之间的类型转换我们可以参照如下例子:

http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters&referringTitle=Home

对于我们不想要某属性有值我们可以采用下面的方式。

1
exp.ForMember(ads =&gt; ads.ZipCode, dto =&gt; dto.Ignore()); <span>//</span><span>如果对于不想某属性有值,我们可以通过Ignore来忽略他,这样在调用AssertConfigurationIsValid时也不会报错.</span>

三,定义类型间的简单映射规则 前面我们看了Convention的映射方式,客观的说还是有很多类型间的映射是无法通过简单的Convention方式来做的,这时候就需要我们使用Configuration了。好在我们的Configuration是在代码中以“强类型”的方式来写的,比写繁琐易错的xml方式是要好的多了。 先来看看BookDto到Publisher的映射。 回顾一下前文中定义的规则:BookDto.Publisher -> Publisher.Name。 在AutoMapperzhong,我们可以这样映射: 

1
2
<span>var</span> map = Mapper.CreateMap&lt;BookDto,Publisher&gt;<span>();  
map.ForMember(d </span>=&gt; d.Name, opt =&gt; opt.MapFrom(s =&gt; s.Publisher));

AutoMapper使用ForMember来指定每一个字段的映射规则: 

引用:The each custom member configuration uses an action delegate to configure each member.

还好有强大的lambda表达式,规则的定义简单明了。 此外,我们还可以使用ConstructUsing的方式一次直接定义好所有字段的映射规则。例如我们要定义BookDto到第一作者(Author)的ContactInfo的映射,使用ConstructUsing方式,我们可以: 

复制代码

1
2
3
4
5
6
7
<span>var</span> map = Mapper.CreateMap&lt;BookDto,ContactInfo&gt;<span>();  
map.ConstructUsing(s </span>=&gt; <span>new</span><span> ContactInfo
{
Blog </span>=<span> s.FirstAuthorBlog,
Email </span>=<span> s.FirstAuthorEmail,
Twitter </span>=<span> s.FirstAuthorTwitter
}); </span>

复制代码

然后,就可以按照我们熟悉的方式来使用了: 

复制代码

1
2
3
4
5
6
BookDto dto = <span>new</span><span> BookDto  
{
FirstAuthorEmail </span>= <span>"</span><span>matt.rogen@abc.com</span><span>"</span><span>,
FirstAuthorBlog </span>= <span>"</span><span>matt.amazon.com</span><span>"</span><span>,
};
ContactInfo contactInfo </span>= Mapper.Map&lt;BookDto, ContactInfo&gt;(dto);

复制代码

如果需要映射的2个类型有部分字段名称相同,又有部分字段名称不同呢?还好AutoMapper给我们提供的Convention或Configuration方式并不是“异或的”,我们可以结合使用两种方式,为名称不同的字段配置映射规则,而对于名称相同的字段则忽略配置。 
例如:对于前面提到的AddressDto到Address的映射,假如AddressDto的字段Country不叫Country叫CountryName,那么在写AddressDto到Address的映射规则时,只需要: 

1
2
<span>var</span> map = Mapper.CreateMap&lt;AddressDto, Address&gt;<span>();  
map.ForMember(d </span>=&gt; d.Country, opt =&gt; opt.MapFrom(s =&gt; s.CountryName));

对于City、Street和PostCode无需定义任何规则,AutoMapper仍然可以帮我们进行正确的映射。

Autofac容器对象实例的几种生命周期类型 - 晓晨Master - 博客园

Excerpt

实例范围决定了如何在同一服务的请求之间共享实例。 请注意,您应该熟悉生命周期范围的概念,以便更好地理解此处发生的情况。 当请求服务时,Autofac可以返回单个实例(单实例作用域),新实例(每个依赖作用域)或某种上下文中的单个实例,例如 线程或HTTP请求(每个生命周期范围)。 这适用于从显式 调用


实例范围决定了如何在同一服务的请求之间共享实例。 请注意,您应该熟悉生命周期范围的概念,以便更好地理解此处发生的情况。

当请求服务时,Autofac可以返回单个实例(单实例作用域),新实例(每个依赖作用域)或某种上下文中的单个实例,例如 线程或HTTP请求(每个生命周期范围)。

这适用于从显式Resolve()调用返回的实例以及容器内部创建的实例,以满足另一个组件的依赖关系。

选择正确的生命周期范围将有助于避免组件寿命过长或不够长的俘获依赖和其他陷阱。 开发人员需要为每个应用程序组件做出正确的选择。

1.Instance Per Dependency#

每次都会返回一个新的实例,并且这是默认的生命周期。

1
var builder = new ContainerBuilder(); // This... builder.RegisterType<Worker>(); // 此句代码的效果同上 builder.RegisterType<Worker>().InstancePerDependency();

当您解析每个依赖项的实例组件时,每次都会得到一个新组件。

1
using(var scope = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { // Every one of the 100 Worker instances // resolved in this loop will be brand new. var w = scope.Resolve<Worker>(); w.DoWork(); } }

2.Single Instance#

单例,所有服务请求都将会返回同一个实例。

1
var builder = new ContainerBuilder(); builder.RegisterType<Worker>().SingleInstance();

当您解析单个实例组件时,无论您请求何处,都始终获得相同的实例。

1
// It's generally not good to resolve things from the // container directly, but for singleton demo purposes // we do... var root = container.Resolve<Worker>(); // We can resolve the worker from any level of nested // lifetime scope, any number of times. using(var scope1 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { var w1 = scope1.Resolve<Worker>(); using(var scope2 = scope1.BeginLifetimeScope()) { var w2 = scope2.Resolve<Worker>(); // root, w1, and w2 are always literally the // same object instance. It doesn't matter // which lifetime scope it's resolved from // or how many times. } } }

3.Instance Per Lifetime Scope#

在一个嵌套语句块中,只会返回一个实例。

1
var builder = new ContainerBuilder(); builder.RegisterType<Worker>().InstancePerLifetimeScope();

在解决每个生命周期实例作用域组件时,每个嵌套作用域将获得一个实例(例如,每个工作单元)。

1
using(var scope1 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { // 每次从这里解析它 // 你会得到相同的实例。 var w1 = scope1.Resolve<Worker>(); } } using(var scope2 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { //每次从这里解析它 //每次解析都会得到一个同样的实例,但是这个示例和上面的循环的实例不是同一个 var w2 = scope2.Resolve<Worker>(); } }

4.Instance Per Matching Lifetime Scope#

这与上面的’每个生命周期的实例范围’概念类似,但允许更精确地控制实例共享。

当您创建嵌套的生存期范围时,您可以“标记”或“命名”范围。具有每匹配生命周期范围的组件每个嵌套生命周期范围最多只有一个实例与给定名称匹配。这允许您创建一种“范围单例”,其中嵌套的生命周期范围可以在不声明全局共享实例的情况下共享某个组件的实例。

这对于特定于单个工作单元的对象是有用的,例如,一个HTTP请求,作为一个嵌套的生命周期可以创建每个工作单元。如果每个HTTP请求都创建一个嵌套的生命周期,那么每个具有每个生命周期范围的组件都将为每个HTTP请求创建一个实例。 (有关每个请求生命周期范围的更多信息。)

在大多数应用中,只有一层容器嵌套足以代表工作单元的范围。如果需要更多级别的嵌套(例如像global-> request-> transaction这样的东西),组件可以配置为使用标签在层次结构中的特定级别共享。

1
var builder = new ContainerBuilder(); builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myrequest");

提供的标记值在启动时与生存期范围关联。 如果在没有正确命名的生命周期范围时尝试解析每个匹配生命周期范围的组件,则会得到一个异常。

1
//使用标签创建生命周期 using(var scope1 = container.BeginLifetimeScope("myrequest")) { for(var i = 0; i < 100; i++) { var w1 = scope1.Resolve<Worker>(); using(var scope2 = scope1.BeginLifetimeScope()) { var w2 = scope2.Resolve<Worker>(); // w1和w2始终是同一个对象        //实例,因为该组件是每个匹配生命周期范围的,        //所以它实际上是一个单例        //命名范围 } } } //使用标签创建另一个生命周期作用域 using(var scope3 = container.BeginLifetimeScope("myrequest")) { for(var i = 0; i < 100; i++) { // w3 will be DIFFERENT than the worker resolved in the // earlier tagged lifetime scope. var w3 = scope3.Resolve<Worker>(); using(var scope4 = scope3.BeginLifetimeScope()) { var w4 = scope4.Resolve<Worker>(); // w3和w4始终是同一个对象,因为        //他们在相同的标记范围内,但他们是        //与之前的w1,w2不一样。 } } } //你无法解析每个匹配生命周期的组件 //如果没有匹配的范围。 using(var noTagScope = container.BeginLifetimeScope()) { //因为这个范围没有,所以抛出一个异常    //有预期的标签,也没有任何父范围! var fail = noTagScope.Resolve<Worker>(); }

5.Instance Per Request#

某些应用程序类型自然适用于“请求”类型语义,例如ASP.NET Web Forms和MVC应用程序。 在这些应用程序类型中,有能力为每个请求提供一种“单例”。

通过提供众所周知的生命周期范围标记,注册便利方法以及针对常见应用程序类型的集成,每个请求的实例基于每个匹配生命周期范围的实例构建。 但在幕后,它仍然只是每个匹配生命周期范围的实例。

这意味着如果您尝试解析注册为每个请求实例但没有当前请求的组件,那么您将得到一个异常。

1
var builder = new ContainerBuilder(); builder.RegisterType<Worker>().InstancePerRequest();

6.Instance Per Owned#

拥有的隐式关系类型创建新的嵌套生命周期作用域。 可以使用每个拥有实例的注册来将依赖关系限定到拥有的实例。

1
var builder = new ContainerBuilder(); builder.RegisterType<MessageHandler>(); builder.RegisterType<ServiceForHandler>().InstancePerOwned<MessageHandler>();

在这个例子中,ServiceForHandler服务将被限制在拥有的MessageHandler实例的生命周期中。

1
using(var scope = container.BeginLifetimeScope()) { //消息处理程序本身以及    //解析依赖的ServiceForHandler服务    //在一个小小的生命周期范围内    // “范围。” 请注意解析一个拥有的<T>    //表示您负责处理。 var h1 = scope.Resolve<Owned<MessageHandler>>(); h1.Dispose(); }

原文:http://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html

复制代码

public static void BuildMvcContainer()
{ var builder = new ContainerBuilder(); var assemblys = AppDomain.CurrentDomain.GetAssemblies().ToList(); //拆分DLL后需要注册,需要注入的DLL
Assembly[] asm = GetAllAssembly(“*.Controllers.dll”).ToArray();
builder.RegisterAssemblyTypes(asm);
       //读取web.config中配置的值 
builder.RegisterModule(new ConfigurationSettingsReader(“autofac”)); var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}

复制代码

复制代码

    #region 加载程序集
    private static List<Assembly> GetAllAssembly(string dllName)
    {
        List<string\> pluginpath = FindPlugin(dllName); var list = new List<Assembly>(); foreach (string filename in pluginpath)
        { try { string asmname = Path.GetFileNameWithoutExtension(filename); if (asmname != string.Empty)
                {
                    Assembly asm \= Assembly.LoadFrom(filename);
                    list.Add(asm);
                }
            } catch (Exception ex)
            {
                Console.Write(ex.Message);
            }
        } return list;
    } //查找所有插件的路径
    private static List<string\> FindPlugin(string dllName)
    {
        List<string\> pluginpath = new List<string\>(); string path = AppDomain.CurrentDomain.BaseDirectory; string dir = Path.Combine(path, "bin"); string\[\] dllList = Directory.GetFiles(dir, dllName); if (dllList.Length > 0)
            {
                pluginpath.AddRange(dllList.Select(item \=> Path.Combine(dir, item.Substring(dir.Length + 1))));
            } return pluginpath;
    } #endregion

复制代码

上面的代码就是注册的代码,我是在MVC4使用,写完之后再Global.asax中的Application_Start调用,确保启动应用后执行

然后是web.config中配置

我web.config中引用一个文件,详细看我之前的博文

复制代码

复制代码

然后在autofac.config中注册服务,type是实现的server,service是接口

在MVC中的使用,根据的是构造函数注入

复制代码

public class AdminLoginController : Controller
{ public readonly IUserServer userServer; public AdminLoginController(IUserServer userServer)
{ this.userServer = userServer;
} public ActionResult Index()
{
userServer.xxxxx//随便写
return View();
}
}

复制代码

 属性注入可以参考这篇文章http://www.cnblogs.com/peteryu007/p/3449315.html

Autofac高级用法之动态代理 - 晓晨Master - 博客园

Excerpt

前言 Autofac的DynamicProxy来自老牌的Castle项目。DynamicProxy(以下称为动态代理)起作用主要是为我们的类生成一个代理类,这个代理类可以在我们调用原本类的方法之前,调用拦截器以实现AOP。那么动态代理是怎么实现的呢,这里简单一下提一下,这里主要是用了emit技术动态


前言

Autofac的DynamicProxy来自老牌的Castle项目。DynamicProxy(以下称为动态代理)起作用主要是为我们的类生成一个代理类,这个代理类可以在我们调用原本类的方法之前,调用拦截器以实现AOP。那么动态代理是怎么实现的呢,这里简单一下提一下,这里主要是用了emit技术动态生成IL,相当于在内存中用IL给我们编写了一个Class。

通过静态代理实现AOP

我们新建一个类Cat,并实现ICat接口

ICat:

1
public interface ICat { void Eat(); }

Cat:

1
public class Cat:ICat { public void Eat() { Console.WriteLine("猫在吃东西"); } }

然然后我们为其创建一个代理类,CatProxy

1
public class CatProxy:ICat { private readonly ICat _cat; public CatProxy(ICat cat) { _cat = cat; } public void Eat() { Console.WriteLine("猫吃东西之前"); _cat.Eat(); Console.WriteLine("猫吃东西之后"); } }

现在我们调用一下试试效果:

1
public class Progarm { static void Main(string[] args) { ICat icat=new Cat(); var catProxy=new CatProxy(icat); catProxy.Eat(); Console.Read(); } }

可以看见,我们已经成功的通过代理实现在猫吃东西之前和之后执行我们定义的代码,这就是一个简单的AOP,这个称之为静态代理,需要我们手动编写代理类,这个是十分耗费时间的,那么有什么方法帮我们自动生成代理呢,当然有了,接下来介绍我们的动态代理。

动态代理(DynamicProxy)实现AOP

我在前言中已经简单提了下动态代理的实现原理,我们这里就只说说怎么用,而不去讨论怎么实现了(烧脑阔)。我们这里使用Autofac的DynamicProxy。

我们依然使用前一章节所用的控制台项目,通过nuget安装两个Package:AutofacAutofac.Extras.DynamicProxy

首先我们需要定义一个拦截器:

1
public class CatInterceptor:IInterceptor { public void Intercept(IInvocation invocation) { Console.WriteLine("猫吃东西之前"); invocation.Proceed(); Console.WriteLine("猫吃东西之后"); } }

然后在Autofac容器中注册我们的拦截器和类型:

1
static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<CatInterceptor>();//注册拦截器 builder.RegisterType<Cat>().As<ICat>().InterceptedBy(typeof(CatInterceptor)).EnableInterfaceInterceptors();//注册Cat并为其添加拦截器 var container = builder.Build(); var cat = container.Resolve<ICat>(); cat.Eat(); Console.Read(); }

我们运行一下看看效果:

通过运行我们可以看出,和上一章节的效果一样,但是我们并不需要取手动定义我们的代理类,而是通过组件动态生成了。

关于这个拦截器,我们还可以通过Attribute的方式绑定到我们的具体类型,而不需要在注册到容器的时候动态指定。

1
[Intercept(typeof(CatInterceptor))] public class Cat:ICat { public void Eat() { Console.WriteLine("猫在吃东西"); } }

注册的代码可改为:

1
builder.RegisterType<Cat>().As<ICat>().EnableInterfaceInterceptors();

动态代理的高级用法

我们前面说了,动态代理是动态生成一个代理类,那么我们可以动态的为这个代理类添加一个接口吗,答案当然是可以。

现在我们定义一个铲屎官类:

1
public class CatOwner { }

可以看出我们的铲屎官类什么都没有,如果我们的铲屎官想喂猫吃东西怎么办,按照我们传统的思维当然是实例化一个cat传入我们的 CatOwner,但是我们可以用我们的DynamicProxy动态生成。

1
var builder = new ContainerBuilder(); builder.RegisterType<CatInterceptor>();//注册拦截器 builder.RegisterType<Cat>().As<ICat>();//注册Cat builder.RegisterType<CatOwner>().InterceptedBy(typeof(CatInterceptor)) .EnableClassInterceptors(ProxyGenerationOptions.Default, additionalInterfaces: typeof(ICat));//注册CatOwner并为其添加拦截器和接口 var container = builder.Build(); var cat = container.Resolve<CatOwner>();//获取CatOwner的代理类 cat.GetType().GetMethod("Eat").Invoke(cat, null);//因为我们的代理类添加了ICat接口,所以我们可以通过反射获取代理类的Eat方法来执行 Console.Read();

我们上面的代码是肯定不能运行的,因为我们的代理类虽然添加了ICat接口,但是却没有具体实现它,所以抛出为卫视现异常:

我们可以使用AOP在我们执行代理类的Eat方法之前去调用我们的具体实现Cat的Eat方法,我们修改一下拦截器。

1
public class CatInterceptor:IInterceptor { private readonly ICat _cat; /// <summary> /// 通过依赖注入 注入ICat的具体实现 /// </summary> /// <param name="cat"></param> public CatInterceptor(ICat cat) { _cat = cat; } public void Intercept(IInvocation invocation) { Console.WriteLine("喂猫吃东西"); invocation.Method.Invoke(_cat, invocation.Arguments);//调用Cat的指定方法 } }

我们看一下运行效果:

可以看见我们从一个什么都没有的CatOwner类,来为其调用了一个具体的猫吃东西的行为,是不是感觉很神奇!

有人可能会说,一个铲屎官为什么要去实现一个ICat接口。我想说纯属胡编乱造,只是想阐明这个用法,这个意思。

应用场景

用过ABP框架的人都应该知道其有个技术名为DynamicWebapi,非常方便可以动态帮我们的应用逻辑层生成webapi,而不需要我们手动去编写webapi来发布。这里据用到了上面所说的技术,动态生成Wabpi Controller,然后为其添加应用逻辑接口,在调用具体的应用逻辑方法时(Action)通过AOP拦截调用具体应用逻辑实现来完成。

Demo:https://github.com/stulzq/BlogDemos/tree/master/AutofacDynamicProxyTest

NetCore作为微服务可以注册到服务中心,服务中心可以远程启动、重启、关闭该微服务

1、创建一个NetCore 2.0 WebApi项目

2、创建一个进程去管理NetCore程序进程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

public class ApplicationManager

{

private static ApplicationManager _appManager;

private IWebHost _web;

private CancellationTokenSource _tokenSource;

private bool _running;

private bool _restart;

public bool Restarting => _restart;

public ApplicationManager()

{

_running = false``;

_restart = false``;

}

public static ApplicationManager Load()

{

if (_appManager == null``)

_appManager = new ApplicationManager();

return _appManager;

}

public void Start()

{

if (_running)

return``;

if (_tokenSource != null && _tokenSource.IsCancellationRequested)

return``;

_tokenSource = new CancellationTokenSource();

_tokenSource.Token.ThrowIfCancellationRequested();

_running = true``;

_web = new WebHostBuilder()

.UseKestrel()

.UseContentRoot(Directory.GetCurrentDirectory())

.UseIISIntegration()

.UseStartup<Startup>()

.Build();

_web.Run(_tokenSource.Token);

}

public void Stop()

{

if (!_running)

return``;

_tokenSource.Cancel();

_running = false``;

}

public void Restart()

{

Stop();

_restart = true``;

_tokenSource = null``;

}

}

  3、把ApplicationManager加入到Main中

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public static void Main(``string``[] args)

{

try

{

var appManager = ApplicationManager.Load();

do

{

appManager.Start();

} while (appManager.Restarting);

}

catch (Exception ex)

{

}

}

  4、在程序的ValuesController中实现重启、关闭的Api

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

namespace NetCoreWebApiDemo.Controllers

{

[Route(``"api/[controller]"``)]

public class ValuesController : Controller

{

private ApplicationManager appManager = ApplicationManager.Load();

[HttpGet]

public IEnumerable<``string``> Get()

{

return new string``[] { "value1"``, "value2" };

}

[HttpGet(``"{cmd}"``)]

public string Get(``string cmd)

{

switch (cmd)

{

case "restart"``: appManager.Restart(); break``;

case "stop"``: appManager.Stop(); break``;

case "start"``: appManager.Start(); break``;

}

return cmd;

}

[HttpPost]

public void Post([FromBody]``string value)

{

}

[HttpPut(``"{id}"``)]

public void Put(``int id, [FromBody]``string value)

{

}

[HttpDelete(``"{id}"``)]

public void Delete(``int id)

{

}

}

}

6、给程序启动和停止加入日志标签

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Logging;

using Microsoft.Extensions.Options;

namespace NetCoreWebApiDemo

{

public class Startup

{

public Startup(IConfiguration configuration)

{

Configuration = configuration;

}

public IConfiguration Configuration { get``; }

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc();

}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)

{

if (env.IsDevelopment())

{

app.UseDeveloperExceptionPage();

}

app.UseMvc();

lifetime.ApplicationStarted.Register(OnStart);

lifetime.ApplicationStopped.Register(UnRegService);

}

private void OnStart()

{

LoadAppConfig();

RegService();

}

private void LoadAppConfig()

{

Console.WriteLine(``"ApplicationStarted:LoadAppConfig"``);

}

private void RegService()

{

Console.WriteLine(``"ApplicationStarted:RegService"``);

}

private void UnRegService()

{

Console.WriteLine(``"ApplicationStopped:UnRegService"``);

}

}

}

5、在程序根目录运行dotnet run

访问:http://localhost:51062/api/values,显示:\["Value1","Value2"\]

访问:http://localhost:51062/api/values/restart:显示restart,再访问http://localhost:51062/api/values正常返回\["Value1","Value2"\]

 

访问:http://localhost:51062/api/values/stop,显示:stop,再访问http://localhost:51062/api/values就是404了

stop后由于netcore进程已经被关闭,没有了http监听,通过url方式是无法重新启动了,这里可以借助类似supervisor的工具来停止进程,启动进程。

https://github.com/andrewfry/AspNetCore-App-Restart

1、除了上面,还可以通过中间件的形式,实现远程关闭

新增一个中间件的类:

复制代码

public class RemoteStopMiddleware
{ private RequestDelegate _next; private const string RequestHeader = “Stop-Application”; private const string ResponseHeader = “Application-Stopped”; public RemoteStopMiddleware(RequestDelegate next)
{
_next = next;
} public async Task Invoke(HttpContext context, IApplicationLifetime lifetime)
{ if (context.Request.Method == “HEAD” && context.Request.Headers[RequestHeader].FirstOrDefault() == “Yes”)
{
context.Response.Headers.Add(ResponseHeader, “Yes”);
lifetime.StopApplication();
} else if (context.Request.Method == “HEAD” && context.Request.Headers[RequestHeader].FirstOrDefault() == “No”)
{
context.Response.Headers.Add(ResponseHeader, “No”); // See you on the next request. //Program.Shutdown();
} else { await _next(context);
}
}
}

复制代码

2、注册中间件

1

2

3

4

5

6

7

8

9

10

11

12

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)

{

if (env.IsDevelopment())

{

app.UseDeveloperExceptionPage();

}

app.UseMvc();

app.UseMiddleware<RemoteStopMiddleware>();

lifetime.ApplicationStarted.Register(OnStart);

lifetime.ApplicationStopped.Register(UnRegService);

}

3、运行程序,用postman发起一个head请求,请求头中加

{

Stop-Application:Yes

}

详细说明可参考:https://www.cnblogs.com/artech/p/application-life-time.html

首先要啰嗦几句。

单元测试是TDD的重要实践方法,也是代码质量的一种保证手段。在项目的工程化开发中,研发人员应该尽量保证书写Unit Test,即使不使用TDD。

(VS中,我们可以直接使用微软提供的一套单元测试框架,一般使用足够了,特别需求的话,可以使用其他更好的框架。)

书写单元测试时,我们并不一定真的要去连接数据库,毕竟就算只使用自己计算机上的研发数据库,也不能保证数据正确性和完备性,毕竟自己经常会操作些垃圾数据。

这个时候就需要模拟一个“数据库”来构造我们想要的一些数据。这个就是Mock最直接的需求。然后当我们进一步实践,会发现我们的Service层等,也可以用Mock模拟对象,而不是非要去new一个真实对象。真实场景是当我们研发小组合作时,你作为更高层的研发人员,可能只拿到了服务层的接口,具体的实现类你的同事还在研发中,这个时候你要做Unit Test,就只有模拟一个假的Service实现了。

C#的单元测试框架中,有一套Mock框架就叫Moq。

Moq可以直接在VS 2013及以后的版本中通过Nuget获取。以前的版本的VS可以到github上下载Moq的dll。

Moq的github地址为:Moq

Moq的QuickStart页面为:QuickStart 深入学习,可以直接看此文档。

MVC中,最直接需要模拟的应该就是HttpContext相关对象,如HttpRequest、Server、Session等对象。以HttpRequest为例。

首先,我们要知道, controller中相关HttpContext的对象是ControllerContext,它就是HttpContextBase。模拟的HttpContext通过它绑定给Controller。

1

controller.ControllerContext = new ControllerContext(mockContext.Object, new RouteData(), controller);

 mockContext.Object就是我们用Moq模拟的HttpContextBase。

这里是绑定的代码,倒推回去,我们应该先生成mockContext,如下:

1

private Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();

 实际我们代码可能使用的是Request[“xxx”]、Session[“yyy”],这些对象又依赖于HttpContextBase,所以我们需要模拟它们,然后绑定到 mockContext。如下

1

2

3

4

5

6

7

8

9

10

var mockRequest = new Mock<HttpRequestBase>();

if (dataIndexed != null``)

{

foreach (``var pair in dataIndexed)

{

mockRequest.Setup(x => x[pair.Key]).Returns(pair.Value);

}

}

mockContext.SetupGet(x => x.Request).Returns(mockRequest.Object);

注意最后一句就是将模拟的Request绑定到模拟的HttpContextBase上。代码含义是:当通过mockContext.Request(即它的get方法)得到Request对象时,把mockRequest模拟的HttpRequest对象返回。

Moq的方法都是比较直白的含义,如上就是:SetupGet(x => x.Request).Returns($$$),针对对象Request使用Get方法时,Returns相应的对象$$$。其他的对象,不管是HttpContextBase的还是Request的再子层对象,都通过这样的方法设置,前提是相应的类中有此属性(get,set)。

回过头继续看上面代码这段mockRequest.Setup(x => x[pair.Key]).Returns(pair.Value); 这个就是直接针对值进行设置,这里是针对类的Indexedr进行设置。

还有一些其他方法,需要时就看QuickStart了。

另外,针对全局的HttpContext对象,在单元测试中,它是null的,所以为了保证单元测试可进行,需要对其进行包装,在项目中使用包装的类进行访问。这样,单元测试时,就注入自己模拟的HttpContext对象。然而HttpContext是sealed类,是不能被Mock的。所以我们可以在包装类中,使用两个对象,分别指向Mock的对象和真实的HttpContext,依据是否模拟的判断在代码中选择调用。也可以使用HttpContextWrapper来包装HttpContext,因为HttpContextWrapper是HttpContextBase的实现。如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

/// <summary>

/// 全局HttpContext的包装类,以便单元测试

/// </summary>

public class CmsHttpContext

{

/// <summary>

/// 当前单例对象

/// </summary>

private static CmsHttpContext _instance = new CmsHttpContext();

/// <summary>

///  包装的HttpContext

/// </summary>

private static HttpContextBase wrapper = null``;

/// <summary>

/// 是否被包装

/// </summary>

private static bool IsWrap = false``;

private CmsHttpContext()

{

}

/// <summary>

/// 当前HttpContext对象

/// </summary>

public static HttpContextBase Current

{

get

{

if (!IsWrap)

{

wrapper = new HttpContextWrapper(HttpContext.Current);

}

return wrapper;

}

}

/// <summary>

/// 包装外部 HttpContext,仅用于单元测试中

/// </summary>

/// <param name="context"></param>

public static void Wrap(HttpContextBase context)

{

IsWrap = true``;

wrapper = context;

}

}

其他要注意的点:

1.Action的方法直接调用即可以执行。针对ViewBag.XXX,使用Controller对象调用,如mController.ViewBag.XXX

2.JsonResult中Json(XXX),如果XXX是动态类型的话,它在传输后会变成object,单元测试中无法识别它相应的属性,可以使用框架ExposedObject(Nuget中可以直接下载)进行包装,将其包装回dynamic进行测试,如下:

1

2

3

4

var jsonData = Exposed.From(result.Data);

Assert.IsTrue(jsonData.total > 0);

Assert.IsTrue(jsonData.list.Count > 0);

image

各种语言实现的oauth认证: http://oauth.net/code/

上一篇文章介绍了如何使用基本的http认证来实现asp.net web api的跨平台安全认证。 这里说明一个如何使用oauth实现的认证。oauth大家可能不陌生。那么这里需要注意的是我们使用的是.net平台一个比较好的开源oauth库。 DOTNETOPENAUTH。

就像上图所示,我们需要一个ISSSUE Server来给我们一个token,然后再去资源服务器请求资源,也就是Web API Server。

image

首先在oAuthIssuer服务端我们需要实现一个DotNetOpenAuth的接口:IAuthorizationServer

image

对接口的实现:

复制代码

public class OAuth2Issuer : IAuthorizationServer
{ private readonly IssuerConfiguration _configuration; public OAuth2Issuer(IssuerConfiguration configuration)
{ if (configuration == null) throw new ArgumentNullException(“configuration”);
_configuration = configuration;
} public RSACryptoServiceProvider AccessTokenSigningKey
{ get { return (RSACryptoServiceProvider)_configuration.SigningCertificate.PrivateKey;
}
} public DotNetOpenAuth.Messaging.Bindings.ICryptoKeyStore CryptoKeyStore
{ get { throw new NotImplementedException(); }
} public TimeSpan GetAccessTokenLifetime(DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accessTokenRequestMessage)
{ return _configuration.TokenLifetime;
} public IClientDescription GetClient(string clientIdentifier)
{ const string secretPassword = “test1243”; return new ClientDescription(secretPassword, new Uri(“http://localhost/“), ClientType.Confidential);
} public RSACryptoServiceProvider GetResourceServerEncryptionKey(DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accessTokenRequestMessage)
{ return (RSACryptoServiceProvider)_configuration.EncryptionCertificate.PublicKey.Key;

    } public bool IsAuthorizationValid(DotNetOpenAuth.OAuth2.ChannelElements.IAuthorizationDescription authorization)
    { //claims added to the token
        authorization.Scope.Add("adminstrator");
        authorization.Scope.Add("poweruser"); return true;
    } public bool IsResourceOwnerCredentialValid(string userName, string password)
    { return true;
    } public DotNetOpenAuth.Messaging.Bindings.INonceStore VerificationCodeNonceStore
    { get { throw new NotImplementedException();
        }
    }
}

复制代码

在 Web API Server端,我们需要使用Http Message Handler来获取httprequest信息;并进行是否有授权认证。

复制代码

public class OAuth2Handler : DelegatingHandler
{ private readonly ResourceServerConfiguration _configuration; public OAuth2Handler(ResourceServerConfiguration configuration)
{ if (configuration == null) throw new ArgumentNullException(“configuration”);
_configuration = configuration;
} protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpContextBase httpContext; string userName;
HashSet<string> scope; if (!request.TryGetHttpContext(out httpContext)) throw new InvalidOperationException(“HttpContext must not be null.”); var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(
(RSACryptoServiceProvider)_configuration.IssuerSigningCertificate.PublicKey.Key,
(RSACryptoServiceProvider)_configuration.EncryptionVerificationCertificate.PrivateKey)); var error = resourceServer.VerifyAccess(httpContext.Request, out userName, out scope); if (error != null) return Task.Factory.StartNew(error.ToHttpResponseMessage); var identity = new ClaimsIdentity(scope.Select(s => new Claim(s, s))); if (!string.IsNullOrEmpty(userName))
identity.Claims.Add(new Claim(ClaimTypes.Name, userName));

        httpContext.User \= ClaimsPrincipal.CreateFromIdentity(identity);
        Thread.CurrentPrincipal \= httpContext.User; return base.SendAsync(request, cancellationToken);
    }

}

复制代码

这里的ResourceServerConfiguration 我们是使用加密证书的。

image

客户端调用代码:

image

调用API获取数据之前需要从IssueServer获取Token。

GetAccessToken:

image

看一下Token信息:

{“access_token”:”gAAAAIoUBVBrZ5jAxe5XeTgnJ8mGwwKsCReknueg4gLGlDQ77lR1yPfxt0yNfWLCBT7hxnHjRjuEwDTJ3J1YAnqML4MIgQg8A2cz2bs0EnxvCMfKnayKEesRM-lxLTFbWMpSxe2Xvjm61IbaXjrMkYDRMnV4Do8-7132tiOLIv02WOGlJAEAAIAAAACJ8F3SsE6cTI1XsioW_xOxHeESDzG16y01Gxm3HikYFUC3XIdekpPw0yMB4tavPmUj-kRyC1halbUX7JKf-Dihm6Ou5mexe9lcYTr9or_kH7WcDN5ZCryUK3OaecvwwjQVr5o9XD2ZyZSNDCNhVRFc5ypvP85zZCBW1KJkP3OTCV4AkMN-ROvgI8jxutYdsLLN-YbB7Ot5iypzWWbW0QxiwOzMEqG9nVtPwnIWOUMOvW5KbiELELhgjap60mwHzGrHG4TtA4jrNy8S9zjixO_q-FrgpAuC06CkSH-R4w9yPCLLDc9m3UoAnknFjd4PUbWLxCvlBpEK2sg03ENa0EOKzc2O5fEic9P-BiYt6afMwTgLkJlGBBjmCBpGZMkfLTw”,”token_type”:”bearer”,”expires_in”:”300”,”scope”:”http:\/\/localhost\/ adminstrator poweruser”}

image

客户端调用:

image

代码:

http://pan.baidu.com/s/1ntkMbCt

1.   IIS设置

1.1 创建SSL证书

   点击左侧菜单栏顶部,点击“功能视图”里的“服务器证书”:

         点击“创建自动签名证书”创建自动签名证书:

1.2 设置SSL证书

         点开网站,在“功能视图”里点击“SSL设置”:

         如图,设置SSL:

1.3 绑定SSL证书

         点开网站,在右侧“操作”栏点击“绑定”:

         添加“网站绑定”,选择https及刚刚创建的SSL证书,主机名(也就是域名)根据需要选设(IIS7默认不支持,需要在配置文件applicationHost.config里进行设置,详见注):

         【注】域名也可以通过配置进行设置:

      打开C:\Windows\system32\inetsrv\config\applicationHost.config在里面找到

<bindings>
<binding protocol=“https” bindingInformation=“*:443:” />
<binding protocol=“http” bindingInformation=“*:80:www.yourdomain.com“ />
</bindings>

  找到https的配置项目,修改为:

<binding protocol=“https” bindingInformation=“*:443:www.yourdomain.com"/>

  这里面需要注意的是:bindings节点有多个,需要找到你配置的站点,默认是

<binding protocol=“https” bindingInformation=“*:443” />

2.强制使用https

  MVC操作非常简单,只需要在网站Index设置RequireHttps特性即可:

[RequireHttps] public ActionResult Index()
{ return View();
}

  这是一种投机的设置,合理的做法应该是在项目Global.asax文件 Application_Start中添加过滤:

GlobalFilters.Filters.Add(new RequireHttpsAttribute());

3.https使用中的问题

3.1 百度地图的问题

  https的网站使用百度地图,如果你引用的地址没写对的话,加载不出来百度地图,被认为是不安全的JS内容。解决方式:

  https://api.map.baidu.com/api?v=2.0&ak=你的密钥&s=1

  加上s=1代表引用的是https的。

3.2 加密会话(SSL)Cookie 中缺少 Secure 属性问题

       服务器开启了Https时,cookie的Secure属性应设为true,否则,在进行IBM AppScan安全扫描时,就会扫出下面的问题:

 

解决办法:

  1. 修改web.config,添加: 

<system.web>
<httpCookies httpOnlyCookies=“true” requireSSL=“true” />
<system.web>

  2. 修改后台写Cookies时的设置 cookie.Secure = true:

复制代码

1 HttpResponse response = HttpContext.Current.Response; 2 var cookie = new HttpCookie(key, value); 3 cookie.HttpOnly = true; 4 cookie.Path = “/“; 5 cookie.Expires = DateTime.Now.AddHours(1); 6 cookie.Secure = true; 7 response.AppendCookie(cookie);

复制代码

参考资料:

IIS配置HTTPS

C# MVC 网站将http强制跳转到https
IBM AppScan 安全扫描:加密会话(SSL)Cookie 中缺少 Secure 属性 处理办法

Asp.Net MVC 获取当前 Controller Action Area - 晓晨Master - 博客园

Excerpt

获取控制器名称: ViewContext.RouteData.Values[“controller”].ToString(); 获取Action名称: ViewContext.RouteData.Values[“action”].ToString(); 获取路


获取控制器名称:

ViewContext.RouteData.Values[“controller”].ToString();

获取Action名称:

ViewContext.RouteData.Values[“action”].ToString();

获取路由参数值:

ViewContext.RouteData.Values[名称].ToString();

如:ViewContext.RouteData.Values[“ID”].ToString(); 获取ID的值

 获取area名称

在代码中:

ControllerContext.RouteData.DataTokens[“area”]

在View中:

ViewContext.RouteData.DataTokens[“area”]

1
2
3
<span>1</span> <span>var</span> action = ViewContext.RouteData.Values[<span>"</span><span>Action</span><span>"</span><span>].ToString().ToLower();
</span><span>2</span> <span>var</span> controllerName = ViewContext.RouteData.Values[<span>"</span><span>controller</span><span>"</span><span>].ToString().ToLower();
</span><span>3</span> <span>var</span> areaName = ViewContext.RouteData.DataTokens[<span>"</span><span>area</span><span>"</span>].ToString().ToLower();

目录

Android四大组件:

1. 服务的概念

1.1 概念

1.2 适用场景

1.3 继承结构图

2. 服务的生命周期

2.1 定义

Service拥有自己的生命周期,不会被捆绑,即便Activity销毁之后,Service也不会销毁。

2.2 启动类型

2.3 结构图

2.4 方法

3. 服务的创建

3.1 创建类并继承Service

3.2 注册服务

3.3 服务启动方式

3.3.1 startService方式启动服务

3.3.2 bindService方式启动服务

3.4 bindService案例

3.4.1 bindService使用场景

绑定服务的最大作用就是用来实现对Service执行的任务进行进度监控。

3.4 服务的关闭与解绑方法

3.5 服务的启动模式

3.6 两种启动方法的区别

4 服务的通信

4.1 通信方式



Android四大组件:

ActivityServiceBroadcastReceiverContentProvider。   

-——————————————————————————————————————————-

1.1 概念

         Service(服务)是一个长期运行在后台没有用户界面的应用组件,即使切换到另一个应用程序或者后台,服务也可以正常运行。因此,服务适合执行一段时间不需要显示界面的后台耗时操作****(需要另启子线程),比如下载网络数据播放音乐等。

1
Service可以看做是一个没有界面的Activity,因此启动,销毁服务跟开启,销毁Activity类似

         Service并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程,即Service运行在主线程中。当某个应用程序进行被杀掉时,所有依赖于该进程的服务都会停止运行

         Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的,也就是说,需要在服务的内部手动创建子线程,并在里面执行具体的任务。否则就会出现主线程被阻塞的情况。

        Activity启动服务,主要用于响应和处理Activity的事件。Service拥有自己的生命周期,不会被捆绑,即便Activity销毁之后,Service也不会销毁。 支持显式和隐式启动服务。

1.2 适用场景

      1)下载网络数据****(在Android3.0之后,只支持子线程下载,因此此功能应该在子线程中启动服务)。

2)播放音乐。

3)访问文件、数据库等一些业务逻辑功能,可以让Service来实现。

1.3 继承结构图

       比较Service与Activity的继承关系结构图如下:

2.1 定义

** Service可以看做是一个没有界面的Activity。Service拥有自己的生命周期,不会被捆绑,即便Activity销毁之后,Service也不会销毁。**

2.2 启动类型

        服务的启动方式有两种,分别是startService()(启动服务)bindService()(绑定服务)方法。使用不同的方法启动服务,其生命周期也是不同的。服务的启动都是在Activity中进行的。

1.startService()

其他组件调用startService()启动一个Service,一旦启动,Service将一直运行在后台,即便启动

Service的组件已经被destory。但是,Service会在后台执行单独的操作,也并不会给启动它的

组件返回结果。

比如说:一个以启动服务方式的Service执行在后台下载或者上传一个文件的操作,

完成之后,Service自动停止。

2.bindService()

其他组件调用bindService()绑定一个Service,通过绑定方式启动的Service是一个client-server

结构,该Service可以与绑定它的组件进行交互。一个绑定的Service仅在仅有组件与其绑定时才会运行,

多个组件可与一个Service绑定,Service不在与人和网组件绑定时,将会被destory

2.3 结构图

        如下图所示:

2.4 方法

1.onCreate()

服务被创建,单例模式,只会创建一次

2.onStartCommand()

服务启动时调用

3.onBind()

绑定时调用,同时会调用onCreate()方法不会调用onStartCommand()方法

4.onUnBind()

解绑时调用 ,同时会调用onDestory()方法

5.onDestory()

销毁时调用

3.1 创建类并继承Service

        Service(服务)是Android四大组件之一。创建方式与广播接收者类似。【new】->【Service】,创建的Service如下所示:

1
public class MyService extends Service{public IBinder onBind(Intent intent) {public int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);public boolean onUnbind(Intent intent) {return super.onUnbind(intent);public void onDestroy() {

3.2 注册服务

         Service创建后,会在清单文件AndroidManifest.xml中进行注册,如下所示:

1
android:name="com.example.service.MyService"

3.3 服务启动方式

3.3.1 startService方式启动服务

startService(Intent intent), 传入Intent对象,通过startService方式启动服务,服务会长期在后台运行,并且服务的状态与开启者的状态没有关系,即便启动服务的组件已经被销毁,服务也依旧会运行

3.3.2 bindService方式启动服务

           通过bindService方式启动服务,服务会与组件进行绑定。一个被绑定的服务提供一个客户端与服务器接口,允许组件与服务进行交互发送请求得到结果多个组件可以绑定一个服务,当调用onUnbind()方法时,这个服务就被销毁了,即调用onDestory()方法。

           bindService()方法完整名为**bindService(Intent service,ServiceConnection conn,int flags)**,该方法的三个参数解释如下:

Intent对象

用于指定要启动的Service。

ServiceConnection对象

用于监听调用者与Service之间的连接状态。当调用者与Service连接成功时,

将回调该对象的onServiceConnected(ComponentName name,IBinder service)方法。

断开连接时,将回调该对象的onServiceDisconnected(ComponentName name)方法。

 flag

绑定时是否自动创建Service(如果Service还未创建)。可指定为0,即不自动创建,

也可指定为“BIND_AUTO_CREATE”,即自动创建。 

      之所以需要ServiceConnection对象,是为了表示绑定与解绑某个具体Service。

3.4 bindService案例

3.4.1 bindService使用场景

绑定服务的最大作用就是用来实现对Service执行的任务进行进度监控。

** 需要使用的两个类如下:**

1.ServiceConnection

是一个接口,主要在绑定服务中用于客户端和服务器端链接的,使用ServiceConnection时

通常要重写**onServiceConnected()OnServiceDisconnected()**方法

2.IBinder

 

     案例如下,Service类:

1
public class MyService extends Service{public IBinder onBind(Intent intent) {class MyBinder extends Binder{public void methodInService(){public boolean onUnbind(Intent intent) {return super.onUnbind(intent);

        可以看出,Binder是实现了IBinder接口。从而在Binder类里面可以自定义方法。

     Activity类:

1
public class MainActivity extends AppCompatActivity{private MyServiceConnection connection;  protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);public void btnBind(View view){            connection = new MyServiceConnection();        Intent intent = new Intent(this,MyService.class);        //绑定服务,BIND_AUTO_CREATE表示绑定时自动创建ServicebindService(intent,connection,BIND_AUTO_CREATE);public void btnUnbind(View view){            unbindService(connection);public void btnCallServiceInMethod(View view){public class MyServiceConnection implements ServiceConnection{public void onServiceConnected(ComponentName componentName, IBinder iBinder) {            Log.i("MainActivity","服务绑定成功,内存地址为:"+iBinder.toString());public void onServiceDisconnected(ComponentName componentName) {            Log.i("MainActivity","服务解绑成功");

          可以看出,onServiceConnected()方法里面的IBinder就是Service里面的onBinder()方法的返回IBinder对象。

3.4 服务的销毁方法

    1)通过startService方式启动的服务时,销毁服务方法

 通过stopService(Intent inetnt)方式;

2)通过bindService方式启动的服务时,销毁服务方法

通过unbindService(ServiceConnection conn)方式。

3.5 服务的启动模式

       1)单例模式:Service在Android中是单例模式,即onCreate()与onDestory()只会被调用一次。

       2)重新创建:如果系统发生异常导致服务终止后,如果内存足够,服务所在的进程会重新创建服务。如果是用户主动销毁的,则不会重新创建。

1
在Android中绑定式服务bindService会随着Activity的结束而结束,但是启动式服务startService不受Activity的影响。

       通过绑定方式开启服务后,服务与Activity是可以通信的,通过Activity可以控制服务进行一些操作。

4.1 通信方式

        在Android中,服务的通信方式有两种:本地服务通信远程服务通信。使用这两种方式进行通信时,必须保证服务是以绑定的形式开启的,否则无法进行通信和数据交换

    1)本地服务通信

           指的是应用程序内部的通信。首先需要创建一个Service类,该类会提供一个onBind()方法,onBind()方法的返回值是一个IBinder对象,IBinder对象会作为参数传递给ServiceConnection类中的onServiceConnected(ComponentName name,IBinder service)方法,这样访问者就可用通过IBinder对象与Service进行通信。如下图所示:

           从上图可以看出,服务在进行通信时实际使用的是IBinder对象,在ServiceConnection类中得到的IBinder对象,通过这个对象可以获取到服务中自定义的方法,执行具体的操作。

2)远程服务通信

          在Android系统中,各个应用程序都运行在自己的进程中,如果想要完成不同进程之间的通信,就需要用到远程服务通信。远程服务通信时通过AIDL(Android Interface Definition Language)实现的,接口定义语言,语法格式简单,与Java中定义接口类似,但存在差异如下:

      **  》AUDL定义接口的源代码必须以.aidl结尾。**

》AIDL接口中用到的数据类型,除了基本数据类型及String、List、Map、CharSequence之外,其他类型全部都需要导入到包,即使它们在同一个包中。

     下面一个音乐播放器案例演示本地服务通信:

      布局文件:

1
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/back1"android:orientation="vertical">android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/musicplayer_et_inputpath"android:text="Mucis/a.mp3"android:textColor="#FFEEF608"/>android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:layout_gravity="center_vertical"android:orientation="horizontal">android:id="@+id/musicplayer_tv_paly"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="#FFFFFF"android:drawableTop="@drawable/weibo"android:drawablePadding="3dp"android:gravity="center"/>android:id="@+id/musicplayer_tv_pause"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="#FFFFFF"android:drawableTop="@drawable/weixin"android:drawablePadding="3dp"android:gravity="center"/>

       Service类:

1
public class MusicPlayerService extends Service {private static final String TAG = "MucisPalyerService";public MediaPlayer mediaPlayer;public MusicPlayerService() {class MyBinder extends Binder{public void play(String path){                    mediaPlayer = new MediaPlayer();                    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);                    mediaPlayer.setDataSource(path);                    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener(){public void onPrepared(MediaPlayer mp){if(mediaPlayer != null && mediaPlayer.isPlaying()){            }else if(mediaPlayer != null && (!mediaPlayer.isPlaying())){public int getCurrentProgress(){if(mediaPlayer != null && mediaPlayer.isPlaying()){return mediaPlayer.getCurrentPosition();        }else if(mediaPlayer != null && (!mediaPlayer.isPlaying())){return mediaPlayer.getCurrentPosition();public IBinder onBind(Intent intent) {

       上述代码使用了MediaPlayer类来实现音乐播放功能,其常用方法如下:

        》setAudioStreamType():指定音频文件的类型,必须在prepare()方法之前使用。

》setDataSource():设置要播放的音频文件的位置,即URI路径,

》prepare():准备播放,调用此方法会使MediaPlayer进入准备状态。

》start():开始或继续播放音频。

》pause():暂停播放音频。

》seekTo():从指定的位置开始播放音频。

》release():释放掉与MediaPlayer对象相关的资源。

》isPlaying():判断当前MediaPlayer是否正在播放音频。

》getCurrentPosition():获取当前播放音频文件的位置。

       Activity类:

1
public class MusicPlayerActivity extends AppCompatActivity implements View.OnClickListener{    MusicPlayerService.MyBinder binder;protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);        setContentView(R.layout.musicplayer_layout);        path = (EditText)findViewById(R.id.musicplayer_et_inputpath);        findViewById(R.id.musicplayer_tv_paly).setOnClickListener(this);        findViewById(R.id.musicplayer_tv_pause).setOnClickListener(this);        intent = new Intent(this,MusicPlayerService.class);        bindService(intent,conn,BIND_AUTO_CREATE);private class MyConn implements ServiceConnection{public void onServiceConnected (ComponentName name, IBinder service){            binder = (MusicPlayerService.MyBinder)service;public void onServiceDisconnected(ComponentName name){public void onClick(View v) {        String pathway = path.getText().toString().trim();        File SDPath = Environment.getExternalStorageDirectory();        File file = new File(SDPath, pathway);        String path = file.getAbsolutePath();case R.id.musicplayer_tv_paly:if (file.exists() && file.length() > 0) {                    Toast.makeText(this, "找不到音乐文件", Toast.LENGTH_SHORT).show();case R.id.musicplayer_tv_pause:protected void onDestory(){