NET插件系统之四——提升系统搜索插件和启动速度的思考

简介:

一. 面临的问题

  开发插件系统的主要优势是扩展性,我们不需要为系统模块的集成再多费脑筋,但这也带来了额外的问题。通常,系统需要在每次启动时搜索固定目录下的符合要求的插件。但是,当系统变得越来越庞大,所引用的dll文件越来越多时,就会出现很严重的问题:开启时间慢,性能差,用户体验降低,尤其是在调试程序时,会浪费大量宝贵的时间。

  我确确实实的面临了这样的问题,有兴趣的读者可以看看我的插件系列文章的前几篇,这两天痛定思痛,决心提升系统搜索插件的性能。

  我们先看一段普通的搜索插件的代码:

   

复制代码
 1  public void GetAllPluginInPath(string Path, string InterFaceName)
 2         {
 3             var DllFileName = from file in Directory.GetFileSystemEntries(Path)
 4                               where file.Contains(".dll")
 5                               select file;
 6 
 7 
 8 
 9             //string[] DllFileName = Directory.GetFileSystemEntries(Path);
10             Type[] types;
11             foreach (string file in DllFileName)
12             {
13 
14                 if (System.IO.Path.GetExtension(file) == ".dll")
15                 {
16                     Assembly assembly;
17 
18                     try
19                     {
20                         assembly = Assembly.LoadFrom(file);
21                     }
22                     catch
23                     {
24                         continue;
25                     }
26 
27                     try
28                     {
29                         types = assembly.GetTypes();
30                     }
31                     catch (Exception ex)
32                     {
33                         continue;
34                     }
35 
36                     foreach (Type type in types)
37                     {
38                         if (type.GetInterface(InterFaceName) != null && !type.IsAbstract)
39                         {
40                             object thisObject = Activator.CreateInstance(type);
41 
42 
43                             IXPlugin rc1 = thisObject as IXPlugin;
44 
45 
46                             //如果要在启动时被加载
47                             if (rc1 != null && rc1.isStartLoaded)
48                             {
49                                 AddPlugin(rc1);
50                             }
51                         }
52                     }
53                 }
54             }
55         }
复制代码

 

  造成启动慢的主要原因有:

  1. 目录下包含大量dll文件(这是因为项目引用了大量第三方库),它们并不包含我们开发的组件,却白白浪费大量搜索时间。有些dll文件不是托管dll,在获取程序集时还会抛出异常,直接捕获后,也会造成时间损失。

  2. 上述代码仅搜索了满足一种接口规范的插件, (见函数的参数InterFaceName)。如果不止一种插件类型,那么可能要做很多次这样的查找,对性能的影响更大。

      3. 为了获取插件的一些信息(比如是否要在启动时加载),不得不实例化其对象获取字段,这种性能损失也是不能承受的。

二. 解决途径

     找到问题,我们对症下药:

  1.成熟的软件系统采用了插件树的机制,将插件存储为树结构,包含父子关系,这样能尽可能的提升搜索和加载性能,同时方便管理,比如Ecilpse。 但是,这种复杂的插件管理机制可能不适用于我们开发的轻量级系统,因此我们仅仅考虑扁平化的插件结构。

  2. 虽然插件的数量是经常变化的,但通常加载的dll文件种类很少变化。我们可以考虑把实际包含所需插件的dll文件名列表存储起来,从而在搜索时仅搜索这些固定的dll文件,提升性能。

  3. 插件的种类可能多种多样,所以我们希望能一次性获得全部类型的插件。

  4. 采用.NET4.0的并行库机制实现插件的并行搜索。

三. 插件结构表述

  该部分已经在我的插件系列文章的.NET插件系统之二——不实例化获取插件信息和可视化方法 中进行了描述,主要是标记接口名称的InterfaceAttribute 和 标记实现接口的插件的XFrmWorkAttribute,你需要在插件接口和插件实现的类上添加这两类标识,此处不再赘述。

  我们定义两个数据结构存储插件名称和插件信息:

    

复制代码
   /// <summary>
    /// 为了便于序列化而简化的插件接口数据类型,是简化的InterfaceAttribute
    /// </summary>
    [Serializable]
 
  public  class PluginNameLite
    {
        public string myName { get; set; }
        public SearchStrategy mySearchStrategy { get; set; }
        public string detailInfo { get; set; }

        public PluginNameLite()
        {
        }
        public PluginNameLite(InterfaceAttribute attr)
        {
            myName = attr.myName;
            mySearchStrategy = attr.mySearchStrategy;
            detailInfo = attr.DetailInfo;
        }

    }

  /// <summary>
    /// 插件集合
    /// </summary>
    public class PluginCollection : ObservableCollection<XFrmWorkAttribute>
    {
        public PluginCollection()
            : base()
        {

        }



  
/// <summary>
        /// 可以被序列化的简化插件字典,仅包含插件接口名称和搜索策略
        /// </summary>
        static List<PluginNameLite> myPluginNameList = new List<PluginNameLite>();

        /// <summary>
        /// 插件字典
        /// </summary>
        static Dictionary<Type, PluginCollection> mPluginDictionary = new Dictionary<Type, PluginCollection>();
复制代码

 

四. 插件搜索的方法

  我们将插件搜索的步骤分为两步:

  1. 搜索所有接口契约(即搜索所有的接口)

  

复制代码
    /// <summary>
        /// 获取所有的插件接口契约名称
        /// </summary>
        /// <param name="Path"></param>
        /// <param name="InterFaceName"></param>
        public static void GetAllPluginName(string folderLocation, bool isRecursiveDirectory)
        {


            List<PluginNameLite> mPluginNameList = new List<PluginNameLite>();  //缓存所有插件名称

            if (!isRecursiveDirectory)  
            {
                try  //如果不执行递归搜索,则查看在目录下是否有保存了插件名称的文件,若有,直接反序列化之,不执行插件名称搜索
                {  
                    mPluginNameList = CustomSerializer.Deserialize<List<PluginNameLite>>(folderLocation + "\\PluginLog.xml");  
                    myPluginNameList.AddRange(mPluginNameList);
                    return;
                }
                catch (Exception ex)
                {

                }
            }
            var DllFile = from file in Directory.GetFileSystemEntries(folderLocation)  //若无缓存文件,获取目录下全部的dll文件执行搜索
                          where file.Contains(".dll")
                          select file;



            Parallel.ForEach(DllFile,   //并行化处理
                 file =>
                 {
                     Type[] types;
                     Assembly assembly;
                     try
                     {
                         assembly = Assembly.LoadFrom(file);

                     }
                     catch
                     {
                         return;
                     }

                     try
                     {
                         types = assembly.GetTypes();
                     }
                     catch
                     {
                         return;
                     }
                     foreach (Type type in types)
                     {

                         if (type.IsInterface == false)
                             continue;
                         // Iterate through all the Attributes for each method.
                         foreach (Attribute attr in
                             type.GetCustomAttributes(typeof(InterfaceAttribute), false))
                         {
                             mPluginNameList.Add(new PluginNameLite(attr as InterfaceAttribute));
                         }
                     }
                 });
            if (isRecursiveDirectory)      ////执行递归搜索
            {
                foreach (var dir in Directory.GetDirectories(folderLocation))
                {
                    GetAllPluginName(dir, isRecursiveDirectory);
                }
            }
            else   //保存当前目录下的插件名称
            {


                CustomSerializer.Serialize(mPluginNameList, folderLocation + "\\PluginLog.xml");
                myPluginNameList.AddRange(mPluginNameList);
            }

        }
复制代码

    流程图如下:

 

  2. 搜索所有实现接口契约的插件

   直接用代码说话

复制代码
        /// <summary>
        /// 获取所有插件
        /// </summary>
        /// <param name="folderLocation"></param>
        /// <param name="isRecursiveDirectory">是否进行目录递归搜索</param>
        public static void GetAllPlugins(string folderLocation, bool isRecursiveDirectory)
        {
            bool isSaved = false;  //是否已经保存了包含插件的dll文件列表
            List<string> mPluginFileList = new List<string>();  //包含插件的dll文件列表
            List<string> allDllFileList = new List<string>();    //所有dll文件列表
            if (!isRecursiveDirectory)
            {
                try
                {
                    mPluginFileList = CustomSerializer.Deserialize<List<string>>(folderLocation + "\\PluginFileLog.xml");
                    isSaved = true;

                }
                catch (Exception ex)
                {
                    allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)
                                      where file.Contains(".dll")
                                      select file).ToList();
                }
            }
            else
            {
                allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)
                                  where file.Contains(".dll")
                                  select file).ToList(); ;
            }
            Type[] types;
            IEnumerable<string> dllPluginFils;  //最终要进行处理的的dll文件
            if (mPluginFileList.Count == 0)       //如果不存在插件文件目录,则获取该目录下所有dll文件
                dllPluginFils = allDllFileList;
            else
                dllPluginFils = from file in mPluginFileList
                                select folderLocation + file;   //否则将插件文件名称拼接为完整的文件路径

            Parallel.ForEach(dllPluginFils,
                file =>
                {

                    Assembly assembly;
                    try
                    {
                        assembly = Assembly.LoadFrom(file);
                    }
                    catch
                    {
                        return;
                    }

                    try
                    {
                        types = assembly.GetTypes();
                    }
                    catch
                    {
                        return;
                    }
                    foreach (Type type in types)
                    {
                        if (type.IsInterface == true)
                            continue;
                        Type interfaceType = null;
                        string interfaceName = null;
                        foreach (var interfacename in myPluginNameList)   //对该Type,依次查看是否实现了插件名称列表中的接口
                        {
                            interfaceType = type.GetInterface(interfacename.myName);
                            if (interfaceType != null)
                            {
                                interfaceName = interfacename.myName;
                                // Iterate through all the Attributes for each method.
                                foreach (Attribute attr in
                                    type.GetCustomAttributes(typeof(XFrmWorkAttribute), false))  //获取该插件的XFrmWorkAttribute标识
                                {
                                    XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;

                                    attr2.myType = type;  //将其类型赋值给XFrmWorkAttribute


                                    if (attr2.MainKind != interfaceName)
                                    {
                                        continue;
                                    }

                                    PluginCollection pluginInfo = null;   //保存到插件字典当中
                                    if (mPluginDictionary.TryGetValue(interfaceType, out pluginInfo))
                                    {
                                        pluginInfo.Add(attr2);       //若插件字典中已包含了该interfaceType的键,则直接添加

                                    }
                                    else
                                    {
                                        var collection = new PluginCollection();
                                        collection.Add(attr2);
                                        mPluginDictionary.Add(interfaceType, collection);    //否则新建一项并添加之

                                    }
                                    file = file.Replace(folderLocation, "");  //获取文件在该文件夹下的真实名称
                                    if (!mPluginFileList.Contains(file))    //若插件文件列表中不包含此文件则添加到文件目录中
                                        mPluginFileList.Add(file);
                                    goto FINISH;


                                }

                            }

                        }
                    FINISH:
                        ;
                    }
                });


            if (isRecursiveDirectory)  //执行递归搜索
            {
                foreach (var dir in Directory.GetDirectories(folderLocation))
                {

                    GetAllPlugins(dir, isRecursiveDirectory);
                }
            }
            else
            {
                if (!isSaved)   //若没有保存插件文件目录,则反序列化保存之。
                    CustomSerializer.Serialize(mPluginFileList, folderLocation + "\\PluginFileLog.xml");

            }

        }
复制代码

    由于篇幅有限,搜索插件的流程与搜索插件名称的流程基本相同,因此省略流程图。
3. 并行化优化

  读者可以看到,在搜索不同dll文件的插件时 ,使用了 Parallel.ForEach ,网上介绍该方法的文章很多,此处不再赘述。 同时,系统直接将所有插件一次性的搜索完成。大幅度的提升了搜索速度。

    

五. 总结和问题

  总结:

  1. 系统尽可能的减少了对插件本身的限制,通过添加attribute的方式即可标记插件,减少了对原生代码的修改。

  2. 实测表明,文件目录下存在60个左右的dll文件,其中只有6个是作者完成的包含插件的文件, 在I7 2600K的电脑上:(Debug版本)

    原始版本的插件搜索和实例化需要将近5s的启动时间  

          通过缓存文件目录和插件目录,时间减少2.7s

      通过并行化搜索dll文件下的插件,时间进一步减少1s

        最终,启动时间仅仅为1.3s左右,同时还避免了多次搜索插件

     存在的问题:

  1. 插件系统可以自动检测出同一dll文件中插件的变化,但在缓存了dll文件名之后,是无法自动检测出dll文件的变化的。这种情况下,需要首先删除记录插件名称和文件的缓存xml文件才能检测出来。

  2. 依然有一定的性能提升的余地。   

  以下是我的插件搜索器的完整代码,欢迎有问题随时交流:

   

完整的插件搜索器代码
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace XFrmWork.Data
{

    /// <summary>
    /// 插件集合
    /// </summary>
    public class PluginCollection : ObservableCollection<XFrmWorkAttribute>
    {
        public PluginCollection()
            : base()
        {

        }




    }


    /// <summary>
    /// 执行搜索策略
    /// </summary>
    public enum SearchStrategy
    {

        /// <summary>
        /// 目录内搜索
        /// </summary>
        FolderSearch,
        /// <summary>
        /// 目录内递归搜索
        /// </summary>
        RecursiveFolderSearch,
    }
    /// <summary>
    /// 该类定义了插件系统的接口契约记录
    /// </summary>

    public class InterfaceAttribute : Attribute
    {
        /// <summary>
        /// 该插件接口的名称
        /// </summary>
        public string myName { get; set; }

        /// <summary>
        /// 搜索策略
        /// </summary>
        public SearchStrategy mySearchStrategy { get; set; }


        /// <summary>
        /// 相关信息
        /// </summary>
        public string DetailInfo { get; set; }


        public InterfaceAttribute(string thisName, string thisDetailInfo, SearchStrategy thisSearchStrategy)
        // 定位参数
        {

            this.myName = thisName;
            this.DetailInfo = thisDetailInfo;
            this.mySearchStrategy = thisSearchStrategy;

        }
    }

    /// <summary>
    /// 为了便于序列化而简化的插件接口数据类型,是简化的InterfaceAttribute
    /// </summary>
    [Serializable]

    public class PluginNameLite
    {
        public string myName { get; set; }
        public SearchStrategy mySearchStrategy { get; set; }
        public string detailInfo { get; set; }

        public PluginNameLite()
        {
        }
        public PluginNameLite(InterfaceAttribute attr)
        {
            myName = attr.myName;
            mySearchStrategy = attr.mySearchStrategy;
            detailInfo = attr.DetailInfo;
        }




    }
    /// <summary>
    /// 单例模式提供的插件搜索器
    /// </summary>
    public class PluginProvider
    {


        PluginProvider()
        {


        }
        /// <summary>
        /// 可以被序列化的简化插件字典,仅包含插件接口名称和搜索策略
        /// </summary>
        static List<PluginNameLite> myPluginNameList = new List<PluginNameLite>();

        /// <summary>
        /// 插件字典
        /// </summary>
        static Dictionary<Type, PluginCollection> mPluginDictionary = new Dictionary<Type, PluginCollection>();

        /// <summary>
        /// 获取某插件在插件目录中的索引号
        /// </summary>
        /// <param name="interfaceName">接口名称</param>
        /// <param name="className">类名</param>
        /// <returns></returns>
        public static int GetObjectIndex(Type interfaceName, Type className)
        {
            foreach (var rc in GetPluginCollection(interfaceName))
            {
                if (rc.myType == className)
                    return GetPluginCollection(interfaceName).IndexOf(rc);
            }
            return 100;
        }



        /// <summary>
        /// 获取所有的插件接口契约名称
        /// </summary>
        /// <param name="Path"></param>
        /// <param name="InterFaceName"></param>
        public static void GetAllPluginName(string folderLocation, bool isRecursiveDirectory)
        {


            List<PluginNameLite> mPluginNameList = new List<PluginNameLite>();  //缓存所有插件名称

            if (!isRecursiveDirectory)
            {
                try  //如果不执行递归搜索,则查看在目录下是否有保存了插件名称的文件,若有,直接反序列化之,不执行插件名称搜索
                {
                    mPluginNameList = CustomSerializer.Deserialize<List<PluginNameLite>>(folderLocation + "\\PluginLog.xml");
                    myPluginNameList.AddRange(mPluginNameList);
                    return;
                }
                catch (Exception ex)
                {

                }
            }
            var DllFile = from file in Directory.GetFileSystemEntries(folderLocation)  //若无缓存文件,获取目录下全部的dll文件执行搜索
                          where file.Contains(".dll")
                          select file;



            Parallel.ForEach(DllFile,   //并行化处理
                 file =>
                 {
                     Type[] types;
                     Assembly assembly;
                     try
                     {
                         assembly = Assembly.LoadFrom(file);

                     }
                     catch
                     {
                         return;
                     }

                     try
                     {
                         types = assembly.GetTypes();
                     }
                     catch
                     {
                         return;
                     }
                     foreach (Type type in types)
                     {

                         if (type.IsInterface == false)
                             continue;
                         // Iterate through all the Attributes for each method.
                         foreach (Attribute attr in
                             type.GetCustomAttributes(typeof(InterfaceAttribute), false))
                         {
                             mPluginNameList.Add(new PluginNameLite(attr as InterfaceAttribute));
                         }
                     }
                 });
            if (isRecursiveDirectory)      ////执行递归搜索
            {
                foreach (var dir in Directory.GetDirectories(folderLocation))
                {
                    GetAllPluginName(dir, isRecursiveDirectory);
                }
            }
            else   //保存当前目录下的插件名称
            {


                CustomSerializer.Serialize(mPluginNameList, folderLocation + "\\PluginLog.xml");
                myPluginNameList.AddRange(mPluginNameList);
            }

        }





        /// <summary>
        /// 获取所有插件
        /// </summary>
        /// <param name="folderLocation"></param>
        /// <param name="isRecursiveDirectory">是否进行目录递归搜索</param>
        public static void GetAllPlugins(string folderLocation, bool isRecursiveDirectory)
        {
            bool isSaved = false;  //是否已经保存了包含插件的dll文件列表
            List<string> mPluginFileList = new List<string>();  //包含插件的dll文件列表
            List<string> allDllFileList = new List<string>();    //所有dll文件列表
            if (!isRecursiveDirectory)
            {
                try
                {
                    mPluginFileList = CustomSerializer.Deserialize<List<string>>(folderLocation + "\\PluginFileLog.xml");
                    isSaved = true;

                }
                catch (Exception ex)
                {
                    allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)
                                      where file.Contains(".dll")
                                      select file).ToList();
                }
            }
            else
            {
                allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)
                                  where file.Contains(".dll")
                                  select file).ToList(); ;
            }





            Type[] types;
            IEnumerable<string> dllPluginFils;  //最终要进行处理的的dll文件
            if (mPluginFileList.Count == 0)       //如果不存在插件文件目录,则获取该目录下所有dll文件
                dllPluginFils = allDllFileList;
            else
                dllPluginFils = from file in mPluginFileList
                                select folderLocation + file;   //否则将插件文件名称拼接为完整的文件路径

            Parallel.ForEach(dllPluginFils,
                file =>
                {

                    Assembly assembly;
                    try
                    {
                        assembly = Assembly.LoadFrom(file);
                    }
                    catch
                    {
                        return;
                    }

                    try
                    {
                        types = assembly.GetTypes();
                    }
                    catch
                    {
                        return;
                    }
                    foreach (Type type in types)
                    {
                        if (type.IsInterface == true)
                            continue;
                        Type interfaceType = null;
                        string interfaceName = null;
                        foreach (var interfacename in myPluginNameList)   //对该Type,依次查看是否实现了插件名称列表中的接口
                        {
                            interfaceType = type.GetInterface(interfacename.myName);
                            if (interfaceType != null)
                            {
                                interfaceName = interfacename.myName;
                                // Iterate through all the Attributes for each method.
                                foreach (Attribute attr in
                                    type.GetCustomAttributes(typeof(XFrmWorkAttribute), false))  //获取该插件的XFrmWorkAttribute标识
                                {
                                    XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;

                                    attr2.myType = type;  //将其类型赋值给XFrmWorkAttribute


                                    if (attr2.MainKind != interfaceName)
                                    {
                                        continue;
                                    }

                                    PluginCollection pluginInfo = null;   //保存到插件字典当中
                                    if (mPluginDictionary.TryGetValue(interfaceType, out pluginInfo))
                                    {
                                        pluginInfo.Add(attr2);       //若插件字典中已包含了该interfaceType的键,则直接添加

                                    }
                                    else
                                    {
                                        var collection = new PluginCollection();
                                        collection.Add(attr2);
                                        mPluginDictionary.Add(interfaceType, collection);    //否则新建一项并添加之

                                    }
                                    file = file.Replace(folderLocation, "");  //获取文件在该文件夹下的真实名称
                                    if (!mPluginFileList.Contains(file))    //若插件文件列表中不包含此文件则添加到文件目录中
                                        mPluginFileList.Add(file);
                                    goto FINISH;


                                }

                            }

                        }
                    FINISH:
                        ;





                    }
                });


            if (isRecursiveDirectory)  //执行递归搜索
            {
                foreach (var dir in Directory.GetDirectories(folderLocation))
                {

                    GetAllPlugins(dir, isRecursiveDirectory);
                }
            }
            else
            {
                if (!isSaved)   //若没有保存插件文件目录,则反序列化保存之。
                    CustomSerializer.Serialize(mPluginFileList, folderLocation + "\\PluginFileLog.xml");

            }

        }
        /// <summary>
        /// 获取接口中固定索引类型的实例
        /// </summary>
        /// <param name="interfaceName">接口名称</param>
        /// <param name="index">索引号</param>
        /// <returns>实例化的引用</returns>
        public static Object GetObjectInstance(Type interfaceName, int index)
        {
            return Activator.CreateInstance(GetPluginCollection(interfaceName)[index].myType);
        }
        /// <summary>
        /// 获取某程序集的接口列表
        /// </summary>
        /// <param name="interfaceName"></param>
        /// <param name="myAssembly"></param>
        /// <returns></returns>
        public static ObservableCollection<XFrmWorkAttribute> GetPluginCollection(Type interfaceName, Assembly myAssembly)
        {
            return GetPluginCollection(interfaceName, myAssembly, false);
        }

        public static ObservableCollection<XFrmWorkAttribute> GetPluginCollection(Type interfaceName, Assembly myAssembly, bool isAbstractRequired)
        {
            if (mPluginDictionary.ContainsKey(interfaceName))
                return mPluginDictionary[interfaceName];
            else  //若发现插件不存在,则再执行搜索(这种可能性很低)
            {

                PluginCollection tc = new PluginCollection();


                Type[] types = myAssembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.GetInterface(interfaceName.ToString()) != null)
                    {
                        if (!isAbstractRequired && type.IsAbstract == true)
                        {
                            continue;
                        }


                        // Iterate through all the Attributes for each method.
                        foreach (Attribute attr in
                            type.GetCustomAttributes(typeof(XFrmWorkAttribute), false))
                        {
                            XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;
                            tc.Add(attr2);
                        }
                    }
                }
                mPluginDictionary.Add(interfaceName, tc);
                return tc;
            }
        }

        public static ObservableCollection<XFrmWorkAttribute> GetPluginCollection(Type interfaceName)
        {
            if (mPluginDictionary.ContainsKey(interfaceName))
                return mPluginDictionary[interfaceName];
            else
            {
                PluginCollection tc = new PluginCollection();

                Assembly assembly = Assembly.GetAssembly(interfaceName);
                Type[] types = assembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.GetInterface(interfaceName.ToString()) != null && !type.IsAbstract)
                    {


                        // Iterate through all the Attributes for each method.
                        foreach (Attribute attr in
                            type.GetCustomAttributes(typeof(XFrmWorkAttribute), false))
                        {
                            XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;
                            tc.Add(attr2);
                        }
                    }
                }
                mPluginDictionary.Add(interfaceName, tc);
                return tc;
            }
        }
    }


}

 

  

  

  


作者:热情的沙漠
出处:http://www.cnblogs.com/buptzym/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

标签: .NET, 插件, 并行化

本文转自FerventDesert博客园博客,原文链接:http://www.cnblogs.com/buptzym/archive/2012/06/05/2534703.html,如需转载请自行联系原作者
目录
相关文章
|
3月前
|
前端开发 JavaScript 关系型数据库
使用 OpenAuth.Net 快速搭建 .NET 企业级权限工作流系统
使用 OpenAuth.Net 快速搭建 .NET 企业级权限工作流系统
105 0
|
6月前
|
SQL 小程序 API
如何运用C#.NET技术快速开发一套掌上医院系统?
本方案基于C#.NET技术快速构建掌上医院系统,结合模块化开发理念与医院信息化需求。核心功能涵盖用户端的预约挂号、在线问诊、报告查询等,以及管理端的排班管理和数据统计。采用.NET Core Web API与uni-app实现前后端分离,支持跨平台小程序开发。数据库选用SQL Server 2012,并通过读写分离与索引优化提升性能。部署方案包括Windows Server与负载均衡设计,确保高可用性。同时针对API差异、数据库老化及高并发等问题制定应对措施,保障系统稳定运行。推荐使用Postman、Redgate等工具辅助开发,提升效率与质量。
201 0
|
10月前
|
前端开发 C# 开发者
.NET使用Umbraco CMS快速构建一个属于自己的内容管理系统
.NET使用Umbraco CMS快速构建一个属于自己的内容管理系统
127 12
|
10月前
|
Web App开发 前端开发 调度
一款基于 .NET + Blazor 开发的智能访客管理系统
一款基于 .NET + Blazor 开发的智能访客管理系统
134 8
|
10月前
|
开发框架 JavaScript 前端开发
精选2款.NET开源的博客系统
精选2款.NET开源的博客系统
129 8
|
10月前
|
前端开发 JavaScript C#
基于.NET8+Vue3开发的权限管理&个人博客系统
基于.NET8+Vue3开发的权限管理&个人博客系统
133 7
|
11月前
|
开发框架 安全 Java
.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力
本文深入探讨了.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力。.NET不仅支持跨平台开发,具备出色的安全性和稳定性,还能与多种技术无缝集成,为企业级应用提供全面支持。
318 3
|
12月前
|
关系型数据库 C# 数据库
.NET 8.0 开源在线考试系统(支持移动端)
【10月更文挑战第27天】以下是适用于 .NET 8.0 的开源在线考试系统(支持移动端)的简介: 1. **基于 .NET Core**:跨平台,支持多种数据库,前后端分离,适用于多操作系统。 2. **结合 Blazor**:使用 C# 开发 Web 应用,支持响应式设计,优化移动端体验。 3. **基于 .NET MAUI**:跨平台移动应用开发,一套代码多平台运行,提高开发效率。 开发时需关注界面设计、安全性与稳定性。
287 4
|
JSON 安全 数据安全/隐私保护
从0到1搭建权限管理系统系列三 .net8 JWT创建Token并使用
【9月更文挑战第22天】在.NET 8中,从零开始搭建权限管理系统并使用JWT(JSON Web Tokens)创建Token是关键步骤。JWT是一种开放标准(RFC 7519),用于安全传输信息,由头部、载荷和签名三部分组成。首先需安装`Microsoft.AspNetCore.Authentication.JwtBearer`包,并在`Program.cs`中配置JWT服务。接着,创建一个静态方法`GenerateToken`生成包含用户名和角色的Token。最后,在控制器中使用`[Authorize]`属性验证和解析Token,从而实现身份验证和授权功能。
1001 4
|
12月前
|
Windows
.NET 隐藏/自定义windows系统光标
【10月更文挑战第20天】在.NET中,可以使用`Cursor`类来控制光标。要隐藏光标,可将光标设置为`Cursors.None`。此外,还可以通过从文件或资源加载自定义光标来更改光标的样式。例如,在表单加载时设置`this.Cursor = Cursors.None`隐藏光标,或使用`Cursor.FromFile`方法加载自定义光标文件,也可以将光标文件添加到项目资源中并通过资源管理器加载。这些方法适用于整个表单或特定控件。
153 0

热门文章

最新文章