ASP.NET MVC3也出来有一段时间了,对于没有RadioButtonList 与CheckBoxList的问题,网上也已经有很多解决方案了,可以for循环拼接出来,也可以引用ASP.NET MVC Toolkit,等等方法。其实本没有必要写出来的,不过看了WebGird中队format的实现方式,一时来了兴趣,就尝试这实现了一下,发现还是有不少机关的,于是就拿出来和大家分享一下。
首先清楚下要实现什么,For<TModel, TProperty> 这样的重载时必须的了,然后还要实现WebGrid的format类似的支持,可以自定义输出格式。当然输出正确的列表HTML是必须的,不过这个不难。
那么就先实现一个最基本的核心函数
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
namespace SymApplauseAward.MvcExtention
{
public static class CheckBoxListExtension
{
public static MvcHtmlString InputListInternal(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
bool allowMultiple,
)
{
string fullHtmlFieldName =
html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
if (string.IsNullOrEmpty(fullHtmlFieldName))
{
throw new ArgumentException("filed can't be null or empty !",
"name");
}
StringBuilder strBuilder = new StringBuilder();
TagBuilder tagBuilder = new TagBuilder("input");
foreach (var item in selectList)
{ //Clear first
tagBuilder.InnerHtml = string.Empty;
if (allowMultiple)
{
tagBuilder.MergeAttribute("type", "checkbox", true);
}
else
{
tagBuilder.MergeAttribute("type", "radio", true);
}
tagBuilder.MergeAttribute("value", item.Value, true);
if (item.Selected)
tagBuilder.MergeAttribute("selected", "selected", true);
tagBuilder.MergeAttribute("name", fullHtmlFieldName, true);
var s = tagBuilder.ToString()+item.Text+"<br/>"
strBuilder.Append(s);
}
return new MvcHtmlString(strBuilder.ToString());
}
}
这里通过TagBuilder创建Html,虽然也可以直接拼字符串,不过既然微软提供了相应的类,就直接用吧。 并且通过html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);获得了合法的name值,这步很重要,它会把“.”转化为“_”从而使name合法。
好了,基本功能实现了,如果用@Html.InputListInternal去调用,就可以显示相应的List了。
不过,这样还不太灵活,我们无法自定义格式,那么下面就实现自定义格式的功能。查看一下WebGrid的文档,原来format参数的类型是Func<dynamic,object>类型,那简单我们加一个好了。加完后代码如下:
public static MvcHtmlString InputListInternal(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
bool allowMultiple,
Func<dynamic, object> format
)
{
//...
if (format == null)
format = i => i.Button + i.Text + "<br/>";
StringBuilder strBuilder = new StringBuilder();
TagBuilder tagBuilder = new TagBuilder("input");
foreach (var item in selectList)
{
//...
var inputItem = new { Button = tagBuilder.ToString(),Text = item.Text };
var s = format(inputItem).ToString();
strBuilder.Append(s);
}
return new MvcHtmlString(strBuilder.ToString());
}
赶快用一下@Html.InputListInternal(“Score”,scores,false,item=>item.Button+”>”+item.Text),事与愿违,报错了“object 不包含Button的定义”。奇怪了,明明传入的是new {Button=tagBuilder.ToString(), Text=item.Text},有Button属性啊!
于是开始查技术文档,原来是dynamic的循环不会涉及扩展方法,换句话说,声明的匿名对象对于dynamic来说是不知道的,于是就强制转换为Object了,Object当然没有Button属性,于是报错了。对于这种问题,有两种解决办法,方法一:不用扩展方法的调用方式,而是回归静态类静态方法调用的方式。方法二:在dynamic的上下文中引用类型。第一种方法不说了,要那样就不做这个扩展了,于是尝试第二种解决办法。
由于是在页面定义的Lamda表达式,因此Context是Page。想想的话,要用这个扩展必然要引用这个命名空间,那么只要传回的类型是这个空间的public类,那么dynamic就能找到相应的类型了。于是定义了一个新类InputListItem,将代码稍作改动:
var btnHtmlString = new MvcHtmlString(tagBuilder.ToString());
var inputItem = new InputListItem { Button = btnHtmlString, Text = item.Text };
var s = format(inputItem).ToString();
strBuilder.Append(s);
再试下,OK,显示正常了,而且也是自定义的格式,至此format事情是搞定了。
最后实现一下For<TModel, TProperty>的重载,这个比较简单,就是传入一个Expression<Func<TModel, TProperty>>的参数,这里多提示一下可以用ExpressionHelper.GetExpressionText来直接提取参数的内容。至于匿名类型的htmlAttributes的处理也可以用HtmlHelper.AnonymousObjectToHtmlAttributes简单完成。在经过进一步封装后,最终代码如下:
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
namespace Pride_Zhou.MvcExtention
{
public class InputListItem
{
public MvcHtmlString Button { get; set; }
public string Text { get; set; }
}
public static class CheckBoxListExtension
{
#region CheckBoxList
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format,
object htmlAttributes)
{
return CheckBoxListFor(html, expression, selectList, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format = null,
IDictionary<string, object> htmlAttributes = null)
{
return CheckBoxList(html, GetName(expression), selectList, format, htmlAttributes);
}
public static MvcHtmlString CheckBoxList(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format,
object htmlAttributes)
{
return CheckBoxList(html, name, selectList, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString CheckBoxList(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format = null,
IDictionary<string, object> htmlAttributes = null)
{
return InputListInternal(html, name, selectList, true, format, htmlAttributes);
}
#endregion
#region RadioButtonList
public static MvcHtmlString RadioButtonList(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format,
object htmlAttributes)
{
return RadioButtonList(html, name, selectList, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString RadioButtonList(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format = null,
IDictionary<string, object> htmlAttributes = null)
{
return InputListInternal(html, name, selectList, false, format, htmlAttributes);
}
public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format,
object htmlAttributes)
{
return RadioButtonList(html, GetName(expression), selectList, format, htmlAttributes);
}
public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
Func<dynamic, object> format = null,
IDictionary<string, object> htmlAttributes = null)
{
return RadioButtonList(html, GetName(expression), selectList, format, htmlAttributes);
}
#endregion
/*-------------------------------------
* Core Function
--------------------------------------*/
private static MvcHtmlString InputListInternal(
this HtmlHelper html,
string name,
IEnumerable<SelectListItem> selectList,
bool allowMultiple,
Func<dynamic, object> format,
IDictionary<string, object> htmlAttributes
)
{
string fullHtmlFieldName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
if (string.IsNullOrEmpty(fullHtmlFieldName))
{
throw new ArgumentException("filed can't be null or empty !", "name");
}
if (format == null)
format = i => i.Button + i.Text + "<br/>";
StringBuilder strBuilder = new StringBuilder();
TagBuilder tagBuilder = new TagBuilder("input");
foreach (var item in selectList)
{ //Clear first
tagBuilder.InnerHtml = string.Empty;
if (allowMultiple)
{
tagBuilder.MergeAttribute("type", "checkbox", true);
}
else
{
tagBuilder.MergeAttribute("type", "radio", true);
}
tagBuilder.MergeAttribute("value", item.Value, true);
if (item.Selected)
tagBuilder.MergeAttribute("selected", "selected", true);
tagBuilder.MergeAttributes<string, object>(htmlAttributes);
tagBuilder.MergeAttribute("name", fullHtmlFieldName, true);
var btnHtmlString = new MvcHtmlString(tagBuilder.ToString());
var inputItem = new InputListItem { Button = btnHtmlString, Text = item.Text };
var s = format(inputItem).ToString();
strBuilder.Append(s);
}
return new MvcHtmlString(strBuilder.ToString());
}
private static string GetName(LambdaExpression expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
return ExpressionHelper.GetExpressionText(expression);
}
}
}
好了,现在可以像WebGrid一样生成批量Html了,例如:
<table style="border:0 solid #000;">
<tbody>
@Html.RadioButtonList(String.Format("{0}{1}", "S_", gId),
Model.GetQuestions(gId).ToList().Select(q => new SelectListItem { Value = q.Id.ToString(), Text = q.Content }),
@<tr style="border:0 solid #000;">
<td style="width:2em;border:0 solid #000;">@item.Button</td>
<td style="border:0 solid #000;">@item.Text</td>
</tr>, new { @class = "score"})
</tbody>
</table>
怎样,和WebGird一样吧,如果觉得item参数过少,扩展InputListItem类就可以了。 总体来说实现不难,就是Dynamic比较坑爹,这里很多代码是通过查看MVC源码的实现来写的,当然检查还不够完美,如果有兴趣可以仿照微软,加上相应的代码,这样用起来也更稳定。
转载于:https://www.cnblogs.com/ebread/archive/2011/07/12/2104621.html