我正在构建一个可扩展的框架,以基于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()
的形式实现此功能,但它似乎无法工作......我认为这可能与我的问题有关。< / p>
接下来,我在同一个程序集中创建了一个抽象类:
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 :(得分: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)以确保。
答案 1 :(得分:0)
Per @HOKBONG,答案here与我正在做的完全相同,所提议的解决方案就像魅力一样。