今天继续记录下全球唯一
的FineUI11改造体验,将思路和关键代码记录一下,作为过程资产,省着自己忘;
我这里还有一个需求,就是开发时控件我都会写个默认的匹配,比如页面的搜索表单,会使用相同的默认属性:
/// <summary> /// 默认属性 <br/> /// 滚动条 true <br/> /// 边框 false <br/> /// 头 false <br/> /// 间距 10 10 0 5 <br/> /// 子项间距 0 5 0 5 <br/> /// </summary> public static FormExtension DefaultAttr(this FormExtension f) { f.AutoScroll(true) ShowBorder(false) ShowHeader(false) BodyPadding("10 10 0 5") BoxConfigChildMargin("0 5 0 5"); return f; }
这样前台会省点地方,我一直提倡的是框架本身也是一个产品,要面向开发,写代码的根来自需求,需求除了客户事业业务需求还应该包括产品需求
和开发需求
测试需求
运维需求
,这些需求都来自项目干系人,然后再落到软件开发上,这个话题打字会很累,这里不深入了。
上一次记录到给控件增加了父标签属性的判断响应,这是一个典型的应用场景,但是有个缺点,每个可能的子控件都要手写一遍,我是讨厌分散割裂的,所以要写在基类上,本来TimePicker
TextBox
TextArea
NumberBox
的基类都一样(DropDownList
的基类跟他们不一样),
namespace FineUICore; // // 摘要: // 数字输入框控件标签 [HtmlTargetElement("f:NumberBox")] [RestrictChildren("Listeners", new string[] { "Attributes" })] public class NumberBoxTagHelper : RealTextFieldTagHelper { ... }
相关信息
就是这个 RealTextFieldTagHelper
,这样我要从TextBoxTagHelperEx
的PreProcess
修改为 RealTextFieldTagHelper.PreProcess
;问题就来了
我可以为顶层显式的类TextBoxTagHelper
做扩展属性,方法,怎么给RealTextFieldTagHelper
做扩展呢,如我简单的给 RealTextFieldTagHelper
加一个扩展方法,比如BeforProcess
,那我在上层还是需要调用的,
protected override void PreProcess(TagHelperContext context, TagHelperOutput output) { base.BeforProcess(context, output); base.PreProcess(context, output); }
每个实现类还得再写一次,并没有解决问题;
有没有一种方法,在不修改(破坏)TextBoxTagHelperEx
代码的基础上 TextBoxTagHelperEx.PreProcess
执行前,先执行基类的BeforProcess
方法呢,
问题一出来答案就很明显了,不吃药也能想到就是切面AOP,之前用Rougamo做了日志的插入,还是很方便的,这里直接拿过来就行了,
cs-AOPusing FineUICore; using FineUICoreEx.BaseEx; using Microsoft.AspNetCore.Razor.TagHelpers; using Rougamo; using Rougamo.Context; using System.Xml.Linq; [assembly: PreProcessAttribute] namespace FineUICoreEx { public class PreProcessAttribute : MoAttribute { //匹配到PreProcess public override string? Pattern => "method(* *.PreProcess(..))"; //方法执行前触发 public void OnEntry(MethodContext contextex) { TagHelperContext context = contextex.Arguments[0] as TagHelperContext; TagHelperOutput output = contextex.Arguments[1] as TagHelperOutput; ... } } }
实现中,我加了接口ITagHelperEx<T>
,只要继承这个接口传入T
,如果当的控件基于T
在绘制前触发ITagHelperEx.BeforProcess
,像这样
//如果控件基类是 RealTextFieldTagHelper public class RealTextFieldTagHelperEx : ITagHelperEx<RealTextFieldTagHelper> { public void BeforProcess(RealTextFieldTagHelper tagHelper, TagHelperContext context, TagHelperOutput output, TagHelperExFun method) { //拿到路径 List<BaseTagHelper> currentPath = method.GetCurrentPath(); //当前控件 var Source = tagHelper.Source; var Form = currentPath.AsEnumerable().Reverse() .FirstOrDefault(m => m.GetType() == typeof(FormTagHelperEx)) as FormTagHelperEx; if (Form != null) { if (Form.AutoEmptyText && string.IsNullOrEmpty(Source.EmptyText)) { Source.EmptyText = $"请填写{Source.Label}"; } } } }
还加入了一个 TagHelperExFun
类,提供一些快捷的方法
像开始提到的 DefaultAttr
默认配置,可以新建一个: ITagHelperEx<FormTagHelperEx>
类来实现
public class FormTagEx : ITagHelperEx<FormTagHelperEx> { public void BeforProcess(FormTagHelperEx tagHelper, TagHelperContext context, TagHelperOutput output, TagHelperExFun method) { //判断属性是否包含 if (method.HasAttr("serchform")) { tagHelper.AutoEmptyText = true; //设置默认值 method.SetDefault(nameof(Form.BodyPadding), "10"); method.SetDefault(nameof(Form.LabelWidth), 100); method.SetDefault(nameof(Form.EnableCollapse), true); } } }
@*加入标记 serchform 提供默认的属性 *@ <f:Form ID="Form1" IsFluid="true" Title="表单 1" serchform> <Rows> ... </Rows> </f:Form>
提醒
我已经将扩展上传到NuGet上,使用FineUI11WebForms
开发模式,可以直接拉下来用,除了我扩展的一些方法还会开放接口,以便应用到实例项目。目前还是预览版,如果想用一用需要看我前几篇的文章,源码没有开源,等正式版发布,我会单独写一个API文档。
ITagHelperExusing FineUICore; using Microsoft.AspNetCore.Razor.TagHelpers; namespace FineUICoreEx.BaseEx { /// <summary> /// 继承该类 会在控件绘制前时执行 BeforProcess /// </summary> /// <typeparam name="T"><see cref="ControlBaseTagHelper"/></typeparam> public interface ITagHelperEx<T> where T : ControlBaseTagHelper { /// <summary> /// 在FineUI的PreProcess前执行 /// </summary> /// <param name="tagHelper"></param> /// <param name="context"></param> /// <param name="output"></param> void BeforProcess(T tagHelper, TagHelperContext context, TagHelperOutput output, TagHelperExFun method); } /// <summary> /// 提供的快捷方法 /// </summary> public record TagHelperExFun() { /// <summary> /// 判断是否包含某属性 /// </summary> public Func<string, bool> HasAttr { init; get; } /// <summary> /// 设置默认值 ,如果前台没设置过,<br/>就是context里没有某个属性,则设置 Source 的值 /// </summary> public Action<string, object> SetDefault { init; get; } /// <summary> /// 得到路径 /// </summary> public Func<List<BaseTagHelper>> GetCurrentPath { init; get; } } }
BaseTagHelperExusing FineUICore; using Microsoft.AspNetCore.Razor.TagHelpers; using System.Reflection; namespace FineUICoreEx.BaseEx { public static class BaseTagHelperEx { public static List<BaseTagHelper> GetCurrentPath(BaseTagHelper tagHelper, TagHelperContext context) { // 通过反射获取 tagHelper 对象的类型 Type tagHelperType = tagHelper.GetType(); // 查找名为 "GetCurrentPath" 的方法,参数类型为 TagHelperContext var method = tagHelperType.GetMethod("GetCurrentPath", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(TagHelperContext) }, null); if (method == null) { return new List<BaseTagHelper>(); } // 调用方法并获取返回值 var result = method.Invoke(tagHelper, new object[] { context }); // 将结果转换为 List<BaseTagHelper> 类型 if (result is List<BaseTagHelper> baseTagHelpers) { return baseTagHelpers; } return new List<BaseTagHelper>(); } } }
PreProcessAttributeusing FineUICore; using FineUICoreEx; using FineUICoreEx.BaseEx; using Microsoft.AspNetCore.Razor.TagHelpers; using Rougamo; using Rougamo.Context; [assembly: PreProcessAttribute] namespace FineUICoreEx { public class PreProcessAttribute : MoAttribute { public override string? Pattern => "method(* *.PreProcess(..))"; public void OnEntry(MethodContext contextex) { TagHelperContext context = contextex.Arguments[0] as TagHelperContext; TagHelperOutput output = contextex.Arguments[1] as TagHelperOutput; // 获取目标对象的类型 Type targetType = contextex.TargetType; // 递归查找所有基类并调用相应的 PreProcess 方法 InvokePreProcessMethods(targetType, contextex.Target, context, output); } private void InvokePreProcessMethods(Type targetType, object targetInstance, TagHelperContext context, TagHelperOutput output) { // 递归调用基类的 PreProcess 方法 if (targetType.BaseType != null && targetType.BaseType != typeof(TagHelper)) { InvokePreProcessMethods(targetType.BaseType, targetInstance, context, output); } // 查找所有实现了 ITagHelperEx<> 接口的类型 var allTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITagHelperEx<>))) .ToList(); foreach (var type in allTypes) { // 找到实现了 ITagHelperEx<targetType> 的类型 var interfaceType = type.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITagHelperEx<>) && i.GetGenericArguments()[0] == targetType); if (interfaceType != null) { var hasattrfun = new Func<string, bool>( (name) => context.AllAttributes.Any(m => m.Name == name)); var setDefaultfun = new Action<string, object>((name, value) => { if (!hasattrfun(name)) { try { var Source = (targetInstance as ControlBaseTagHelper).Source; Source.GetType().GetProperty(name)?.SetValue(Source, value); } catch { } } }); var getcurrentpathfun = new Func<List<BaseTagHelper>>( () => BaseTagHelperEx.GetCurrentPath( (targetInstance as BaseTagHelper), context)); // 创建接口方法的参数 object[] parameters = new object[] { targetInstance, context, output,new TagHelperExFun() { HasAttr = hasattrfun, SetDefault = setDefaultfun, GetCurrentPath = getcurrentpathfun }}; // 获取 BeforProcess 方法 var method = interfaceType.GetMethod("BeforProcess"); if (method != null) { // 创建扩展类的实例 var tagHelperExInstance = Activator.CreateInstance(type); // 调用 PreProcess 方法 method.Invoke(tagHelperExInstance, parameters); } } } } } }
本文作者:没想好
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!