继续不走寻常路:ASP.NET MVC中使用Web Forms用户控件

目前我们正在用ASP.NET MVC(Razor)开发新版博客后台,在开发中遇到一个棘手的问题:如何在ASP.NET MVC中使用第三方开发的Web Forms用户控件,比如CuteEditor。

如果是商业软件,你无法用ASP.NET MVC进行重写;即使是开源软件,你也不可能花时间去重写。你只要两个选择:要么搞定这个问题?要么放弃使用ASP.NET MVC?

搞软件开发,一个吸引人的地方就是“一切皆有可能”,对于面临的技术问题,只要下定决心去解决,通常都能找到解决方法。

对于这个问题,我们的思路是:Web Forms用户控件最终输出的就是一段包含HTML代码的字符串,只要拿到这个字符串,通过控制器将字符串传给视图,就能解决问题。

有了这个思路,我们首先要解决的就是在控制器中得到用户控件的输出字符串,这不是难题,可以用三架马车(StringBuilder+StringWriter+HtmlTextWriter)搞定,代码如下:

string controlOutput = string.Empty;
BlogEditor editor
= new CuteEditorForCNBlogs();
StringBuilder sb
= new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
editor.RenderControl(htw);
controlOutput
= sb.ToString();
}
}

然后,通过ViewBag传递给视图,代码如下:

ViewBag.EditorHtml = controlOutput;

最后,在视图中显示一下就搞定(只搞定了部分Web Forms控件),代码如下:

@{
Layout = "~/Views/Admin/_AdminLayout.cshtml";
}
<div id="editor">
<div id="editor_main">
<div>标题</div>
<input type="text" id="txtTitle" value="" />
<div>内容</div>
@Html.Raw(ViewBag.EditorHtml)
<div><input type="button" value="发布"/></div>
</div>
</div>

到这步,原以为大功告成...在测试CuteEditor控件时,发现革命尚需努力,CuteEditor未能正常输出。

通过错误信息发现CuteEditor调用了this.Page.Request,而实际Page的值为null。

于是,我们创建了一个Page的实例,通过Controls.Add将CuteEditor加载到Page实例中,代码如下:

Page page = new Page();
string controlOutput = string.Empty;
BlogEditor editor
= new CuteEditorForCNBlogs();
page.Controls.Add(editor);
...

增加Page实例之后,发现Page.Request为null,而且Page.Request为只读属性,无法在创建Page的实例之后进行赋值。

找遍Page的方法与属性,都没有找到为Page.Request赋值的办法。

剩下的唯一的希望就是借助Reflector深入“虎穴”,功夫不负有心人,终于发现“虎子”—— SetIntrinsics(HttpContext context):

private void SetIntrinsics(HttpContext context)
{
this.SetIntrinsics(context, false);
}
private void SetIntrinsics(HttpContext context, bool allowAsync)
{
this._request = context.Request;
}

可是,SetIntrinsics是私有方法, 仅供内部使用,咋办?用“反射”挖地道呗。挖地道秘方如下:

Page page = new Page();
page.GetType().InvokeMember(
"SetIntrinsics", BindingFlags.NonPublic |
BindingFlags.Instance
| BindingFlags.InvokeMethod, null, page,
new object[] { System.Web.HttpContext.Current });

地道挖好,就大功告成,ASP.NET MVC控制器中的完整代码:

public ActionResult NewPost()
{
Page page
= new Page();
page.GetType().InvokeMember(
"SetIntrinsics", BindingFlags.NonPublic |
BindingFlags.Instance
| BindingFlags.InvokeMethod, null, page,
new object[] { System.Web.HttpContext.Current });
string controlOutput = string.Empty;
BlogEditor editor
= new CuteEditorForCNBlogs();
page.Controls.Add(editor);
StringBuilder sb
= new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
editor.RenderControl(htw);
controlOutput
= sb.ToString();
}
}
ViewBag.EditorHtml
= controlOutput;
return View("EditPost");
}

继续ASP.NET MVC之旅...

更新:

根据Ivony...的建议,使用Server.Execute更简单,代码如下:

public ActionResult NewPost()
{
Page page
= new Page();
string controlOutput = string.Empty;
BlogEditor editor
= new CuteEditorForCNBlogs();
page.Controls.Add(editor);
StringBuilder sb
= new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
Server.Execute(page, htw,
false);
controlOutput
= sb.ToString();
}
}
ViewBag.EditorHtml
= controlOutput;
return View("EditPost");
}

posted @ 2011-03-19 18:08  dudu  阅读(9169)  评论(19编辑  收藏  举报