ASP.NET MVC Preview 2 - RedirectToAction
[ 2008-03-14 11:54:31 | 作者: yuhen ]
其实 RedirectToAction 本身并没有多少可说的,无非是用 "Response.Redirect" 让客户端进行跳转而已。
我们关注的焦点是在 RedirectToAction 调用时,如何将相关数据传递给下一个 Action 方法。MVC Controller 提供了一个称之为 TempData 的字典属性作为 "转场"。
如果我们看代码认真一些,会发现一个问题:RedirectToAction 调用 Response.Redirect(url) 必然会导致新的 Controller 实例被创建,而这个 TempData 同样也会是一个在 Controller.Execute() 中诞生的 "新人"。那么 "老" 从何而来?对于会话(Session)级别的数据共享,我们会想到谁?HttpContext.Session ?也许是吧,既然 Controller.Execute() 里面没有线索,我们不妨深入到 TempDataDictionary 里面看看。
在 EnsureReadData() 中,MVC 首先使用一个固定的 key —— TempDataSessionStateKey 从 Session 中获取 _sessionData,如果这个 _sessionData 不为空,那么就对其做些 "处理",并将其引用从 Session 中删除。当然,没找到的话,自然是创建一个新的了。这应该就是 "老数据" 得以传递的秘密了。
等等…… 就算 TempData 从 Session 中获取了 "老数据",那么我们在 Controller.RedirectToAction() 中并没有看到有任何代码将这个 TempData 写入 Session …… 嗯,这是个大问题,没有写,又如何读呢?问题的奥秘还是在 TempDataDictionary 中。
无论是 Add 方法还是索引器,只要我们视图 "修改" 这个字典时都会调用一个名为 "EnsureWriteData(key)" 的方法。
而这个 "EnsureWriteData(string key)" 第一要做的就是将 _sessionData 加到 HttpContext.Session 里面。好了,找到读,也找到了写,似乎是结束了。但还有一个问题,第一个 Action 将数据写入 TempData,然后调用 RedirectToAction() 跳转到第二个 Action,这第二个 Action 除了读取 "老数据" 外,还做了一份额外的工作,就是将 "老数据" 从 HttpContext.Session 中删除了。也就是说,"老数据" 的生命周期也就到此为止了,是不可能再往后传递的。
输出:
Action2: Hello...
Action3:
问题似乎没说清楚…… 假如我们 "修改" Action2 TempData,那么这个 _sessionData 又会再次写入 HttpContext.Session,这样是否可以将 "Action1 老数据" 继续带到 Action3 呢?试验一下。
输出:
Action2: Hello...
Action3:
试验结果证明不行。为什么会这样?问题还是出在 TempDataDictionary 身上。TempDataDictionary._sessionData 使用一种看起来很奇怪的存储方式,First 是一个 Dictionary,用来存储我们添加的数据,Second 是一个 HashSet 用来存储 First.Key,这看起来有些莫名奇妙。别着急,当 "老数据" 在 Action2 中被提取时,它做了些附加工作。
我们先看 "注释3",它将老数据的 Key 登记从 Second 中全部清除了,也就是说以后登记的都只是 Action2 中新 "修改" 的数据键。在 Action2 时,"注释1" 和 "注释2" 处的代码并没有起到作用,可一旦执行流程到了 Action3 时,它们就积极行动起来了。在 "注释1" 处,所有保留在 First 中的 "Action1 老数据" 必然无法从 Second 中找到登记记录,所以通通被加入到待删除列表 set 中。"注释2" 则是个 "杀手",它砍死了所有的 "Action1 老数据",留下的只有 "Action2 老数据" 了。唉~~~~ 只有新人笑,不见旧人哭啊。
好了,老来老去的,但愿你没犯迷糊。
评论Feed: http://www.rainsts.net/feed.asp?q=comment&id=671
protected virtual void RedirectToAction(RouteValueDictionary values)
{
VirtualPathData virtualPath = this.RouteCollection.GetVirtualPath(this.ControllerContext, values);
string url = null;
if (virtualPath != null)
{
url = virtualPath.VirtualPath;
}
this.HttpContext.Response.Redirect(url);
}我们关注的焦点是在 RedirectToAction 调用时,如何将相关数据传递给下一个 Action 方法。MVC Controller 提供了一个称之为 TempData 的字典属性作为 "转场"。
public class Controller : IController
{
protected internal virtual void Execute(ControllerContext controllerContext)
{
// ...
this.ControllerContext = controllerContext;
this.TempData = new TempDataDictionary(controllerContext.HttpContext);
// ...
}
public TempDataDictionary TempData { get; set; }
}如果我们看代码认真一些,会发现一个问题:RedirectToAction 调用 Response.Redirect(url) 必然会导致新的 Controller 实例被创建,而这个 TempData 同样也会是一个在 Controller.Execute() 中诞生的 "新人"。那么 "老" 从何而来?对于会话(Session)级别的数据共享,我们会想到谁?HttpContext.Session ?也许是吧,既然 Controller.Execute() 里面没有线索,我们不妨深入到 TempDataDictionary 里面看看。
public class TempDataDictionary : ...
{
public TempDataDictionary(HttpContextBase httpContext)
{
// ...
this.HttpContext = httpContext;
this.EnsureReadData();
}
private void EnsureReadData()
{
if (this._sessionData == null)
{
if (this.HttpContext.Session != null)
{
this._sessionData = this.HttpContext.Session[TempDataSessionStateKey] as
Pair<Dictionary<string, object>, HashSet<string>>;
}
if (this._sessionData != null)
{
this.HttpContext.Session.Remove(TempDataSessionStateKey);
HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string str in this._sessionData.First.Keys)
{
if (!this._sessionData.Second.Contains(str))
{
set.Add(str);
}
}
foreach (string str2 in set)
{
this._sessionData.First.Remove(str2);
}
this._sessionData.Second.Clear();
}
else
{
this._sessionData = new Pair<Dictionary<string, object>, HashSet<string>>(
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase),
new HashSet<string>(StringComparer.OrdinalIgnoreCase));
}
}
}
}在 EnsureReadData() 中,MVC 首先使用一个固定的 key —— TempDataSessionStateKey 从 Session 中获取 _sessionData,如果这个 _sessionData 不为空,那么就对其做些 "处理",并将其引用从 Session 中删除。当然,没找到的话,自然是创建一个新的了。这应该就是 "老数据" 得以传递的秘密了。
等等…… 就算 TempData 从 Session 中获取了 "老数据",那么我们在 Controller.RedirectToAction() 中并没有看到有任何代码将这个 TempData 写入 Session …… 嗯,这是个大问题,没有写,又如何读呢?问题的奥秘还是在 TempDataDictionary 中。
public class TempDataDictionary : ...
{
public void Add(string key, object value)
{
this.EnsureWriteData(key);
this._sessionData.First.Add(key, value);
}
public object this[string key]
{
get
{
object obj2;
if (this._sessionData.First.TryGetValue(key, out obj2))
{
return obj2;
}
return null;
}
set
{
this.EnsureWriteData(key);
this._sessionData.First[key] = value;
}
}
}无论是 Add 方法还是索引器,只要我们视图 "修改" 这个字典时都会调用一个名为 "EnsureWriteData(key)" 的方法。
private void EnsureWriteData(string key)
{
HttpSessionStateBase session = this.HttpContext.Session;
if (session != null)
{
session[TempDataSessionStateKey] = this._sessionData;
}
if (key == null)
{
this._sessionData.Second.Clear();
}
else if (!this._sessionData.Second.Contains(key))
{
this._sessionData.Second.Add(key);
}
}而这个 "EnsureWriteData(string key)" 第一要做的就是将 _sessionData 加到 HttpContext.Session 里面。好了,找到读,也找到了写,似乎是结束了。但还有一个问题,第一个 Action 将数据写入 TempData,然后调用 RedirectToAction() 跳转到第二个 Action,这第二个 Action 除了读取 "老数据" 外,还做了一份额外的工作,就是将 "老数据" 从 HttpContext.Session 中删除了。也就是说,"老数据" 的生命周期也就到此为止了,是不可能再往后传递的。
public void Action1()
{
this.TempData["a"] = "Hello...";
this.RedirectToAction("action2");
}
public void Action2()
{
Debug.WriteLine(this.TempData["a"], "Action2");
this.RedirectToAction("action3");
}
public void Action3()
{
Debug.WriteLine(this.TempData["a"], "Action3");
Response.Write("Action3...");
}输出:
Action2: Hello...
Action3:
问题似乎没说清楚…… 假如我们 "修改" Action2 TempData,那么这个 _sessionData 又会再次写入 HttpContext.Session,这样是否可以将 "Action1 老数据" 继续带到 Action3 呢?试验一下。
public void Action1()
{
this.TempData["a"] = "Hello...";
this.RedirectToAction("action2");
}
public void Action2()
{
Debug.WriteLine(this.TempData["a"], "Action2");
this.TempData["b"] = "xxx";
this.RedirectToAction("action3");
}
public void Action3()
{
Debug.WriteLine(this.TempData["a"], "Action3");
Response.Write("Action3...");
}输出:
Action2: Hello...
Action3:
试验结果证明不行。为什么会这样?问题还是出在 TempDataDictionary 身上。TempDataDictionary._sessionData 使用一种看起来很奇怪的存储方式,First 是一个 Dictionary,用来存储我们添加的数据,Second 是一个 HashSet 用来存储 First.Key,这看起来有些莫名奇妙。别着急,当 "老数据" 在 Action2 中被提取时,它做了些附加工作。
internal sealed class Pair<TFirst, TSecond>
{
public TFirst First { get; }
public TSecond Second { get; }
}
public class TempDataDictionary : ...
{
private Pair<Dictionary<string, object>, HashSet<string>> _sessionData;
private void EnsureReadData()
{
if (this._sessionData == null)
{
if (this.HttpContext.Session != null)
{
this._sessionData = this.HttpContext.Session[TempDataSessionStateKey] as
Pair<Dictionary<string, object>, HashSet<string>>;
}
if (this._sessionData != null)
{
this.HttpContext.Session.Remove(TempDataSessionStateKey);
HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// 1. 循环获取 "老数据" 字典(First) 的键(Key)
foreach (string str in this._sessionData.First.Keys)
{
// 如果这个键不包含在 Second 中,则添加到一个哈希列表中。
if (!this._sessionData.Second.Contains(str))
{
set.Add(str);
}
}
// 2. 将那些没有在 Second 登记的数据从 First 字典中删除
foreach (string str2 in set)
{
this._sessionData.First.Remove(str2);
}
// 3. 清空 Second。
this._sessionData.Second.Clear();
}
else
{
this._sessionData = new Pair<Dictionary<string, object>, HashSet<string>>(
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase),
new HashSet<string>(StringComparer.OrdinalIgnoreCase));
}
}
}
}我们先看 "注释3",它将老数据的 Key 登记从 Second 中全部清除了,也就是说以后登记的都只是 Action2 中新 "修改" 的数据键。在 Action2 时,"注释1" 和 "注释2" 处的代码并没有起到作用,可一旦执行流程到了 Action3 时,它们就积极行动起来了。在 "注释1" 处,所有保留在 First 中的 "Action1 老数据" 必然无法从 Second 中找到登记记录,所以通通被加入到待删除列表 set 中。"注释2" 则是个 "杀手",它砍死了所有的 "Action1 老数据",留下的只有 "Action2 老数据" 了。唉~~~~ 只有新人笑,不见旧人哭啊。
好了,老来老去的,但愿你没犯迷糊。
[最后修改由 yuhen, 于 2008-03-14 11:58:01]
评论Feed: http://www.rainsts.net/feed.asp?q=comment&id=671
这篇日志没有评论。






