Use MEF to realize general parameter setting

Use MEF to realize general parameter setting

  The necessary functional modules of the universal background management system include log management, authority management, data dictionary, parameter configuration and other functions. Parameter settings are mainly used to set some basic configuration items required for system operation, such as redis cache, mq message queue, system version and other information. Good parameter settings need to achieve the following points: 1. Simple to use 2. Powerful and easy to expand 3. Beautiful interface. This article will take you to implement common parameter settings, the knowledge you need to know before reading, ASP.NET MVC, Entity Framework, MEF. Online preview address: http://config.myscloud.cn

Read the table of contents

back to the top

Add configuration items and use

 In order to verify that the system has achieved these goals: 1. Simple to use 2. Powerful and easy to expand 3. Beautiful interface , here is an example to demonstrate how to add configuration items and how to use the configuration items.

  1. Add configuration item group

  Just add a class inherited from the ConfigOption abstract class. Here we call the class inherited from ConfigOption a configuration item group .

   ///<summary>
   ///Test configuration information
   ///</summary>
    [Export(typeof(ConfigOption))]

    [ConfigType(Group = "TestConfig", GroupCn = "Test Configuration Item", ImmediateUpdate = true)]
    public class TestConfig: ConfigOption
    {
       ///<summary>
       ///Whether to record the execution of SQL
       ///</summary>
        [Config(Name = "Record execution SQL", DefaultValue = false)]
        public static bool IfLogSQL {get; set;}
    }

  Note: Inherit the ConfigOption abstract class and add the ConfigTypeAttribute attribute description (Group: grouping GroupCn: group name, used to display on the interface)

  2. Add configuration items

  Each static attribute in the configuration item group is called a configuration item

  ///<summary>
  ///Whether to record the execution of SQL
  ///</summary>
  [Config(Name = "Record execution SQL", DefaultValue = false)]
  public static bool IfLogSQL {get; set;}

  Note: Name in the description of the ConfigAttribute attribute: Chinese description of the corresponding configuration item DefaultValue: default value ConfigValueType: bool type will be displayed as a single-choice radio, more settings can refer to the ConfigAttribute class

     3. Optional steps to achieve personalized business

  ///<summary>
  ///Check before saving
  ///</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":
          //Turn on or turn off EF logging
          bool curValue = Convert.ToBoolean(item.Value);
          if (curValue != TestConfig.IfLogSQL)
          {
            //EfLogConfig.ManagerEFLog(curValue);
          }
          break;
        default:
          break;
      }
    }
  }

  Use BeforeSave and AfterSave methods to achieve personalized business     

4. Use of parameters

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

   Since the definition is a static property, it can be used directly

Summary: Just by adding the configuration item class, no need to modify other things, the required saving logic and interface have been completed. Here is a question. How do I know which control of radio, text, and password should be used when rendering the front-end interface ?

back to the top

Realization ideas

 General configuration management achieves the following goals

   1. Simple to use

     By adding configuration item classes, the work can be completed without additional operations

   2. Powerful function, easy to expand

    Other tasks such as the interface have been completed by the framework. For personalized configuration such as verification or additional work, you can expand through beforesave and aftersave

   3. Beautiful interface

Based on bootstrap implementation, the overall effect is quite good

 Class diagram of the system

System main flow

back to the top

Key code analysis

 1. Initialization (Global.asax.cs)

//1.MEF initialization
MefConfig.Init();
//2.EF initialization
EFInitializer.UnSafeInit();
//3. Initialize system parameter configuration
ConfigManager configManager =MefConfig.TryResolve<ConfigManager>();
configManager.Init();

 The MefConfig.Init() method initializes the combined container

 EFInitializer.UnSafeInit() Entity Framework database connection and type initialization

 configManager.Init() Read all configuration items Read all configuration item values ​​from the database for assignment

 2. The key class ConfigManager

  ///<summary>
  ///All configuration information of the system
  ///</summary>
  [ImportMany(typeof(ConfigOption))]
  public IEnumerable<ConfigOption> AllConfig
  {
    get
    {
      return _allConfig;
    }
    set
    {
      if (_allConfig == null)
      {
         _allConfig = value;
      }
    }
  }

 Read all configuration item groups through the MEF importer and store them in the static variable _allConfig

     ///<summary>
       ///Initialize system parameter configuration information
       ///</summary>
        public void Init()
        {
           //All option values
            List<Options> listOption = ConfigService.GetAllOptions();

            ConfigDescription desc = null;
           //Code existing configuration items
            foreach (ConfigOption item in AllConfig)
            {
               //Reflective read configuration item ConfigTypeAttribute ConfigAttribute information
                desc = ConfigDescriptionCache.GetTypeDiscription(item.GetType());

               //Set the GroupType of the current configuration item
                desc.GroupTypePropertyInfo.SetValue(item, Convert.ChangeType(desc.Group, desc.GroupTypePropertyInfo.PropertyType), null);

               //Each value information
                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)
                    {
                       //Set the default value
                        prop.SetValue(null, Convert.ChangeType(ca.DefaultValue, prop.PropertyType), null);
                    }
                    else
                    {
                        prop.SetValue(null, Convert.ChangeType(op.Value, prop.PropertyType), null);
                    }
                }
            }
        }

 ConfigService.GetAllOptions() reads all option values ​​from the database, and reads the relevant values ​​of all static properties through ConfigDescriptionCache.GetTypeDiscription(item.GetType()) reflection

Mapping relationship between attribute type and foreground control

       ///<summary>
       ///Set the value type of the default item-convert according to the attribute type
       ///</summary>
       ///<param name="configAttr"></param>
       ///<param name="propertyType">Property type</param>
        private static void SetConfigValueType(ConfigAttribute configAttr,Type propertyType)
        {
            switch (propertyType.ToString()) {
                case "System.String":
                    configAttr.ValueType = ConfigValueType.String;//Text box
                    break;
                case "System.Boolean":
                    configAttr.ValueType = ConfigValueType.Bool;//corresponding to the front desk radio
                    break;
                case "System.Int32":
                case "System.Double":
                    configAttr.ValueType = ConfigValueType.Number;//corresponding value input box
                    break;
                default:
                    configAttr.ValueType = ConfigValueType.String;
                    break;
            }
        }

The type of password can be specified by yourself

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

Provided interface for obtaining configuration items

       ///<summary>
       ///Get all configuration information
       ///</summary>
       ///<returns>All configuration information</returns>
        public List<OptionViewModel> GetAllOption(string GroupType = "")
       ///<summary>
       ///Get the configuration information of the specified item
       ///</summary>
       ///<param name="GroupType">Group item</param>
       ///<returns>All configuration information</returns>
        public OptionViewModel GetOptionByGroup(string GroupType)
       ///<summary>
       ///Get the configuration information of the specified item
       ///</summary>
       ///<param name="GroupType">Group item</param>
       ///<returns>All configuration information</returns>
        public Options GetOptionByGroupAndKey(string GroupType, string key){}

Save method implementation

       ///<summary>
       ///Save configuration information
       ///</summary>
       ///<param name="value">Configuration information</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 = "Parameter null exception occurred when saving parameter configuration";
                return result;
            }
           //Process event before calling save
            ConfigOption curConfigOption = AllConfig.FirstOrDefault(e => e.GroupType.Equals(GroupType, StringComparison.OrdinalIgnoreCase));
            if (curConfigOption == null)
            {
               //If no match is found
                result.Message = string.Format("The currently saved configuration information {0} does not correspond to the background task configuration class", GroupType);
                return result;
            }//Call the pre-save verification event of the configuration item
            if (!curConfigOption.BeforeSave(value))
            {
                result.Message = "The current configuration item is not allowed to be saved";
                return result;
            }

           //save data

            try
            {
                using (CommonDbContext cdb = new CommonDbContext())
                {
                    var dbSet = cdb.Set<Options>();
                    var delObjs=dbSet.Where(e => e.OptionType == GroupType).ToList();
                   //Delete the original data
                    foreach (var item in delObjs)
                    {
                        cdb.Entry(item).State = EntityState.Deleted;
                    }
                   //save data
                    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;
            }

           //Assign current configuration items
            SetValue(curConfigOption, value.ListOptions);

            result.HasError = false;
            return result;
        }

       ///<summary>
       ///Assign values ​​to the current configuration items when saving
       ///</summary>
       ///<param name="item">Current configuration item</param>
       ///<param name="ListOptions">Configuration item value</param>
        public void SetValue(ConfigOption item, List<Options> ListOptions)
        {
           //Process event after calling save
            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)
                {
                   //If the configuration item does not exist, clear the current value
                    prop.SetValue(null, Convert.ChangeType(null, prop.PropertyType), null);
                }
                else
                {
                    prop.SetValue(null, Convert.ChangeType(option.Value, prop.PropertyType), null);
                }
            }
        }

Front-end rendering logic (config.js)

  //Initialization data
    initData = function () {
        $.ajax({
            type: "get",
            url: "/Config/GetAllOption",//Call the background to get all configuration items interface
            dataType: "json",
            beforeSend: function () {
               //Load waiting layer
                index = layer.load(0);
            },
            complete: function () {
                layer.close(index);
            },
            success: function (data) {
                BaseData = data;
                drawConfig(BaseData);//Drawing interface
            }
        });
    }

back to the top

summary

      The parameter configuration can be easily transplanted to your own system. The parameter configuration function used in the TaskManagerV2.0 blog is the code of the system directly transplanted. In addition, remember to modify the database connection string in Web.config when using it.

  Source code download address: http://files.cnblogs.com/files/yanweidie/CommonParamConfig.rar ,

Reference: https://cloud.tencent.com/developer/article/1014513 Use MEF to achieve general parameter settings-Cloud + Community-Tencent Cloud