[转载]ASP.NET MVC3不能正确识别JSON中的Enum枚举值 - dotnetgeek - 博客园

mikel阅读(1158)

[转载]MVC3不能正确识别JSON中的Enum枚举值 – dotnetgeek – 博客园.

一、背景

在MVC3项目里,如果Action的参数中有Enum枚举作为对象属性的话,使用POST方法提交过来的JSON数据中的枚举值却无法正确被识别对应的枚举值。

二、Demo演示

为了说明问题,我使用MVC3项目创建Controller,并且创建如下代码演示:

复制代码
    //交通方式枚举
    public enum TrafficEnum
    {
        Bus = 0,
        Boat = 1,
        Bike = 2,
    }
    public class Person
    {
        public int ID { get; set; }
        public TrafficEnum Traffic { get; set; }
    }

    public class DemoController : Controller
    {
        public ActionResult Index(Person p)
        {
            return View();
        }
    }
复制代码

网站生成成功之后,就可以使用Fiddler来发送HTTP POST请求了,注意需要的是,要在Request Headers加上请求头content-type:application/json,这样才能通知服务器端Request Body里的内容为JSON格式。

点击右上角的Execute执行HTTP请求,在程序断点情况下,查看参数p,属性ID已经正确的被识别到了值为9999,而枚举值属性Traffic却 被错认为枚举中的首个值Bus,这俨然是错误的,纵使你将Traffic修改成Bike,也就是值等于2,结果也是一样。

 

三、解决方法

方法一:

升级MVC4,亲测在MVC4项目下,这个问题已经被修复了;

方法二:

假若因为各种原因,项目不想或者不能升级为MVC4,可以在MVC3项目上做些改动,亦可修复这个问题,

1、在项目中,新建一个类,加入以下代码,需要引用一下 using System.ComponentModel;  using System.Web.Mvc; 命名空间;

复制代码
    /// <summary>
    /// 处理在MVC3下,提交的JSON枚举值在Controller不能识别的问题
    /// </summary>
    public class EnumConverterModelBinder : DefaultModelBinder
    {
        protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
        {
            var propertyType = propertyDescriptor.PropertyType;
            if (propertyType.IsEnum)
            {
                var providerValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
                if (null != providerValue)
                {
                    var value = providerValue.RawValue;
                    if (null != value)
                    {
                        var valueType = value.GetType();
                        if (!valueType.IsEnum)
                        {
                            return Enum.ToObject(propertyType, value);
                        }
                    }
                }
            }
            return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
        }
    }
复制代码

2、在Global.asax的Application_Start方法中,进行EnumConverterModelBinder类的实例化操作:

       protected void Application_Start()
        {
            //处理在MVC3下,提交的JSON枚举值在Controller不能识别的问题
            ModelBinders.Binders.DefaultBinder = new EnumConverterModelBinder();
        }

进行配置改造之后,我再次生成网站,重新发送HTTP请求看,MVC Action中的参数里的枚举就能被正确的识别到了。

 

四、研究

我觉得这应该是mvc3里面一个小小的缺陷吧,随着mvc的升级,这已经在新版本里被完善修复了,可还用着mvc3的人如果在项目中遇到这个问题,可以研究一下。

遇到一个问题,去百度谷歌找解决方案是可以,但是复制粘贴完代码之后,最好问下自己,为什么这样可以解决问题。

从现象和解决代码中猜想,应该是在MVC生命周期中的Model Binders 这一环节出了问题。

     因为MVC已经开源了,所以我尝试着调试源码,首先下载MVC3的源码,其他项目可以移除,只保留红色框中的项目即可,然后新建一个MVC3测试项目,并且将此测试项目的system.web.mvc引用移除,转而引用本解决方案中的system.web.mvc 项目,这样子,我们才可以对MVC源码进行调试操作。

搜回来的代码中可知,我们自定义的类继承DefaultModelBinder父类,并且重写了GetPropertyValue方法,那我们就从这点开始,在MVC3源码中的System.Web.MVC项目中找到该类,在此方法上插入断点。

F5调试程序,发送一个POST请求。

其实BindProperty方法是会被多次执行的,BindProperties方法会对请求的实体类的属性进行遍历,每一个属性都要经过BindProperty方法的处理;

现在已经截获到第一个属性ID了。

紧接着,程序进入propertyBinder.BindModel 方法。

只贴部分关键代码了,通过bindingContextValueProvider 获得属性的相关信息,如果不等于null的话,转到执行BindSimpleModel 方法。

BindSimpleModel方法里,首先通过Type.IsInstanceOfType方法判断确定指定的对象是否是当前 Type 的实例,如果是,则直接返回rawValue,这里的属性类型是Int32类型,返回True符合条件,所以直接把rawValue给返回去了。

第一个Int32类型属性的部分关键代码执行到这里就已经确认到值了,接下来,我们看出了问题的Enum枚举类型属性。

 循环来到了第二个属性了,这时我留意到有个Model属性,对比Int32类型执行的时候,这个属性当时为0,而此时则为Bus,可见这是一个默认值,指定枚举中值为0的那个类型(即使你不为枚举显式指定值),同样的,经过BindModel方法来到了BindSimpleModel方法。

此时,对比Int32类型的属性ID,这次ModelType.IsInstanceOfType(valueProvideResult.RawValue)False,并且接下来不是string类型就执行以下的判断,也不是数组类型,所以,来到了最后一个,根据绿色的注释可以看出,这应该是一个判断是否collection集合类型的方法,Enum都不是,所以,返回了Null

这时,Type collectionType变量为Null,执行最后一个case 3

ConvertProviderResult方法里,也进行了一系列的类型判断转换,目的就是将JSON中的数字类型转换成枚举值,但是执行过程中抛出异常了,原因是

No type converter can convert between these types ” 也就是说,在MVC3的机制中,并没有相应的type converter来处理数值与枚举的对应。

经过以上这些处理方法,都没完成把对应的值确认下来,怎么给原来的BindProperty 老大方法交差呢,所以,小的只好将Value=Null 和 modelState.Errors 模型错误状态信息如实带回去了,让老大决定怎么做,老大后面处理这里有点绕,但是我看源码估计也是拿默认值来充当Value了,所以就造成了JSON传过来的值与对应枚举的值不对应的情况,无论传什么值,结果都是第一个枚举的值。

 五、总结

这篇文章只是我在工作上遇到的一个小问题,然后有点小兴趣就从源码的角度上来研究和分析,缺乏理论的依据,因为之前没有很深入的去研究MVC的底层运行机制与生命周期,所以这方面还需要得加强学习一下,如果你也有兴趣,可以下载我修改好的源码来分析一下,甚至可以下载MVC4的源码来进行对比。

[转载]c# asp.net微信公众平台开发(3)微信消息封装及反射赋值 - 沫尘 - 博客园

mikel阅读(1007)

[转载]asp.net微信公众平台开发(3)微信消息封装及反射赋值 – 沫尘 – 博客园.

上一篇已经搭建好整体框架,实现了入口的验证,   验证通过后就交给LookMsgType方法处理,LookMsgType方法主要是对微信发来的不同的消息进行分解,不同的类型交给业务逻辑层不同的方 法处理,   对不同类型的消息判断,可以用if,也可以用switch   一般来说超过5个的if用switch会更好, 这里贴出LookMsgType方法:

public void LookMsgType(string msgType)
{

#region 判断消息类型
switch (msgType)
{
case "text":
RText mText = new RText();
mText = ReadXml.GetModel(mText, xmlModel);
BLLWei.DoText(dbHome, mText);//文本消息
break;
case "image":
RImg mImg = new RImg();
mImg = ReadXml.GetModel(mImg, xmlModel);
BLLWei.DoImg(dbHome,mImg);//图片
break;
case "voice": //声音
RVoice mVoice = new RVoice();
mVoice = ReadXml.GetModel(mVoice, xmlModel);
BLLWei.DoVoice(dbHome,mVoice);
break;

case "video"://视频
RVideo mVideo = new RVideo();
mVideo = ReadXml.GetModel(mVideo, xmlModel);
BLLWei.DoVideo(dbHome, mVideo);
break;

case "location"://地理位置
RLocation mLocation = new RLocation();
mLocation = ReadXml.GetModel(mLocation, xmlModel);
BLLWei.DoLocation(dbHome,mLocation);
break;
case "link"://链接
RLink mLink = new RLink();
mLink = ReadXml.GetModel(mLink, xmlModel);
BLLWei.DoLink(dbHome,mLink);
break;
#region 事件
case "event":

switch (ReadXml.ReadModel("Event", xmlModel))
{
case "subscribe":

if (ReadXml.ReadModel("EventKey", xmlModel).IndexOf("qrscene_") &gt;= 0)
{
RCodeNotSub mNotSub = new RCodeNotSub();
mNotSub = ReadXml.GetModel(mNotSub, xmlModel);
BLLWei.DoCodeNotSub(dbHome,mNotSub);//未关注的新用户,扫描带参数的二维码关注
}
else
{
RSub mSub = new RSub();
mSub = ReadXml.GetModel(mSub, xmlModel);
BLLWei.DoSub(dbHome,mSub);//普通关注
}
break;
case "unsubscribe":
RUnsub mUnSub = new RUnsub ();
mUnSub = ReadXml.GetModel(mUnSub, xmlModel);
BLLWei.DoUnSub(dbHome,mUnSub);//取消关注
break;

case "SCAN":
RCodeSub mCodeSub = new RCodeSub();
mCodeSub = ReadXml.GetModel(mCodeSub, xmlModel);
BLLWei.DoCodeSub(dbHome,mCodeSub);//已经关注的用户扫描带参数的二维码
break;
case "LOCATION"://用户上报地理位置

RSubLocation mSubLoc = new RSubLocation();
mSubLoc = ReadXml.GetModel(mSubLoc, xmlModel);

BLLWei.DoSubLocation(dbHome, mSubLoc);
break;
case "CLICK"://自定义菜单点击

RMenuClick mMenuClk = new RMenuClick();
mMenuClk = ReadXml.GetModel(mMenuClk, xmlModel);
BLLWei.DoMenuClick(dbHome, mMenuClk);
break;
case "VIEW"://自定义菜单跳转事件

RMenuView mMenuVw = new RMenuView();
mMenuVw = ReadXml.GetModel(mMenuVw, xmlModel);
BLLWei.DoMenuView(dbHome, mMenuVw);
break;
};
break;
#endregion
}
#endregion
}

外层switch判断msgtype,   在event类型时,再次switch判断具体的事件类型(关注、取消关注、自定义菜单事件等),  至此所有的微信发来的消息都有处理了,在上面代码中用到消息模型以及ReadXml.GetModel方法给模型赋值, 赋值之后传递给业务逻辑层对应的方法处理,  下面写出消息封装和给模型赋值的方法。

1、消息封装:

对所有微信发来的消息进行封装, 在datamodel中建一个Receive文件夹和一个send文件夹,在其中分别建立对应消息的类,完成之后,完整的datamodel类库如下图:

 

举例

—–接收消息:

文本消息RText.cs

public class RText
    {
        public string ToUserName { get; set; }// 开发者微信号
        public string FromUserName { get; set; }// 用户号(OpenID)
        public long CreateTime { get; set; }// 创建时间
        public string MsgType { get; set; } //消息类型
        public string Content { get; set; }//内容
        public long MsgId { get; set; }//消息ID

    }

自定义菜单点击RMenuClick.cs

public class RMenuClick
    {
        public string ToUserName { get; set; }// 开发者微信号
        public string FromUserName { get; set; }// 用户号(OpenID)
        public long CreateTime { get; set; }// 创建时间
        public string MsgType { get; set; } //消息类型

        public string Event { get; set; }//事件类型
        public string EventKey { get; set; }//事件key
        
    }

其他也都类似,不一一列举。

—–发送消息

发送文本消息SText.cs

public class SText
    {



        public string ToUserName { get; set; }// 用户号(OpenID)
        public string FromUserName { get; set; }// 开发者微信号

        public long CreateTime { get; set; }// 创建时间

        public string MsgType { get { return "text"; } } //消息类型

        public string Content { get; set; }//内容


    }

SText
[/sharp]
发送图文消息SNews.cs

namespace DataModel.Send
{
    public class SNews
    {
        public string ToUserName { get; set; }// 用户号(OpenID)
        public string FromUserName { get; set; }// 开发者微信号

        public long CreateTime { get; set; }// 创建时间

        public string MsgType { get { return "news"; } } //消息类型

        public int ArticleCount { get; set; }//图文个数

        public List<ArticlesModel> Articles { get; set; }//图文列表
    }
    public class ArticlesModel //默认第一条大图显示
    {
        public string Title { get; set; }//标题
        public string Description { get; set; }//描述
        public string PicUrl { get; set; }//图片链接  
        public string Url { get; set; }//点击之后跳转的链接

    }
}

发送图文消息

在发送图文消息中,因为回复给微信的图文消息中,具体的图文内容是多条(最多可以10条),所以单独会有ArticlesModel。 后面文章会写出图文消息的发送。

2、通过反射给model赋值

在上篇文章写的入口处,已经有了解析xml的方法,现在封装了消息,通常的做法,是每次用到对应的model就手动写代码赋值, 而我这里LookMsgType方法中所有给消息赋值时全用的ReadXml.GetModel这同一个方法, 这里用的就是反射,方法如下:

/// <summary>
        /// 通过反射给接收消息model赋值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <returns></returns>
        public static T GetModel<T>(T model, Dictionary<string, string> xmlModel) where T : class
        {
            var m = model.GetType();
            foreach (PropertyInfo p in m.GetProperties())
            {
                string name = p.Name;
                if (xmlModel.Keys.Contains(name))
                {
                    string value=xmlModel.Where(x => x.Key == name).FirstOrDefault().Value;
                    p.SetValue(model,
                    string.IsNullOrEmpty(value) ? null : Convert.ChangeType(value, p.PropertyType), null); 
                }
            }
            return model;
        }

T model 就是要使用的消息类, xmlmodel是在入口处传递进来的解析的微信发来的xml信息, 这样,就不需要每次手动写代码赋值了。

好了,此篇实现了lookmsgtype方法, 实现了消息封装和反射赋值, 接下去就是到了业务逻辑层中的处理和具体实现了…

[转载]编写高质量代码改善C#程序的157个建议[勿选List做基类、迭代器是只读的、慎用集合可写属性] - aehyok - 博客园

mikel阅读(1088)

[转载]编写高质量代码改善C#程序的157个建议[勿选List做基类、迭代器是只读的、慎用集合可写属性] – aehyok – 博客园.

前言

  本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

建议23、避免将List<T>作为自定义集合类的基类

建议24、迭代器应该是只读的

建议25、谨慎集合属性的可写操作

建议23、避免将List<T>作为自定义集合类的基类

 如果要实现一个自定义的集合类,最好不要以List<T>作为基类,而应该扩展相应的泛型接口,通常是 Ienumerable<T>和ICollection<T>(或ICollection<T>的子接口,如 IList<T>。

public class Employee1:List<Employee>
public class Employee2:IEnumerable<Employee>,ICollection<Employee>

不过,遗憾的是继承List<T>并没有带来任何继承上的优势,反而丧失了面向接口编程带来的灵活性,而且可能不稍加注意,隐含的Bug就会接踵而至。

来看一下Employee1为例,如果要在Add方法中加入一点变化

    public class Employee
    {
        public string Name { get; set; }
    }
public class Employee1:List
{
public new void Add(Employee item)
{
item.Name += "Changed";
base.Add(item);
}
}

进行调用

public static void Main(string[] args)
{
Employee1 employee1 = new Employee1() {
new Employee(){Name="aehyok"},
new Employee(){Name="Kris"},
new Employee(){Name="Leo"}
};
IList employees = employee1;
employees.Add(new Employee(){Name="Niki"});
foreach (var item in employee1)
{
Console.WriteLine(item.Name);
}
Console.ReadLine();
}

结果竟然是这样

这样的错误如何避免呢,所以现在我们来来看看Employee2的实现方式

public class Employee2:IEnumerable,ICollection
{
List items = new List();
public IEnumerator GetEnumerator()
{
return items.GetEnumerator();
}

///省略
}

这样进行调用就是没问题的

public static void Main(string[] args)
{
Employee2 employee1 = new Employee2() {
new Employee(){Name="aehyok"},
new Employee(){Name="Kris"},
new Employee(){Name="Leo"}
};
ICollection employees = employee1;
employees.Add(new Employee() { Name = "Niki" });
foreach (var item in employee1)
{
Console.WriteLine(item.Name);
}
Console.ReadLine();
}

运行结果

建议24、迭代器应该是只读的

 前端时间在实现迭代器的时候我就发现了这样一个问题,迭代器中只有GetEnumeratior方法,没有SetEnumerator方法。所有的集合也没有一个可写的迭代器属性。原来这里面室友原因的:

其一:这违背了设计模式中的开闭原则。被设置到集合中的迭代可能会直接导致集合的行为发生异常或变动。一旦确实需要新的迭代需求,完全可以创建一个新的迭代器来满足需求,而不是为集合设置该迭代器,因为这样做会直接导致使用到该集合对象的其他迭代场景发生不可知的行为。

其二:现在,我们有了LINQ。使用LINQ可以不用创建任何新的类型就能满足任何的迭代需求。

关于如何实现迭代器可以来阅读我这篇博文http://www.cnblogs.com/aehyok/p/3642103.html

现在假设存在一个公共集合对象,有两个业务类需要对这个集合对象进行操作。其中业务类A只负责将元素迭代出来进行显示:

IMyEnumerable list = new MyList();
IMyEnumerator enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
object current = enumerator.Current;
Console.WriteLine(current.ToString());
}
Console.ReadLine();

业务类B出于自己的某种需求,需要实现一个新的针对集合对象的迭代器,于是它这样操作:

MyEnumerator2 enumerator2 = new MyEnumerator2(list as MyList);
(list as MyList).SetEnumerator(enumerator2);
while (enumerator2.MoveNex())
{
object current = enumerator2.Current;
Console.WriteLine(current.ToString());
}
Console.ReadLine();

问题的关键就是,现在我们再回到业务类A中执行一次迭代显示,结果将会是B所设置的迭代器完成输出。这相当于BG在没有通知A的情况下对A的行为进行了干扰,这种情况应该避免的。

所以,不要为迭代器设置可写属性。

建议25、谨慎集合属性的可写操作

 如果类型的属性中有集合属性,那么应该保证属性对象是由类型本身产生的。如果将属性设置为可写,则会增加抛出异常的几率。一般情况下,如果集合属性没有值,则它返回的Count等于0,而不是集合属性的值为null。我们来看一段简单的代码:

public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
public class StudentTeamA
{
public List Students { get; set; }
}
class Program
{
static List list = new List()
{
new Student(){Name="aehyok",Age=25},
new Student(){Name="Kris",Age=23}
};
static void Main(string[] args)
{
StudentTeamA teamA = new StudentTeamA();
Thread t1 = new Thread(() =&gt;
{
teamA.Students = list;
Thread.Sleep(3000);
Console.WriteLine("t1"+list.Count);
});
t1.Start();
Thread t2 = new Thread(() =&gt;
{
list= null;
});
t2.Start();
Console.ReadLine();
}
}

首先运行后报错了

这段代码的问题就是:线程t1模拟将对类型StudentTeamA的Students属性进行赋值,它是一个可读/可写的属性。由于集合属性是一个引用类型,而当前针对该属性对象的引用却有两个,即集合本身和调用者的类型变量list。

线程t2也许是另一个程序猿写的,但他看到的只有list,结果,针对list的修改会直接影响到另一个工作线程中的对象。在例子中,我们将 list赋值为null,模拟在StudentTeamA(或者说工作线程t1)不知情的情况下使得集合属性变为null。接着,线程t1模拟针对 Students属性进行若干操作,导致异常的抛出。

下面我们对上面的代码做一个简单的修改,首先,将类型的集合属性设置为只读,其次,集合对象由类型自身创建,这保证了集合属性永远只有一个引用:

public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
public class StudentTeamA
{
public List Students { get;private set; }
public StudentTeamA()
{
Students = new List();
}
public StudentTeamA(IEnumerable list):this()
{
Students.AddRange(list);

}
}
class Program
{
static List list = new List()
{
new Student(){Name="aehyok",Age=25},
new Student(){Name="Kris",Age=23}
};
static void Main(string[] args)
{
StudentTeamA teamA = new StudentTeamA();
teamA.Students.AddRange(list);
teamA.Students.Add(new Student() { Name="Leo", Age=22 });
Console.WriteLine(teamA.Students.Count);
///另外一种实现方式
StudentTeamA teamB = new StudentTeamA(list);
Console.WriteLine(teamB.Students.Count);
Console.ReadLine();
}
}

修改之后,在StudentTemaA中尝试对属性Students进行赋值,就会发现如下问题

上面也发现了两种对集合进行初始化的方式。

[转载]移动app创业的7个认识误区 - 创业ABC - 创业邦

mikel阅读(1084)

[转载]移动app创业的7个认识误区 – 创业ABC – 创业邦.

互联网创业公司相比,移动互联网的生态系统其实还是很新的,因此,许多首次进入app开发领域的创业者对整个过程有一些错误的认识。

以下是进入这个领域里认识上的一些误区。避开它们,将帮助首次进入app开发市场的创业者们踏上正确的道路,并且让他们知道创建并且推广产品的过程中,会面临什么样的困难。

1.技术压倒一切:如果你并不具备技术背景,不考虑把技术作为创业公司的顶点,那么技术只是一个推动者——你的项目、你的公司和商业目的才是核心。你不必知道开发一个技术产品,代码该怎么写。雇用精通这门业务的人,或者外包给其他机构,你可以通过这些技术手段帮助你实现商业目标。

2.开发出产品,任务就完成了:如果你认为,你的app在app sotre上架了,你的工作就完成了,那你就大错特错了。开发一个app是创建公司的过程中最容易的事情,让用户一直使用你的app才是最困难的部分。

3.创意就是一切:如 果你的想法是独一无二的,以至于没有人曾经想到过,那么很有可能没有人会认同它。康奈尔大学的一篇公开发表的论文就曾得出这样的结论:创意越是新颖,这些 问题的不确定性就越高——这个想法是否可行、是否有用、是否无差错,是否可以被可靠地复制。这些都是关于执行的问题——你如何实现你的想法才是最重要的。 有很多伟大的想法没有成功,不是因为创意,是因为它们没有被很好地执行。

4.仅仅有个创意原型就可以获得融资:现在,科技创业企业的整体成本比起以前创建传统的软件公司来,已经大幅下降了不少。Hackathons大赛上,一个app几天之内就可以做出demo和第1个版本来。投资者们在考虑投资时,往往会寻找己经经过用户测试的,用户注册数量和用户留存率己经出现增长的产品。

5.可以向朋友对你的创意项目进行验证:询问某人对一个假想的产品反馈,你得到的也是一种想象中的回应。省点力气吧,不用去咨询那些“不必对意见承担后果”的人的意见,你应该去相信的,是那些真正使用你的app的人的意见。更好的一种做法是,从有偿使用你的app的人那里寻求建议。

6.营销意味着支出:当 然,付费营销是最简单的路径,但不一定是最有效的。销售增长的基础是,你的app确实是一个很好的产品,并且能让你的第一批客户感到满意。每一个成功的 app公司都是基于口碑才渐渐受到普遍欢迎的。人们倾向于支持脱颖而出的产品,付费营销可能会让你获得下载数量的增长,但不会留住用户。

7.获得资金是公司的首要任务:筹 集风险资金是你要做的最艰难的事情之一。当你想建立一个创业企业时,请专注于开发产品以及获得客户。假设在启动前,你不能保证能够获得足够的投资,那么你 的首要计划就始终应该是打入市场。你创业的雄心不应依赖于风险投资,但是你却一定要下决心成为一个成功的企业家,开拓自己的市场和用户。

作者: Rahul Varshneya,Arkenea LLC的创始人之一,该公司致力于帮助创业者开拓市场,并将他们的移动app商业化。

(Via:entrepreneur.创业邦编译。Entrepreneur内容授权创业邦独家合作,未经允许请勿转载)

[转载]改变手机IMEI的方法-月光博客

mikel阅读(1147)

[转载]改变手机IMEI的方法-月光博客.

某些移动应用会通过技术手段阻止用户使用,例如被陌陌封号了以后,使用者会注意到,自己的手机再也无法使用陌陌了,即便是再注册一个帐号也无济于事。本文给出一个办法:即通过PDroid修改app所能见到的IMEI,来合法安全地解决这个问题。

产生这种状况的原因:

1,通过在Android Package的Androidmanifest.xml文件中添加Android.permission.READ_PHONE_STATE对 象,android应用程序可以通过Android提供的API获取访问蜂窝设备的IMEI号码的权限。

2,在Android手机上,陌陌会索取手机的IMEI号码,并且把它当作用户校验的手段。当你试图在别的手机上登陆你的陌陌账户时,会被拒绝。同样,陌陌的运营者可以决定阻止带有特定IMEI的设备登陆。

3,每个手机都有一个独一无二的IMEI号码,这意味着IMEI和用户高度相关,和手机号码一样是一个可以精确追踪到用户的标记,只要不换手机,无论你使用什么帐号,IMEI都是不变的。

——有一个例外,山寨机的IMEI号码往往会不符合以上情况

问题出在哪里:

显然,陌陌是无权封禁设备的。因为它不可以具备追踪到设备的能力。换个说法,只要用户希望,用户就应当能获得阻断陌陌追踪到用户设备的能力。

IMEI与用户的高度相关决定了它是用户的隐私,只有用户的蜂窝网络运营商以及一些直接的利益相关者(如负责公共安全的政府部门,厂商在维修设备时)才可以无条件知晓特定IMEI与用户之间的联系。除此以外,用户理应可以自行决定哪些应用程序可以知晓自己设备的串号。

而陌陌将IMEI作为了用户登陆的必要条件。如果陌陌无法从Android.permission.READ_PHONE_STATE获得IMEI,它就不会登陆。

所以?

就像越狱苹果手机那样,我们需要一个由用户可行的,剥夺这种(可能是非法的)追踪到具体设备能力的手段。在罕见的情况下,使用二手设备的用户不得不通过这样的方法来绕过自己手机的前一个使用者被封禁,导致自己也不能用陌陌的故障。

简单地说:

既然形式上陌陌需要一个IMEI,那么就如其所愿,公平地给它一个李鬼IMEI吧。如果陌陌一旦获得了IMEI,只要内容不为空,它就会允许注册/登陆

以下介绍的内容无法改变手机本身的IMEI(这个东西是固化在更底层的地方,不是通过修改系统就能做到的),所以没有法律或安全上的问题;相反的,它会 给android用于向app提供IMEI的API做点手脚,让你可以自行决定,是否向应用程序提供IMEI,或者提供一个自定义/随机的IMEI。

实践以前,请检查一下是不是满足以下条件:

最重要的:因为接下来介绍的方法是公开的,涉及到的代码也是开源(PDroid patch的代码本体以GPLv3授权)的,因此以下的方法以“原样”提供,没有任何保证,实践者需要自行承担操作带来的一切后果(比如,包含且不限于设备损坏,被封号等状况)

1,设备需要满足的基本条件:

设备的bootloader必须解开(对于HTC设备,需要处于s-off状态)

仅可在这些系统实行Android 2.3(Gingerbread)/4.0(Ice Cream Sandwitch)/4.1(Jelly Bean)

Android必须允许root权限(意味着你可以通过superuser/superSU/LBE授权管理等为需要的app提供root权限),以使得PDroid patch可以工作,实际上PDroid app本身并不需要root权限。

Android必须deodexed

2,开发环境

对于Android 2.3,你需要:

一个安装了JDK1.6/1.7的windows环境,
.Net Framework 2.0虚拟机,
设备的recovery环境需要clockwork recovery(TWRP recovery可能不被支持)

对于Android 4.0,你需要:

一个安装了JDK1.6/1.7的linux、MAC或cygwin环境

或者:
在有足够java,android开发能力的前提下,你可以按照XDA-developers论坛上给出的源代码,指示,自行编译。

3,你需要掌握的知识

会给android手机刷机,某些情况下需要知道怎么解锁bootloader,以及通过fastboot
安装linux内核;可能需要知道最基本的linux终端(terminal)的使用方法

4,其它

如果是android 2.3,设备的framework.jar必须没有改动过
如果是android 4.0/4.1,只有特定的rom种类被支持。支持名单包括(到2012年8月25日为止):CM7,CM9,CM10,AOSP(Jelly Bean以及部分低级版本),AOKP,ParanoidAndroid,以及其它某些未指明的版本。

——CM是Cyanogen Mod的缩写,AOSP是Android Open Source Project的缩写,AOKP是Android Open Kang Project的缩写。

实际操作:

将会做什么?

通过给android系统打一个叫PDroid patcher的权限管理补丁,配合上专用的app(也就是PDroid app)后,可以给陌陌的app提供一个自定义IMEI号码,从而绕过它的IMEI检测系统。

具体步骤?

对于Android 2.3

1,下载PDroid作者svyat编译好的自动补丁制作工具PDroid Patcher

2,在recovery环境中将当前的ROM进行nandroid备份并导出。当然,也可以用全新的刷机包(aroma installer格式的可能不被支持)

3,启动PDroid Patcher,选择上一步准备好的备份/刷机包(.zip文件),点击patch后,软件会针对当前的.zip制作一对补丁,一个是用于打上PDroid补丁的,另一个是恢复包,用于打完补丁后的还原(在出故障,或不希望继续使用等场合下)

4,把上一步制作好的两个补丁复制到设备的SD卡(对于不支持SD卡的设备,则放在/sdcard分区),如果是安装全新系统,不要忘了刷机包。

5,进入recovery环境,安装PDroid补丁。新安装系统的,则先安装系统后再安装PDroid补丁。

6,到Google Play Store安装PDroid app;如手机没有Gapps,或因众所周知的原因而无法访问Play Store时,则可以到作者在XDA-developer的页面获取。

7,打开PDroid app,给陌陌指定一个IMEI号码即可——同样的,这个IMEI号码会和你新注册的陌陌帐号绑定。

对于Android 4.0/4.1

原作者svyat提供的补丁工具只适用于Android 2.3,因此我们需要mateorod的帖子中,适用于4.0/4.1的工具。这个工具只能在Linux、Mac,以及Cygwin环境下运行。所以实践 者可能需要一台安装了JDK(OpenJDK和Oracle开发的JDK都可以)的linux电脑/虚拟机,或者按照作者提供的方法自行配置cygwin 环境。

1,下载专用的补丁制作工具,解压至实践者自行指定的工作目录

2,在terminal中进入前面所说的工作目 录,以”./autopatcher ROM.zip pdroid”的形式运行,稍等一会,补丁(以及恢复包)就会制作完成。也可以” ./autopatcher ROM.zip pdroid,v6supercharger”的形式运行,这样补丁就会同时包括pdroid与supercharger V6(一个调整了默认Android系统内存管理的脚本)

接下来的步骤和前面相同。

iOS系统怎么办:

在iOS系统下(需要越狱),也有类似的应对方法。有一个叫UDIDFaker的工具可以为指定的app更改UDID,也可以用来绕过陌陌(可能是非法) 的限制。

类似的方法也可以用于whatsapp等强制要求IMEI才可工作的app,如果你不希望它们知晓你的IMEI的话。

可能需要的相关XDA-developers内容的地址以及参考:

mateorod  —  [Script][8-08][JB] Auto-Patcher
http://forum.xda-developers.com/showthread.php?t=1719408

svyat  –  [APP] PDroid – The Better Privacy Protection [2012-03-18: Bugfixes in Patch 1.32]
http://forum.xda-developers.com/showthread.php?t=1357056

[App] PDroidAddon Camera and Microphone Permission [Update: v1.2, 2012-08-08]
http://forum.xda-developers.com/showthread.php?t=1794411

IMEI
http://baike.baidu.com/view/90099.htm

来源:willproven投稿

[转载]加密与解密 ,URL请求保护,注册码-Android开发经验分享-eoe 移动开发者论坛 - Powered by Discuz!

mikel阅读(1176)

[转载]加密与解密 ,URL请求保护,注册码-Android开发经验分享-eoe 移动开发者论坛 – Powered by Discuz!.

懒得排版,懒得给代码,懒得写太多。大牛绕道。伸手党绕道。
加密分很多种,但是按其方向来分就两个。
单向加密,加密之后永远得不到最初的密码。
可加可解,加密之后通过双方约定的钥匙来解密。

用途:单向加密自然就不希望别人知道密码,在校验的时候只需要比较加密后的信息是否相同进行通过就可以了,可以保护原始信息,也是较为安全的方式。
MD5 SHA等等基本都是这个的思路。很多人会提到,这些东西安全个什么,稍微厉害的服务器就可以反找密码。试想,如果在MD5上在加上你自己的加密算法可能会有人解出来?
跑题…

可加可解:可加可解,既然要解,所以肯定加密后的密码不仅仅是用于校验,还有一部分信息是需要提取的,用于其他用途,至于干嘛,稍后例子详解。

URL请求保护

有人问,怎么保证数据库的信息安全,我需要怎么写,怎么加密?
其实我遇到这种问题很反感,太宽泛。但是本人又不是大牛,大哲,只好把一些琢磨的思路扔出来。
想把所有数据都加密再解密,除非是非常严格需求,否则根本不现实。尤其在Android这种移动设备上,仅仅是为了数据安全做这些东西。所以我是不建议在输入输出流上做文章。
因此就有了请求保护的想法,其实请求保护说穿了就是oath认证的一些东西。但我在这里不拔oath的东西,有兴趣的可以去研究。
下面就说一下实现思路,
简单的思路,定义一个软件私有key 做单向加密 , 服务器做同样处理看两次加密后结果是否一致,一致则通过。
这个方案有什么问题呢?你的加密后的口令一旦被泄露,就可以被所有人都使用了。你只能通过升级版本来改变key值。
稍微复杂的思路,定义一个软件私有key,做 单向加密Code1。时间戳做双向加密Code2,Code1和Code2做双向加密CodeSum。服务器 得到密码CodeSum 解密得到Code1和Code2。可以得到时间和校验口令。这里要注意,时间戳要自己定义时间延时量。一般根据网速定一个合理的范围,只要时间差的不多就 放行。
这个方案比较第一个已经相当不错了,黑客就算截取了你的一次请求,顶多就在几秒内可用。而且黑客加密复杂度极高做不到逆向解码。
扩展思路,上面的需求基本都是一个用户的信息,在实际处理中往往涉及到多用户的请求。只需要稍微对上面的加密方式进行修改就可以了,在加密因子里面加上加密的sessionID作为处理。

没有什么真知灼见,但是这样处理你是解决的每次请求,有效的避免了非法请求。也是互联网应用的不同,需要实效性。

注册码

注册码的校验分为很多情况,可以服务器校验,可以手机客户端校验。针对不同场景不同方案。
服务器校验每次激活需要联网HTTPS,然后客户端获取权限进行使用。流程:手机传递IMEI和激活码,验证成功后 服务器保存激活码,返回可以激活口令,客户端进行激活。扩展:给用户提供解绑定功能,和多台IMEI激活等设定。IMEI其实和PC的硬盘码 机器码十分类似。(注意:平板没IMEI)
客户端校验:用户不需要联网,在指定网站上根据IMEI购买后直接注册,一个IMEI对应一个手机。(稍微霸道)

写在后面:其实这些校验的东西说的再细,都要建立在优秀的混淆包,防DECODE的基础上,否则再强大的加密模式都毫无意义。但是一个好的加密方式会给你很多补救,也不会泄漏用户信息。(吐槽CSDN?)
明文要不得啊,只加SHA无异于明文啊!

[转载]常用上网增强类Chrome扩展-月光博客

mikel阅读(903)

[转载]常用上网增强类Chrome扩展-月光博客.

Chrome是个非常好用的浏览器,拥有丰富的扩展资源库,能够满足网民各种各样的需求,对于网民来说,通过Chrome扩展来增强上网体验是 一个基本需求,但是安装过多的扩展有容易耗费大量系统资源,今天月光博客就给大量挑选一些常用的上网增强类Chrome扩展,供大家参考。

LastPass:用于管理大量网站的密码,给不同网站设置不同的密码,支持自动登录,支持手机两步验证。建议在普通和隐身模式下都启用这个扩展。

Adblock Plus:广告屏蔽扩展,可以屏蔽不少网站的广告,包括屏蔽Youtube视频广告、Facebook广告、横幅广告以及其他广告,还可以自定义规则,在网络广告泛滥的年代里,可以做到眼不见心不烦。该扩展是一个社区驱动的开源项目 ,有不少志愿者为其作出了贡献。

网页截图:Google提供的网页截图工具,该扩展可以轻松截取当前标签页的可见区域,当前网页的指定区域,或是整张网页的页面,截图后,可以利用图片编辑工具编辑图片,然后将编辑后的图片保存为PNG格式的图片文件。

Google Mail Checker:显示Google Mail收件箱中的未读邮件数,点击该按钮还可以打开Gmail收件箱。

RSS Subscription:Google提供的显示网站的RSS地址的扩展,可以在工具栏上显示RSS图标,用于一键订阅。

Feedly:RSS阅读器网站Feedly自带扩展。

AutoPager Chrome:自动翻页扩展,在Google搜索网页或浏览论坛帖子时,可以自动翻页并将每一页的内容接在现有内容的末尾,支持自定义规则。

User-Agent Switcher:设置浏览器的UserAgent,将浏览器伪装成为其他系统的浏览器,例如iPhone等。

Readium:EPUB阅读扩展,支持在Chrome里阅读EPUB书籍。

Proxy SwitchySharp:天朝必备,用于安全上网的扩展,你懂的。

Readability:这个工具其实主要用于科学上网,遇到看不了的文章,点一下,就可以在Readability里面看到。

IE Tab:在Chrome标签页中以IE内核显示网页,平时没啥用,使用中国的网银系统时必备。

Google Webspam Report:Google提供的举报垃圾网页的工具,可以举报Google搜索结果里的垃圾网页,提升Google搜索质量和搜索体验。

TemperMonkey:这是Chrome上的Greasemonkey(油猴),Tampermonkey通过加载第三方的脚本文件,可以让Chrome支持更多UserScript的Chrome 扩展,例如改变页面中的CSS和JS元素,或在网页中增加额外的功能等,高手必备。

常用上网增强类Chrome扩展

[转载]Android系统手机端抓包方法 - 一江水 - 博客园

mikel阅读(1040)

[转载]Android系统手机端抓包方法 – 一江水 – 博客园.

抓包准备

1. Android手机需要先获得root权限。一种是否获得root权限的检验方法:安装并打开终端模拟器(可通过安卓市场等渠道获得)。在终端模拟器界面输入su并回车,若报错则说明未root,若命令提示符从$变#则为rooted;

2. 如果Android手机尚未root,可通过superoneclick或其它方法进行root处理(需要先安装Microsoft .NET Framework)。Superoneclick刷root权限教程:(http://soft.shouji.com.cn/news/501.shtml)

3. 需要先获得 Android SDK

4. 需要获得tcpdump软件,获取地址(http://www.strazzere.com/android/tcpdump)

抓包步骤
1. 将Android手机与电脑USB相连,打开windows命令提示符窗口

2. 将tcpdump程序copy至android手机(该命令前面那个目录文件为本地地址,后面那个目录为目的手机端地址)

C:\android-sdk-windows\platform-tools>adb push c:/tcpdump /data/local/tcpdump

3. 修改tcpdump的权限

C:\android-sdk-windows\platform-tools>adb shell
#chmod 777 /data/local/tcpdump

4. 进入root权限

C:\android-sdk-windows\platform-tools>adb shell
$ su

在运行su指令后,手机终端桌面会出现相应提示信息以确认您对root操作的认可。

5. 运行tcpdump,输入以下命令启动抓包。

/data/local/tcpdump -p -vv -s 0 -w /sdcard/capture.pcap

6. 在手机端执行相应需要进行抓包分析的操作,执行完成后在命令提示符窗口执行Ctrl+C中断抓包进程

7. 将抓包结果复制至本地(前面那个目录为手机端地址,后面那个目录为本地地址)

C:\android-sdk-windows\platform-tools>adb pull /sdcard/capture.pcap c:/

8. 使用Wireshark等工具查看抓包文件capture.pcap

[转载]Android-view listView 每个item里动态添加不定量的控件,Android 自动换行 - helloworld.MR-zz - 博客园

mikel阅读(976)

[转载]【Android-view】listView 每个item里动态添加不定量的控件,Android 自动换行 – helloworld.MR-zz – 博客园.

问题描述:

开发过程中,遇到了在listview里面的每个item都有可能显示图片,并且需要显示的图片的数量不确定,需要自动换行。

如图:第一行显示三张图片,第二行显示四张图片。数量0—正无穷(内存支持的情况下)

解决办法:

最初就是直接从网上找Android自动换行的控件,再此感谢eoe论坛里**dahege **分享的源码。

  dahege eoe论坛原文地址:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=195276

  但是我直接拿来用的时候出现了个问题,就是当有四张图片的时候只显示三行。

具体解决办法如下:

a.修改values下attrs.xml文件

增加一个每行显示多少列的属性,类似gridview

<resources>
    <declare-styleable name="FlowLayout">
        <attr name="horizontalSpacing" format="dimension" />
        <attr name="verticalSpacing" format="dimension" />
        <attr name="numColumns" format="integer" /><!--这个属性为新加的-->
    </declare-styleable>
    <declare-styleable name="FlowLayout_LayoutParams">
        <attr name="layout_breakLine" format="boolean" />
        <attr name="layout_horizontalSpacing" format="dimension" />
    </declare-styleable>
</resources>

  b.修改FlowLayout.java 源文件

    1.在构造方法里得到用户在cml文件里设置的numColumns

public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        try {
            mHorizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 0);
            mVerticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 0);
            numColumns = a.getInt(R.styleable.FlowLayout_numColumns, 3);//得到用户在布局文件中设置的没行显示的列数
        } finally {
            a.recycle();
        }
        
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(0xffff0000);
        mPaint.setStrokeWidth(2.0f);
    }

2.修改onMeasure方法,由于对这块还不太了解,所以只是简单的改了一下,测试之后效果是实现了,暂未发现其他问题。应该还有更好的解决方案,希望有人指正。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight();
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        boolean growHeight = widthMode != MeasureSpec.UNSPECIFIED;

        int width = 0;
        int height = getPaddingTop();

        int currentWidth = getPaddingLeft(); 
        int currentHeight = 0;               

        boolean breakLine = false;
        boolean newLine = false;
        int spacing = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);

            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            spacing = mHorizontalSpacing;
            if (lp.horizontalSpacing >= 0) {
                spacing = lp.horizontalSpacing;
            }

            if (growHeight && (i == numColumns||breakLine || currentWidth + child.getMeasuredWidth() > widthSize)) {
//                newLine = true;
                
                height += currentHeight + mVerticalSpacing;
                width = Math.max(width, currentWidth - spacing);
                
                currentHeight = 0;
                currentWidth = getPaddingLeft();
                
            }
//            else {
//                newLine = false;
//            }
            if (i>numColumns&&i%numColumns==0) {//主要修改的是这个判断语句,原版的判断语句是29,38,39,40行的被隐掉的。我自己用原版的判断语句有问题,
                newLine = false;
            }

            lp.x = currentWidth;
            lp.y = height;

            currentWidth += child.getMeasuredWidth() + spacing;
            currentHeight = Math.max(currentHeight, child.getMeasuredHeight());
            
            breakLine = lp.breakLine;
        }

        if (!newLine) {
            height += currentHeight;
            width = Math.max(width, currentWidth - spacing);
        }

        width += getPaddingRight();
        height += getPaddingBottom();

        setMeasuredDimension(resolveSize(width, widthMeasureSpec),
                resolveSize(height, heightMeasureSpec));
    }

至此,就修改完毕了。

具体使用办法如下。

a.在listview的item布局文件中使用framelayout自定义控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:f="http://schemas.android.com/apk/res/你的androidmanifest.xml文件中的package属性值"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
     >

   
        <com.xingyunhudong.view.FlowLayout
            android:id="@+id/flowlaytou"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
           f:numColumns="3"<!--也可以不指定,如果不指定,在FrameLayout的构造函数里,默认取值为3列-->
             >
        </com.xingyunhudong.view.FlowLayout>

        

</LinearLayout>

b.在adapter中设值

public class XXXAdapterextends BaseAdapter {

    private LayoutInflater inflater;
    private List<XXX> xxxList;
    private Context context;

    private ViewGroup.LayoutParams paramsImg, paramsVideo;
    private int sw;

    public HuaTiAdapter(Context context, List<xxx> xxxList) {
        this.xxxList = xxxList;
        this.context = context;
        inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        sw = CommonUtils.getScreenWidth((Activity) context);
        int w = context.getResources().getDimensionPixelSize(
                R.dimen.xxx_img_total_width);
        sw = sw - w;
        paramsImg = new ViewGroup.LayoutParams(sw / 3, sw / 3);
        paramsVideo = new ViewGroup.LayoutParams(sw, 0);//为了图片适配
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return xxxList.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return xxxList.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    class ViewHolder {
        FlowLayout ll;
        int flag;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ViewHolder holder = null;
        xxxBean huati = xxxList.get(position);
        if (convertView == null
                || ((ViewHolder) convertView.getTag()).flag != position) {//第一个判断是优化listview加载速度及内存消耗,第二个判断是为了防止图片错位
            holder = new ViewHolder();
            holder.flag = position;
            convertView = inflater.inflate(R.layout.xxx_item_layout, null);
            
            holder.ll = (FlowLayout) convertView.findViewById(R.id.flowlaytou);
            ImageBean video = xxx.getVideoImg();
            if (video != null && video.getUrl() != null
                    && !"".equals(video.getUrl().trim())) {
                paramsVideo.height = video.getHeight() * sw / video.getWidth();
                addVideoView(holder.ll, video.getUrl(), paramsVideo, inflater);
            }
            List<ImageBean> imgList = huati.getImgList();
            if (imgList != null && imgList.size() > 0) {
                for (int i = 0; i < imgList.size(); i++) {
                    addImageView(holder.ll, imgList.get(i).getUrl(), paramsImg,
                            inflater, imgList, i);
                }
            }
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        return convertView;
    }

    private void addVideoView(FlowLayout ll, final String url,
            LayoutParams params, LayoutInflater inflater) {
        ImageView v = (ImageView) inflater.inflate(
                R.layout.yyy_image_layout, null);//这个layout里面就只有一个imageview空间,特别简单
        v.setLayoutParams(params);
        ImageUtil.display(url, v);
        v.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                //这里的点击事件也完全没有问题,不会错位,不会点击失效
            }
        });
        ll.addView(v);

    }

    private void addImageView(FlowLayout ll, String url, LayoutParams params,
            LayoutInflater inflater) {
        // TODO Auto-generated method stub
        ImageView v = (ImageView) inflater.inflate(
                R.layout.weixiu_image_layout, null);
        v.setLayoutParams(params);
        ImageUtil.display(url, v);
        v.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                //这里的点击事件也完全没有问题,不会错位,不会点击失效
 } }); ll.addView(v); } }

至此,就就结束了,希望可以帮到一部分人。

[转载]ASP.NET Web API的Controller是如何被创建的? - Artech - 博客园

mikel阅读(943)

[转载]ASP.NET Web API的Controller是如何被创建的? – Artech – 博客园.

Web API调用请求的目标是定义在某个HttpController类型中的某个Action方法,所以消息处理管道最终需要激活目标 HttpController对象。调用请求的URI会携带目标HttpController的名称,该名称经过路由解析之后会作为路由变量保存到一个 HttpRouteData对象中,而后者会被添加到代表当前请求的HttpRequestMessage对象的属性字典中。ASP.NET Web API据此解析出目标HttpController的类型,进而实现针对目标HttpController实例的激活。[本文已经同步到《How ASP.NET Web API Works?》]

目录
一、程序集的解析
二、HttpController类型的解析
1、DefaultHttpControllerTypeResolver
2、HttpController类型的缓存
三、HttpController的选择
1、DefaultHttpControllerSelector
2、获取目标HttpController的名称
3、建立HttpController名称与HttpControllerDescriptor之间的映射
4、根据请求选择HttpController
四、HttpController的创建
1、HttpControllerActivator
2、DefaultHttpControllerActivator
3、DependencyResolver
4、HttpRequestMessage中的DependencyResolver
5、DependencyResolver在DefaultHttpControllerActivator中的应用

一、程序集的解析

ASP.NET Web API的HttpController激活系统中,AssembliesResolver为目标HttpController类型解析提供候选的程序集。换句话说,候选HttpController类型的选择范围仅限于定义在由AssembliesResolver提 供的程序集中的所有实现了IHttpController接口的类型。所有的AssembliesResolver均实现了接口 IAssembliesResolver,该接口定义在命名空间“System.Web.Http.Dispatcher”下,如果未作特别说明,本节新 引入的类型均定义在此命名空间下。如下面的代码片断所示,IAssembliesResolver接口中仅仅定义了一个唯一的GetAssemblies 方法,该方法返回的正是提供的程序集列表。

   1: public interface IAssembliesResolver
   2: {
   3:     ICollection<Assembly> GetAssemblies();
   4: }

默认使用的AssembliesResolver类型为DefaultAssembliesResolver。如下面的代码片断所示,DefaultAssembliesResolver在实现的GetAssemblies方法中直接返回当前应用程序域加载的所有程序集列表。

   1: public class DefaultAssembliesResolver : IAssembliesResolver
   2: {
   3:     public virtual ICollection<Assembly> GetAssemblies()
   4:     {
   5:         return AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
   6:     }
   7: }

DefaultAssembliesResolver是默认使用的AssembliesResolver,那么默认的AssembliesResolver类型在ASP.NET Web API是如何确定的呢?要回答这个问题,需要涉及到另一个重要的类型ServicesContainer,它定义在命名空间“System.Web.Http.Controllers”下。

由于DefaultAssembliesResolver在 为HttpController类型解析提供的程序集仅限于当前应用程序域已经加载的程序集,如果目标HttpController定义在尚未加载的程序 集中,我们不得不预先加载它们。但是这样的问题只会发生在Self Host寄宿模式下,如果采用Web Host寄宿模式则无此困扰,原因在于后者默认使用的是另一个AssembliesResolver类型。我们知道在Web Host寄宿模式下用于配置ASP.NET Web API消息处理管道的是通过类型GlobalConfiguration的静态只读属性Configuration返回的 HttpConfiguration对象。从如下的代码片断我们可以发现,当GlobalConfiguration的Configuration属性被 第一次访问的时候,在ServicesContainer中注册的AssembliesResolver会被替换成一个类型为 WebHostAssembliesResolver的对象。

   1: public static class GlobalConfiguration
   2: {
   3:     //其他成员
   4:     static GlobalConfiguration()
   5:     {
   6:         _configuration = new Lazy<HttpConfiguration>(delegate
   7:            {
   8:                HttpConfiguration configuration = new HttpConfiguration( new HostedHttpRouteCollection(RouteTable.Routes));
   9:                configuration.Services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver());
  10:                //其他操作
  11:                 return configuration;
  12:            });
  13:       //其他操作
  14:      }
  15:
  16:      public static HttpConfiguration Configuration
  17:   {
  18:       get
  19:       {
  20:           return _configuration.Value;
  21:       }
  22:   }
  23: }

WebHostAssembliesResolver是一个定义在程序集“System.Web.Http.WebHost.dll”中的内部类 型。从如下的代码片断可以看出WebHostAssembliesResolver在实现的GetAssemblies方法中直接通过调用 BuildManager的GetReferencedAssemblies方法来获取最终提供的程序集。

   1: internal sealed class WebHostAssembliesResolver : IAssembliesResolver
   2: {
   3:     ICollection<Assembly> IAssembliesResolver.GetAssemblies()
   4:     {
   5:         return BuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>();
   6:     }
   7: }

由于BuildManager的GetReferencedAssemblies方法几乎返回了在运行过程中需要的所有程序集,如果我们将 HttpController类型定义在单独的程序集中,我们只要确保该程序集已经正常部属就可以了。如果有人对此感兴趣,可以试着将上面演示的实例从 Self Host寄宿模式转换成Web Host寄宿模式,看看ASP.NET Web API的HttpController激活系统能否正常解析出分别定义在Foo.dll、Bar.dll和Baz.dll中的 HttpController类型。

二、HttpController类型的解析

注册在当前ServicesContainer上的AssembliesResolver对象为HttpController类型的解析提供了可供选择的程序集,真正用于解析HttpController类型的是一个名为HttpControllerTypeResolver的 对象。所有的HttpControllerTypeResolver类型均实现了接口IHttpControllerTypeResolver,如下面的 代码片断所示,定义其中的唯一方法GetControllerTypes借助于提供的AssembliesResolver解析出所有的 HttpController类型。

   1: public interface IHttpControllerTypeResolver
   2: {
   3:     ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
   4: }

与AssembliesResolver注册方式类似,默认使用的HttpControllerTypeResolver同 样是注册在当前HttpConfiguration的ServicesContainer对象上。我们可以通过ServicesContainer具有如 下定义的扩展方法GetHttpControllerTypeResolver得到这个注册的HttpControllerTypeResolver对 象。

   1: public static class ServicesExtensions
   2: {
   3:     //其他成员
   4:     public static IHttpControllerTypeResolver GetHttpControllerTypeResolver(this ServicesContainer services);
   5: }

我们同样可以通过HttpConfiguration默认采用的DefaultServices的构造函数得到默认注册的 HttpControllerTypeResolver对象的类型。如下面的代码片断所示,这个默认注册的 HttpControllerTypeResolver是一个类型为DefaultHttpControllerTypeResolver的对象。

   1: public class DefaultServices : ServicesContainer
   2: {
   3:     //其他成员
   4:     public DefaultServices(HttpConfiguration configuration)
   5:     {
   6:         //其他操作
   7:         this.SetSingle<IHttpControllerTypeResolver>(new DefaultHttpControllerTypeResolver());
   8:     }
   9: }

1、DefaultHttpControllerTypeResolver

如下面的代码片断所示, DefaultHttpControllerTypeResolver具有一个Predicate<Type>类型的只读属性 IsControllerTypePredicate,返回的委托对象用于判断指定的类型是否是一个有效的HttpController类型。

   1: public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
   2: {
   3:     public DefaultHttpControllerTypeResolver();
   4:     public DefaultHttpControllerTypeResolver(Predicate<Type> predicate);
   5:
   6:     public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
   7:
   8:     protected Predicate<Type> IsControllerTypePredicate { get; }
   9: }

如果我们具有特别的HttpController类型有效性验证规则,可以在调用构造函数实例化 DefaultHttpControllerTypeResolver对象时通过参数指定这个Predicate<Type>委托对象。在默 认情况下,这个自动初始化的Predicate<Type>对象体现了默认采用的HttpController类型有效验证规则。具体来说, 默认情况下一个给定的类型必须同时满足如下的条件才是一个有效的HttpController类型。

  • 是一个外部可见(IsVisible = true)的实例(IsAbstract = false)类(IsClass = true)。
  • 类型直接或者间接实现了接口IHttpController。
  • 类型名称必须以“Controller”为后缀,但是不区分大小写(可以使用“controller”作为后缀)。

用于提供所有有效HttpController类型的GetControllerTypes方法的实现逻辑其实很简单。它通过指定的 AssembliesResolver得到一个程序集列表,对于定义在这些程序集中的所有类型,如果满足上述的要求就是返回的 HttpController类型之一。定义在类型DefaultHttpControllerTypeResolver中的针对有效 HttpController类型的解析逻辑基本上体现在如下所示的代码中。

   1: public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
   2: {
   3:     //其他成员
   4:     public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
   5:     {
   6:         List<Type> types = new List<Type>();
   7:         foreach (Assembly assembly in assembliesResolver.GetAssemblies())
   8:         {
   9:             foreach (Type type in assembly.GetTypes())
  10:             {
  11:                 if (this.IsControllerTypePredicate(type))
  12:                 {
  13:                     types.Add(type);
  14:                 }
  15:             }
  16:         }
  17:         return types;
  18:     }
  19: }

2、HttpController类型的缓存

由于针对所有HttpController类型的解析需要大量使用到反射,这是一个相对耗时的过程,所以ASP.NET Web API会对解析出来的HttpController类型进行缓存。具体的缓存实现在具有如下定义的HttpControllerTypeCache类型 中,这是一个定义在程序集“System.Web.Http.dll”中的内部类型。

   1: internal sealed class HttpControllerTypeCache
   2: {
   3:     //其他成员   
   4:     internal Dictionary<string, ILookup<string, Type>> Cache { get; }
   5: }

缓存的HttpController类型通过只读属性Cache获取,这是一个类型为Dictionary<string, ILookup<string, Type>>的字典对象。该字典的Key表示HttpController的名称(HttpController类型名称去除 “Controller”后缀),其Value返回的ILookup<string, Type>对象包含一组具有相同名称的HttpController类型列表,自身的Key表示HttpController类型的命名空间。

三、目标HttpController的选择

AssembliesResolver仅仅是将所有合法的HttpController类型解析出来,针对具体的调用请求,系统必须从中选择一个与 当前请求匹配的HttpController类型出来。HttpController的选择通过HttpControllerSelector对象来完 成,所有的HttpControllerSelector类型均实现了具有如下定义的接口IHttpControllerSelector。

   1: public interface IHttpControllerSelector
   2: {
   3:     IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
   4:     HttpControllerDescriptor SelectController(HttpRequestMessage request);
   5: }

如上面的代码片断所示,该接口中定义了GetControllerMapping和SelectController两个方法。GetControllerMapping返回一个描述所有HttpController类型的HttpControllerDescriptor对象与对应的HttpController名称之间的映射关系。针对请求的HttpController选择实现在SelectController方法中,它返回描述目标HttpController的HttpControllerDescriptor对象。

1、DefaultHttpControllerSelector

默认使用HttpControllerSelector依然注册到当前的ServicesContainer对象中,我们可以调用ServicesContainer如下所示的扩展方法GetHttpControllerSelector得到注册的HttpControllerSelector对象。

   1: public static class ServicesExtensions
   2: {
   3:     //其他成员
   4:     public static IHttpControllerSelector GetHttpControllerSelector(this ServicesContainer services);
   5: }

如下的代码片断所示,默认使用的DefaultServices在初始化的过程中会根据指定的HttpConfiguration对象创建一个 DefaultHttpControllerSelector对象,并将其注册为默认的HttpControllerSelector。

   1: public class DefaultServices : ServicesContainer
   2: {
   3:     //其他成员
   4:     public DefaultServices(HttpConfiguration configuration)
   5:     {
   6:         //其他操作
   7:         this.SetSingle<IHttpControllerSelector>(new DefaultHttpControllerSelector(configuration));
   8:     }
   9: }

如下面的代码片断所示,DefaultHttpControllerSelector不仅仅实现了IHttpControllerSelector 接口中定义的两个方法,还定义了另一个名为GetControllerName方法,我们可以调用此方法根据指定HttpRequestMessage对 象得到该请求访问的目标HttpController的名称。

   1: public class DefaultHttpControllerSelector : IHttpControllerSelector
   2: {
   3:     public DefaultHttpControllerSelector(HttpConfiguration configuration);
   4:
   5:     public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
   6:     public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request);
   7:
   8:     public virtual string GetControllerName(HttpRequestMessage request);
   9: }

2、获取目标HttpController的名称

如果采用Web Host寄宿模式,消息管道的缔造者HttpControllerHandler在根据当前HTTP上下文创建用于表示请求的 HttpRequestMessage对象后,会将ASP.NET路由系统解析当前请求得到的RouteData对象转换成HttpRouteData对 象并添加到HttpRequestMessage的属性字典中。对于Self Host寄宿模式来说,处于消息处理管道末端的HttpRoutingDispatcher会利用ASP.NET Web API的路由系统对当前请求进行路由解析并直接得到封装了路由数据的HttpRouteData对象,此HttpRouteData同样会被添加到表示当 前请求的HttpRequestMessage对象的属性字典之中。

由于被附加到当前请求的HttpRouteData已经包含了目标HttpController的名称(对应的变量名为 “controller”),所以我们可以从HttpRequestMessage中直接获取目标HttpController的名称。如下面的代码片断 所示,DefaultHttpControllerSelector的GetControllerName方法也是按照这样的逻辑从指定的HttpMessageMessage中提取目标HttpController的名称。

   1: public class DefaultHttpControllerSelector : IHttpControllerSelector
   2: {
   3:     //其他成员
   4:     public virtual string GetControllerName(HttpRequestMessage request)
   5:     {
   6:         IHttpRouteData routeData = request.GetRouteData();
   7:         if (routeData == null)
   8:         {
   9:             return null;
  10:         }
  11:         string str = null;
  12:         routeData.Values.TryGetValue<string>("controller", out str);
  13:         return str;
  14:     }
  15: }

3、建立HttpController名称与HttpControllerDescriptor之间的映射

DefaultHttpControllerSelector 的GetControllerMapping方法会返回一个类型为IDictionary<string, HttpControllerDescriptor>的字典,它包含了描述所有HttpController的 HttpControllerDescriptor对象与对应HttpController名称之间的映射关系。

   1: public class DefaultHttpControllerSelector : IHttpControllerSelector
   2: {
   3:     //其他成员
   4:     private readonly HttpControllerTypeCache _controllerTypeCache;
   5:     public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
   6: }

GetControllerMapping方法的实现逻辑其实很简单。如上面的代码片断所 示,DefaultHttpControllerSelector具有一个HttpControllerTypeCache类型的只读字段,通过它可以得 到HttpController类型与名称之间的关系,GetControllerMapping方法只需要根据HttpController类型生成对 应的HttpControllerDescriptor对象即可。但是有个问题必须要考虑,由于同名的HttpController类型可能定义在不同的 命名空间下,而且这里所指的“HttpController名称”是不区分大小写的,所以一个HttpController名称可能对应着多个 HttpController类型,这也是为何HttpControllerTypeCache缓存的数据是一个类型为 Dictionary<string, ILookup<string, Type>>的字典对象的原因。

4、根据请求选择HttpController

其实HttpControllerSelector的终极目标还是根据请求实现对目标HttpController的选择,这体现在它的SelectController方法上。对于默认注册的DefaultHttpControllerSelector 来说,其SelectController方法的实现逻辑非常简单,它只需要调用GetControllerName方法从给定的 HttpRequestMessage提取目标HttpController的名称,然后根据此名称从GetControllerMapping方法的返 回值中提取对应的HttpControllerDescriptor对象即可。实现在SelectController方法中针对请求的 HttpController选择机制虽然简单,但是针对几种特殊情况的处理机制我们不应该忽视。

首先,如果调用GetControllerName方法返回的HttpController名称为Null或者是一个空字符串,意味着 ASP.NET路由系统(针对Web Host寄宿模式)或者ASP.NET Web API路由系统(针对Self Host寄宿模式)在对请求的解析过程中并没有得到表示目标HttpController名称的路由变量。这种情况下 DefaultHttpControllerSelector会直接抛出一个响应状态为HttpStatusCode.NotFound的 HttpResponseException异常,客户端自然就会接收到一个状态为“404, Not Found”的响应。

其次,如果在调用GetControllerMapping方法返回的字典中并没有一个与目标HttpController名称相匹配的HttpControllerDescriptor对象,通过上面的分析我们知道如下两种情况会导致这样的问题。

  • 在通过AssembliesResolver提供的程序集中并不曾定义这么一个有效的HttpController类型。
  • 在通过AssembliesResolver提供的程序集中定义了多个同名的HttpController类型,可能是多个HttpController类型在不区分大小写情况下同名,或者是完全同名的多个HttpController类型定义在不同的命名空间下。

这两种情况下自然不能通过GetControllerMapping方法返回的字典对象来判断,但是却可以通过用于缓存 HttpController类型的HttpControllerTypeCache对象来判断。对于第一种情 况,DefaultHttpControllerSelector依然会抛出一个响应状态为HttpStatusCode.NotFound的 HttpResponseException异常。在第二种情况下,它会抛出一个InvalidOperationException异常,并提示“具有 多个匹配的HttpController”。

四、HttpController的创建

通过上面的介绍我们知道利用注册的HttpControllerSelector对象可以根据表示当前请求的HttpRequestMessage 得到描述目标HttpController的HttpControllerDescriptor对象。在前面介绍 HttpControllerDescriptor的时候我们提到过它自身就具有创建对应HttpController的能力。 HttpControllerDescriptor创建被描述HttpController的能力体现在它的CreateController方法上。接 下来我们就来着重介绍实现在这个CreateController方法中的HttpController创建机制。

   1: public class HttpControllerDescriptor
   2: {
   3:     //其他成员
   4:     public virtual IHttpController CreateController(HttpRequestMessage request);
   5: }

1、HttpControllerActivator

针对请求对目标HttpController的激活机制最终落实到一个名为HttpControllerActivator的对象上,所有的 HttpControllerActivator类型均实现了IHttpControllerActivator接口。如下面的代码片断所示,定义其中的 唯一方法Create会根据表示当前请求的HttpRequestMessage对象、描述目标HttpController的 HttpControllerDescriptor对象以及目标HttpController的类型来创建对应的HttpController对象。

   1: public interface IHttpControllerActivator
   2: {
   3:     IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType);
   4: }

我们已经知道了像这样的“标准化组件”一定是注册到当前ServicesContainer上被HttpController激活系统使用的。我们可以通过ServicesContainer具有如下定义的扩展方法GetHttpControllerActivator直接获取注册的HttpControllerActivator对象。

   1: public static class ServicesExtensions
   2: {
   3:     //其他成员
   4:     public static IHttpControllerActivator GetHttpControllerActivator(this ServicesContainer services);
   5: }

实际上HttpControllerDescriptor的CreateController方法就是调用这个扩展方法得到注册的 HttpControllerActivator对象,并调用它的Create方法来创建目标HttpController的。如下的代码体现了 CreateController方法真正的实现逻辑。

   1: public class HttpControllerDescriptor
   2: {
   3:     //其他成员
   4:     public virtual IHttpController CreateController(HttpRequestMessage request)
   5:     {
   6:         return this.Configuration.Services.GetHttpControllerActivator().Create(request, this, this.ControllerType);
   7:     }
   8: }

2、DefaultHttpControllerActivator

我们照例利用通过DefaultServices的构造函数定义分析出默认注册的HttpControllerActivator是个怎样的对象。 如下面的代码片断所示,当DefaultServices被初始化的时候它会创建并注册一个类型为 DefaultHttpControllerActivator对象。

   1: public class DefaultServices : ServicesContainer
   2: {
   3:     //其他成员
   4:     public DefaultServices(HttpConfiguration configuration)
   5:     {
   6:         //其他操作
   7:         this.SetSingle<IHttpControllerActivator>(new DefaultHttpControllerActivator());
   8:     }
   9: }

接下来我们就来分析一下在DefaultHttpControllerActivator类 型的Create方法中是如何激活目标HttpController实例的,不过要真正了解实现在 DefaultHttpControllerActivator的HttpController激活机制之前,我们需要认识另一个名为 DependencyResolver的对象。

3、DependencyResolver

说到DependencyResolver,我们又不得不谈到IoC的概念。我们知道IoC常和另一个术语“依赖注入(DI,Dependency Injection)”联系在一起。通过IoC容器激活的对象可能具有针对其他对象的依赖,而且被依赖的对象可能具有针对另一个对象的依赖,所以IoC容 器需要在提供所需对象之前帮助我们解决这些依赖。从命名也可以看出来,这里介绍DependencyResolver与依赖注入有关,我们可以将它视为 ASP.NET Web API内部使用的IoC容器。所有的DependencyResolver实现了具有如下定义的接口IDependencyResolver,它定义在命 名空间“System.Web.Http.Dependencies”下。这个接口的定义有点特别,它具有唯一个返回类型为 IDependencyScope的BeginScope方法,IDependencyResolver接口本身同时也继承 IDependencyScope这个接口,并且这两个接口又都继承自IDisposable接口。

   1: public interface IDependencyResolver : IDependencyScope, IDisposable
   2: {
   3:     IDependencyScope BeginScope();
   4: }
   5:
   6: public interface IDependencyScope : IDisposable
   7: {
   8:     object GetService(Type serviceType);
   9:     IEnumerable<object> GetServices(Type serviceType);
  10: }

通过DependencyResolver的BeginScope方法创建的IDependencyScope对象可以视为一个用于激活目标对象的 上下文,我们可以通过调用它的GetService和GetServices方法根据指定的“服务接口类型”获取对应的服务实例。由于 IDependencyScope继承自IDisposable,所以与此上下文关联的资源释放工作可以通过实现的Dispose方法来完成。

与上面我们介绍的那些“标准化组件”不同,默认使用的DependencyResolver并未注册到当前的ServicesContainer对象上,而是直接注册到了当前HttpConfiguration上面。如下面的代码片断所示,当前使用的DependencyResolver直接通过HttpConfiguration的DependencyResolver属性来获取和设置。

   1: public class HttpConfiguration : IDisposable
   2: {
   3:     //其他成员    
   4:     public HttpConfiguration(HttpRouteCollection routes)
   5:     {
   6:         this._dependencyResolver = EmptyResolver.Instance;
   7: }
   8:
   9:     public IDependencyResolver DependencyResolver
  10:     {
  11:         get
  12:         {
  13:             return this._dependencyResolver;
  14:         }
  15:         set
  16:         {
  17:             this._dependencyResolver = value;
  18:         }
  19:     }
  20: }

从上面的代码片断我们还可以看出默认注册到HttpConfiguration上的DependencyResolver是通过类型 EmptyResolver的静态属性Instance返回的EmptyResolver对象。EmptyResolver是一个定义在程序集 “System.Web.Http.dll”中的内部类型,其成员定义如下。之所以将它如此命名,原因在于它仅仅是一个“空”的IoC容器。它的 BeginScope返回的是它自身,GetService和GetServices方法分别返回Null和一个空对象集合,Dispose方法也没有任 何资源释放工作要做。

   1: internal class EmptyResolver : IDependencyResolver, IDependencyScope,
   2:     IDisposable
   3: {
   4:     public IDependencyScope BeginScope();
   5:     public void Dispose();
   6:     public object GetService(Type serviceType);
   7:     public IEnumerable<object> GetServices(Type serviceType);
   8:     public static IDependencyResolver Instance { get; }
   9: }

4、HttpRequestMessage中的DependencyResolver

虽然当前使用的DependencyResolver是注册到当前HttpConfiguration上的,但是我们可以直接从表示当前请求的 HttpRequestMessage对象中获取由它创建的DependencyScope对象。如下面的代码片断所 示,HttpRequestMessage具有一个返回类型为IDependencyScope接口的扩展方法GetDependencyScope。

   1: public static class HttpRequestMessageExtensions
   2: {
   3:     //其他成员    
   4:     public static IDependencyScope GetDependencyScope(this HttpRequestMessage request);
   5: }

其实这个扩展方法实现逻辑很简单,因为DependencyScope对象也存放于HttpRequestMessage的属性字典中。如果此 DependencyScope对象尚未添加,该方法则会通过当前的HttpConfiguration得到注册的DependencyResolver 对象,然后利用它创建一个新的DependencyScope对象并添加到HttpRequestMessage对象的属性字典中,后续过程如果需要使用 到此DependencyScope就可以直接从HttpRequestMessage中提取了。

5、DependencyResolver在DefaultHttpControllerActivator中的应用

在对DependencyResolver有了基本了解后,我们再来讨论DefaultHttpControllerActivator的 Create方法是如何根据当前请求来激活目标HttpController对象的。其实实现机制非常简 单,DefaultHttpControllerActivator先通过调用表示当前请求的HttpRequestMessage对象的扩展方法 GetDependencyScope得到通过当前DependencyResolver创建的DependencyScope对象,然后将目标 HttpController的类型作为参数调用其GetService方法。如果该方法返回一个具体的HttpController对象,该对象就是 Create方法的返回值,否则直接根据目标HttpController的类型进行反射创建一个HttpController对象并返回。如下所示的代 码片断基本上体现了DefaultHttpControllerActivator的HttpController激活机制。

   1: public class DefaultHttpControllerActivator : IHttpControllerActivator
   2: {
   3:     public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
   4:     {
   5:         IDependencyScope depedencyScope = request.GetDependencyScope();
   6:         object httpController = depedencyScope.GetService(controllerType)?? Activator.CreateInstance(controllerType);
   7:         return httpController as IHttpController;
   8:     }
   9: }

由于默认请求下注册到当前HttpConfiguration上的DependencyResolver是一个EmptyResolver对象,它 的GetService方法总是返回Null,所以默认情况下对HttpController的激活总是利用针对目标HttpController类型的 反射实现的。关于HttpController的激活,我还想强调一点,在默认情况下解析出来的所有HttpController类型会被缓存,创建的用 于描述HttpController的HttpControllerDescriptor对象也会被缓存,但是HttpController激活系统并不 会对创建的HttpController对象实施缓存。换言之,对于多个针对相同的HttpController类型的请求来说,最终被激活的 HttpController实例都是不同的。