avalon

宠辱不惊,看庭前花开花落;去留无意,望天空云卷云舒

导航

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

Posted on 2012-07-03 16:09  expt  阅读(289)  评论(0)    收藏  举报

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


先说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,具体的方法参见反射就可以了。

 
  /// <summary> 
 /// 创建插件
 
/// </summary>
 
/// <typeparam name="T">插件类型</typeparam>
 
/// <param name="pluginInfo">插件信息</param>
 
/// <param name="classType">当返回类型为接口时,使用该类型实例化</param>
 
/// <returns>插件</returns>
 public static T CreatePlugin<T>(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);
 }

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

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

 /// <summary>
 
/// 加载DLL
 
/// </summary>
 
/// <param name="dllPath">Dll路径</param>
 
/// <returns>加载结果</returns>
 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;
   }
 }

 

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

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


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

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