C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper
前言:之前学习过很多的Bootstrap组件,博主就在脑海里构思:是否可以封装一套自己Bootstrap组件库呢。再加上看到MVC的Razor语法里面直接通过后台方法输出前端控件的方式,于是打算仿照HtmlHelper封装一套BootstrapHelper,今天只是一个开头,讲述下如何封装自己的Html组件,以后慢慢完善。
本文原创地址:http://www.cnblogs.com/landeanfen/p/5729551.html
BootstrapHelper系列文章目录
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(二)
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)
一、揭开HtmlHelper的“面纱”
经常使用Razor写法的园友都知道,在cshtml里面,我们可以通过后台的方法输出成前端的html组件,比如我们随便看两个例子:

输出成Html之后

博主的好奇心又来了,它是怎么做到的呢?于是将 Html 对象以及 Label() 方法转到定义


由此可以看出Html对象是HtmlHelper类型的一个实例,而Label()方法则是HtmlHelper类型的一个扩展方法,所以就可以直接通过Html.Label()这种方式直接调用。不熟悉C#扩展方法的园友可以看看http://www.cnblogs.com/landeanfen/p/4632467.html。
既然我们想要封装自己的HtmlHelper,那么我们就必须要了解Label()方法里面是如何实现的,我们伟大的Reflector又派上用场了。我们来反编译System.Web.MVC.dll看看。找到LabelExtensions这个类

经过一系列的转到定义,我们找到最终的方法

同样,我们找到TextBox()最终定义的方法

哟西,原来就是TagBuilder这个一个小东西,让人觉得神奇得不要不要的。所以有时我们需要敢于反编译,或许看似高级的背后其实很简单呢~~
二、BootstrapHelper组件封装准备
1、定义BootstrapHelper
有了以上的基础做准备,接下来就是具体的实现了,我们新建了一个空的MVC项目,添加如下文件。

编译发现报错如下

将HtmlHelper转到定义发现它有两个构造函数,分别有两个、三个参数

那么,我们的BootstrapHelper也定义两个构造函数,于是代码变成这样:
namespace Extensions
{
public class BootstrapHelper : System.Web.Mvc.HtmlHelper
{
/// <summary>
/// 使用指定的视图上下文和视图数据容器来初始化 BootstrapHelper 类的新实例。
/// </summary>
/// <param name="viewContext">视图上下文</param>
/// <param name="viewDataContainer">视图数据容器</param>
public BootstrapHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
: base(viewContext, viewDataContainer)
{ } /// <summary>
/// 使用指定的视图上下文、视图数据容器和路由集合来初始化 BootstrapHelper 类的新实例。
/// </summary>
/// <param name="viewContext">视图上下文</param>
/// <param name="viewDataContainer">视图数据容器</param>
/// <param name="routeCollection">路由集合</param>
public BootstrapHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
: base(viewContext, viewDataContainer, routeCollection)
{ }
}
}
这样通过子类复用父类的构造函数的方式即可解决以上问题。编译通过!
2、定义LabelExtensions
上面我们研究过HtmlHelper,在HtmlHelper里面,不同的html组件定义了不同的Extension(扩展),下面我们就以最简单的Label标签为例定义我们BootstrapHelper里面的Label标签。
同样,在Extensions文件夹里面我们新建了一个文件LabelExtensions.cs,用于定义Label标签的扩展,它里面的基本实现如下:
namespace Extensions
{
public static class LabelExtensions
{
/// <summary>
/// 通过使用指定的 HTML 帮助器和窗体字段的名称,返回Label标签
/// </summary>
/// <param name="html">扩展方法实例</param>
/// <param name="id">标签的id</param>
/// <param name="content">标签的内容</param>
/// <param name="cssClass">标签的class样式</param>
/// <param name="htmlAttributes">标签的额外属性(如果属性里面含有“-”,请用“_”代替)</param>
/// <returns>label标签的html字符串</returns>
public static MvcHtmlString Label(this BootstrapHelper html, string id, string content, string cssClass, object htmlAttributes)
{
//定义标签的名称
TagBuilder tag = new TagBuilder("label");
//给标签增加额外的属性
IDictionary<string, object> attributes = BootstrapHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (!string.IsNullOrEmpty(id))
{
attributes.Add("id", id);
}
if (!string.IsNullOrEmpty(cssClass))
{
//给标签增加样式
tag.AddCssClass(cssClass);
}
//给标签增加文本
tag.SetInnerText(content);
tag.AddCssClass("control-label");
tag.MergeAttributes(attributes);
return MvcHtmlString.Create(tag.ToString());
}
}
}
我们暂且只定义一个方法,其他的重载我们很好扩展,这里给所有的BootstrapHelper里面的Label标签统一添加了“control-label”样式,当然,如果你的项目里面的label标签定义了自己的样式,那么这里改成你需要的样式即可。以上代码都比较基础,这里就不一一讲解。
3、定义BootstrapWebViewPage
以上定义了BootstrapHelper和LabelExtensions,准备工作是做好了,但是还少一个对象,比如我们在cshtml页面里面 @Html.Label("姓名") 这样写,Html变量是一个HtmlHelper类型的对象,那么,如果我们需要使用类似 @Bootstrap.Label() 这种写法,以此类推,Bootstrap变量应该也是一个BootstrapHelper类型的对象,那么如果我们要这么用,必须要先定义一个Bootstrap变量,这个变量到底在哪里定义呢。于是博主思考,Html变量是定义在哪里的呢?再次转到定义

原来是在WebViewPage这个类的子类中,同样,我们在Extensions文件夹里面也新建一个WebViewPage的子类BootstrapWebViewPage,实现代码如下:
namespace Extensions
{
public abstract class BootstrapWebViewPage<T> : System.Web.Mvc.WebViewPage<T>
{
//在cshtml页面里面使用的变量
public BootstrapHelper Bootstrap { get; set; } /// <summary>
/// 初始化Bootstrap对象
/// </summary>
public override void InitHelpers()
{
base.InitHelpers();
Bootstrap = new BootstrapHelper(ViewContext, this);
} public override void Execute()
{
//throw new NotImplementedException();
}
}
}
至于这里的泛型,我们以后再来做讲解,这里先不做过多纠结
4、实践
有了以上三步,所有需要的方法和变量都齐全了,貌似已经“万事俱备只欠东风”了,是不是这样呢?我们来试一把
编译,将Index.cshtml页面关闭重新打开,发现仍然找不到Bootstrap对象

怎么回事呢,Html是可以找到的,那Bootstrap变量去哪里了呢。。。
经过一番查找资料,发现在View文件夹里面有一个web.config文件(之前一直没怎么在意这个东西,现在想想里面还是有学问的哦),里面有一个节点system.web.webPages.razor下面有一个pages节点,默认是这样的:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="BootstrapHelper" />
</namespaces>
</pages>
</system.web.webPages.razor>
我们将pages节点的pageBaseType改成我们的WebViewPage
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="Extensions.BootstrapWebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="BootstrapHelper" />
</namespaces>
</pages>
</system.web.webPages.razor>
然后编译,重新打开Index.cshtml。

OK,可以找到Bootstrap对象了。我们将Index.cshtml里面写入如下内容:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@Html.Label("姓名")
@Html.TextBox("a", "Jim")
@Bootstrap.Label(null, "Bootstrap Label标签", null, null)
</div>
</body>
</html>
运行看看效果:

怎么还是报错呢?这个问题应该不难理解,因为在razor里面使用@调用后台变量和方法的时候也存在命名空间的概念,这个命名空间在哪里引用呢,还是在View文件夹里面的web.config里面,在system.web.webPages.razor节点下面存在namespace的节点,我们将自定义的Label()扩展方法所在的命名空间加进去即可。于是配置变成这样:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="Extensions.BootstrapWebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="BootstrapHelper" />
<add namespace="Extensions"/>
</namespaces>
</pages>
</system.web.webPages.razor>
再次运行

三、BootstrapHelper组件完善
通过上面一系列发现坑、填坑的经历,一个最最简单的BootstrapHelper组件已经基本可用。我们将LabelExtensions简单完善下:
namespace Extensions
{
public static class LabelExtensions
{
public static MvcHtmlString Label(this BootstrapHelper html, string id)
{
return Label(html, id, null, null, null);
}public static MvcHtmlString Label(this BootstrapHelper html, string id, string content)
{
return Label(html, id, content, null, null);
} public static MvcHtmlString Label(this BootstrapHelper html, string id, string content, object htmlAttributes)
{
return Label(html, id, content, null, htmlAttributes);
} /// <summary>
/// 通过使用指定的 HTML 帮助器和窗体字段的名称,返回Label标签
/// </summary>
/// <param name="html">扩展方法实例</param>
/// <param name="id">标签的id</param>
/// <param name="content">标签的内容</param>
/// <param name="cssClass">标签的class样式</param>
/// <param name="htmlAttributes">标签的额外属性(如果属性里面含有“-”,请用“_”代替)</param>
/// <returns>label标签的html字符串</returns>
public static MvcHtmlString Label(this BootstrapHelper html, string id, string content, string cssClass, object htmlAttributes)
{
//定义标签的名称
TagBuilder tag = new TagBuilder("label");
//给标签增加额外的属性
IDictionary<string, object> attributes = BootstrapHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (!string.IsNullOrEmpty(id))
{
attributes.Add("id", id);
}
if (!string.IsNullOrEmpty(cssClass))
{
//给标签增加样式
tag.AddCssClass(cssClass);
}
//给标签增加文本
tag.SetInnerText(content);
tag.AddCssClass("control-label");
tag.MergeAttributes(attributes);
return MvcHtmlString.Create(tag.ToString());
}
}
}
呵呵,是不是有模有样~~可能又有人要说博主“山寨”了,呵呵,不管山寨不山寨,你觉得爽就行。
四、总结
这篇先到这里,一路填坑,基本功能总算可用。还有一些需要完善的地方,比如泛型,比如lamada表达式等等,来日方长,博主有时间完善下。还有最基础的一些表单控件,我们都需要封装,这个估计还有点工作量,只能慢慢来完善了,等完善都一定的程度会开源在git上,希望自己能够坚持下去!如果你觉得本文对你有帮助,请帮忙推荐下,您的推荐是博主坚持完善的动力。
本文原创出处:http://www.cnblogs.com/landeanfen/
欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利
C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper的更多相关文章
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)
前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...
- 一步一步学Silverlight 2系列(8):使用样式封装控件观感
述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 系列文章--一步一步学Silverlight2
概述 由TerryLee编写的<Silverlight 2完美征程>一书,已经上市,在该系列文章的基础上补充了大量的内容,敬请关注.官方网站:http://www.dotneteye.cn ...
- 一步一步学Silverlight 2系列文章
概述 由TerryLee编写的<Silverlight 2完美征程>一书,已经上市,在该系列文章的基础上补充了大量的内容,敬请关注.官方网站:http://www.dotneteye.cn ...
- 一步一步学Silverlight 2系列(21):如何在Silverlight中调用JavaScript
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 一步一步学Silverlight 2系列(17):数据与通信之ADO.NET Data Services
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 一步一步学Silverlight 2系列(16):数据与通信之JSON
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 一步一步学Silverlight 2系列(15):数据与通信之ASMX
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 一步一步学Silverlight 2系列(14):数据与通信之WCF
一步一步学Silverlight 2系列(14):数据与通信之WCF 概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框 ...
随机推荐
- 利用WCF的双工通讯实现一个简单的心跳监控系统
何为心跳监控系统? 故名思义,就是监控某个或某些个程序的运行状态,就好比医院里面的心跳监视仪一样,能够随时显示病人的心跳情况. 心跳监控的目的是什么? 与医院里面的心跳监视仪目的类似,监控程序运行状态 ...
- android 模拟2048
利用节日休息时间在ANDROID上进行学习并模拟2048游戏. 效果如下图: 制作思路: 1.画出2048游戏主界面,根据手机屏幕宽高度进行计算并画出每个方块的大小. @Override protec ...
- Razor语法中绑定一个值给checkbox
在ASP.NET MVC开发中,需要绑定一个值给checkbox标签,如下面写法,它们运行时是没有问题,照样能跑. 看看上面的语法,在绑定时,它却出现绿浪线.提不绑定的值is not a valid ...
- Github pages + jekyll 博客快速搭建
Github pages + jekyll 博客快速搭建 寻找喜欢的模版 https://github.com/jekyll/jekyll/wiki/sites http://jekyllthemes ...
- jQuery使用
jQuery jQuery是一个快速,小,功能丰富的JavaScript库. 它使 HTML文档遍历和操作.事件处理. 动画和Ajax更简单和易于使用的API,在工作 众多的浏览器. 和多功能性的结合 ...
- WCF入门教程2——创建第一个WCF程序
本节目标 掌握接口 理解契约式编程 创建宿主程序 创建客户端程序访问服务 什么是接口 认识一下接口 必须知道的接口特性 接口不可以被实例化(常作为类型使用) 实现类必须实现接口的所有方法(抽象类除外) ...
- mysql 插入数据失败防止自增长主键增长的方法
mysql设置了自增长主键ID,插入失败的那个自增长ID也加一的,比如失败5个,下一个成功的不是在原来最后成功数据加1,而是直接变成加6了,失败次数一次就自动增长1了,能不能让失败的不增长的? 或者说 ...
- HTTP协议下保证密码不被获取更健壮方式
说到在http协议下用户登录如何保证密码安全这个问题: 小白可能第一想法就是,用户在登录页面输入密码进行登录时,前台页面对用户输入的密码进行加密,然后把加密后的密码作为http请求参数通过网络发 ...
- JS正则表达式(JavaScript regular expression)
RegExp直接量和对象的创建 就像字符串和数字一样,程序中每个取值相同的原始类型直接量均表示相同的值,这是显而易见的.程序运行时每次遇到对象直接量(初始化表达式)诸如{}和[]的时候都会创建新对象. ...
- Linux常用命令大全
系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIO ...