OSGi.NET - An OSGi-based modularization framework for .NET





5.00/5 (6 votes)
This article describes the usage of a modularization framework called OSGi.NET.
Introduction
OSGi.NET helps .NET developers to reform and build software systems consisting of individual logical physical modules which are communicating with each others by Service Bus (a combination of pre-defined service interfaces, could add/get/remove specified service instance) or by extension point/extension (a mechanism that allow one module to publish extra infomations (extension, xml format) to another who subscribes the same topic (same extension ponit name) in oder to extend the subscriber dynamically. Modules (always called "Bundle") are under the control of OSGi.NET Runtime with life-cycle management (Installed, Resolved, Starting, Active, Stopping, Uninstalled) which is accessble by others when want to start it, or stop it later. All configuration is simply put in a readable, editable and reconfigurable file, Manifest.xml. Almost supports all types of .NET applications, even MVC (starts from MVC 3). Minimum supporting .NET version is 2.0.
Background
"The key reason OSGi technology is so successful is that it provides a very mature component system that actually works in a surprising number of environments. The OSGi component system is actually used to build highly complex applications like IDEs (Eclipse), application servers (GlassFish, IBM Websphere, Oracle/BEA Weblogic, Jonas, JBoss), application frameworks (Spring, Guice), industrial automation, residential gateways, phones, and so much more. " --Benefits of Using OSGi
OSGi.NET, a dynamic modularization framework, which is a .NET implementation tightly based on OSGi specifications from OSGi Alliance, is designed and developed by Xi’an UI Information Technology, Inc., in China, from 2008.
Using the code
What's a Manifest.xml for?
Every Bundle has a Manifest.xml to self-describe as a "Plugin" to OSGi.NET runtime environment, which more or less contains several parts as below:
- What's the Bundle's basic description, what's his name, what's the version, wht's the order to start?
- Where are the assemblies should be loaded? Does it depend on any other Bundles or any Shared aseemblies form other Bundles?
- Optionally: Where is Bundle entry point and exit point when it's started or stopped?
- Optionally: Define a Service which can be called by other Bundles with an interface and one of its public sub-class that inherits it
- Optionally: Define an Extension Ponit to subscribe a topic that's specified by Point attribute. Well, coming in pairs, define an Extension in other Bundles with same Point attribute to publish extra xml-formatted message
- Optionally: Details information of this Bundle about its author, company, category or copyright, etc. if has any
The following example describes the implementation of 3 Bundles ("OSGi.NET.MP3DecoderPlugin", "OSGi.NET.APEDecoderPlugin" and "OSGi.NET.AudioFormatService") or 2 types ( 2 Plugins and 1 Service) which work together with a Host ("OSGi.NET.AudioPlayerShell") to simulate a very simple Audio Player, just outputs the supported media types for choosing and its running status.
Plugins
Let's start with OSGi.NET.MP3DecoderPlugin, check out its Manifest.xml
<?xml version="1.0" encoding="utf-8" ?>
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" SymbolicName="OSGi.NET.MP3DecoderPlugin" Name="OSGi.NET.MP3DecoderPlugin" Version="1.0.0.0" InitializedState="Active">
<Activator Type="OSGi.NET.MP3DecoderPlugin.Activator"/>
<Runtime>
<Assembly Path="bin\OSGi.NET.MP3DecoderPlugin.dll"/>
</Runtime>
<Extension Point="OSGi.NET.AudioFormat">
<AudioFormat Type="MP3" DecorderClass="OSGi.NET.MP3DecoderPlugin.Decoder" />
</Extension>
</Bundle>
- "SymbolicName" is the inner name for indexing and it must be unique
- "InitializedState" indicates the first state after it's loaded, another option is "Installed" that meas will be "Avtive" later by manual if needed
- "Activator" with Type specified indicates the start and stop ponit of this Bundle before it's being "Active" or "Stopping", here we do nothing
public class Activator : IBundleActivator
{
public void Start(IBundleContext context)
{
//todo anything before Bundle is Active, like init a database connection
}
public void Stop(IBundleContext context)
{
//todo anything before Bundle is Stopping, like close a datbase connection
}
}
public class Decoder : IAudioDecoder
{
public void Decode()
{
Console.WriteLine("MP3 decoder is decoding...");
}
}
OSGi.NET.APEDecoderPlugin does the same things so we continue to Service
Service
This is the Manifest.xml of OSGi.NET.AudioFormatService
<?xml version="1.0" encoding="utf-8"?>
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" Name="OSGi.NET.AudioFormatService" SymbolicName="OSGi.NET.AudioFormatService" Version="1.0.0.0" StartLevel="2" InitializedState="Active">
<Activator Type="OSGi.NET.AudioFormatService.Activator" />
<Runtime>
<Assembly Path="bin\OSGi.NET.AudioFormatService.dll" Share="true" />
</Runtime>
<ExtensionPoint Point="OSGi.NET.AudioFormat" />
</Bundle>
- "StartLevel" indicates the starting order of this Bundle, the less means start earlier, minimum is 2, default is 50. Start the Service before any others in order to comsume when it's ready
- Another difference between plugin above is "ExtensionPoint" with Point specified that indicates this Bundle will subscrtibe all Extension information of this Point. This definition is not necessary, we can handler the Extention in code with/without it
- This is a Service Bundle but there is no definition about any Service, same reason, we can handler it in code, too
- That's the way we do here, read the comments for details
public void Start(IBundleContext context)
{
//Add a IAudioFormatExtensionService instance to Service Bus
//AudioFormatExtensionService is inherits IAudioFormatExtensionService
context.AddService<IAudioFormatExtensionService>(new AudioFormatExtensionService());
//Handler the extension if it's changed in run-time dynamically
context.ExtensionChanged += context_ExtensionChanged;
//Get the specified extension by Ponit
List<Extension> extensionList = context.GetExtensions("OSGi.NET.AudioFormat");
if (extensionList != null && extensionList.Count > 0)
{
foreach (Extension extension in extensionList)
{
//deal with each extension in AudioFormatExtensionContainer at run-time start
AudioFormatExtensionContainer.Intance.CollectAudioFormatExtension(extension);
}
}
}
void context_ExtensionChanged(object sender, ExtensionEventArgs e)
{
if (e.ExtensionPoint == "OSGi.NET.AudioFormat")
{
if (e.Action == CollectionChangedAction.Remove)
{
//remove the extension if the owner bundle is removed in run-time dynamically
AudioFormatExtensionContainer.Intance.RemoveAudioFormatExtension(e.Extension);
}
else if (e.Action == CollectionChangedAction.Add)
{
//add the extension if the owner bunlder is added in run-time dynamically
AudioFormatExtensionContainer.Intance.CollectAudioFormatExtension(e.Extension);
}
}
}
public interface IAudioFormatExtensionService
{
List<string> GetAllAudioFormatTypes();
IAudioDecoder GetAudioDecoderByType(string type);
}
public class AudioFormatExtensionService : IAudioFormatExtensionService
{
public List<string> GetAllAudioFormatTypes()
{
List<string> types = new List<string>();
AudioFormatExtensionContainer.Intance.AudioFormatExtensionList.ForEach(
extension => types.Add(extension.Type));
return types;
}
public IAudioDecoder GetAudioDecoderByType(string type)
{
AudioFormatExtension audioFormatExtension = AudioFormatExtensionContainer.Intance.AudioFormatExtensionList.Find(
extension => extension.Type == type);
if (audioFormatExtension != null)
return audioFormatExtension.Decoder;
else
return null;
}
}
Host
Host will get all supporting media types var Service above, here is the code with comments
static void Main(string[] args)
{
//Create BundleRuntime context, inherits IDisposable
using (BundleRuntime bundleRuntime = new BundleRuntime())
{
//Start BundleRuntime
bundleRuntime.Start();
//TODO
while (true)
{
//Get first or default IAudioFormatExtensionService service from Service Bus
IAudioFormatExtensionService audioFormatExtensionService = bundleRuntime.GetFirstOrDefaultService<IAudioFormatExtensionService>();
if (audioFormatExtensionService != null)
{
Console.WriteLine("Please select the audio type:");
//loop all types and output
audioFormatExtensionService.GetAllAudioFormatTypes().ForEach(type => Console.WriteLine(type));
Console.WriteLine();
string selectedType = Console.ReadLine();
Console.WriteLine(string.Format("your selection is {0}", selectedType));
//Get IAudioDecoder by its Type
IAudioDecoder decoder = audioFormatExtensionService.GetAudioDecoderByType(selectedType);
if (decoder != null)
decoder.Decode();
}
};
}
}
Output
More Fun
With the Remote Console Tool, we can do a litte fun here with host running
- The init screenshot
- Input "list" or "l"
- Input "stop 4" or "sp 4" (4 is the Id of Bundle OSGi.NET.MP3DecoderPlugin in runtime)
The press key "Enter" in Host application, the MP3 option is gone - Input "start 4" or "s 4"
The MP3 will be back
- Stop the Host, and zip the OSGi.NET.MP3DecoderPlugin folder before delete it, that means OSGi.NET.MP3DecoderPlugin will be out of this application.
- Here let's demo how to install a Plugin remotely. Asume the path of zip file is "D:\OSGi.NET.MP3DecoderPlugin.zip".
Start Host application, there is no MP3 option
Then in Remote Console Tool, input: i "OSGi.NET.MP3DecoderPlugin" "D:\OSGi.NET.MP3DecoderPlugin.zip" "D:\codeproject.com\OSGi.NET\Demo1\OSGi.NET.AudioPlayerShell\OSGi.NET.AudioPlayerShell\bin\plugins\FormatTypes\Lossy\OSGi.NET.MP3DecoderPlugin"
Show the list and start it
Press Enter in Host, MP3 is back then
Points of Interest
Let’s brief how this happens:
- Host starts the BundleRuntime
- then runtime search all Manifest.xml files under a specified path, here it’s “bin\Plugins”of Host itself
- from Manifest, runtime gets where assembly should be load and where the entry ponit Start() in Activator is
- at last, run the Start()
- Also, in Start() Bundle registries Service and Handles Extention from Manifest.xml
- Host comsumes the Service var BundleRuntime, that's all
Interesting thing are:
- Host never know whether there are some Plugins or not, but eventually BundleRuntime will load them all or none
- Plugins never konw wheter there is a Host or not, but eventually it will be loaded by BundleRuntime if there is one
- Every single Plugin is in a single Folder, you can add it or remove it from “bin\Plugins”, we don’t need re-build Host or other Plugins but they could work together without any problems
More info about OSGi.NET project, please check http://osgi.codeplex.com/documentation.
History
- 24 Mar 2013 - Created the article.