你的位置:首页 > ASP.net教程

[ASP.net教程]使用MEF实现通用参数设置


  通用后台管理系统必备功能模块包含日志管理,权限管理,数据字典,参数配置等功能。参数设置主要用于设置系统运行所需的一些基础性配置项,比如redis缓存,mq消息队列,系统版本等信息。好的参数设置需要达到以下几点1.使用简单  2.功能强大,方便拓展 3.界面美观。本篇将带你实现通用参数设置,在阅读之前你需要了解的知识,ASP.NET MVC,Entity Framework,MEF。在线预览地址:http://config.myscloud.cn

阅读目录

  • 添加配置项及使用
  • 实现思路
  • 关键代码解析
  • 总结
回到顶部

添加配置项及使用

 为了验证系统实现了这几个目标1.使用简单  2.功能强大,方便拓展 3.界面美观,这里先通过实例来演示如何添加配置项以及怎么使用该配置项。

  1.添加配置项组

  只需添加一个继承于ConfigOption抽象类的类,这里我们称继承于ConfigOption的类为配置项组

  /// <summary>  /// 测试配置信息  /// </summary>  [Export(typeof(ConfigOption))]  [ConfigType(Group = "TestConfig", GroupCn = "测试配置项", ImmediateUpdate = true)]  public class TestConfig : ConfigOption  {    /// <summary>    /// 是否记录执行SQL    /// </summary>    [Config(Name = "记录执行SQL", DefaultValue = false)]    public static bool IfLogSQL { get; set; }  }

  注意点:继承ConfigOption抽象类,添加ConfigTypeAttribute属性描述(Group:分组  GroupCn:分组名称,用于显示在界面)
 
  2.添加配置项
  配置项组里面的每个静态属性称为配置项

  /// <summary>  /// 是否记录执行SQL  /// </summary>  [Config(Name = "记录执行SQL", DefaultValue = false)]  public static bool IfLogSQL { get; set; }


  注意点:ConfigAttribute属性描述中 Name:对应配置项中文说明  DefaultValue:默认值  ConfigValueType:bool类型会显示成单选radio,更多设置可参考ConfigAttribute类
  
     3.可选步骤  实现个性化业务   
  /// <summary>  /// 保存前校验  /// </summary>  /// <param name="value"></param>  /// <returns>bool</returns>  public override bool BeforeSave(OptionViewModel value)  {    foreach(var item in value.ListOptions)    {      switch (item.Key)      {        case "IfLogSQL":         if (string.IsNullOrEmpty(item.Value))         {           return false;         }         break;        default:           break;      }    }    return base.BeforeSave(value);  }
    public override void AfterSave(List<Options> ListOptions)  {   foreach (Options item in ListOptions)   {      switch (item.Key)      {        case "IfLogSQL":          //开启或者关闭EF日志记录          bool curValue = Convert.ToBoolean(item.Value);          if (curValue != TestConfig.IfLogSQL)          {            //EfLogConfig.ManagerEFLog(curValue);          }          break;        default:          break;      }    }  }

  


  使用BeforeSave和AfterSave方法可以实现个性化业务     

4.参数使用

    public ActionResult Index()    {      ViewBag.Title = SystemConfig.SystemTitle;      return View();    }  

   由于定义的是静态属性,所以可以直接使用


 
小结:只需通过添加配置项类,无需修改其它东西,所需的保存逻辑和界面都已经完成。这里留个疑问,我是如何知道前台界面渲染的时候该用radio,text,password中哪种控件的呢?
回到顶部

实现思路

 通用配置管理达到以下目标

   1.使用简单

     通过添加配置项类,无需额外操作即可完成工作

   2.功能强大,方便拓展

    界面等其它工作都已经由框架完成,对于个性化的配置比如需要实现校验,或者额外工作,可以通过beforesave,aftersave进行拓展

   3.界面美观

   基于bootstrap实现,整体效果还是挺不错的

  

 系统的类图

系统主流程

 
回到顶部

关键代码解析

 1.初始化(Global.asax.cs)

//1.MEF初始化MefConfig.Init();//2.EF初始化EFInitializer.UnSafeInit();//3.初始化系统参数配置ConfigManager configManager =MefConfig.TryResolve<ConfigManager>();configManager.Init();

 MefConfig.Init()               方法初始化组合容器
 EFInitializer.UnSafeInit()   Entity Framework数据库连接和类型初始化
 configManager.Init()         读取所有配置项 从数据库读取所有配置项值进行赋值
 
 2.关键类ConfigManager
  /// <summary>  /// 系统所有配置信息  /// </summary>  [ImportMany(typeof(ConfigOption))]  public IEnumerable<ConfigOption> AllConfig  {    get    {      return _allConfig;    }    set    {      if (_allConfig == null)      {         _allConfig = value;      }    }  }


 通过MEF导入器读取所有配置项组,存储在静态变量 _allConfig 中
     /// <summary>    /// 初始化系统参数配置信息    /// </summary>    public void Init()    {      //所有选项值      List<Options> listOption = ConfigService.GetAllOptions();      ConfigDescription desc = null;      //代码现有配置项      foreach (ConfigOption item in AllConfig)      {        //反射读取配置项ConfigTypeAttribute ConfigAttribute 信息        desc = ConfigDescriptionCache.GetTypeDiscription(item.GetType());        //设置当前配置项的GroupType        desc.GroupTypePropertyInfo.SetValue(item, Convert.ChangeType(desc.Group, desc.GroupTypePropertyInfo.PropertyType), null);        //每项值信息        List<Options> itemOptions = listOption.Where(e => e.OptionType.Equals(desc.Group, StringComparison.OrdinalIgnoreCase)).ToList();        Options op = null;        ConfigAttribute ca = null;        foreach (PropertyInfo prop in desc.StaticPropertyInfo)        {          op = itemOptions.FirstOrDefault(e1 => e1.Key.Equals(prop.Name, StringComparison.OrdinalIgnoreCase));          ca = desc.MemberDict[prop.Name];          if (op == null)          {            //设置默认值            prop.SetValue(null, Convert.ChangeType(ca.DefaultValue, prop.PropertyType), null);          }          else          {            prop.SetValue(null, Convert.ChangeType(op.Value, prop.PropertyType), null);          }        }      }    }

 ConfigService.GetAllOptions()从数据库中读取所有选项值,通过ConfigDescriptionCache.GetTypeDiscription(item.GetType())反射读取所有静态属性的相关值

属性类型和前台控件映射关系

    /// <summary>    /// 设置默认项数值类型-根据属性类型进行转换    /// </summary>    /// <param name="configAttr"></param>    /// <param name="propertyType">属性类型</param>    private static void SetConfigValueType(ConfigAttribute configAttr,Type propertyType)    {      switch (propertyType.ToString()) {        case "System.String":          configAttr.ValueType = ConfigValueType.String; //文本框          break;        case "System.Boolean":          configAttr.ValueType = ConfigValueType.Bool; //对应前台 radio          break;        case "System.Int32":        case "System.Double":          configAttr.ValueType = ConfigValueType.Number; //对应数值输入框          break;        default:          configAttr.ValueType = ConfigValueType.String;          break;      }    }

 密码类型的可以自行进行指定


    /// <summary>    /// MQ连接密码    /// </summary>    [Config(Name = "密码", ValueType = ConfigValueType.Password)]    public static string Password { get; set; }

 提供的获取配置项的接口

    /// <summary>    /// 获取所有配置信息    /// </summary>    /// <returns>所有配置信息</returns>    public List<OptionViewModel> GetAllOption(string GroupType = "")


    /// <summary>    /// 获取指定项配置信息    /// </summary>    /// <param name="GroupType">分组项</param>    /// <returns>所有配置信息</returns>    public OptionViewModel GetOptionByGroup(string GroupType)

    /// <summary>    /// 获取指定项配置信息    /// </summary>    /// <param name="GroupType">分组项</param>    /// <returns>所有配置信息</returns>    public Options GetOptionByGroupAndKey(string GroupType, string key){}

保存方法实现

    /// <summary>    /// 保存配置信息    /// </summary>    /// <param name="value">配置信息</param>    public ApiResult<string> Save(OptionViewModel value)    {      ApiResult<string> result = new ApiResult<string>();      result.HasError = true;      string GroupType = value.Group.GroupType;      if (value.Group == null || string.IsNullOrEmpty(GroupType) || value.ListOptions == null)      {        result.Message = "保存参数配置时发生参数空异常";        return result;      }      //调用保存前处理事件      ConfigOption curConfigOption = AllConfig.FirstOrDefault(e => e.GroupType.Equals(GroupType, StringComparison.OrdinalIgnoreCase));      if (curConfigOption == null)      {        //如果没有找到匹配项        result.Message = string.Format("当前保存配置信息{0}不对应后台的任务配置类", GroupType);        return result;      }
       //调用配置项的保存前校验事件 if (!curConfigOption.BeforeSave(value)) { result.Message = "当前配置项不允许保存"; return result; } //保存数据 try { using (CommonDbContext cdb = new CommonDbContext()) { var dbSet = cdb.Set<Options>(); var delObjs=dbSet.Where(e => e.OptionType == GroupType).ToList(); //删除原有数据 foreach (var item in delObjs) { cdb.Entry(item).State = EntityState.Deleted; } //保存数据 foreach (var item in value.ListOptions) { item.OptionId = Guid.NewGuid().ToString("N"); } dbSet.AddRange(value.ListOptions); cdb.SaveChanges(); } } catch (Exception ex) { result.Message = ex.Message; return result; } //对当前配置项进行赋值 SetValue(curConfigOption, value.ListOptions); result.HasError = false; return result; } /// <summary> /// 保存时 对当前配置项进行赋值 /// </summary> /// <param name="item">当前配置项</param> /// <param name="ListOptions">配置项值</param> public void SetValue(ConfigOption item, List<Options> ListOptions) { //调用保存后处理事件 item.AfterSave(ListOptions); var desc = ConfigDescriptionCache.GetTypeDiscription(item.GetType()); Options option = null; foreach (PropertyInfo prop in desc.StaticPropertyInfo) { option = ListOptions.First(e => e.Key.Equals(prop.Name, StringComparison.OrdinalIgnoreCase)); if (option == null) { //不存在该配置项,则清空当前值 prop.SetValue(null, Convert.ChangeType(null, prop.PropertyType), null); } else { prop.SetValue(null, Convert.ChangeType(option.Value, prop.PropertyType), null); } } }


 前台渲染逻辑(config.js)
  //初始化数据  initData = function () {    $.ajax({      type: "get",      url: "/Config/GetAllOption", //调用后台获取所有配置项接口      dataType: "json",      beforeSend: function () {        //加载等待层        index = layer.load(0);      },      complete: function () {        layer.close(index);      },      success: function (data) {        BaseData = data;        drawConfig(BaseData);  //绘制界面      }    });  }

 


回到顶部

总结

      该参数配置可以很简单的移植到自己系统里面,在TaskManagerV2.0这边博客中使用的参数配置功能就是直接移植的该系统的代码。另外使用的时候记得修改Web.config中的数据库连接字符串,本篇写到这里就完结了,在介绍一下与文章无关的内容。我在博客上添加的打赏功能,分别位于公告和文章结尾处,欢迎资助我持续写作!下篇将结合参数配置介绍消息队列RabbitMQ的使用,敬请期待!

  源代码下载地址:http://files.cnblogs.com/files/yanweidie/CommonParamConfig.rar,svn源码地址http://code.taobao.org/svn/commonparamconfig。

 

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【关注我】。

如果,想给予我更多的鼓励,求打

因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。