Asp.Net Mvc 自定义扩展
目录:
- 自定义模型IModelBinder
- 自定义模型验证
- 自定义视图引擎
- 自定义Html辅助方法
- 自定义Razor辅助方法
- 自定义Ajax辅助方法
- 自定义控制器扩展
- 自定义过滤器
- 自定义ActionResult
IModelBinder主要解决的问题是,将请求的数据转换成需要的数据这部分逻辑进行了封装。比如说 http://localhost:4742/Person/Person/2 请求的参数Id是2,通过参数2,获得2相关的所有数据。
这样做的好处是:
- 使代码变得更加简洁
- 帮助我们获取HTTP请求中的数据
- 帮助我们完成必要的数据类型转换
Controller部分:
当访问http://localhost:4742/Person/Person?Name=admin&Age=12时,自动转换为setting对象
[HttpPost] public ActionResult Person2([ModelBinder(typeof(SettingsBinder))]Settings settings) { return View("Person"); }
IModelBinder部分
SettingsBinder继承了IModelBinder,并实现了BindModel,通过controllerContext.HttpContext.Request获取请求,从请求中获取参数值,然后转换为对象
public class SettingsBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var request = controllerContext.HttpContext.Request; // 从request中获取参数值 var name = request["Name"]; var age = request["Age"]; // 然后将参数值转为对象 var setting = new { Name = name, Age = age }; return setting; } }
注:当Person2([ModelBinder(typeof(SettingsBinder))]Settings settings)已存在时,不能再定义Person2方法
自定义验证
通过定义自定义验证特性类,实现View的模型验证,NotEqualTo即自定义验证
public class RegisterModel { [Required] [StringLength(6, MinimumLength = 2)] //加 [Display(Name = "用户名")] public string UserName { get; set; } [NotEqualTo("UserName", ErrorMessage = "不能与用户名的值相同")] public string OtherName { get; set; } // NotEqualTo 是自定义模型验证特性 }
NotEqualToAttribute继承了ValidationAttribute和IClientValidatable ,并实现了IsValid和GetClientValidationRules方法
通过NotEqualTo构造参数的值UserName,获得该对象对应该参数值的属性值,匹配属性的值和约束的值OtherName是否相等,然后返回结果信息
using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Web.Mvc; namespace MvcValidation.Extension { // ValidationAttribute 验证特性 // IClientValidatable 客户端验证接口(View视图验证) public class NotEqualToAttribute : ValidationAttribute, IClientValidatable { public string OtherProperty { get; set; } // 构造参数 public NotEqualToAttribute(string otherProperty) { OtherProperty = otherProperty; } // 验证方法 protected override ValidationResult IsValid(object value, ValidationContext validationContext) { //从验证上下文中可以获取我们想要的的属性 var property = validationContext.ObjectType.GetProperty(OtherProperty); if (property == null) { return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "{0} 不存在", OtherProperty)); } //获取属性的值 var otherValue = property.GetValue(validationContext.ObjectInstance, null); // 判断并返回验证结果 if (object.Equals(value, otherValue)) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } return null; } // 客户端验证 public System.Collections.Generic.IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { // 设置客户端验证结果信息 var rule = new ModelClientValidationRule { ValidationType = "notequalto", ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()) }; rule.ValidationParameters["other"] = OtherProperty; yield return rule; } } }
自定义视图引擎
系统提供了视图和视图引擎,我们需要了解它之后继承并重写它的逻辑。
/* * 思路 * 1、控制器方法返回ActionResult是一个抽象类 * 2、ActionResult的其中一个子类ViewResult,正是她使用IView实例最终渲染出视图 * 3、需要自定义IView * 4、IViewEngine管理着IView,同时也需要自定义IViewEngine * 5、自定义IViewEngine是需要全局注册的 */
//namespace System.Web.Mvc //public interface IView //{ // 第一个参数ViewContext包含了需要被渲染的信息,被传递到前台的强类型Model也包含在其中。 // 第二个参数TextWriter可以帮助我们写出想要的html格式。 // void Render(ViewContext viewContent, TextWriter textWriter); //}
//namespace System.Web.Mvc //public interface IViewEngine //{ // // FindPartialView:在当前控制器上下文ControllerContext中找到部分视图。 // System.Web.Mvc.ViewEngineResult FindPartialView(System.Web.Mvc.ControllerContext controllerContext, string partialViewName, bool useCache); // // FindView:在当前控制器上下文ControllerContext中找到视图。 // System.Web.Mvc.ViewEngineResult FindView(System.Web.Mvc.ControllerContext controllerContext, string viewName, string masterName, bool useCache); // // ReleaseView:释放当前控制器上下文ControllerContext中的视图。 // void ReleaseView(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.IView view); //}
我们将派生出它们的类,通过自定义视图引擎类实现渲染,并显示。
准备资源:
public class Student { public int Id { get; set; } public string Name { get; set; } public int Score { get; set; } } public class DataAccess { List<Student> students = new List<Student>(); public DataAccess() { for (int i = 0; i < 10; i++) { students.Add(new Student() { Id=i+1, Name="Name"+Convert.ToString(i+1), Score = i+80 }); } } public List<Student> GetStudents() { return students; } }
自定义扩展
public class StudentView : IView { /// <summary> /// 渲染 /// 通过获得视图上下文数据,然后自定义输出格式通过TextWriter输出数据 /// </summary> /// <param name="viewContent"></param> /// <param name="writer"></param> public void Render(ViewContext viewContent, TextWriter writer) { // 从视图上下文ViewContext拿到model var model = viewContent.ViewData.Model; var students=model as List<Student>; // 自定义输出视图的html格式 writer.Write("<table border=1><tr><th>编号</th><th>名称</th><th>分数</th></tr>"); foreach (Student stu in students) { writer.Write("<tr><td>" + stu.Id + "</td><td>" + stu.Name + "</td><td>" + stu.Score + "</td></tr>"); } writer.Write("</table>"); } } public class StudentViewEngine : IViewEngine { //呈现部分视图 public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { throw new NotImplementedException(); } //呈现视图 public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { if (viewName == "StudentView") { // 呈现自定义视图 return new ViewEngineResult(new StudentView(), this); } else { return new ViewEngineResult(new string[] { "针对Student的视图还没创建!" }); } } //显示视图 public void ReleaseView(ControllerContext controllerContext, System.Web.Mvc.IView view) { } }
至此自定义视图引擎完成。接下来我们进行调用:
我们只需要在View中指定自定义视图的名称即可。
public ActionResult Index() { var students = new DataAccess().GetStudents(); ViewData.Model = students; return View("StudentView"); }
设置默认的,全局的视图引擎
只需要添加ViewEngines.Engines.Add(new StudentViewEngine());即可实现。
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); ViewEngines.Engines.Add(new StudentViewEngine()); }
自定义HtmlHelper辅助方法
定义扩展方法类和扩展方法
using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; // 设置命名空间为统一的Html命名空间 namespace System.Web.WebPages.Html { // 设置静态类 public static class HtmlExtensions { // 为HtmlHelper类提辅助方法Img,参数为src和alt public static MvcHtmlString Img(this HtmlHelper html,string src,string alt)
{
return MvcHtmlString.Create("<img src=\"" + src + "\" alt=\"" + alt + "\" />");
} } }
在View页面中调用该扩展方法
@* 调用Img方法,前者为src,后者为alt,生成的结果是:<img src="C:\images\btn.ico\" alt="" /> *@ @Html.Img("C:\\images\\btn.ico","")
自定义Razor辅助方法
在MVC项目根目录下新建一个文件夹名为:App_Code,用于存放MyHelpers.cshtml
编写MyHelpers.cshtml内容如下:
相当于定义了MyHelpers.li(List<string> arrays)方法,通过关键字helper进行声明,这样其他的cshtml页面都可以访问该方法
@helper li(List<string> arrays) { <ul> @foreach (var item in arrays) { <li>@(item)</li> } </ul> }
cshtml页面访问li方法
Index.cshtml页面调用:
@MyHelpers.li(new List<string>() { "甲","乙","丙","丁"})
页面输出结果:
自定义AjaxHelper辅助方法
与HtmlHelper扩展一样,为AjaxHelper扩展一个方法Textbox,并设置相应的属性
public static class HtmlExtensions { // 为HtmlHelper类提辅助方法Img,参数为src和alt public static MvcHtmlString Img(this HtmlHelper html, string src, string alt) { return MvcHtmlString.Create("<img src=\"" + src + "\" alt=\"" + alt + "\" />"); } public static MvcHtmlString Textbox(this AjaxHelper ajaxHelper, string name, AjaxOptions ajaxOptions, object htmlAttributes) { // 设置标签名 var tag = new TagBuilder("input"); // 设置属性值 tag.MergeAttribute("name", name); tag.MergeAttribute("type", "text"); tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); tag.MergeAttributes((ajaxOptions ?? new AjaxOptions()).ToUnobtrusiveHtmlAttributes()); tag.MergeAttribute("value", "自定义Ajax扩展"); // 输出Html return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal)); } }
View调用:
@Ajax.Textbox("search", new AjaxOptions { Url = @Url.Action("GetTime"), UpdateTargetId = "divTime", InsertionMode = InsertionMode.Replace }, new { size = 50 })
输出结果:
输出结果的源码:
<input data-ajax="true" data-ajax-mode="replace" data-ajax-update="#divTime" data-ajax-url="/AjaxHelperExt/GetTime" name="search" size="50" type="text" value="自定义Ajax扩展"></input>
自定义UrlHelper辅助方法
略.
自定义控制器扩展BaseController
// BaseController 针对Controller进行重写 // 并且提供了一些公用的方法如权限校验,Action跳转、日志记录等 public class BaseController : Controller { protected override void OnException(ExceptionContext filterContext) { // 处理异常 base.OnException(filterContext); } protected override void Initialize(RequestContext requestContext) { // 处理初始化信息,如Cookie,Session等缓存信息 base.Initialize(requestContext); } protected override void OnActionExecuting(ActionExecutingContext filterContext) { // 在调用操作方法前调用 base.OnActionExecuting(filterContext); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { // 在调用操作方法后调用 base.OnActionExecuted(filterContext); } }
自定义过滤器
路由访问过滤器
/// <summary> /// 路由访问过滤器 /// </summary> public class SystemIActionFilter : IActionFilter { // // Summary: // Called after the action method is invoked. // 在Action返回之后 // Parameters: // filterContext: // Information about the current request and action. public void OnActionExecuted(ActionExecutedContext filterContext) { } // // Summary: // Called before the action method is invoked. // 在进入Action之前 // 说明:使用RedirectToRouteResult进行路由值进行重定向时 // RouteName 路由名称 // RouteValues 路由值 特别注意第三个值 Permanent 获取一个值 // 该值指示重定向是否应为永久重定向 如果为true 在本程序会出现问题 // Parameters: // filterContext: // Information about the current request and action. public void OnActionExecuting(ActionExecutingContext filterContext) { //验证 控制器 视图 string tempAction = filterContext.RouteData.Values["action"].ToString(); string tempController = filterContext.RouteData.Values["controller"].ToString(); string tempLoginAction = filterContext.RouteData.Values["action"].ToString(); if (tempAction == "HomeLogin" && tempController == "Home" || tempLoginAction == "UserLogin" ? false : true) { //请求登录时 if (tempAction == "UserLogin" && tempController == "Home" ? false : true) { //Cookie HttpCookie tempToken = filterContext.HttpContext.Request.Cookies["exclusiveuser_token"]; if (tempToken == null) { filterContext.Result = new RedirectToRouteResult("HomeLogin", new RouteValueDictionary(new { controller = "Home", action = "HomeLogin" }), false); } //登录token不为null时 进行合法性验证token 头部,载荷,签名,cookie过期时间 if (tempToken == null ? false : true) { //UserToken 方法 将验证 token 合法性 包括token 签名 ,token载荷,cookie 过期时间等 //string SystemToken = new SecondTrackToken().UserToken(); //if (SystemToken == null) //{ // filterContext.Result = new RedirectToRouteResult("HomeLogin", new RouteValueDictionary(new { controller = "Home", action = "HomeLogin" }), false); //}; } } } } }
异常处理过滤器
/// <summary> /// 异常处理过滤器 /// </summary> public class SystemIExceptionFilter : IExceptionFilter { void IExceptionFilter.OnException(ExceptionContext filterContext) { Exception exception = filterContext.Exception; if (filterContext.ExceptionHandled) { return; } HttpException http = new HttpException(null, exception); /* * filterContext.Exception.Message 错误信息 */ string messager = filterContext.Exception.Message; /* * 错误日志 */ //Log4NetHelp help = new Log4NetHelp(); //help.ErrorString(filterContext.Exception.Message); /* * 设置自定义异常已经处理,避免其他过滤器异常覆盖 */ filterContext.ExceptionHandled = true; /* * 在派生类重写时,设置或者重写一个值该值指定是否禁用ISS7.0中自定义错误 */ filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; } }
授权处理(获取客户端信息)
public class SystemIAuthorizationFilter : IAuthorizationFilter { void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext) { //当前操作计算机用户 string pcName = ((System.Web.HttpServerUtilityWrapper)((System.Web.HttpContextWrapper)filterContext.RequestContext.HttpContext).Server).MachineName; //视图 string action = ((System.Web.Mvc.ReflectedActionDescriptor)filterContext.ActionDescriptor).ActionName; //控制器 string controller = ((System.Web.Mvc.ReflectedActionDescriptor)filterContext.ActionDescriptor).ControllerDescriptor.ControllerName; //请求时间 string time = filterContext.RequestContext.HttpContext.Timestamp.ToString(); //请求相对路径 string absturl = ((System.Web.UnvalidatedRequestValuesWrapper)((System.Web.HttpRequestWrapper)((System.Web.HttpContextWrapper)filterContext.RequestContext.HttpContext).Request).Unvalidated).Url.AbsoluteUri; //状态 string code = ((System.Web.HttpResponseWrapper)((System.Web.HttpContextWrapper)filterContext.RequestContext.HttpContext).Response).Status; // 浏览器版本 string browser = ((System.Web.HttpBrowserCapabilitiesWrapper)((System.Web.HttpRequestWrapper)((System.Web.HttpContextWrapper)filterContext.RequestContext.HttpContext).Request).Browser).Type; //请求方式 string gepPost = ((System.Web.HttpRequestWrapper)((System.Web.Mvc.Controller)filterContext.Controller).Request).RequestType; //本地主机名称解析DNS本身处理。 string server = ((System.Web.HttpRequestWrapper)((System.Web.HttpContextWrapper)filterContext.HttpContext).Request).UserHostAddress; #region server 说明 /* * 版权(c)1993 - 2009微软(msft . o:行情)。 * * 这是一个示例所使用的主机文件微软为Windows TCP / IP。 * * 这个文件包含IP地址到主机名的映射。 每一个 * 条目应该保存在单个行。 IP地址应 *被放置在第一列对应的主机名。 *的IP地址和主机名应该由至少一个 *空间。 * *此外,评论(这样的)可能是插入的个人 *线或后机器名称用“*”符号。 * 例如: * * 102.54.94.97 rhino.acme.com源服务器 * 38.25.63.10 x.acme.com x客户机主机 *本地主机名称解析DNS本身处理。 * 127.0.0.1 localhost *::1 localhost */ #endregion //用户 //部门 //职位 } }
自定义属性过滤器
public class CheckLogin: ActionFilterAttribute { public override void OnResultExecuting(ResultExecutingContext filterContext) { HttpCookieCollection CookieCollect = System.Web.HttpContext.Current.Request.Cookies; if (CookieCollect["username"] == null || CookieCollect["password"] == null) { filterContext.Result = new RedirectResult("/Home/Login"); } else { if (CookieCollect["username"].Value != "admin" && CookieCollect["password"].Value != "123456") { filterContext.Result = new RedirectResult("/Home/Login"); } } } }
过滤器定义好后,需要在过滤器配置类FilterConfig中添加
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); //将自定义异常过滤器的优先级提高,防止异常被默认的HandleError处理(也可以自定义类重写HandleErrorAttribute 实现错误处理) filters.Add(new SystemIExceptionFilter(), 1); //控制器过滤器 filters.Add(new SystemIActionFilter(), 2); //授权过滤器 filters.Add(new SystemIAuthorizationFilter(), 3);
// 自定义属性过滤器
filters.Add(new CheckLogin()); } }
自定义属性过滤器在控制器中调用:
在方法的上面加上特性:CheckLogin,当调用该方法时会先进行过滤再执行下面的逻辑。
[CheckLogin] public ActionResult About() { ViewBag.Message = "Your application description page."; return View(); }
自定义ActionResult
扩展的ActionResult,继承自ActionResult,需要重写ExecuteResult方法,通过构造函数传入参数值,
使用ExecuteResult方法的ControllerContext上下文获得HttpResponse,HttpResponse使用输出参数值结果。
/// <summary> /// 自定义JObject返回结果 /// </summary> public class JObjectActionResult : ActionResult { /// <summary> /// 结果集 /// </summary> public JObject JObject { get; set; } public Encoding ContentEncoding { get; set; } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } HttpResponseBase response = context.HttpContext.Response; response.ContentType = "application/json"; if (ContentEncoding != null) { response.ContentEncoding = ContentEncoding; } if (JObject != null) { response.Write(JObject.ToString()); } } }
通过response.Write(JObject.ToString());最后输出结果。
接下来我们看看调用:
public ActionResult Index() { return View(); }
默认的View()是Controller.View()的方法,因此我们为Controller类扩展一个方法。定义扩展内容如下:
public static class JObjectActionResultExtensions { public static JObjectActionResult JObjectResult(this Controller controller, JObject obj) { return new JObjectActionResult { JObject = obj }; } public static JObjectActionResult JObjectResult(this Controller controller, bool success) { JObject obj = new JObject(); obj.Add("success", success); if (success) { obj.Add("code", 200); obj.Add("msg", "Success!"); } else { obj.Add("code", 500); obj.Add("msg", "Error!"); } return JObjectResult(controller, obj); } public static JObjectActionResult JObjectResult(this Controller controller, bool success, int code, string msg) { JObject obj = new JObject(); obj.Add("success", success); obj.Add("code", code); obj.Add("msg", msg); return JObjectResult(controller, obj); } }
控制器中调用输出:
使用this关键字调用JObjectResult即可输出结果。
public ActionResult About() { ViewBag.Message = "Your application description page."; return this.JObjectResult(true); }
接下来我们再扩展一个序列化的Result.
/// <summary> /// 泛型的序列化结果 /// </summary> /// <typeparam name="TData"></typeparam> public class CustomView<TData> : ActionResult where TData:class,new() { /// <summary> /// 构造函数传入参数 /// </summary> /// <param name="t"></param> public CustomView(TData t) { data = t; } public TData data; protected JsonSerializerSettings SerializerSettings; protected void InitSerialization(ControllerContext context) { HttpResponseBase response = context.HttpContext.Response; response.ContentType = "text/html"; if (SerializerSettings == null) { SetSerializerSettings(); } response.Write(JsonConvert.SerializeObject(data, Formatting.None, SerializerSettings)); } protected virtual void SetSerializerSettings() { SerializerSettings = new JsonSerializerSettings { Converters = new List<JsonConverter> { new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd hh:mm" } } }; } public override void ExecuteResult(ControllerContext context) { InitSerialization(context); } }
调用它,只需要实例化一下就行了。
public ActionResult Contact() { ViewBag.Message = "Your contact page."; User u = new Models.User(); return new CustomView<User>(u); }
大家还有什么好的扩展方法,可以回复该帖哦。