Log4net - how to log calling method name in WCF - singleton

I have written a Singleton wrapper for log4net and it will be called across all layers of my WCF service. I am unable to log calling method name. Pls suggest if any inputs from my below code. Thanks.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Imports log4net
Imports System.Reflection
Public Class Logger
Implements ILog
Private Shared m_instance As Logger
Private Shared log As log4net.ILog = Nothing
Public Sub New()
If log Is Nothing Then
'log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType) ' -> This always logs 'Logger'
log = log4net.LogManager.GetLogger(Assembly.GetCallingAssembly(), "MyLogger") ' -> This always logs 'MyLogger'
End If
End Sub
Public Shared ReadOnly Property Write() As Logger
Get
m_instance = New Logger()
Return m_instance
End Get
End Property
Public Sub Debug(msg As String) Implements ILog.Debug
log.Debug(msg)
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Interface ILog
Sub Debug(msg As String)
End Interface
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
I have made it more simple as below to log method name. But I have to pass classname as parameter. Pls suggest is this acceptable design?
''''' Calling code ''''''''''''''''''''
Logger(of MyClassName).Debug("message")
''''''Log4net wrapper'''''''''''''''''''''
Imports log4net
Imports System.Reflection
Public NotInheritable Class Logger(Of T)
Private Shared log As log4net.ILog = Nothing
Private Shared ReadOnly Property LogProvider() As log4net.ILog
Get
If (log Is Nothing) Then
Dim CallingMethod As String = GetType(T).ToString() & "." & (New StackTrace()).GetFrame(2).GetMethod().Name
log = log4net.LogManager.GetLogger(CallingMethod)
End If
Return log
End Get
End Property
Public Shared Sub Debug(msg As String)
LogProvider.Debug(msg)
End Sub

A logging instance can only have one name, you can't change it. If you want to use a diffrent name in each method you need to make a logger for ech method.
log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType) ' -> This always logs 'Logger'
log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().Name) ' -> This gives you the name of the current method
log = log4net.LogManager.GetLogger(Assembly.GetCallingAssembly(), "MyLogger") ' -> This always logs 'MyLogger'
Creating the logger inside your method like:
ILog log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().Name) ' -> This gives you the name of the current method
You will have a logger with the name of the method, I would add the name of the class either:
ILog log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType + "." + MethodBase.GetCurrentMethod().Name) ' -> This gives you the name of the current method

Related

How to Access Public Properties from Global.asax

I am new to working with the global.asax file. I have an application where I need to set some application wide variables the values for which as sourced from the web.config file. The global asax seemed to be the logical place to hold this content. My global asax file is as follows:
Imports System.Web.Http
Imports System.Web.Optimization
Imports System.Net.Http.Formatting
Imports System.Net.Http.Headers
Imports utl = Common.Utilities
Public Class WebApiApplication
Inherits System.Web.HttpApplication
Private _SptZones As String
Private _spdZones As String
Private _spmZones As String
Sub Application_Start()
GlobalConfiguration.Configuration.Formatters.XmlFormatter.MediaTypeMappings.Add(New QueryStringMapping("xml", "true", "application/xml"))
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SupportedMediaTypes.Add(New MediaTypeHeaderValue("text/html"))
AreaRegistration.RegisterAllAreas()
GlobalConfiguration.Configure(AddressOf WebApiConfig.Register)
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
RouteConfig.RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
AppInitialise()
End Sub
''' <summary>
''' Initialise the application, load settings from web.config file
''' </summary>
Private Sub AppInitialise()
_SptZones = utl.GetSetting("SptZones")
_spdZones = utl.GetSetting("SpdZones")
_spmZones = utl.GetSetting("SpmZones")
End Sub
Public ReadOnly Property SptZones As String
Get
Return _SptZones
End Get
End Property
Public ReadOnly Property SpdZones As String
Get
Return _spdZones
End Get
End Property
Public ReadOnly Property SpmZones As String
Get
Return _spmZones
End Get
End Property
End Class
I then need to access the Properties in global.asax from the Controller as in:
If scadaSource.IndexOf("SPT") > -1 Then
'pseudo code
criteria &= global asax property SptZones
End If
Since these are not shared properties, they can be obtained from the current instance of HttpApplication
DirectCast(HttpContext.ApplicationInstance, WebApiApplication).SptZones

How correctly to access own Logger class

I got my own simple logger class in class library project which is used in many other projects belongs to my solution. Right now i am using it in this way in every class that consume it in top:
Private Logger As New Logger("C:\someLogFile.log")
then later in code i am using it:
Logger.LogIt("Start part extracting files...")
I always need in every class create instance of it and its boring sometimes... I know i could create static Logger class and overcome the issue but would it be correct way or the way i am doing is ok according to OOP? What do you think?
EDIT: What do you think about this solution which is using Interface i've just implemented it what do you think?:
First project (Logger project) which is used in many other projects around one solution:
Imports System.IO
Public NotInheritable Class Logger
#Region "Fields"
Private Shared ReadOnly _locker As New Object()
#End Region
#Region "Constructors"
Public Sub New()
End Sub
#End Region
#Region "Log function"
Public Sub LogIt(ByVal msg As String, ByVal logMessage As String, Optional Path As String = "", Optional ByVal IsDebug As Boolean = False)
If File.Exists(Path) Then
If IsDebug Then
Debug.Print(DateTime.Now & "> | " & msg & " | " & logMessage)
Else
Using w As TextWriter = File.AppendText(Path)
w.WriteLine(DateTime.Now & "> | " & msg & " | " & logMessage)
w.Flush()
End Using
End If
Else
If IsDebug Then
Debug.Print(DateTime.Now & "> | " & msg & " | " & logMessage)
Else
SyncLock _locker
Using w As TextWriter = File.CreateText(Path)
w.WriteLine(DateTime.Now & "> | " & msg & " | " & logMessage)
w.Flush()
End Using
End SyncLock
End If
End If
End Sub
#End Region
i added additionaly ILog.vb interface to this project:
Public Interface ILog
Property LoggerPath As String
Sub LogIt(ByVal msg As String, ByVal logMessage As String, ByVal Path As String, Optional ByVal IsDebug As Boolean = False)
End Interface
Now one of class which would use Logger for instance this below. (LoggerPath will come to constructor of MainProcessRunner from xml serialization on the beggining) e.g C:/file.txt:
Public Class MainProcessRunner
Implements ILog
Private Property LoggerPath As String Implements ILog.LoggerPath
Private CollectionList As New List(Of ImportRunner)
Public Sub New(ByVal LoggerPath As String)
Me.LoggerPath = LoggerPath
CollectionList.Add(New ImportRunner(GPCollectTimePeriod.EveryMidnight, KpiName.Availability, MobileGenerationName.GSM, Me))
For Each item In CollectionList
If TypeOf item Is ICollectPeriod Then
Dim runDaily As ICollectPeriod = TryCast(item, ICollectPeriod)
runDaily.RunDaily()
End If
Next
...
Public Sub LogIt(msg As String, logMessage As String, Path As String, Optional IsDebug As Boolean = False) Implements ILog.LogIt
Dim Logger As New Logger
Logger.LogIt(Alert.Write(MsgType.INFO), logMessage, Me.LoggerPath, True)
End Sub
End class
As you can see inside constructor i am calling other class by:
runDaily.RunDaily()
then inside below class ILog interface is passed by so therefore i will have log path already from MainProcessRunner and then i need to only fill msgtype and message string:
Imports ImportJob
Public Class ImportRunner
Implements ICollectPeriod
Private Log As ILog
Private BeginImport As New ImportStart
Private GPCollectTimePeriodInMinutes As GPCollectTimePeriod
Private KpiName As KpiName
Private MobileGeneration As MobileGenerationName
Public Sub New(ByVal GPCollectTimePeriodInMinutes As GPCollectTimePeriod, ByVal KpiName As KpiName, ByVal MobileGenerationName As MobileGenerationName, ByVal log As ILog)
Me.GPCollectTimePeriodInMinutes = GPCollectTimePeriodInMinutes
Me.KpiName = KpiName
Me.MobileGeneration = MobileGenerationName
Me.Log = log
End Sub
Public Sub RunDaily() Implements ICollectPeriod.RunDaily
Log.LogIt(Alert.Write(MsgType.INFO), "sdsd", Log.LoggerPath, True)
....
End Class
In this line Log.LoggerPath is known as (C:/file.txt:)
Log.LogIt(Alert.Write(MsgType.INFO), "sdsd", Log.LoggerPath, True)
From class ImpotStart i can pass through Log as ILog further to the next class constructor if needed if that class would implement ILog interface. and so on..
What do you think about that solution?
What you need is a design pattern "Singleton" which assures only a single instance (the singleton) of the class can be created:
using System;
namespace Singleton.Structural
{
class Singleton
{
private static Singleton _instance;
// Constructor is 'protected'
protected Singleton()
{
}
public static Singleton Instance()
{
// Uses lazy initialization.
// Note: this is not thread safe.
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
The OOP way would be to perform dependency injection to send the same instance of the Logger class into each class that requires it.
However, there is absolutely nothing wrong with just making your class static.
You are doing the right thing only. If you tired of calling object.Method name.
Put this (i.e object method ) in base class of you project with a Common Method and call that method in everypage.
Public void LogError(string error)
{
Logger.LogIt(error);
}
You can call the same by LogError in catch statement.
Better you read below link for better Logging method for you project
http://www.asp.net/web-forms/overview/getting-started/getting-started-with-aspnet-45-web-forms/aspnet-error-handling
i read that static/share can be used if some object is used in almost every area in solution so logger is good enough to use it as static/shared and make change only to message text and path pass by its constructor.
I dont think that singleton could be good in this case because then he would need to track his reference to every class, and it would be really complicated if he would have 20 projects with multiple classes over one solution so i think its better to make Logger static.
or just stay what you doing right now that every class has their own Logger instance. The problem could be with file path only if you got big flow of a data.

How do VB.NET static classes work?

I understood them to be modules, such as my little one:
Public Module Config
Public Property ImportSettings As ImportConfig
Sub New()
ImportSettings = ImportConfig.Read()
End Sub
End Module
Yet, I cannot access ImportSettings. I'm told it's not declared, and its value is 'Nothing'.
Static(C#)/Shared(VB) method/property in a class:
Public Class Config
Public Shared ReadOnly Property ImportSettings As ImportConfig
Get
Return ImportConfig.Read()
End Get
End Property
End Class
Usage:
Dim configs = Config.ImportSettings
Since it is Static/Shared we do not need to initialize the Config class.

Windows service unable write into event log

I have created the following report service but I am unable to log any thing in the EventLog. In the project settings I have set App Type as windows Service and Startup project as SUB Main... Can any one please suggest whats wrong with the code
Program.vb
Imports System.ServiceProcess
Namespace ReportingService
Public Class Program
Private Sub New()
End Sub
''' <summary>The main entry point for the application.</summary>
Private Shared Sub Main(args As String())
Dim ServicesToRun As ServiceBase()
ServicesToRun = New ServiceBase() {
New ReportingImmediate()
}
ServiceBase.Run(ServicesToRun)
End Sub
End Class
End Namespace
ReportService.vb
Imports System.ServiceProcess
Imports System.Timers
Imports System.Configuration
Public Class ReportingImmediate
Inherits ServiceBase
#Region "Members"
Private ReadOnly time As Timer
Private ReadOnly rptHelper As ReportingHelper
Private ReadOnly timeInterval As String
#End Region
Public Sub New()
' Initialize Logs
InitializeComponent()
' Initialize other components
time = New Timer()
timeInterval = ConfigurationManager.AppSettings("ImmediateReRunInterval") '10000
time.Interval = Integer.Parse(timeInterval)
AddHandler time.Elapsed, AddressOf TimeElapsed
rptHelper = New ReportingHelper()
End Sub
#Region "Timer Event"
''' <summary>time Elapsed</summary>
''' <param name="sender">The object that raised the event sender</param>
''' <param name="e">Event data passed to the handler e</param>
Private Sub TimeElapsed(sender As Object, e As ElapsedEventArgs)
time.Enabled = False
rptHelper.WriteToEventLog("Logging Report Service")
' Enable the Timer
time.Enabled = True
time.Interval = Integer.Parse(timeInterval)
End Sub
#End Region
#Region "Service Events"
''' <summary>On Start</summary>
''' <param name="args">Arguments</param>
Protected Overrides Sub OnStart(args As String())
time.Enabled = True
End Sub
''' <summary>On Stop</summary>
Protected Overrides Sub OnStop()
time.Enabled = False
End Sub
#End Region
End Class
ReportHelper.vb
Imports System.Data.OleDb
Imports System.Configuration
Imports System.Threading.Tasks
Imports System.IO
Imports System.IO.Packaging
Imports System.Text
Imports System.Net.Mail
Imports System.Net
Public Class ReportingHelper
Public Function WriteToEventLog(ByVal entry As String, Optional ByVal appName As String = "ReportingService", Optional ByVal eventType As _
EventLogEntryType = EventLogEntryType.Information, Optional ByVal logName As String = "RepotingServiceImmediate") As Boolean
Dim objEventLog As New EventLog
Try
'Register the Application as an Event Source
If Not EventLog.SourceExists(appName) Then
EventLog.CreateEventSource(appName, logName)
End If
'log the entry
objEventLog.Source = appName
objEventLog.WriteEntry(entry, eventType)
Return True
Catch Ex As Exception
Return False
End Try
End Function
End Class
Your service will not be able to EventLog.CreateEventSource because it does not run as an administrator (ref: the Note in the Remarks section of EventLog.CreateEventSource Method):
To create an event source in Windows Vista and later or Windows Server 2003, you must have administrative privileges.
The reason for this requirement is that all event logs, including security, must be searched to determine whether the event source is unique. Starting with Windows Vista, users do not have permission to access the security log; therefore, a SecurityException is thrown.
As you should not run your service as an administrator, the solution is to write a small program which you run with Administrator privileges to create the event source.
The Try...Catch in DWPReportingHelper.WriteToEventLog may be hiding an error from you.

unloading a DLL until it's needed

I'm having a hard time wrapping my head around some of the answers I've been reading here about unloading a plugin DLL using AppDomains. Here's my architecture:
In my solution, I have a SharedObjects project containing a ModuleBase class that all plugins (separate projects within the solution) inherit. In the SharedObjects project I also have an interface that all plugins implement (so if I have six plugins, they all implement the same interface and therefore the main program using these plugins doesn't need to know or even care what the name of the plugin's class was when it was compiled; they all implement the same interface and therefore expose the same information). Each plugin project has a project reference to the SharedObjects project. (As a side note, may be important, may not be - that SharedObjects project has a project reference to another solution, CompanyObjects containing a number of commonly-used classes, types, objects, etc.) When it's all said and done, when any given plugin compiles, the output directory contains the following DLLs:
The compiled DLL of the plugin itself
The DLL from the SharedObjects project
The DLL from the CompanyObjects project
Four prerequisite 3rd-party DLLs referenced in the CompanyObjects project
My main program creates a reference to the class where I'm doing all my plugin-related work (that class, PluginHelpers, is stored in the SharedObjects project). The program supplies an OpenFileDialog so that the user can choose a DLL file. Now, as it's running right now, I can move just the plugin DLLs to a separate folder and load them using the Assembly.LoadFrom(PathToDLL) statement. They load without error; I check to make sure they're implementing the interface in the SharedObjects project, gather some basic information, and initialize some background work in the plugin DLL itself so that the interface has something to expose. Problem is, I can't upgrade those DLLs without quitting the main program first because as soon as I use LoadFrom those DLLs are locked.
From this MSDN site I found a solution to the problem of locked DLLs. But I'm getting the same "File or dependency not found" error as the OP using the code that worked for the OP. I even get the error when I open the DLL from the release folder which includes the rest of those DLLs.
The FusionLog is even more confusing: there's no mention of the path I was trying to open; it's trying to look in the directory where I'm debugging the main program from, which is a completely different project on a completely different path than the plugins, and the file it's looking for is the name of the DLL but in the folder where the program is running. At this point I have no idea why it's disregarding the path I gave it and looking for the DLL in a completely different folder.
For reference, here's my Loader class and the code I'm using to (try to) load the DLLs:
Private Class Loader
Inherits MarshalByRefObject
Private _assembly As [Assembly]
Public ReadOnly Property TheAssembly As [Assembly]
Get
Return _assembly
End Get
End Property
Public Overrides Function InitializeLifetimeService() As Object
Return Nothing
End Function
Public Sub LoadAssembly(ByVal path As String)
_assembly = Assembly.Load(AssemblyName.GetAssemblyName(path))
End Sub
Public Function GetAssembly(ByVal path As String) As Assembly
Return Assembly.Load(AssemblyName.GetAssemblyName(path)) 'this doesn't throw an error
End Function
End Class
Public Sub Add2(ByVal PathToDll As String)
Dim ad As AppDomain = AppDomain.CreateDomain("TempPluginDomain")
Dim l As Loader = ad.CreateInstanceAndUnwrap(
GetType(Loader).Assembly.FullName,
GetType(Loader).FullName
)
Dim theDll As Assembly = l.GetAssembly(PathToDll) 'error happens here
'there's another way to do it that makes the exact point of the error clear:
'Dim theDll As Assembly = Nothing
'l.LoadAssembly(PathToDll) 'No problems here. The _assembly variable is successfully set
'theDll = l.TheAssembly 'Here's where the error occurs, as soon as you try to read that _assembly variable.
AppDomain.Unload(ad)
End Sub
Can anyone point me in the right direction so I can load and unload DLLs only as-needed and without any dependency errors?
I think I finally got it. It ended up being a few things - I needed the shared DLLs all in one place, and as Hans mentioned above, I needed my appdomains squared away. My solution architecture looks like this: a folder with all my plugin projects; a "Shared Objects" assembly with one class file for the base plugin architecture, and a second class containing my "plugin wrapper" class and supporting classes; and a console app that ties everything together. Each plugin project has a project reference to the shared objects project, as does the console app. Nothing references the plugins directly.
So in my Shared Objects project, I have the code for my PluginBase class and my IPlugin interface:
Public Interface IPlugin
ReadOnly Property Result As Integer
Sub Calculate(ByVal param1 As Integer, ByVal param2 As Integer)
End Interface
Public MustInherit Class PluginBase
Inherits MarshalByRefObject
'None of this is necessary for the example to work, but I know I'll need to use an inherited base class later on so I threw it into the example now.
Protected ReadOnly Property PluginName As String
Get
Return CustomAttributes("AssemblyPluginNameAttribute")
End Get
End Property
Protected ReadOnly Property PluginGUID As String
Get
Return CustomAttributes("AssemblyPluginGUIDAttribute")
End Get
End Property
Protected IsInitialized As Boolean = False
Protected CustomAttributes As Dictionary(Of String, String)
Protected Sub Initialize()
CustomAttributes = New Dictionary(Of String, String)
Dim attribs = Me.GetType.Assembly.GetCustomAttributesData
For Each attrib In attribs
Dim name As String = attrib.Constructor.DeclaringType.Name
Dim value As String
If attrib.ConstructorArguments.Count = 0 Then
value = ""
Else
value = attrib.ConstructorArguments(0).ToString.Replace("""", "")
End If
CustomAttributes.Add(name, value)
Next
IsInitialized = True
End Sub
End Class
<AttributeUsage(AttributeTargets.Assembly)>
Public Class AssemblyPluginNameAttribute
Inherits System.Attribute
Private _name As String
Public Sub New(ByVal value As String)
_name = value
End Sub
Public Overridable ReadOnly Property PluginName As String
Get
Return _name
End Get
End Property
End Class
<AttributeUsage(AttributeTargets.Assembly)>
Public Class AssemblyPluginGUIDAttribute
Inherits System.Attribute
Private _g As String
Public Sub New(ByVal value As String)
_g = value
End Sub
Public Overridable ReadOnly Property PluginGUID As String
Get
Return _g
End Get
End Property
End Class
And I have my PluginWrapper class with its supporting classes:
Imports System.IO
Imports System.Reflection
''' <summary>
''' The wrapper for plugin-related activities.
''' </summary>
''' <remarks>Each wrapper contains: the plugin; code to load and unload it from memory; and the publicly-exposed name and GUID of the plugin.</remarks>
Public Class PluginWrapper
Private _pluginAppDomain As AppDomain = Nothing
Private _isActive As Boolean = False
Private _plugin As IPlugin = Nothing
Private _pluginInfo As PluginInfo = Nothing
Private _pluginPath As String = ""
Public ReadOnly Property IsActive As Boolean
Get
Return _isActive
End Get
End Property
Public ReadOnly Property PluginInterface As IPlugin
Get
Return _plugin
End Get
End Property
Public ReadOnly Property PluginGUID As String
Get
Return _pluginInfo.PluginGUID
End Get
End Property
Public ReadOnly Property PluginName As String
Get
Return _pluginInfo.PluginName
End Get
End Property
Public Sub New(ByVal PathToPlugin As String)
_pluginPath = PathToPlugin
End Sub
Public Sub Load()
Dim l As New PluginLoader(_pluginPath)
_pluginInfo = l.LoadPlugin()
Dim setup As AppDomainSetup = New AppDomainSetup With {.ApplicationBase = System.IO.Directory.GetParent(_pluginPath).FullName}
_pluginAppDomain = AppDomain.CreateDomain(_pluginInfo.PluginName, Nothing, setup)
_plugin = _pluginAppDomain.CreateInstanceAndUnwrap(_pluginInfo.AssemblyName, _pluginInfo.TypeName)
_isActive = True
End Sub
Public Sub Unload()
If _isActive Then
AppDomain.Unload(_pluginAppDomain)
_plugin = Nothing
_pluginAppDomain = Nothing
_isActive = False
End If
End Sub
End Class
<Serializable()>
Public NotInheritable Class PluginInfo
Private _assemblyname As String
Public ReadOnly Property AssemblyName
Get
Return _assemblyname
End Get
End Property
Private _typename As String
Public ReadOnly Property TypeName
Get
Return _typename
End Get
End Property
Private _pluginname As String
Public ReadOnly Property PluginName As String
Get
Return _pluginname
End Get
End Property
Private _pluginguid As String
Public ReadOnly Property PluginGUID As String
Get
Return _pluginguid
End Get
End Property
Public Sub New(ByVal AssemblyName As String, ByVal TypeName As String, ByVal PluginName As String, ByVal PluginGUID As String)
_assemblyname = AssemblyName
_typename = TypeName
_pluginname = PluginName
_pluginguid = PluginGUID
End Sub
End Class
Public NotInheritable Class PluginLoader
Inherits MarshalByRefObject
Private _pluginBaseType As Type = Nothing
Private _pathToPlugin As String = ""
Public Sub New()
End Sub
Public Sub New(ByVal PathToPlugin As String)
_pathToPlugin = PathToPlugin
Dim ioAssemblyFile As String = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(_pathToPlugin), GetType(PluginBase).Assembly.GetName.Name) & ".dll")
Dim ioAssembly As Assembly = Assembly.LoadFrom(ioAssemblyFile)
_pluginBaseType = ioAssembly.GetType(GetType(PluginBase).FullName)
End Sub
Public Function LoadPlugin() As PluginInfo
Dim domain As AppDomain = Nothing
Try
domain = AppDomain.CreateDomain("Discovery")
Dim loader As PluginLoader = domain.CreateInstanceAndUnwrap(GetType(PluginLoader).Assembly.FullName, GetType(PluginLoader).FullName)
Return loader.Load(_pathToPlugin)
Finally
If Not IsNothing(domain) Then
AppDomain.Unload(domain)
End If
End Try
End Function
Private Function Load(ByVal PathToPlugin As String) As PluginInfo
Dim r As PluginInfo = Nothing
Try
Dim objAssembly As Assembly = Assembly.LoadFrom(PathToPlugin)
For Each objType As Type In objAssembly.GetTypes
If Not ((objType.Attributes And TypeAttributes.Abstract) = TypeAttributes.Abstract) Then
If Not objType.GetInterface("SharedObjects.IPlugin") Is Nothing Then
Dim attribs = objAssembly.GetCustomAttributes(False)
Dim pluginGuid As String = ""
Dim pluginName As String = ""
For Each attrib In attribs
Dim name As String = attrib.GetType.ToString
If name = "SharedObjects.AssemblyPluginGUIDAttribute" Then
pluginGuid = CType(attrib, AssemblyPluginGUIDAttribute).PluginGUID.ToString
ElseIf name = "SharedObjects.AssemblyPluginNameAttribute" Then
pluginName = CType(attrib, AssemblyPluginNameAttribute).PluginName.ToString
End If
If (Not pluginGuid = "") And (Not pluginName = "") Then
Exit For
End If
Next
r = New PluginInfo(objAssembly.FullName, objType.FullName, pluginName, pluginGuid)
End If
End If
Next
Catch f As FileNotFoundException
Throw f
Catch ex As Exception
'ignore non-valid dlls
End Try
Return r
End Function
End Class
Finally, each plugin project looks a little like this:
Imports SharedObjects
<Assembly: AssemblyPluginName("Addition Plugin")>
<Assembly: AssemblyPluginGUID("{4EC46939-BD74-4665-A46A-C99133D8B2D2}")>
Public Class Plugin_Addition
Inherits SharedObjects.PluginBase
Implements SharedObjects.IPlugin
Private _result As Integer
#Region "Implemented"
Public Sub Calculate(ByVal param1 As Integer, ByVal param2 As Integer) Implements SharedObjects.IPlugin.Calculate
If Not IsInitialized Then
MyBase.Initialize()
End If
_result = param1 + param2
End Sub
Public ReadOnly Property Result As Integer Implements SharedObjects.IPlugin.Result
Get
Return _result
End Get
End Property
#End Region
End Class
To set it all up, the main program creates a new instance of the PluginWrapper class, supplies a path to a DLL, and loads it:
Dim additionPlugin As New PluginWrapper("C:\path\to\Plugins\Plugin_Addition.dll")
additionPlugin.Load()
Once you're done doing whatever you need to do with the program...
additionPlugin.PluginInterface.Calculate(3, 2)
...and retrieving the results...
Console.WriteLine("3 + 2 = {0}", additionPlugin.PluginInterface.Result)
...just unload the plugin:
additionPlugin.Unload()
If you need to reload it while the wrapper is still in memory, just call the Load() method again - all the information it needs to create a new AppDomain and reload the assembly is in there. And, in answer to my initial question, once the Unload() method has been called, the assembly is freed and can be replaced/upgraded as necessary, which was the whole point of doing this in the first place.
Part of where I was getting tripped up earlier was that I wasn't including the SharedObjects.dll file in the same folder as the plugins. What I found is that any referenced assembly needs to be present. So in my post-build events for both my plugins and the Shared Objects project, I have this: xcopy /y $(ProjectDir)$(OutDir)$(TargetFileName) c:\path\to\Plugins. Every time I build the solution, all the DLLs are placed in the folder where they need to be.
Sorry if this is a little long, but this is a little complicated. Maybe there's a shorter way to do it...but at the moment, this gets me everything I need.