基于.net的可扩展框架设计 - AppEx及相关接口

这篇文章主要介绍整个框架中都在使用的接口,虽然设计目的是非侵入式的,但是多少还是有些接口要继承的。


先说PluginInfo,这个类基本上只是用来记录插件信息的,需要说明的也就是ConfigFilePath,这个属性对应着该插件对应的配置文件,配置文件是简单的键值对,在启动时会将该配置读出,并复制给插件的Config属性。

IPlugin接口,所有插件都要继承该接口,在初始化加载时,系统会扫描该类型,并初始化它,然后调用Initialize方法,其他方法看名字应该也差不多可以猜出含义来了。

关于其中的Message的方法将会在后面的Message中说明。


IApp接口是Appex的接口,该接口主要是Service容器,其中的Config为AppSettings的内容,LoginWindow与MainWindow是登录界面与主界面,如果系统中查找到了UIPlugin并在其中找到了登录与主界面,则会在启动时,展示登录界面(为了节省登录时间,在登录界面展示的同时,系统依然在加载剩余的DLL,一般在用户输入完用户名与密码后,差不多能够加载完成)。


 * 根据上图可以看出,所有的UI控件都继承自IControl,这里所谓Control其大小大与WinForm的UserControl相似,并不是指一个单独的控件。

如果用户按下的取消键后,系统会放弃此次启动,在加载完成所有控件后,直接调用CloseSystem方法。

如果用户按下确认按钮,则会调用 权限验证模块 进行权限验证。

验证通过并且系统全部加载完成后显示主界面。

IApp中的GotoLogin与GotoMainForm主要作用就是显示相关的界面。

如果IApp中的ShowUi为False的话,不会显示任何界面,一般用于服务的启动。



上图是AppEx(IApp的实现)的使用到的相关的类,其中MethodInfoHelper的方法只有一个,就是获取当前调用方法的名称:

            // 获取调用堆栈
            var st = new StackTrace(true);
            // 上移两帧
            StackFrame frame = st.GetFrame(frameCount);
            // 获取调用函数信息
            methodInfo = frame.GetMethod();

启动的FrameCount是上移帧数,0帧为其自身,1帧为调用该方法的方法,2帧一般是我们需要的,该方法可以用在实体类的属性变更时获取属性名称,好处是在属性名称变更时,就不需要再修改PropertyChange函数的参数。(在.net 4.5中可以使用默认参数实现该功能。)

ServiceProvider类将根据PluginInfo获取IPlugin实例,同时也用于加载Assembly,具体的方法参见反射就可以了。

        /// 
        /// 创建插件
        /// 
        /// 插件类型
        /// 插件信息
        /// 当返回类型为接口时,使用该类型实例化
        /// 插件
        public static T CreatePlugin(PluginInfo pluginInfo, String classType = null)
        {
            // 加载引用Dll
            foreach (AssemblyName referenceDll in pluginInfo.ReferenceDlls)
            {
                // LoadAssembly(referenceDll.FullName);
            }


            // 加载项目Dll
            Assembly assembly = LoadAssembly(pluginInfo.DllPath);
            if (assembly == null) throw new FileLoadException();
            Type type = typeof(T);
            string typeName = type.IsInterface ? String.IsNullOrEmpty(classType) ? pluginInfo.TypeName : classType : type.FullName;
            if (String.IsNullOrEmpty(typeName)) throw new TypeLoadException("Can not fund FullName at this type.");
            Type typeFromAss = assembly.GetType(typeName);
            if (typeFromAss == null) throw new TypeLoadException("Can not fund type at this assembly.");
            return (T)Activator.CreateInstance(typeFromAss);
        }
        /// 
        /// 加载DLL
        /// 
        /// Dll路径
        /// 加载结果
        public static Assembly LoadAssembly(string dllPath)
        {
            if (String.IsNullOrEmpty(dllPath)) return null;


            try
            {
                string dllName = Path.GetFileName(dllPath);
                if (LoadedFiles.Contains(dllName))
                    return
                        AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(
                            a => Path.GetFileName(a.Location) == dllName);


                LoadedFiles.Add(dllName);
                return Assembly.LoadFrom(dllPath);
            }
            catch (Exception)
            {
                return null;
            }
        }

ServiceInterceptor类是使用动态代理是需要提供的拦截器,关于动态代理会在下一篇文章中说明。

DynamicProxy是获取动态代理的一个包装类,同样也会在下一篇文章中一起说明。


在Appex的RegService中如果注册类型为IService类型,并且注册时,没有提供类型的情况下,会查找该类型的接口类型,例如 IServiceDemo:IService,当调用RegService(serviceDemo)时(serviceDemo为实现了IServiceDemo接口的类型的实例),注册的类型是IServiceDemo。

在GetService时,如果为客户端程序则会返回动态代理类型,如果是服务端,则会直接返回注册的实例。


AppEx总体来说还是比较简单的,其中用到了反射和动态代理,相信对于有过经验的设计人员对这两点应该也都很熟悉,动态代理是AOP的基础,反射更应该是家常便饭了。

因为个人能力有限,文章中应该有不少的问题,还请大家多多指正。


你可能感兴趣的:(设计,.net)