来自 澳门新葡亰 2019-11-12 13:32 的文章
当前位置: 澳门新葡亰app > 澳门新葡亰 > 正文

哪怕神蹟候TempData的值为null

方今在做mvc跨调节器传值的时候开掘一个标题,便是奇迹候TempData的值为null,然后查阅了超级多素材,发掘了好多都以逻辑和原理什么的(想看规律能够查阅原理的篇章,本文是用法卡塔尔国,但是真的消除的主意怎么着案例都未曾,

近几来抽空看了瞬间ASP.NET MVC的后生可畏对源码,顺带写篇文章做个笔记以便日后查看。

于是就把团结的代码当成案例给贴出来,方便更加直观的解决难题。

在UrlRoutingModule模块中,将号召管理程序映射到了MvcHandler中,由此,提起Controller的激活,首先要从MvcHandler入手,MvcHandler达成了三个接口:IHttpAsyncHandler, IHttpHandler, IRequiresSessionState。 其拍卖逻辑重要实以往一同和异步的ProcessRequest方法中,简单的说,该办法在实施的时候,差不离经验以下多少个步骤:

因为TempData生命周期确实比超短,所以须求悠久化一下:

  1. 预处理(在响应头中增加版本信息并剔除未赋值的可选路由参数卡塔 尔(英语:State of Qatar)
  2. 透过ControllerBuilder获取ControlerFactory,并利用Controller工厂创立Controller
  3. 依赖是或不是是异步管理,调用Controller中相应的艺术(ExecuteCore或BeginExecute)
  4. 释放Controller
        public ActionResult Index()
        {
            TempData["message"] = "123asd";
            return view();
        }

        public ActionResult GetTemData()
        {
            var foredid = TempData["message"].ToString();
            var  result=_content.userinfo(foredid);
            return View();
        }

中间第一步在ProcessRequestInit方法中举办拍卖,本文主倘诺深入分析第两步中的controller是什么样创建出来的。

在这个时候此刻Action方法中调用Keep方准绳有限支撑在现阶段伏乞中TempData对象中所存款和储蓄的键都不会被移除。

Controller的创导是通过ControllerFactory达成的,而ControllerFactory的创设又是在ControllerBuilder中成就的,因而大家先精晓一下ControllerBuilder的劳作原理。

 

ControllerBuilder

从源码中得以看出,在ControllerBuilder类中,并不曾平素达成对controller工厂的创始,ControllerFactory的创始实际上是委托给叁个延续自IResolver接口的SingleServiceResolver类的实例来落成的,那一点从GetControllerFactory方法中可以见见,它是由此调用SingleServiceResolver对象的Current属性来造成controller工厂的创建的。

public IControllerFactory GetControllerFactory()
{
    return _serviceResolver.Current;  //依赖IResolver接口创建工厂
}

何况在源码中还开掘,SingleServiceResolver类是internal等级的,那代表外界不能直接访问,那么ControllerBuilder是何许借助SingleServiceResolver来完毕工厂的挂号呢?继续看代码,ControllerBuilder类和SingleServiceResolver类都有一个Func<IControllerFactory>体系的嘱托字段,大家一时称为工厂委托,

//ControllerBuilder.cs
private Func<IControllerFactory> _factoryThunk = () => null;  //工厂委托
//SingleServiceResolver.cs
private Func<TService> _currentValueThunk;  //工厂委托

该信托实现了工厂的成立,而经过SetControllerFactory方法唯有是退换了ControllerBuilder类的工厂委托字段,并未变动SingleServiceResolver类的厂子委托字段,

public void SetControllerFactory(IControllerFactory controllerFactory)
{
    if (controllerFactory == null)
    {
        throw new ArgumentNullException("controllerFactory");
    }

    _factoryThunk = () => controllerFactory;  //更改ControllerBuilder的工厂委托字段
}

据此必需将相应的改变应用到Single瑟维斯Resolver类中才干达成真正的注册,大家领悟,倘使是单纯的援引赋值,那么更正叁个引用并不会对其余一个援用产生改换,例如:

Func<object> f1 = ()=>null;
Func<object> f2 = f1;  //f1与f2指向同一个对象
object o = new object();
f1 = ()=>o;  //更改f1后,f2仍然指向之前的对象
bool b1 = f1() == o;   //true
bool b2 = f2() == null;  //true,  f1()!=f2()

进而,ControllerBuilder在实例化SingleServiceResolver对象的时候,并不曾将作者的工厂委托字段直接赋值给Single瑟维斯Resolver对象的附和字段(因为那样的话SetControllerFactory方法注册的委托不能够使用到SingleServiceResolver对象中卡塔 尔(英语:State of Qatar),而是经过信托来拓宽了包装,那样就能够产生三个闭包,在闭包中举办援用,如下所示:

Func<object> f1 = ()=>null;
Func<object> f2 = ()=>f1();  //通过委托包装f1,形成闭包
object o = new object();
f1 = ()=>o;  //更改f1后,f2与f1保持同步
bool b1 = f1() == o;  //true
bool b2 = f2() == o;  //true,  f1()==f2()

//ControllerBuilder.cs
internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
{
    _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
                                              () => _factoryThunk(),  //封装委托,闭包引用
                                              new DefaultControllerFactory { ControllerBuilder = this },
                                              "ControllerBuilder.GetControllerFactory");
}

如此SingleServiceResolver对象中的工厂委托就能与ControllerBuilder对象中的对应字段保持同步了,SetControllerFactory方法也就高达了替换私下认可工厂的指标。

闭包引用测量检验代码:

using System;

class Program
{
    public static void Main(string[] args)
    {
        Func<object> f1 = ()=>null;
        Func<object> f2 = f1;  //f1与f2指向同一个对象
        object o = new object();
        f1 = ()=>o;  //更改f1后,f2仍然指向之前的对象
        bool b1 = f1() == o;   //true
        bool b2 = f2() == null;  //true,  f1()!=f2()

        Print("直接赋值:");
        Print(f1(),"f1() == {0}");
        Print(f2(),"f2() == {0}");
        Print(f1() == f2(),"f1() == f2() ? {0}");

        Func<object> ff1 = ()=>null;
        Func<object> ff2 = ()=>ff1();  //通过委托包装f1,形成闭包
        object oo = new object();
        ff1 = ()=>oo;  //更改f1后,f2与f1保持同步
        bool bb1 = ff1() == oo;  //true
        bool bb2 = ff2() == oo;  //true,  f1()==f2()

        Print("委托赋值:");
        Print(ff1(),"ff1() == {0}");
        Print(ff2(),"ff2() == {0}");
        Print(ff1() == ff2(),"ff1() == ff2() ? {0}");

        Console.ReadLine();
    }

    static void Print(object mess,string format = "{0}")
    {
        string message = mess == null ? "null" : mess.ToString();
        Console.WriteLine(string.Format(format,message));
    }
}

下边看一下Single瑟维斯Resolver类是如何兑现目标的始建的,该类是个泛型类,那象征能够协会任何类型的靶子,不仅只限于ControllerFactory,实际上在MVC中,该类在比很多地方都赢得了利用,比方:ControllerBuilder、DefaultControllerFactory、BuildManagerViewEngine等,达成了对各类指标的创始。

总结:

SingleServiceResolver

此类完毕了IResolver接口,主要用来提供钦赐项目标实例,在SingleServiceResolver类中有三种办法来创制对象:

1、private Lazy<TService> _currentValueFromResolver;  //内部调用_resolverThunk
2、private Func<TService> _currentValueThunk;  //委托方式
3、private TService _defaultValue;   //默认值方式

private Func<IDependencyResolver> _resolverThunk;  //IDependencyResolver方式

从Current方法中得以见见他们的开始时期级:

public TService Current
{
    get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; }
}

_currentValueFromResolver实际是对_resolverThunk的包装,内部依然调用_resolverThunk来贯彻指标的结构,所以优先级是:_resolverThunk > _currentValueThunk > _defaultValue,即:IDependencyResolver方式> 委托情势 > 私下认可值模式。

SingleServiceResolver在构造函数中默许完毕了二个DefaultDependencyResolver对象封装到委托字段_resolverThunk中,该暗中认可的Resolver是以Activator.CreateInstance(type)的主意创设对象的,不过有个前提,钦赐的type不能够是接口或许抽象类,不然间接回到null。
在ControllerBuilder类中实例化SingleServiceResolver对象的时候钦命的是IControllerFactory接口类型,所以其里面包车型地铁SingleServiceResolver对象不恐怕通过IDependencyResolver格局创造对象,那么创制ControllerFactory对象的职务就直达了_currentValueThunk(委托方式卡塔 尔(阿拉伯语:قطر‎和_defaultValue(默许值形式卡塔 尔(阿拉伯语:قطر‎那多个方法上,前边说过,SingleServiceResolver类中的委托字段实际上是由此闭包引用ControllerBuilder类中的相应委托来创立对象的,而在ControllerBuilder类中,那一个相应的嘱托私下认可是回来null,

private Func<IControllerFactory> _factoryThunk = () => null;

故而,私下认可意况下Single瑟维斯Resolver类的第二种艺术也失效了,那么那时候也只能依据默许值方式来提供对象了,在ControllerBuilder类中这几个暗中同意值是DefaultControllerFactory:

internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
{
    _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
                                              () => _factoryThunk(),
                                              new DefaultControllerFactory { ControllerBuilder = this }, //默认值
                                              "ControllerBuilder.GetControllerFactory");
}

就此,在暗许情状下是运用DefaultControllerFactory类来协会Controller的。
在开创SingleServiceResolver对象的时候,能够从多少个位置剖断出真正创造对象的主意是哪类:

new SingleServiceResolver<IControllerFactory>(   //1、看泛型接口,如果为接口或抽象类,则IDependencyResolver方式失效
    () => _factoryThunk(),  //2、看_factoryThunk()是否返回null,如果是则委托方式失效
    new DefaultControllerFactory { ControllerBuilder = this },  //3、以上两种都失效,则使用该默认值
    "ControllerBuilder.GetControllerFactory");

由此上述创建对象的进程能够获知,有二种方法得以替换私下认可的靶子提供器:

  1. 轮番暗中认可的DependencyResolver,能够通过DependencyResolver类的静态方法SetResolver方法来兑现:

    CustomDependencyResolver customResolver = new  CustomDependencyResolver();
    DependencyResolver.SetResolver(customResolver);
    

    将上述语句放在程序运维的地点,举个例子:Application_Start

  2. 透过前边介绍的ControllerBuilder类的SetControllerFactory方法

注:第风流倜傥种办法的优先级更加高。

1.当接纳TempData对象存款和储蓄值而未调用TempData.Keep方法时,那个时候后生可畏经该指标被已读,然后该目的中的全体项将被标志为除去状态。

ControllerFactory

通过ControllerBuilder创设出ControllerFactory对象后,上面将在选择该指标达成具体Controller的创始,ControllerFactory都完毕了IControllerFactory接口,通过完毕CreateController艺术成功对Controller的实例化,CreateController的里边逻辑极度轻便,就两步:获取Controller类型,然后创设Controller对象。

2.若调用TempData.Keep(string key)方法,此时不博览会开标志。

获取Controller类型

依照调节器名称获取调整器Type的经过,有不可贫乏浓重领会一下,以便于我们在之后遇上相关难点的时候能够越来越好的扩充不当定位。获取项指标逻辑都封装在GetControllerType方法中,该过程依据路由数据中是还是不是含有命名空间音讯,分为多个阶段举办项目寻觅:

  • 首先,假若当前路由数据中存在命名空间音信,则在缓存中依照调节器名称和命名空间找寻对应的项目,如若找到唯风流潇洒一个等级次序,则赶回该类型,找到八个一向抛分外
  • 其次,假如当前路由数据中不设有命名空间消息,或在首先品级的搜寻没有找到相应的门类,并且UseNamespaceFallback==true,此时会博得ControllerBuilder中设置的命名空间消息,利用该音信和调整器名称在缓存中开展项目寻找,假如找到唯生机勃勃叁个品种,则赶回该品种,找到五个一贯抛万分
  • 终极,假如路由数据和ControllerBuilder中都未有命名空间音信,只怕在以上七个级次都并没有搜索到相应的Controller类型,那么会忽视命名空间,在缓存中仅依据调整器名称进行项目寻找,借使找到唯一一个连串,则赶回该品种,找到七个向来抛分外

据此,命名空间的事先级是:RouteData > ControllerBuilder

在缓存中找找类型的时候,假设是首先次寻觅,会调用ControllerTypeCache.EnsureInitialized方法将保留在硬盘中的Xml缓存文件加载到一个字典类型的内部存款和储蓄器缓存中。固然该缓存文件不设有,则会遍历当前利用援用的享有程序集,搜索所有public权限的Controller类型(决断标准:实现IController接口、非抽象类、类名以Controller结尾),然后将这么些类型消息实行xml种类化,生成缓存文件保留在硬盘中,以便于下一次径直从缓存文件中加载,同不平日候将类型音讯分组以字典的样式缓存在内部存款和储蓄器中,升高找出频率,字典的key为ControllerName(不带命名空间)。

澳门新葡亰官网APP,Controller类型寻找流程如下图所示:

澳门新葡亰官网APP 1

3.RedirectToRouteResult和RedirectResult总是会调用TempData.Keep()方法,保险该指标中的全体项不会被移除。

创建Controller对象

获得Controller类型将来,接下去就要举行Controller对象的创制。在DefaultControllerFactory类的源码中能够看看,同ControllerBuilder相近,该类的构造函数中也实例化了三个SingleServiceResolver对象,依照事先介绍的主意,大家一眼就能够观察,该目的是应用暗中同意值的秘技提供了二个DefaultControllerActivator对象。

_activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>(  //1、泛型为接口,IDependencyResolver方式失效
                     () => null,  //2、返回了null,委托方式失效
                     new DefaultControllerActivator(dependencyResolver),  //3、以上两种方式均失效,则使用该提供方式
                     "DefaultControllerFactory constructor");

其实DefaultControllerFactory类仅落成了项指标物色,对象的的确创建进程必要由DefaultControllerActivator类来成功,默许情状下,DefaultControllerActivator成立Controller的长河是异常粗略的,因为它事实上接受的是叁个叫做DefaultDependencyResolver的类来進展Controller创立的,在这里类内部平昔调用Activator.CreateInstance(serviceType)澳门新葡亰app,艺术成功目标的实例化。

从DefaultControllerFactory和DefaultControllerActivator这五个类的开创进程能够开掘,MVC提供了多样方式(IDependencyResolver格局、委托方式、默许值方式)来提供对象,因而在对MVC相关模块实行扩充的时候,也可以有多样办法得以利用。

Controller中的数据容器

Controller中涉嫌到多少个给view传值的数据容器:TempData、ViewData和ViewBag。前两个的差别之处在于TempData仅存储一时数据,里面包车型客车数目在率先次读取之后会被移除,即:只好被读取贰遍;ViewData和ViewBag保存的是同大器晚成份数据,只可是ViewBag是动态指标,对ViewData实行了包装。

public dynamic ViewBag
{
    get
    {
        if (_dynamicViewDataDictionary == null)
        {
            _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData); //封装ViewData
        }
        return _dynamicViewDataDictionary;
    }
}  

上边简单说一下TempData的达成原理。

TempData

先是看下MSDN上是什么样讲授的:

你能够按使用 ViewDataDictionary 对象的风流倜傥致方式利用 TempDataDictionary 对象传递数据。 可是,TempDataDictionary 对象中的数据仅从一个号召保持到下二个呼吁,除非您接纳 Keep 方法将多少个或五个键标志为需保留。 假设键已标志为需保留,则会为下叁个呼吁保留该键。
TempDataDictionary 对象的天下无敌用法是,在数据重定向到四个操作方法时从另叁个操作方法传递数据。 举例,操作方法大概会在调用 RedirectToAction 方法早先,将有关错误的音信囤积在调节器的 TempData 属性(该属性重回TempDataDictionary 对象卡塔尔国中。 然后,下四个操作方法能够管理错误并展现呈现错误新闻的视图。

TempData的特点正是能够在四个Action之间传递数据,它会保留风流浪漫份数据到下二个Action,并趁机再下二个Action的过来而失效。所以它被用在七个Action之间来保存数据,举例,那样二个场合,你的一个Action选用一些post的数据,然后提交另三个Action来拍卖,并展现到页面,当时就足以行使TempData来传递那份数据。

TempData达成了IDictionary接口,同一时候中间含有一个IDictionary类型的村办字段,并增多了连带措施对字典字段的操作进行了决定,那显然是代理情势的贰个施用。因为TempData必要在Action之间传递数据,由此需求其能够对自个儿的数量开展封存,TempData正视ITempDataProvider接口完毕了多少的加载与封存,默许情状下是利用SessionStateTempDataProvider对象将TempData中的数据贮存在Session中。

上边看一下TempData是什么调整数据操作的,TempDataDictionary源码中犹如此一段定义:

internal const string TempDataSerializationKey = "__tempData";

private Dictionary<string, object> _data;
private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

私有字典字段_data是真的寄放数据之处,哈希集结_initialKeys和_retainedKeys用来标识数据,_initialKeys中存放还未有被读取的数据key,_retainedKeys存放能够被一再访谈的key。
TempDataDictionary对数码操作的调整行为器重反映在在读取数据的时候并不会及时从_data中去除相应的数码,而是通过_initialKeys和_retainedKeys那八个hashset标识每条数据的状态,最终在经过ITempDataProvider举办封存的时候再依据以前标志的情景对数码进行过滤,这时候才去除已拜会过的数码。

连带的主宰措施有:TryGetValue、Add、Keep、Peek、Remove、Clear

1、TryGetValue

public bool TryGetValue(string key, out object value)
{
    _initialKeys.Remove(key);
    return _data.TryGetValue(key, out value);
}

该措施在读取数据的时候,会从_initialKeys群集中移除对应的key,前边说过,因为_initialKeys是用来标志数据未访问状态的,从该集合中除去了key,之后在经过ITempDataProvider保存的时候就能够将数据从_data字典中除去,下一遍呼吁就不恐怕再从TempData访谈该key对应的数目了,即:数据只可以在二次呼吁中运用。

2、Add

public void Add(string key, object value)
{
    _data.Add(key, value);
    _initialKeys.Add(key);
}

累积数据的时候在_initialKeys中打上标识,注明该key对应的数目能够被访问。

3、Keep

public void Keep(string key)
{
    _retainedKeys.Add(key);
} 

调用Keep方法的时候,会将key增添到_retainedKeys中,评释该条记录能够被频仍拜望,为何能够被频仍做客呢,能够从Save方法中找到原因:

public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
    // Frequently called so ensure delegate is stateless
    _data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) =>
        {
            string key = entry.Key;
            return !tempData._initialKeys.Contains(key) 
                && !tempData._retainedKeys.Contains(key);
        }, this);

    tempDataProvider.SaveTempData(controllerContext, _data);
}

能够看看,在保留的时候,会从_data中收取每一条数据,判别该数额的key是或不是留存于_initialKeys和_retainedKeys中,假使都空中楼阁才会从_data中移除,所以keep方法将key添加到_retainedKeys后,该数据就不会被删除了,即:能够在多少个乞求中被访问了。

4、Peek

public object Peek(string key)
{
    object value;
    _data.TryGetValue(key, out value);
    return value;
}

从代码中能够见见,该方法在读取数据的时候,仅仅是从_data中打开了获得,并从未移除_initialKeys集合中对应的key,由此通过该情势读取数据不影响多少的情状,该条数据依旧得以在下三遍号令中被选择。

5、Remove 与 Clear

public bool Remove(string key)
{
    _retainedKeys.Remove(key);
    _initialKeys.Remove(key);
    return _data.Remove(key);
}

public void Clear()
{
    _data.Clear();
    _retainedKeys.Clear();
    _initialKeys.Clear();
}

那七个方法没什么多说的,只是在剔除数据的时候还要删除其相应的境况。

本文由澳门新葡亰app发布于澳门新葡亰,转载请注明出处:哪怕神蹟候TempData的值为null

关键词: