就是有时候TempData的值为null,Q51. 如何持久化Te

作者: 编程  发布:2019-11-05

最近在做mvc跨控制器传值的时候发现一个问题,就是有时候TempData的值为null,然后查阅了许多资料,发现了许多都是逻辑和原理什么的(想看原理可以查看原理的文章,本文是用法),但是真正解决的办法什么案例都没有,

最近抽空看了一下ASP.NET MVC的部分源码,顺带写篇文章做个笔记以便日后查看。

本系列主要翻译自《ASP.NET MVC Interview Questions and Answers 》- By Shailendra Chauhan,想看英文原版的可访问自行下载。该书主要分为两部分,ASP.NET MVC 5、ASP.NET WEB API2。本书最大的特点是以面试问答的形式进行展开。通读此书,会帮助你对ASP.NET MVC有更深层次的理解。
由于个人技术水平和英文水平也是有限的,因此错误在所难免,希望大家多多留言指正。
系列导航
Asp.net mvc 知多少(一)
Asp.net mvc 知多少(二)
Asp.net mvc 知多少(三)
Asp.net mvc 知多少(四)
Asp.net mvc 知多少(五)

于是就把自己的代码当成案例给贴出来,方便更直观的解决问题。

在UrlRoutingModule模块中,将请求处理程序映射到了MvcHandler中,因此,说起Controller的激活,首先要从MvcHandler入手,MvcHandler实现了三个接口:IHttpAsyncHandler, IHttpHandler, IRequiresSessionState。 其处理逻辑主要实现在同步和异步的ProcessRequest方法中,总的来说,该方法在执行的时候,大致经历以下几个步骤:

本节主要讲解几种页面传值方式和http请求与action的映射

因为TempData生命周期确实很短,所以需要持久化一下:

  1. 预处理(在响应头中添加版本信息并去除未赋值的可选路由参数)
  2. 通过ControllerBuilder获取ControlerFactory,并使用Controller工厂创建Controller
  3. 根据是否是异步处理,调用Controller中相应的方法(ExecuteCore或BeginExecute)
  4. 释放Controller

Q50. 介绍下ViewData, ViewBag, TempData 和 Session间的不同之处?
Ans. 在ASP.NET MVC 中有三种方式从controller传值到view中:ViewData, ViewBag 和 TempData。Asp.net WebForm 中可以在一次用户会话中使用Session去持久化数据。

        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是如何创建出来的。

9159.com 1

在当前Action方法中调用Keep方法则保证在当前请求中TempData对象中所存储的键都不会被移除。

Controller的创建是通过ControllerFactory实现的,而ControllerFactory的创建又是在ControllerBuilder中完成的,因此我们先了解一下ControllerBuilder的工作原理。

ViewData

 

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的工厂委托字段
}

因此必须将相应的更改应用到SingleServiceResolver类中才能实现真正的注册,我们知道,如果是单纯的引用赋值,那么更改一个引用并不会对另外一个引用造成改变,比如:

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对象的时候,并没有将自身的工厂委托字段直接赋值给SingleServiceResolver对象的对应字段(因为这样的话SetControllerFactory方法注册的委托无法应用到SingleServiceResolver对象中),而是通过委托来进行了包装,这样就会形成一个闭包,在闭包中进行引用,如下所示:

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));
    }
}

下面看一下SingleServiceResolver类是如何实现对象的创建的,该类是个泛型类,这意味着可以构造任何类型的对象,不仅限于ControllerFactory,实际上在MVC中,该类在很多地方都得到了应用,例如:ControllerBuilder、DefaultControllerFactory、BuildManagerViewEngine等,实现了对多种对象的创建。

  • ViewData 是一个继承自ViewDataDictionary类的字典对象。
    public ViewDataDictionary ViewData { get; set; }
  • ViewData 用来从controller中传值到相对应的view中。
  • 生命周期仅存在于当前此次请求。
  • 如果发生重定向,那么值将会被清空。
  • 从ViewData中取值时需要进行类型转换和Null Check以避免异常。

总结:

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;

因此,默认情况下SingleServiceResolver类的第二种方式也失效了,那么此时也只能依靠默认值方式来提供对象了,在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方法

注:第一种方式的优先级更高。

ViewBag

1.当利用TempData对象存储值而未调用TempData.Keep方法时,此时只要该对象被已读,然后该对象中的所有项将被标记为删除状态。

ControllerFactory

通过ControllerBuilder创建出ControllerFactory对象后,下面就要利用该对象完成具体Controller的创建,ControllerFactory都实现了IControllerFactory接口,通过实现CreateController方法完成对Controller的实例化,CreateController的内部逻辑非常简单,就两步:获取Controller类型,然后创建Controller对象。

  • ViewBag ViewBag是一个动态属性,是基于C# 4.0的动态语言的特性。
    public Object ViewBag { get;}
  • 是对ViewData的一次包装,也是用来从controller中传值到相对应的view中。
  • 生命周期仅存在于当前此次请求。
  • 如果发生重定向,那么值将会被清空。
  • 从ViewBag中取值时不需要进行类型转换。

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(不带命名空间)。

Controller类型搜索流程如下图所示:

9159.com 2

TempData

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)方法完成对象的实例化。

从DefaultControllerFactory和DefaultControllerActivator这两个类的创建过程可以发现,MVC提供了多种方式(IDependencyResolver方式、委托方式 、默认值方式)来提供对象,因此在对MVC相关模块进行扩展的时候,也有多种方式可以采用。

  • TempData 是一个继承于TempDataDictionary类的字典对象,存储于Session中 。
    public TempDataDictionary TempData { get; set; }
  • TempData 用来进行跨页面请求传值。
  • TempData被请求后生命周期即结束。
  • 从TempData中取值时需要进行类型转换和Null Check以避免异常。
  • 主要用来存储一次性数据信息,比如error messages, validation messages。
    详情可参考:TempData知多少,
    Session
  • ASP.NET MVC中Session是Controller中的一个属性,Session是HttpSessionStateBase类型。
    public HttpSessionStateBase Session { get; }
  • Session保存数据直到用户会话结束(默认session过期时间为20mins)。
  • Session对所有的请求都有效,不仅仅是单一的跳转。
  • 从Session中取值时需要进行类型转换和Null Check以避免异常。

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();
}

这两个方法没什么多说的,只是在删除数据的时候同时删除其对应的状态。

Q51. 如何持久化TempData?
Ans. TempData的生命周期十分短暂,只能存活到目标视图完全加载之后。
但是我们可以通过调用Keep方法去持久化TempData至下一次访问。

  • void Keep() - 调用这个方法将保证此次请求之后所有的TempData都将会被持久化。

    public ActionResult Index()
    {
     ViewBag.Message = TempData["Message"];
     Employee emp = TempData["emp"] as Employee; //need type casting
     TempData.Keep();//persist all strings values
     return View();
    }
    
  • void Keep(string key) - 调用这个方法将保证此次请求之后指定的TempData会被持久化。

    public ActionResult Index()
    {
     ViewBag.Message = TempData["Message"];
     Employee emp = TempData["emp"] as Employee; //need type casting
     //persist only data for emp key and Message key will be destroy
     TempData.Keep("emp");
     return View();
    }
    

Q52. ASP.NET MVC中如何控制session的行为?
Ans. 默认ASP.NET MVC 支持 session state(会话状态). Session用来存储跨请求
期间的数据。 不管你是否在session中存储数据,ASP.NET MVC都必须为所有的controller管理 session state,且是耗时的 。因此session是存储在服务器端的,消耗服务器的内存,所以必然影响你的应用程序的性能。 如果你的某些controller不需要session控制,可以手动关闭session控制,来增加微小的性能提升。
可以通过 session state的配置项来简化它。
ASP.NET MVC4中的SessionState特性中,可以通过指定SessionStateBehavior枚举来实现更多对session-state的控制。

  • Default :默认的session state控制方式。
  • Disabled: Session state完全关闭。
  • ReadOnly:只读的session state。
  • Required:完全的可读写的 session state。

9159.com 3


Q53. ASP.NET MVC中 TempData与Session 有什么关联关系?
Ans. ASP.NET MVC中TempData使用session存储跨请求的临时数据。因此,当你关闭了controller的session,当你去使用TempData时,就会抛出以下异常。
9159.com 4


Q54. ASP.NET MVC中什么是Action方法?
Ans. Controller中的action是定义在Controller类中的方法用来执行基于用户请求的操作,并在Model的帮助下将结果传递会View。
Asp.net MVC 中集成了以下几种ActionResults类型及对应的帮助类方法:

  1. ViewResult - 使用Controller中提供的View()方法返回一个ViewResult用来呈现指定或默认的View。
  2. PartialViewResult- 使用Controller中提供的PartialView()方法返回一个PartialViewResult用来呈现指定或默认的分部视图。
  3. RedirectResult - 使用Controller中提供的Redirect()方法返回一个RedirectResult用来发起一个 HTTP 301 或 302 到指定URL的跳转。
  4. RedirectToRouteResult - 使用Controller中提供的RedirectToAction(), RedirectToActionPermanent(), RedirectToRoute(), RedirectToRoutePermanent()方法返回一个RedirectToRouteResult用来发起一个 HTTP 301或 302 到指定action或者路由的跳转。
  5. ContentResult - 使用Controller中提供的Content()方法返回一个ContentResult用来呈现指定的文本。
  6. JsonResult - 使用Controller中提供的Json()方法返回一个JsonResult用来呈现序列化的Json格式数据。
  7. JavaScriptResult - 使用Controller中提供的JavaScript()方法返回一个JavaScriptResult用来呈现一段JavaScript代码,一般仅用于Ajax请求的场景。
  8. FileResult - 使用Controller中提供的File()方法返回一个FileResult用来呈现文件(PDF, DOC, Excel等)内容。
  9. EmptyResult - 返回一个空的结果。
  10. HttpNotFoundResult - 使用Controller中提供的HttpNotFound()方法返回一个HTTP 404状态。
  11. HttpUnauthorizedResult - 返回一个HttpUnauthorizedResult类型用来表示HTTP 401状态(未认证)。用来要求用户登录以完成认证。
  12. HttpStatusCodeResult - 返回 HttpStatusCodeResult用来表示指定Http状态。

Q56. ASP.NET MVC中如何标记Non-Action方法?
Ans. ASP.NET MVC 将所有的公共方法默认为action方法。
如果不想某个公共的方法被暴露为Action,仅需要用NonActionAttribute标记方法即可。

[NonAction]
public void DoSomething()
{
 // Method logic
}

Q57. 能否更改Action方法的命名?
Ans. 可以通过ActionName特性来修改Action的命名。修改后Action将用ActionName中定义的名称被调用。

[ActionName("DoAction")]
public ActionResult DoSomething()
{
 //TODO:
 return View();
}

这样,DoSomething action就会被会被标记为DoAction action。


Q58. 如何限制action仅能被相应的HTTP GET, POST, PUT or DELETE请求访问?
Ans. 默认,每一个action方法都可以被任何HTTP请求访问(i.e. GET, PUT, POST,
DELETE). 但是可以通过为action方法指定HttpPost、 HttpPut 、 HttpDelete 特性来限制action的行为。

[HttpGet]
public ActionResult Index()
{
 //TODO:
 return View();
}

Q59. 如何决定一个action是被HTTP GET还是POST请求?
Ans. 通过使用HttpRequestBase类的HttpMethod属性可以判断action是被哪种HTTP请求调用。

public ActionResult Index(int? id)
{
 if (Request.HttpMethod == "GET")
 {
 //TODO:
 }
 else if (Request.HttpMethod == "POST")
 {
 //TODO:
 }
 else
 {
 //TODO:
 }
return View();
}

Q60. 如何判断一个AJAX请求?
Ans. 通过使用Request.IsAjaxRequest()来判断。

public ActionResult DoSomething()
{
 if (Request.IsAjaxRequest())
 {
 //TODO:
 }
 return View();
}

本文由9159.com发布于编程,转载请注明出处:就是有时候TempData的值为null,Q51. 如何持久化Te

关键词: