2014-02-13 198 views
0

我正在構建一個可擴展框架,以基於基於MVC4 WebAPI系統的基於客戶端的業務邏輯和數據結構與我們的通用基礎架構分離。無法實例化接口的實現

爲此,我創建了一個接口IApiServiceEntryPoint像這樣:

public interface IApiServiceEntryPoint : IDisposable 
{ 
    /// <summary> 
    /// Gets the name of the API Plugin 
    /// </summary> 
    string Name { get; } 

    /// <summary> 
    /// Registers the assembly in the application, 
    /// sets up the routes, and enables invocation of API requests 
    /// </summary> 
    void Register(RouteCollection routes); 

    /// <summary> 
    /// Gets the routing namespace of the plugin 
    /// </summary> 
    string UrlNameSpace { get; } 
} 

然後,在同一個組件中,我創建了一個PluginHelper類,如下所示:

public static class PluginHelper 
{ 
    private static readonly List<IApiServiceEntryPoint> _plugins = new List<IApiServiceEntryPoint>(); 
    public static List<IApiServiceEntryPoint> Plugins { get { return _plugins; } } 

    private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(PluginHelper)); 
    /// <summary> 
    /// Registers all IApiServiceEntryPoint plugin classes. 
    /// </summary> 
    /// <param name="pluginPath">The directory where the plugin assemblies are stored.</param> 
    public static void Register(string pluginPath, RouteCollection routes) 
    { 
     Logger.InfoFormat("Registering plugins found at \"{0}\"...", pluginPath); 
     foreach (var plugin in _plugins) 
     { 
      Logger.DebugFormat("Disposing plugin {0}...", plugin.Name); 
      plugin.Dispose(); 
     } 

     Logger.DebugFormat("Clearing the plugin cache..."); 
     _plugins.Clear(); 

     var libraryFiles = System.IO.Directory.GetFiles(pluginPath, "*.*") 
      .Where(fn => fn.ToLowerInvariant().EndsWith(".dll") 
       || fn.ToLowerInvariant().EndsWith(".exe")) 
      .ToList(); 
     Logger.DebugFormat("Found {0} assemblies in the plugin directory...", libraryFiles.Count); 

     var assemblies = libraryFiles.Select(lf => Assembly.LoadFrom(lf)) 
      .ToList(); 
     Logger.DebugFormat("Loaded {0} assemblies into memory: {1}", assemblies.Count, string.Join(", ", assemblies.Select(a=>a.FullName).ToArray())); 

     var pluginTypes = assemblies.Where(assy => assy != null) 
      .SelectMany(assy => assy.GetTypes()) 
      .Where(t => !t.IsInterface && !t.IsAbstract && t.Namespace != null) 
      .ToList(); 
     Logger.DebugFormat("Located a total of {0} classes.", pluginTypes.Count); 

     pluginTypes = pluginTypes.Where(t => t.IsTypeOf<IApiServiceEntryPoint>()) 
      .ToList(); 
     Logger.DebugFormat("Located a total of {0} plugin entry points.", pluginTypes.Count); 

     foreach (var type in pluginTypes) 
     { 
      Logger.DebugFormat("Registering plugin type '{0}'...", type.Name); 
      var plugin = (IApiServiceEntryPoint)Activator.CreateInstance(type); 
      Logger.InfoFormat("Registering plugin \"{0}\"...", plugin.Name); 
      plugin.Register(routes); 
      Logger.InfoFormat("Plugin \"{0}\" Registered.", plugin.Name); 
      _plugins.Add(plugin); 
     } 

     Logger.InfoFormat("All {0} plugin(s) have been registered.", Plugins.Count); 
    } 

    public static bool IsTypeOf<T>(this Type type) 
    { 
     return type.GetInterfaces().Any(t =>t.Name == typeof(T).Name); 
    } 
} 

注擴展方法IsTypeOf() ...我原本試圖使用IsAssignableFrom()的形式實現這一點,但它似乎從來沒有工作......我認爲這可能與我的問題有關。

接下來,我在同一個部件中創建一個抽象類:

public abstract class ApiPlugin : IApiServiceEntryPoint, IAccessControl 
{ 
    private static readonly ILog Logger = log4net.LogManager.GetLogger(typeof(ApiPlugin)); 
    public abstract string Name { get; } 

    public virtual void Register(RouteCollection routes) 
    { 
     var rt = string.Format("{0}/{{controller}}/{{id}}", UrlNameSpace); 
     var nameSpace = this.GetType().Namespace; 
     Logger.DebugFormat("Route Template: {0} in namespace {1}...", rt, nameSpace); 
     var r = routes.MapHttpRoute(
      name: Name, 
      routeTemplate: rt, 
      defaults: new { id = RouteParameter.Optional, controller = "Default" } 
      ); 
     r.DataTokens["Namespaces"] = new[] { nameSpace }; 

     Logger.InfoFormat("Plugin '{0}' registered namespace '{1}'.", Name, nameSpace); 
    } 

    public abstract string UrlNameSpace { get; } 

    public bool IsAuthorized<T>(Func<T> method) 
    { 
     var methodName = method.Method.Name; 
     var userName = User.Identity.Name; 
     return 
      ValidateAccess(userName, methodName) && 
      ValidateLicense(userName, methodName); 
    } 

    protected virtual bool ValidateAccess(string userName, string methodName) 
    { 
     // the default behavior to allow access to the method. 
     return true; 
    } 

    protected virtual bool ValidateLicense(string userName, string methodName) 
    { 
     // the default behavior is to assume the user is licensed. 
     return true; 
    } 

    public abstract IPrincipal User { get; } 

    public abstract void Dispose(); 

    public virtual bool ClientAuthorized(object clientId) 
    { 
     return true; 
    } 
} 

到目前爲止,一切都順順當當的工作。現在在自己的程序集中編寫我的第一個插件。我把它很簡單:

public class DefaultPlugin : ApiPlugin 
{ 
    private static readonly ILog Logger = log4net.LogManager.GetLogger(typeof(DefaultPlugin)); 

    [HttpGet] 
    public DateTime GetSystemTimeStamp() 
    { 
     if (IsAuthorized(GetSystemTimeStamp)) 
     { 
      return DateTime.UtcNow; 
     } 
     throw new AuthorizationException(); 
    } 

    public override string Name 
    { 
     get { return "Default API Controller"; } 
    } 


    public override string UrlNameSpace 
    { 
     get { return "Default"; } 
    } 

    public override System.Security.Principal.IPrincipal User 
    { 
     get { return new GenericPrincipal(new GenericIdentity("Unauthenticated User"), new[] { "None" }); } 
    } 

    public override void Dispose() 
    { 
     //TODO: Unregister the plugin. 
    } 
} 

我建這個,我在我的插件註冊的引用調用這個插件的二進制文件目錄在我的MVC項目。

PluginHelper.Register()方法被調用時,我發現插件類,但在下面一行:

var plugin = (IApiServiceEntryPoint)Activator.CreateInstance(type); 

我結束了以下InvalidCastException的被拋出:

Unable to cast object of type 'myPluginNameSpace.DefaultPlugin' to type 'myInterfaceNamespace.IApiServiceEntryPoint' 

這裏的東西:它絕對是該接口的實現。

現在我已經做過這種插件的事情,所以我知道它可以工作,但對於我的生活,我無法弄清楚我做錯了什麼。我期望它與特定的構建/版本或者強大的命名有關?請指教。

+0

我還沒有重新創建您的問題,但請看看這個鏈接是否可以幫助您: http://stackoverflow.com/questions/1596796/net-unable-to-cast-object-to-interface-it-implements – HOKBONG

+0

@HOKBONG這個鏈接肯定有幫助。我也在做同樣的事情。 –

+0

@HOKBONG如果你想寫一個小小的摘要,我會將它標記爲答案。 –

回答

0

Per @HOKBONG,答案here與我所做的完全相同,所提議的解決方案像一個魅力一樣工作。

0

嘗試

public static bool IsTypeOf<T>(this Type type) 
{ 
    return typeof(T).IsAssignableFrom(type); 
} 

public static bool IsTypeOf<T>(this Type type) 
{ 
    return type.GetInterfaces().Any(t => t.FullName == typeof(T).FullName); 
} 

此外,試圖拋出一個Debug.Assert的(typeof運算(T).IsInterface)只是要確定。

+0

這幾乎是我開始的,它沒有任何明顯的影響。 –

+0

你有PostSharp或任何重寫IL?我意識到這是多餘的,但是如果明確地添加接口會發生什麼。ex public class DefaultPlugin:ApiPlugin,IApiServiceEntryPoint?在您的項目中,您沒有任何機會在事故中定義了2個ApiPlugin或IApiServiceEntryPoint(錯誤使用語句)。否則,你的PluginHelper代碼看起來很好。它顯示「共有1個插件入口點。」?嘗試使用反射來執行構造函數而不是Activator.CreateNewInstance()和/或記錄插件類型的接口。如果一切都失敗,請考慮嘗試MEF – scottt732