Title: ViperWorks.Ignition WinForms Framework Overview
Author: Tyron Harford
Email: tharford (dot) nz [at] gmail {dot} com
Member ID: 451298
Language: C#, VB.NET, .NET 4.0
Platform: Windows
Technology: WinForms, WCF, WPF (some)
Level: Intermediate
Description: A WinForms application framework for rapid application development
Requirements: Visual Studio 2010, DevExpress 2010
Downloads / Links
• The Source - version 5.0.1005.31 (approximately 40MB - 7zip file)
That link will take you to CodePlex where you can download the latest version.
• ViperWorks Blog - http://viperworks.wordpress.com
That link will take you the Ignition blog - that will be kept up to date with any activity.
Updates
03 June 2010
- Added updates section.
- Changed Licence to Microsoft Public License.
08 June 2010
- Added link to blog.
About Me
I'm 27, I like long walks on the beach. Waitttt a minute, it's not that type of article :). I thought I'd write
a quick note about me as well. When I look at articles, I tend to look at the profile of the person - I like seeing
or understanding people - kind of makes you understand a little more about the article really. I've been programming for
quite a while now, my first ever experience was a TI-85 graphing calculator - I got the calculator, well, because it was
big and it could graph! How Cool! Little did I know that you could actually program on these devices. So I got into
programming on them and found it was pretty darn cool. I then found about this tool called QBasic. My first ever computer
program was some Pythagoras Theorem program about triangles. Wasn't the most fancy program, but it was my first. I was
a bit disappointed to find out the code generated had to be run in QBasic!
Fast forward a couple years (bout 16 I guess) and here I am with my brand spanking new version of Borland C++ Builder.
My first real Windows development came with this tool and it was cool. I created a Monopoly game and my crown jewel of
the time, an encryption/decryption algorithm. Only encrypted text files and wasn't anything like public/private key
encryption, but it did use different rounds of encryption and you could encrypt the same document twice with different
results. Took me about a year to write that. Could write that now in 2 days flat. How things change!
Fast forward a few years more if you will, here I am sitting in my first programming job (bout 21). Life was awesome.
Thought I had everything figured out. Great job, nice car (well, nice being a '77 2.0 liter Ford Escort - 5 speed manual
no less). I was introduced to .NET programming. The company more or less was into VB.NET programming (VS2003) and
I loved it. Took a while to get that love, I was a bit nervous with the .NET framework and my first project (commercial)
is still used to this day. To really understand .NET, I ended up writing two versions of that project. One in VB.NET and
one in C++ Builder. Needless to say the .NET way was substantially quicker.
So here I am. Two thousand and ten the year is. I no longer have that car (wrote it off in an accident). I've now
got a motorbike, have travelled the length and breadth of New Zealand I have, and plan todo the same for Europe, Australia
and the USA. Maybe even Africa. I've been programming for about 11 years now, commercially for 6. That's
not a long stretch by any means of the imagination, but 6 years is a long time in one company. 5 of those 6 years I spent
nearly every waking hour outside of work, working on Ignition. Picture that. Go to work, work your usual stuff and then
go home, do your own work. I know I'm not a unique case, but its hard work sometimes, to keep your head up and keep
going on the project. You start to have no social life and you start to question if this is the right thing to do. I guess
it depends on how much one analyzes themselves to ask these questions.
I'm immensely proud of Ignition, it's the biggest thing I've ever worked on and I've spilt blood, sweat and precious metal
over the project. Blood being breaking my hand, sweat because of a sweaty office and metal? Well, I guess I lost my first
car during this time, does that count? Before this turns into a slightly odd biography, I'll stop here. I could go on and
on, but that's a little about me!
Introduction
Ignition is a WinForms framework, designed for rapid application development. Instead of creating full blown WinForms applications
from scratch, Ignition serves as a framework, providing database access, reporting, scheduling, webservice access and one of
the best interfaces in the WinForms world to help get your application off the ground. Ignition uses DevExpress. Extensively.
To recompile most of the code, you'll need DevExpress. There is also not many (if any) code examples in this article.
The concept of Ignition is a WinForms shell. The shell has the common elements such as reporting, navigation - all the
required parts of a standard business application. It has buttons like New, Delete, Save, Refresh, Load. And Search. When you
design screens in Ignition, you inherit from a common screen (user control). You then override those commands (New, Delete, Save...).
So your screen doesn't have much in it - just deal with the business process. Ignition takes care of the rest.
Ignition is about 95% complete, but still needs work. At the bottom is a list of work that still needs to be done. As I will say
multiple times, this project is big. Very big. I can't cover everything in this article, so if you have any questions at all,
post them, and I'll try and answer them.
Installation
The easiest way to get started is:
• Make sure you have something like 7zip to extract the download.
• Make sure you have .NET 4.0 installed.
• If disabled, turn on the Net.Tcp Port Sharing Service (on the server - doesn't have to be started on client machines).
I haven't included an all-in-one installer - just because the system is reasonably easy to start using.
Once you've downloaded the source (which includes the binaries), extract to location of your choice, then:
• [Your Location]\ViperWorks\Binaries\ViperWorks.Ignition.Reactor and start the Console EXE
• [Your Location]\ViperWorks\Binaries\ViperWorks.Ignition.Client.Explorer and start the EXE
• Login with admin (no password) or demo (no password)
In the server folders, there is also a Windows service EXE. Install this the usual .NET way to get a
service instead of a console. Using the console can be great just for testing the waters. You don't need
VS 2010 or DevExpress 2010 to run it, but you will to recompile it. You can use the evaluation of DevExpress 2010
but you'll get a whole lot of annoying demo notices.
To make things even clearer, here's a pretty picture which has the layout:

Here's the structure. So exciting! (I had too much sugar this morning).
So at the root, you have ViperWorks. Under that you've got Binaries and Source. Under binaries, you have
all the binaries (bet you didn't see that coming) for the client and server apps. For the server apps, you
always have two EXE's. One for the console mode, one for the Windows service. Use the batch file to
install the services. There is also a Utilities directory. The Utilities directory has a key generator app
for Ignition as well as WebServiceStudio for web service testing near the bottom of the article. You may also
see that there are two Fusion directories. One has a .Basic extension on the dir. Now both services do the
exact same thing and listen on the same ports, so use one or the either. The .Basic version is setup to run
with basic bindings only. We will use this one for webservice testing later on with WebServiceStudio.
And then you have the source folder. Under that, you've got references - this has all the main references
the source code uses, except DevExpress references. Those references come from the GAC I believe. Under source
you have another folder called ViperWorks, and under that you've got Controls, Core and Ignition. Controls and
Core contain all-purpose assemblies, and the Ignition folder contains the source to Ignition. I've put all the
Ignition projects in one solution. Mainly for debug purposes, but you could seperate this out to make it
easier to manage/maintain.
The supplied database is in the binaries folder, inside the Reactor folder, subfolder Data. It's an MDB file.
If you want to try MSSQL, just use the upsize tool in Access. Don't add timestamps or anything like that, just
do the structure, indexes and data. I didn't include the MSSQL files because of size. If people really want it
I can upload the MSSQL backup or the MDF/LDF files.
You may need to open up ports on your Firewall as well. These are used:
- 61555 (UDP) (all applications)
- 8810 (Reactor TCP)
- 8820 (all health WCF contracts)
- 8830 (Fusion HTTP)
- 8840 (Fusion TCP)
If you are running Vista or Windows 7 and have UAC at a medium or tight level, you may get problems with Fusion binding
to port 80 if you change that. Either implement the required rules in Windows or run with no UAC.
Another really important note is that Ignition is designed to run on a network. If you are doing local testing, you shouldn't
have any problems on a Windows Vista/7 machine. If you are on Windows XP/2003 and you have no valid network connections,
you will need to create a loopback adapter for Ignition to work. See Google for how to do this. If you have started Reactor
and the WinForms client, and have no environments in the drop down box - you'll need to create the loopback adapter.
On a side note, if you have ever worked with Virtual PC's on a laptop without a network connection getting data to and
fro can be a right pain using the network. Just create a loopback adapter to fix that problem and you have an ad hoc network.
ViperWorks
Why use ViperWorks in the namespace? It sounds cool. So cool, there is no word to describe it. Even cool is afraid of ViperWorks. Seriously though,
the name came from Lockheed SkunkWorks. If you've never heard of the SR-71, go check it out. That's why I called it ViperWorks.
The SR-71 is JetFire in the second Transformers movie by the way. I was going to call my company this name. But things change.
I also wanted a professional look to the naming of assemblies.
This plane is the fastest ever air-breathing plane ever created. And she was born in the late 60's. Makes me wonder if
we've lost our way with innovation sometimes.
Who should read this
WinForms frameworks aren't everybodys cup of tea. Technology seems to be heading back to main frames. The web for some reason
attracts the most developers. Ignition is a WinForms frameworks - although not written, I have done prototype testing with XBAP's.
Making the possibility of hosting in the web very real. If you want to see a possible way to write a framework (I admin, may not
be the best way, but it's a way), check this out. There is a heap of code in here, so just download, check it out and see what you think.
If you want to read about WCF, continue on. If you want to read about multicasting in a real world scenario, read on.
If you want to see how to dynamically load classes from assemblies, read on. If you want to see a way of creating
a system that is database independent (so far with MSSQL, MySQL and Access support), guess what.... read on.
The system is big. Plain and simple. It's huge. This is the biggest project I have ever undertaken. And I'm dammmnnn proud
of it to boot. It has many different technology examples in it. Only problem is... did I mention it's big? Overtime I've forgotten
what I did, and it may have fallen through the cracks. But the code is here for everyone to read and analyze.
A couple of cool features in it:
• An implementation of licensing
• Basic implementation of runtime multi-lingual support (meaning language support is not hard coded in DLLs)
• Use of multicasting to broadcast service information - removing needs for config files
• Runtime class loading from assemblies
• Implementation of database independence
• How to tightly integrate help (as well as user added help)
• How to use the DevExpress skinning
• And many other examples
What you can do
So you know a little bit about Ignition, but what can you do with it? Ignition was designed from the start to abstract the
database - that's the primary use of it. Say you're developing a restaurant application. You want to have screens that manage
staff, menus, bookings and customers. To do this part you want a professional WinForms application to manage these parts. But
then you want a touch screen interface (maybe WPF) that lets staff assign customers to tables. Maybe this app isn't part of
Ignition, but you want to use the business logic. Just design a separate app, use the business logic via webservices exposed
using the Fusion service (more detail on this included service later) and that's the Point of Sale system done. But maybe the
business gets bigger - you want customers to book tables online. Although Ignition doesn't have a web front end, you could easily
hook into the webservices, but only expose the basic webservices externally.
That's a basic example, the framework is designed to get you started and do the dirty work for you. Doesn't do everything, but
a large portion of it.
Background
The story of Ignition comes from many moons ago. Not sure how many moons exactly, but it's about five years in the making.
It originally came from a spare-time project at work - we had a backup system in place consisting of batch files. Basically these
batch files ran each night, copying files across the network to a central location. NTBackup would run after that, putting
all the important data onto a tape drive. Now, at the time, I thought "This is pretty old school - there must be a better way!".
I was pretty naive back then (still am, just with different things these days) and thought a .NET app would solve all my problems.
We still use those batch files.
But, during that process, I wanted to keep a database of the different computers that would need to be backed up. I got sick and
tired of writing TSQL to perform CRUD operations, so set of writing a way to automate this. ORM frameworks hadn't really hit the
market back then. If they did, there were much too complex, still in the infancy stage, or beyond my understanding. So I rolled
my own. I got it working, was pretty darn cool - basically fire a class into this code, and the class would be analyzed and then the
relevant TSQL would be generated (ie. INSERT, DELETE, SELECT, etc...). Worked a charm.
Now the other thing I observed was that in the company I noticed that at this point, we had 3 .NET apps in our suite. And one
VB 6.0 app. Don't get me started on that one. But all these apps had different ways of getting data, they all looked very different
and the user interface in some of them just didn't make sense. So I thought, there must be a way to solve this. At this company,
we also have a very powerful 4GL. Now I didn't have the pleasure of meeting this person, but he created this powerful 4GL from
scratch. This was pre-Windows days. I thought, this 4GL is cool, but it's way too restrictive. You can't jazz up screens or do
anything really cool. Having this restriction makes sense though - all screens created look more or less the same - no matter what
developer wrote the screens.
So I set about writing my own framework - not as advanced as the one I looked at for inspiration in terms of functionality, but
it was more flexible. Five years later and after many ideas, most implemented, some not (see end for what's not done), here's
the result. Initially, it was a server/client system using remoting. Then it went to WCF. Then I added in a scheduler service
to automate certain tasks. Then I added in a service to dynamically host webservices. Then I added in tight security to that
service for webservice hosting. Then I thought about notifications of modified data. Then I thought about the main WinForms
client some more and added skins. Then I thought about corporate branding. Then I thought about multi-lingual ability.
Basically the list goes on and on.
You may remember seeing a project a couple years back called Draco.Ignition. See the open source paragraph below as to the
story behind this. This project has evolved after many years of work (the very, very first iteration was based on the Pet Shop
example from Microsoft - it's that old - in technology years).
So in a super small nutshell (like, smaller than a peanut shell - like an M and M shell perhaps?) that's the story of Ignition. Obviously not the most interesting
story, but it's a story. Doesn't equate to anything like climbing Mount Everest, but it's a story other developers may relate
to. And the project has heaps of cool stuff in there. This is part zero, an introduction if you will, other parts will follow, not
on everything, but on the major aspects of the framework. This isn't really a code article like the usual, it's a story of how the framework
came to be.
The system is also database independent. It runs on MSQL, MySQL and Access. Most of my development has been on Access.
I did this, not because I have an affinity for MDB files, but because it's the lowest common denominator. What I create,
has to run on Access. It serves quite well for creating a DB independent system. That's not to say you can't use more
advanced features. DB independence is done via interfaces, so the implementation can be however you want. For example,
I havn't used spatial functionality in MSSQL, but you could put it in. And then do a workaround or something like it in Access.
Or if you don't ever see yourself working with MDB files, just through a not supported/implemented exception.
Why Open Source?
You may be asking, why spend five years on a project, a marketable one at that, and release everything for free (to a degree)?
It's because I'm nearly just over this project. Five years is an eternity in this technology focused world. Five years is a long time
in the real world. Things change. You can get married, you can have children, you can do many things in five years. You learn.
Your priorities change. You buy a motorbike and travel a country. You start realizing technology isn't the only answer to problems.
You start asking yourself questions and end up writing in short sentences. Like this.
I basically want todo other things. I want to get back into photography. I want to try different jobs. This project ended up being
like an addiction. Every waking moment outside of my paying job in nearly the five year span was spent on this project.
It gave me knowledge, it gave me near insanity (those things are so closely related). Or at least made the insane problem worse.
Now I got very close to running a business, complete with a support website and awesome infrastructure. I think it would have been a
blast. If I were truly interested in my life going down that track that is. Being a programmer and creating applications from
words is one thing, running
a business is something else. Especially in the IT realm. I would have sold Ignition as it is to other developers, or failing that, I would
have created some applications to put inside Ignition and sold them. But I asked myself one night, "Do you want to do this. Do you
100% want to sell this product. Would you love doing it?". And I realized I didn't want to do it.
So here I am, making it open source. The other option would be to keep it on my computer and never show it to the world, but
I didn't want to do that. I want people to learn. The only reason why Ignition exists is because of sites like CodeProject and the
open source community, so it's just giving back. What goes around, comes around.
When I say everything free (to a degree) what I mean is that the source is freely available. But you can't take it, rebrand everything
and sell it as your own, closed source. Apart from karma biting you in the a$$, it's just not right. If you make changes, and think
people can benefit - make it open source.
Other Open Source Work I Have Used
Over the years, I've lost count of all the open source I used. If you see your code in Ignition and I have not mentioned you
as a source, I sincerely apologize. Give me a yell, and I'll add your name to the list. With the graphics, I've obviously thought
Adobe did a bang up job with their icons, so my icons look very similar. Splash screens, logon screens, logos were all designed
by myself - basically everything graphical is my work, except for the non-app icon images. I've used Windows Vista/7 icons as
well as the Silk icons (check that package out - very cool stuff).
Assembly: ViperWorks.Core
Namespace: ViperWorks.Core.FileIO.CSV
Class: [Multiple]
Author(s): Sebastien Lorion
URL: http:
Assembly: ViperWorks.Core
Namespace: ViperWorks.Core.Security.Encryption
Class: [Multiple]
Author(s): wumpus1
URL: http:
Assembly: ViperWorks.Core
Namespace: ViperWorks.Core.Shell
Class: [Multiple]
Author(s): Mattias Sjögren
URL: http:
Assembly: ViperWorks.Core.Logging
Namespace: ViperWorks.Core.Logging
Class: [Multiple]
Author(s): [Multiple]
URL: [this is log4net with modifications]
Assembly: ViperWorks.Controls.CodeEditor
Namespace: ViperWorks.Controls.CodeEditor
Class: [Multiple]
Author(s): dotnetfireball, valda [dotnetfireball actually is another project]
URL: http:
Assembly: ViperWorks.Controls.Input
Namespace: ViperWorks.Controls.Input
Class: [Multiple]
Author(s): [inputsimulator]
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.Calendar
Class: [Multiple]
Author(s): Dinesh Chandnani
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.Charting
Class: [Multiple]
Author(s): mattsj1984
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.General
Class: BindableListView
Author(s): Ian Griffiths
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.General
Class: GradientPanel
Author(s): Mark Jackson
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.General
Class: LoadingCircle
Author(s): Martin Gagne
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.General.Renderers.Office2007
Class: [Multiple]
Author(s): Phil Wright
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.PerfChart
Class: [Multiple]
Author(s): eclipse2k1
URL: http:
Assembly: ViperWorks.Controls.Core
Namespace: ViperWorks.Controls.Core.Thermometer
Class: [Multiple]
Author(s): Niel M.Thomas
URL: http:
Assembly: ViperWorks.Controls.Compression
Namespace: ViperWorks.Controls.Compression
Class: [Multiple]
Author(s): Mike Krueger
URL: http:
Assemblies
It helps knowing what the different assemblies are used for, so here's the list (alpha sort):
ViperWorks.Controls.CodeEditor (C#)
This is not my creation. It comes from dotnetfireball (which comes from something else). It's basically a control
that provides syntax highlighting. Used in a couple screens.
ViperWorks.Controls.Controls (C#)
Partly mine, partly other open source work. Contains general UI controls.
ViperWorks.Controls.Input (VB.NET)
Handles input from controls. Contains open source work. This is basically the
InputSimulator available from CodePlex. Not used yet in Ignition, but the main
idea was to get the mouse back and forward buttons for navigation. You can't easily
do this using the normal mouse events.
ViperWorks.Controls.Xbox360 (VB.NET)
Handles input from Xbox 360 controllers. Based off work on a CodeProject article.
Not used in Ignition, but I do have a project for my own personal use that uses this.
ViperWorks.Core.Compression (C#)
Handles file compression. Not my work.
Not heavily used in Ignition, only for help import/export.
Would be used if functionality export makes it in.
ViperWorks.Core.Data (VB.NET)
Contains data implementations for MSSQL, MySQL and Access.
ViperWorks.Core (VB.NET)
Contains core libaries. My own work and others.
ViperWorks.Core.Logging (C#)
Contains logging code (essentially log4net)
ViperWorks.Core.Office (VB.NET)
Integration with the Word document format.
Note heavily used in Ignition.
ViperWorks.Ignition.Core (VB.NET)
Core library used by all Ignition applications.
Applications
It helps knowing what the different applications are used for, so here's the list (alpha sort):
ViperWorks.Ignition.Client.Explorer
This is the main WinForms application. This is essentially Ignition.
ViperWorks.Ignition.Client.Plasma
This is an admin tool, there were greater plans for this, like installing/uninstalling services - stuff like that.
Right now, you can use it to see what the services are doing.
ViperWorks.Ignition.Firestorm
This was to be the XBAP/ASP.NET application for hosting Ignition in a web environment.
ViperWorks.Ignition.Fusion
This application hosts webservices from your code. Very cool stuff, via reflection it looks in your assembly
and creates a wrapper around your code. Tight security is included.
ViperWorks.Ignition.Modules
This is a bunch of DLL's, your application goes in here.
ViperWorks.Ignition.Reactor
This is the service that does the data work.
ViperWorks.Ignition.Scheduler
This is the service that performs scheduling of requests.
The Client
Right, with all that stuff out the way, onto the good stuff - screenshots!

This is the logon screen for Ignition.

This is the screen you get when you start Ignition.

This is an example of a report.

This is an example of a more complex screen.

This is an example of a more complex screen but with a different skin.

This is an example of a basic multi-lingual implementation.
So hopefully with those screenshots, you get an idea of what Ignition looks like. It didn't use to always look like
that, it's gone through quite a few stages. Should have taken screenshots back then! So the client
is a WinForms client. The idea is you create screens and business logic in your own assemblies and use them
in the client. All the complicated stuff is done for you - just concentrate on your business logic and
basic screen presentation. That UI can be confusing at first - here's a breakdown:

Part A - Navigation
These are your back and forwards buttons - navigation is like a web browser.
Part B - Record Actions
From left to right, this lets you create a new record, open an existing record (this only works on screens that
have a user friendly ID field - like a field name), save the record, delete the record, refresh the record,
revert any changes made (before save) or add to your favourites. You need an active screen loaded
to use any of these buttons. The availability of these buttons depends on your security level (the
security in Ignition is role based) and on the setup of the screen (more details on this follows).
Part C - System Menus
The left most button opens up the configuration menu. You can't configure much in Ignition, convention
over configuration is the name of the game. The Explorer bar is also doubled in here - just in case you
want to access the system that way. You can however:
• Open up the output window (useful for live debugging)
• See the daily tip
• Change the skin
• Change the language
• See the About box
• Send a message (not implemented)
• Switch to fullscreen mode (use F12 to go back)
• Hide the explorer bar (Part F)
In the middle is a special button - when Ignition is first shipped - you get the standard way of accessing
screens using a tree type structure. You can't easily change this (more on this later). This middle button
lets you create a menu structure (including sub menus) that can be applied to the whole organization. Great
for when you are not happy with the system default way of showing you functions to get to. Change it to
how you want. You can even include file path information (so put that URL in there). Ignition was designed
as a framework for a portal to everything else. Kinda like SharePoint I guess, but for the WinForms world.
At the end is the Home button - this lets you close all screens and go back to the home screen. Note that
you can set a screen as the home screen, this will take you to that screen.
Part D - QuickSearch
The QuickSearch box lets you get quick access to any screen in the system. The shown example will let
you type in a field name and when you hit the magnifying glass, a list of search results will show. If you
enter the exact field name, you'll go straight to the screen used to maintain that detail. This is 100%
customizable. You could change this to search for contacts in your contact system. Or maybe ID's of
students for your student management system. How you change this is further down.
Part E - Explorer Bar
The Explorer bar is used to show the main system menu, show your favourites menu, show what tasks
you can do with the current screen, show search options, show the history for the current record,
show the help pane, show the attachments and stickies for the screen/record, show reporting options,
show the user directory, show the data browser, and show the security for the current user. It's how
you use the system. You can hide this (as described above) and get access via the gear drop down.
Part F - Favourites Bar
The Favourites Bar can be used to store your frequent favourite screens, records or reports. It's an easy
way to get to common things you do. You also have your full blown favourites list which can be structured
into folders, but sometimes you just want the basics.
Part G - Tab Header
Nothing too special here - just a tab header. You can drag/drop tabs. Because Ignition is a tabbed interface,
just like the web browsers, you need to get to those tabs. Usually the tabs are created by you, navigating
to different screens. There are also system tabs, like Help, Tab Listing (like IE), Search Results, Current Report
and a button to create a New Tab.
Part H - Screen Header
The screen header displays information on the current screen - the title of it, what module you are in,
the ID of the screen (IE. CORE2014), a Help button and an indicator for the current status (ie. Edit Record).
The logo at the far right can be changed via the control panel. Corporate branding is a huge part of Ignition.
Part I - Explorer Pane
This pane changes depending what you clicked on in Part E. This is detailed further on.
Part J - Your Screen
This part holds your user control. Naming should be like CORE2012. Don't have spaces of the usual crap.
CORE being the ID of the module. So it would be like SWIM for a swimming module. Whatever a swimming
module would do.
Customization
A big part of Ignition is branding. From the ground up, Ignition was designed to be used in corporate scenarios. However
it was also designed for the one man band operation. It was designed to grow with a business. Maybe you're a developer
needing a framework to manage your business. You want to write some invoicing system as well as mass-emailing. You can
write that functionality in Ignition with minimal fuss. You have your own logo - you can put that in the startup screen.
You can replace the logon screen (background image). You can replace the splash image. Crikeys, if you have DevExpress 2010,
you can use their tools to create your own skin assembly and use that assembly for your skin. But you might not want to do all
of that right off, maybe in time you want to brand it how you want - the option is there.
Same thing with the DB. Maybe you are a one man band (even like a builder) and you can run off an MDB to start off with. But
then you get some help and find the MDB doesn't cut it. You decide to take the MySQL approach. Then you find you are a multi-national
company and need to-do fancy reporting and decide to go MSSQL. You can do this with Ignition.
I realise you can do this with many applications these days, but keep in mind alot of the core functionality was written when
most of these tools didn't exist (like Entity Framework) or that new MVC pattern stuff.
Also - in the client directory, there is a directory called Branding. If you stick in PNG's in there (example supplied),
you can override the logon screen and splash screen. Format is like "DEMO.Logon.png" where DEMO is the environment.
The other one is "DEMO.Splash.png".
The Explorer Panes
The Explorer panes form a fundamental part of Ignition. Here's what they are:
Menu
The Menu pane forms the way in which you access Ignition. Depending on your security, this may
look different for different users. The Menu is one way of accessing screens. The other ways include
Favourites and your custom menu (check out Part C above). You can right click on items to open
in the active tab, a new tab, or a new window. You can also add items to your favourite list.
And you can set your home screen as well. If you are an admin, you can customize this menu
by adding your own folders and items. Perfect for integration scenarios.
Favourites
The Favourites pane lets you organize your favourites in a tree like fashion. Right clicking enables
you to open in a new tab, active tab, new window, delete the favourite or pin the favourite to the
favourites bar. Favourites are per user.
Tasks
The Tasks pane is separated into two parts - the top part lets you perform record or field tasks. Record
tasks being searching. Field tasks being opening up a multilined textbox in a bigger window, or spellchecking.
More info on spellchecking below. The bottom pane is related tasks. Say you have a Invoice system. If you
had a screen that maintained invoices, you may want to navigate to the contact for that invoice. Thats what
tasks are for. Tasks join the system together. Tasks can also be inter-module so you can combine your modules
together and provide seamless integration.
Search
The Search pane is also separated into two parts - the top part lets you select the type of search. Different screens
may have different ways to search for data. If you had a contacts system, you may want to search by address or
search by surname. You setup your searches via metadata (also, see below). The second box provides parameters
for the search selected. Some searches don't have any parameters - in this case, the second box doesn't do didly
squat.
History
The History pane shows you a list of modifications to the record you are looking at. The amount of detail will
change, depending on how you set the screen up. In some cases you don't want any transactional history, in
some cases you want to only record that something changed. In other cases you want to see what fields got changed.
This pane lets you see that information. More info as to how to set this up is later on.
Help
The Help pane does exactly that - shows help. Within the Help pane are three tabs. One tab lets you view in a
tree structure the system help. As of uploading, there isn't much there. The second tab shows field help. If you
are on a screen and press F1, this is what you'll see. The third tab lets you search the help. Help comes from metadata.
When you create the metadata for a table or field, or core system component, you can create help in HTML form.
End-users can also add their own notes, this data is separate from the core help and can be removed by admins.
Attachments and Stickies
The Attachments and Stickies panes lets you add attachments to a record (so your Excel report). Stickies lets you
add notes to a record. Just important info, but not so important it forms a part of the data.
Reporting
The Reporting pane (more on this in a separate article) lets you view the reports for a screen. If you are on an invoice
screen, you may want a report setup that is the print version. You can also setup chart reports, pivot reports,
gauge reports, CSV reports, schedule type reports, web links or grid reports. It's a big part and is described separately.
Directory
The Directory pane is a complete listing of all users. This is designed for quick searching of users in the system.
Directory
The Data Browser will not actually open a pane, it will open a data browser that you can use to search the system.
With the results found, you can double click on a record and it will open up in the related screen(s).
How does it work? (Data)
Right, so we've gone over what it is (a WinForms framework put simply) and we've gone over the basics of the
interface. But how does it work you ask? Good question.
As with most applications, it starts at the database. In the database, there are quite a few tables prefixed with
XSYS. There are the system tables and these tables drive Ignition. Note that I haven't implemented relations yet.
This does make it harder to see how the system is connected, and I'll try and fix this later. By default, I've shipped
Ignition with an MDB. But if you want to try MSSQL, just upsize the MDB. A broad overview:
XSYS_ATTACHMENT
This stores attachments you have on records.
XSYS_BUSINESSAREA
A Business Area is a component that sits under XSYS_BUSINESSMODULE. You could consider an Area like
a subsection and a Module like a Section. Generally a module is an application. There is the CORE module, this
module performs all core functionality - it drives Ignition. Underneath CORE you have things like DATA - data is used
for data manipulation. You also have SYSTEM. System drives system related functions. Obviously.
XSYS_BUSINESSFIELD
Fields are metadata on top of columns. They duplicate some things like size and type, but also provide other
things like help, column names, values allowed and so fourth.
XSYS_BUSINESSINDEX
Indexes are a recent addition. Basically they add support for DB indexes. The plan was to store the metadata in
Ignition, so when you import/export functionality, Indexes can be then built via TSQL.
XSYS_BUSINESSINDEX_COLUMN
These are the columns of an index.
XSYS_BUSINESSMODULE
Modules are your applications. One record per application. When you create a module, you create two assemblies.
One for the business logic, one for the screens. Underneath modules you have areas (as above).
XSYS_BUSINESSOBJECT
Objects are the nuts and bolts. Looking back, this should be called Business Function or something like that.
Each screen you go to in Ignition is a business object. Same goes with processes.
XSYS_BUSINESSOBJECT_CHILD
This table contains any child objects for the given object. This is used when you have menus. A menu is still
considered a business object, but it has child objects attached to it.
XSYS_BUSINESSOBJECT_TASK
When you create an object, you sometimes create tasks (so an invoice screen links to a contact). You define
this relationship through tasks.
XSYS_BUSINESSOBJECT_WEBSERVICE
This is a complicated one. When Ignition was first created, it was designed for WinForms. When .NET 3.5 came out,
I decided to investigate WPF. Overtime, it was slowly put in, but WPF works slightly different than WinForms. WinForms
can talk directly to the business logic. But WPF is designed to communicate with the webservice layer (how it should be).
When you load a WPF screen, I need to know what webservices that screen needs. That's what this table is for.
XSYS_BUSINESSRELATION
Same concept as the index table, but used a little more. It's goal was to be used as metadata so when importing
functionality, relations can be built in the DB. It also serves a purpose in the report writer. You can have master-detail
reports, but for that, you need to know the relations between tables. Sadly not all the relations are created, a later
version will remedy this.
XSYS_BUSINESSTABLE
This table stores... Tables. Apart from being a container for fields, this also stores class name info for code generation.
Yes - this is a screen in Ignition you can use to generate code, making development even quicker.
XSYS_CLIENTEXCEPTION
Because handling exceptions from clients in a live environment can be a right pain, this table can store all the
exceptions that client applications generate. Nifty.
XSYS_CUSTOMMENU_*
This table stores all custom menu folders and items.
XSYS_DATACOMMAND
This table stores data commands you can execute.
XSYS_DTX_*
These tables store data transfer information. Can be used to import/export data.
XSYS_EMAIL*
These table stores a history of emails and attachments sent from the application.
XSYS_FAVOURITE_*
These tables store all user favourite information.
XSYS_FUSIONBINDING
This is to-do. Basically when using webservices in Ignition, you need to know how to bind
to them. Data is entered into here as to how to bind. An application that was going to get created
was going to be called Firestorm. This application could have been externally facing. Because of this
you may have wanted SSL certs, so a different binding would have been required.
XSYS_LANGUAGE
This table stores the different languages available. It also links to the spellcheck table.
XSYS_LANGUAGEWORD
Multi-lingual support is essentially limited to word replacement. You define what words here.
XSYS_MESSAGE_USER
The Ignition messaging sub-system stores messages in here.
XSYS_ORGANIATION*
These tables store organization information and structure.
XSYS_QUICKSEARCH
This stores any quicksearch definitions you have (executed from top right in GUI).
XSYS_QUOTATION
Stores all quotes seen on the startup page.
XSYS_RECORDLOCK
This table stores all record locks. For example, when you open a record, click into a field,
a lock is placed, preventing other users from editing data.
XSYS_REPORT*
These tables store all report information. Executed via the Reports pane.
XSYS_ROLE
Stores all roles in the organization.
XSYS_SCHEDULE
Stores all schedules that should be run. For a schedule to run, the Scheduler service needs to be running.
XSYS_SCRIPTING
In Ignition, you can create scripts that execute and return data. You can use this scripts in searches. Sometimes
the built in searching doesn't cut the mustard, and even TSQL (yes, you can use custom TSQL in searches) doesn't
work. That's when you can use scripts. A working example is when you search for security items.
XSYS_SEARCH*
These tables are probably the most important - they drive all searching in Ignition. Without these, you
wouldn't be able to find records. This is so important, it's described fully later on.
XSYS_SECURITY
Security is based on roles and View, Add, Change, Delete, Deny access. Security can be applied at Module (highest)
level, Area (middle) or Object (lowest level) level.
XSYS_SETTING
Sometimes a whole table is overkill for storing data. Like the users choice of skin. The setting table stores
setting such as this.
XSYS_SPELLCHECK
When you are in large fields, you want to spellcheck. Because Ignition supports multiple languages (though I haven't
got around to doing the translations), you need to support multiple spell check languages. Data is stored in OpenOffice
format.
XSYS_STATISTIC
For the bean counters. Ignition stores statistics on usage. System stats are stored here, and can be view in a screen
in Ignition.
XSYS_STICKY
This table stores any stickies the users create.
XSYS_SYSTEMHELP
Along with the help you put on Modules, Areas, Objects and Fields (and settings), you can create your own
help. Some examples are already in there on how to create tree like doco.
XSYS_SYSTEMHELP_IMAGES
Sometimes images speak louder than words. Store any images along with doco here.
XSYS_SYSTEMHISTORY
Earlier I mentioned Ignition can keep transaction details for screens. Obviously the data can get big, so this table
stores the names of the different tables used to store this data.
XSYS_TIP
Stores the tip of the day data.
XSYS_TRANSACTION
WIP. Certain tables can be configured to write transaction information. This information could then be passed to
a service that performs notifications. This is un-completed at this point.
XSYS_USER
This table stores all users in the system.
XSYS_USERHELP
Stores all user added help.
XSYS_USERROLE
Contains mapping of users to roles. Users can have multiple roles.
XSYS_WEBSERVICE
Contains a listing of all webservices that should be hosted.
XSYS_WEBSERVICE_USER
Contains a mapping of which users can access what webservices. Sometimes you don't want
all users accessing all webservices. Some may be confidential.
XSYS_WORLDEVENT
Simple table used to store world events.
So... How Does it work again?
We've described in basic detail what the different tables are for, but how does this data travel? Well, it
starts off with some data (surprise!). Take this code for example:
<Serializable()> _
Public Class BusinessObjectWebServiceObject
Inherits ViperWorks.Core.Data.GeneralObject
Private _BusinessObjectWebServiceObjectID As String
Private _BusinessObjectWebServiceWebServiceID As String
Default Public Overloads Overrides Property Item(ByVal Name As String) As Object
Get
Select Case Name.ToLower
Case "businessobjectwebserviceobjectid"
Return Me.BusinessObjectWebServiceObjectID
Case "businessobjectwebservicewebserviceid"
Return Me.BusinessObjectWebServiceWebServiceID
Case Else
Return Nothing
End Select
End Get
Set(ByVal value As Object)
If (value IsNot Nothing And TypeOf value Is DBNull = False) Then
Select Case Name.ToLower
Case "businessobjectwebserviceobjectid"
Me.BusinessObjectWebServiceObjectID = CStr(value)
Case "businessobjectwebservicewebserviceid"
Me.BusinessObjectWebServiceWebServiceID = CStr(value)
End Select
End If
End Set
End Property
Public Property BusinessObjectWebServiceObjectID() As String
Get
Return Me._BusinessObjectWebServiceObjectID
End Get
Set(ByVal value As String)
Me._BusinessObjectWebServiceObjectID = value
End Set
End Property
Public Property BusinessObjectWebServiceWebServiceID() As String
Get
Return Me._BusinessObjectWebServiceWebServiceID
End Get
Set(ByVal value As String)
Me._BusinessObjectWebServiceWebServiceID = value
End Set
End Property
Public Sub New()
Me._BusinessObjectWebServiceObjectID = String.Empty
Me._BusinessObjectWebServiceWebServiceID = String.Empty
End Sub
End Class
This code represents a table in the database. Each property is a field. When you create a class that represents
a table, you must inherit from ViperWorks.Core.Data.GeneralObject. The GeneralObject is the main object that
is used to transmit over WCF into the data service (called Reactor). When you want to update data for example, you call:
Me._ActiveContract.DataInsert(Me._ClientID, MyBusinessObjectWebService.MetaFieldList, Me._TableName, Me._KeyColumns)
The _ActiveContract class is ViperWorks.Ignition.Core.Reactor.IReactorContract. The MetaFieldList property is a property
on the GeneralObject which will disassemble the class into a generic list of BusinessField objects. This list is then used to
create an INSERT or UPDATE statement (usually via parameters). If I were to pass the class itself, the service
that does the data work would need a reference to it. I didn't want that, so that's why the class is deconstructed into
an object the data service does know about (GeneralObject).
So what is _ActiveContract? It's a class that contains the WCF contract. This class (well, interface) that forms
the bulk of communication in Ignition. Your business logic doesn't need to know how to create an instance. This is done
for you by Ignition when it starts up. All you need to know is how to use this class to do your data business. The
implementation of this contract is in Reactor.
The Business Logic
If you develop in Ignition, you'll spend your time in three areas:
• Visual Studio - creating business logic
• Visual Studio - creating screens
• Ignition - creating metadata and searches
You'll spend some time in Ignition creating searches, metadata, and the screen (Business Object) so that
Ignition knows how to treat your screen you created in code. We'll go over the business logic now and have
a look at a more complicated piece of logic:
Public Class BusinessFieldBus
Private _ClientID As Guid
Private _ActiveContract As Reactor.IReactorContract
Private _TableName As String
Private _KeyColumns(0) As String
Public Sub New(ByVal rClient As Reactor.ReactorContainer, Optional ByVal Arguments As Dictionary(Of String, String) = Nothing)
Me._ClientID = rClient.Client.ClientID
Me._ActiveContract = rClient.Contract
Me._TableName = "XSYS_BUSINESSFIELD"
Me._KeyColumns(0) = "BusinessFieldID"
End Sub
Public Function GetPrimaryKeyField(ByVal bField As Data.BusinessField) As Data.BusinessField
Dim bTable As Data.BusinessTable
Dim bFieldTable As ViperWorks.Core.Data.BusinessField
Dim bFieldKey As ViperWorks.Core.Data.BusinessField
Dim MyFieldList As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Dim MyBusinessField As ViperWorks.Core.Data.BusinessField = Nothing
Try
MyFieldList = New Generic.List(Of ViperWorks.Core.Data.BusinessField)
bFieldKey = New ViperWorks.Core.Data.BusinessField("BusinessFieldPrimaryKey", True, _
Data.BusinessField.eBusinessFieldType.Boolean)
bFieldTable = New ViperWorks.Core.Data.BusinessField("BusinessFieldBusinessTableID", _
bField.BusinessFieldBusinessTableID, Data.BusinessField.eBusinessFieldType.String)
MyFieldList.Add(bFieldKey)
MyFieldList.Add(bFieldTable)
bTable = Me._ActiveContract.DataSelect(Me._ClientID, Me._TableName, MyFieldList)
If (bTable IsNot Nothing AndAlso bTable.BusinessFields.Count > 0) Then
MyBusinessField = New ViperWorks.Core.Data.BusinessField
ViperWorks.Core.Data.DataHelper.ConvertObjectFromBusinessTable(CType(MyBusinessField, _
ViperWorks.Core.Data.GeneralObject), bTable)
End If
Return MyBusinessField
Catch ex As Exception
Throw
End Try
End Function
Public Function SelectByTable(ByVal BusinessTableID As String, _
Optional ByVal OrderByFieldOrder As Boolean = False) As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Dim SearchList As Generic.List(Of Searching.SearchInfo)
Dim OrderList As Generic.List(Of Searching.OrderInfo)
Try
SearchList = New Generic.List(Of Searching.SearchInfo)
OrderList = New Generic.List(Of Searching.OrderInfo)
SearchList.Add(New Searching.SearchInfo(Enumerations.eCondition.AND, "BusinessFieldBusinessTableID", _
Enumerations.eOperator.Exactly, BusinessTableID, Data.BusinessField.eBusinessFieldType.String, True))
If (OrderByFieldOrder) Then
OrderList.Add(New Searching.OrderInfo("BusinessFieldOrder", Searching.OrderInfo.eSortType.Ascending))
Else
OrderList.Add(New Searching.OrderInfo("BusinessFieldName", Searching.OrderInfo.eSortType.Ascending))
End If
Return Me._ListSearch(SearchList, OrderList)
Catch ex As Exception
Throw
Return Nothing
End Try
End Function
Public Function Insert(ByVal MyBusinessField As ViperWorks.Core.Data.BusinessField) As String
Return Me._Insert(MyBusinessField)
End Function
Public Function Update(ByVal MyBusinessField As ViperWorks.Core.Data.BusinessField) As Boolean
Return Me._Update(MyBusinessField)
End Function
Public Function [Select](ByVal BusinessFieldID As String, ByVal UseFieldName As Boolean) As ViperWorks.Core.Data.BusinessField
Return Me._Select(BusinessFieldID, UseFieldName)
End Function
Public Sub Delete(ByVal ColumnName As String, ByVal ColumnData As String)
Me._Delete(ColumnName, ColumnData)
End Sub
Public Sub Delete(ByVal BusinessField As ViperWorks.Core.Data.BusinessField)
Me._Delete(Me._KeyColumns(0), BusinessField.BusinessFieldID)
End Sub
Public Sub Delete(ByVal BusinessFieldID As String)
Me._Delete(Me._KeyColumns(0), BusinessFieldID)
End Sub
Public Function ListSelectAll() As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Return Me._ListSearch(Nothing, Nothing)
End Function
Public Function ListSelectAllFriendly() As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Dim OrderList As List(Of ViperWorks.Core.Searching.OrderInfo)
Dim DisplayList(2) As String
Try
DisplayList(0) = "BusinessFieldID"
DisplayList(1) = "BusinessFieldName"
DisplayList(2) = "BusinessFieldFriendly"
OrderList = New List(Of ViperWorks.Core.Searching.OrderInfo)
OrderList.Add(New ViperWorks.Core.Searching.OrderInfo("BusinessFieldName", Searching.OrderInfo.eSortType.Ascending))
Return Me._ListSearch(Nothing, OrderList, DisplayList)
Catch ex As Exception
Return Nothing
End Try
End Function
Public Function TableSelectAll() As DataTable
Return Me._TableSearch(Nothing, Nothing)
End Function
Public Function DeleteAll() As Boolean
Return Me._DeleteAll()
End Function
Private Function _Insert(ByVal MyBusinessField As ViperWorks.Core.Data.BusinessField) As String
Dim Keys As String()
Try
Keys = Me._ActiveContract.DataInsert(Me._ClientID, MyBusinessField.MetaFieldList, Me._TableName, Me._KeyColumns)
If (Not Keys Is Nothing AndAlso Keys.Length >= 1) Then
Return Keys(0)
Else
Return Nothing
End If
Catch ex As Exception
Throw
End Try
End Function
Private Function _Update(ByVal MyBusinessField As ViperWorks.Core.Data.BusinessField) As Boolean
Dim bField As ViperWorks.Core.Data.BusinessField
Dim MyFieldList As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Try
bField = New ViperWorks.Core.Data.BusinessField(Me._KeyColumns(0), _
MyBusinessField.BusinessFieldID, Data.BusinessField.eBusinessFieldType.String)
MyFieldList = New Generic.List(Of ViperWorks.Core.Data.BusinessField)
MyFieldList.Add(bField)
Return Me._ActiveContract.DataUpdate(Me._ClientID, MyBusinessField.MetaFieldList, Me._TableName, MyFieldList)
Catch ex As Exception
Throw
End Try
End Function
Private Function _Delete(ByVal ColumnName As String, ByVal ColumnData As String) As Boolean
Dim bField As ViperWorks.Core.Data.BusinessField
Dim MyFieldList As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Try
bField = New ViperWorks.Core.Data.BusinessField(ColumnName, ColumnData, Data.BusinessField.eBusinessFieldType.String)
MyFieldList = New Generic.List(Of ViperWorks.Core.Data.BusinessField)
MyFieldList.Add(bField)
Return Me._ActiveContract.DataDelete(Me._ClientID, Me._TableName, MyFieldList)
Catch ex As Exception
Throw
End Try
End Function
Private Function _DeleteAll() As Boolean
Return Me._ActiveContract.DataDeleteAll(Me._ClientID, Me._TableName)
End Function
Private Function _Select(ByVal BusinessFieldID As String, ByVal UseFieldName As Boolean) As ViperWorks.Core.Data.BusinessField
Dim bTable As Data.BusinessTable
Dim bField As ViperWorks.Core.Data.BusinessField
Dim MyFieldList As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Dim MyBusinessField As ViperWorks.Core.Data.BusinessField = Nothing
Try
If (UseFieldName) Then
bField = New ViperWorks.Core.Data.BusinessField("BusinessFieldName", _
BusinessFieldID, Data.BusinessField.eBusinessFieldType.String)
Else
bField = New ViperWorks.Core.Data.BusinessField(Me._KeyColumns(0), _
BusinessFieldID, Data.BusinessField.eBusinessFieldType.String)
End If
MyFieldList = New Generic.List(Of ViperWorks.Core.Data.BusinessField)
MyFieldList.Add(bField)
bTable = Me._ActiveContract.DataSelect(Me._ClientID, Me._TableName, MyFieldList)
If (bTable IsNot Nothing AndAlso bTable.BusinessFields.Count > 0) Then
MyBusinessField = New ViperWorks.Core.Data.BusinessField
ViperWorks.Core.Data.DataHelper.ConvertObjectFromBusinessTable( _
CType(MyBusinessField, ViperWorks.Core.Data.GeneralObject), bTable)
End If
Return MyBusinessField
Catch ex As Exception
Throw
End Try
End Function
Private Function _ListSearch(ByVal SearchList As Generic.List(Of ViperWorks.Core.Searching.SearchInfo), _
ByVal OrderList As Generic.List(Of ViperWorks.Core.Searching.OrderInfo), _
Optional ByVal DisplayList() As String = Nothing) As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Dim ReturnDS As DataSet
Dim ReturnList As Generic.List(Of ViperWorks.Core.Data.BusinessField)
Try
ReturnList = New Generic.List(Of ViperWorks.Core.Data.BusinessField)
ReturnDS = Me._ActiveContract.DataSearch(Me._ClientID, SearchList, Me._TableName, OrderList, 0, DisplayList)
If (Not ReturnDS Is Nothing AndAlso ReturnDS.Tables.Count > 0) Then
For i As Integer = 0 To ReturnDS.Tables(0).Rows.Count - 1
Dim NewBusinessField As New ViperWorks.Core.Data.BusinessField
ViperWorks.Core.Data.DataHelper.ConvertObjectFromRow( _
CType(NewBusinessField, ViperWorks.Core.Data.GeneralObject), ReturnDS.Tables(0).Rows(i))
ReturnList.Add(NewBusinessField)
Next
End If
Return ReturnList
Catch ex As Exception
Throw
Return Nothing
End Try
End Function
Private Function _TableSearch(ByVal SearchList As Generic.List(Of ViperWorks.Core.Searching.SearchInfo), _
ByVal OrderList As Generic.List(Of ViperWorks.Core.Searching.OrderInfo)) As DataTable
Dim ReturnDS As DataSet
Try
ReturnDS = Me._ActiveContract.DataSearch(Me._ClientID, SearchList, Me._TableName, OrderList, 0)
If (Not ReturnDS Is Nothing AndAlso ReturnDS.Tables.Count > 0) Then
Return ReturnDS.Tables(0)
Else
Return Nothing
End If
Catch ex As Exception
Throw
Return Nothing
End Try
End Function
End Class
So that's all you need to do to provide the basic INSERT, UPDATE, SELECT, DELETE functionality. It's not super easy,
but it's easier than writing all the required TSQL. That code above will work on all supported databases. It's also
not the most efficient code. If you are in a bind and need super efficient code, you can access the actual database
connection from code as well (via the WCF contract).
So what do these different screens do?
Out of the box, Ignition comes with a few modules:
• CORE - this is the driving module behind Ignition.
• MISC - this is a sample module, not required for functionality but has some cool stuff (like a WPF example).
• ROSTER - a WinForms example showing how to create a schedule like program.
We're going to go over each of these screens. To explain the framework, it'll help to understand what these different
screens do, because changing the data in these screens can change functionality. Basically start Ignition, go to the
Menu and expand all the nodes and I'll explain what they do.
CORE2070
Ignition > Data > Export Profiles
This screen maintains export profiles. You can use export profiles to export data. How you export data can be with
the export profile itself (limited in some ways) to using TSQL to using a .NET script. This screen has a couple
tasks hanging off it. Some tasks include setting up criteria, ordering, output columns and some tasks that
let you export all data or just a sample. Samples don't with with TSQL or .NET scripts.
CORE2080
Ignition > Data > Import Profiles
This screen maintains import profiles. You can use import profiles to miport data. How you import data can be with
the import profile itself (limited in some ways) or using a .NET script to import data. This screen has some tasks
hanging off it including import columns, the actual import and a test import with no data modification.
CORE2116
Ignition > Email > Email Log
This screen displays a listing of all emails that have been sent using the Ignition framework.
CORE2096_EXPORT
Ignition > Help > Export System Help
Lets you export all help in Ignition. Can be used to just update help without updating functionality.
CORE2096_IMPORT
Ignition > Help > Import System Help
Lets you import all help in Ignition. Can be used to just update help without updating functionality.
CORE2098
Ignition > Reporting > Report Maintenance
Lets you maintain reports. This is not finished 100% and needs more work (as mentioned at the end).
CORE2112
Ignition > Scheduling > Schedule Maintenance
Manages schedule data. Schedules are like the Task Scheduler in Windows. Idea is to run your code at
specific intervals. Useful for data integration scenarios. Schedules can also be used to run scripts.
CORE2025
Ignition > Searching > QuickSearch Items
Lets you maintain QuickSearch items (top right of UI). QuickSearch items lets you quickly search the
system for data. Great for customer service scenarios when they need quick access to any bit of data.
CORE2014
Ignition > Searching > Search Definitions
Search Definitions form the bulk of Ignition. When you are on a screen and need to find data - what do
you do? You normally search for the data. Searching lets you define styles (so when a certain row meets
a certain condition, you can change the appearance. Like negative dollar amounts). You define the ordering,
grouping, criteria and what gets shown. Searches can use TSQL and .NET scripts. This is a big, big part,
so any questions on this, give me a shout.
CORE2040
Ignition > Security > Manual Security
Lets you define manual security for screens, as well as custom menu items.
CORE2034
Ignition > Security > Roles
Lets you setup the roles in Ignition.
CORE2036
Ignition > Security > Users
Lets you setup the user in Ignition.
CORE2056
Ignition > Security > Active Directory
If you are on a Windows domain with AD, you can hook Ignition into AD. Can be used to import
users as well as use the Windows password for authentication.
CORE2038
Ignition > Security > Security Configuration
Same thing as manual security but in a nice, easy to use GUI.
CORE2008
Ignition > System > Business Fields
Lets you maintain field metadata. For every field in the DB, you should have the same field here.
CORE2000
Ignition > System > Business Modules
You need one for your application. A module is essentially a DLL with your custom functionality.
CORE2004
Ignition > System > Business Objects
Bad naming looking back, but this is your screen (ie. user control).
CORE2010
Ignition > System > Business Tables
Lets you maintain the tables in Ignition. For every table in the DB, you should have one here.
There is also a task off here letting you create the code that corresponds.
CORE2126
Ignition > System > Languages
Lets you defined multi-lingual support. When you create a language, you must also specify the spell check
dictionaries to use (defined on CORE2124). Ignition comes with English, two versions of Maori,
Modern French and Spanish. Multi-lingual support is limited (at the moment) to word replacement.
CORE2118
Ignition > System > Relations
This is partially implemented - basically you setup the relations from one table to another.
The only place right now this is used is reporting - for the XtraReport designer to figure out
relations, you must put them in here.
CORE2124
Ignition > System > SpellChecking
This screen lets you load the spell check dictionaries. You setup the name, put in the core words,
the grammar and the user words.
CORE2092
Ignition > System > System Help
Lets you put in your own help. Great for task based help instead of function.
CORE2092.IMAGES
Ignition > System > System Help Images
Lets you upload any images you want to be used in help. The best way to learn how
this works is to check out the existing help.
CORE2068
Ignition > System > System History
Lets you defined the system history tables that are used for transaction recording.
You can then see all the transactions processed on that table.
CORE2088
Ignition > System > System Scripts
Lets you create scripts that can be used in reporting, data imports/exports and
scripts for searching. To see a working example, check out the "Security Search Script".
This is used when searching for manual security items.
CORE2094
Ignition > System > System Settings
Lets you see and modify all system settings.
CORE2110
Ignition > System > Control Panel
Configures core functionality. Things like the header color, environment colors, the environment
type, if the password should be forced entry, active directory settings, shortcut settings, help
settings (like title, version, pre and post processing), email settings, custom logos and images,
the product key, and any custom skins used. You can also restrict what skins can be used and you
can also set a forced skin for all users.
CORE2122.IMPORTKEY
Ignition > System > Import Product Key
Lets you import the product key. Ignition comes with an app that lets you generate the key.
CORE2046
Ignition > Utilities > Attachments
Lets you browse all attachments in the system.
CORE2060
Ignition > Utilities > Ignition Tips
Lets you modify the tip of the days used.
CORE2030
Ignition > Utilities > Quotations
Lets you modify the system quotes.
CORE2044
Ignition > Utilities > Stickies
Lets you browse all stickies in the system.
CORE2028
Ignition > Utilities > World Events
Lets you modify the system world events.
CORE2026
Ignition > Utilities > Exceptions
Lets you view exceptions that have been raised. This includes all client exceptions
and also exceptions from the server apps.
CORE2012
Ignition > Utilities > Record Locks
Lets you see the locks that have been placed on records.
CORE2062
Ignition > Utilities > Statistics
Lets you view how the system is being used.
CORE2012.CLEARALL
Ignition > Utilities > Clear All Locks
Simple routine that will clear all record locks. Great example to see how a function
is used in Ignition as opposed to a user entry screen.
CORE2120
Ignition > Web > Fusion Bindings
Lets you maintain Fusion bindings. These aren't used for the normal WinForms user controls,
but they must be used if you are going down the WPF road. WPF screens require a webservice
for business logic processing. Obviously the Fusion service needs to be working.
CORE2114
Ignition > Web > WebService Access
Lets you setup what webservices will be available and what users can access the webservice.
Webservices can also be setup to be available to all authenticated users. For the more secure
webservices, you can restrict security to designated users.
Thes screens apply to the ROSTER module.
ROSTER2020
Rostering > Schedule > My Schedule
Read only screen to see what the logged in user has scheduled for work.
ROSTER2000
Rostering > Schedule > Schedule Management
Main screen to modify all users schedules. You can setup security so certain profiles can edit data
on this screen, but other users can only view.
ROSTER1140
Rostering > Control Panel > Categories
Lets you maintain categories for schedules.
ROSTER1000
Rostering > Control Panel > Employees
Lets you maintain employees for schedules. The ROSTER module doesn't work off the user
table because you may want a custom list. You can however indicate a user as a system
user.
ROSTER1160
Rostering > Control Panel > Locations
Lets you maintain locations for schedules.
ROSTER1020
Rostering > Control Panel > Positions
Lets you maintain positions for schedules.
ROSTER1100
Rostering > Control Panel > Shifts
Lets you maintain shifts for schedules.
ROSTER1060
Rostering > Control Panel > Skills
Lets you maintain skills for schedules.
ROSTER1040
Rostering > Control Panel > Teams
Lets you maintain teams for schedules.
Thes screens apply to the MISC module.
MISC2000
Miscellaneous > Developer > Code Snippets
Simple screen that I use to store code snippets.
MISC2020
Miscellaneous > Developer > Expression Tester
I did not create the bulk of this code. I forgot where I got this from, but this code
IS NOT mine. I wanted a built in way to Ignition to manipulate regular expressions and this
worked out pretty fine. It's also a real complicated screen.
MISC2010
Miscellaneous > Developer > XML Tester
Quite often I work with XML and need to format it - this screen does that.
And stores the XML too.
MISC2006
Miscellaneous > Other Tools > Dictionary
Test screen more than anything else to test large records.
MISC2002
Miscellaneous > Other Tools > Journal
Another test screen to test journal entry. Great example of how a search can be used
to only return data for the logged in user.
MISC2004
Miscellaneous > Other Tools > WPF Test
Need to have Fusion working to get this screen to work. Simple test of a WPF screen.
The other client app - Plasma
The other client application is called Plasma. There is nothing real special about this app, it
just uses the multicasting described below to find all the services on the network and provide
diagnostic information about the services.

Plasma seeing four different services in two environments (DEMO, LIVE).
So why would you want to use this tool? It is for admins, it's not for end users. For Reactor, you
can see the connected clients, the recent database transactions, the actual database connections and
any events the service deems rather important. For Fusion, you can see the tokens that have been
requested (both un-authorized and authorized), the functions that have been executed and the
services that are hosted. For Scheduler, you can see the running schedules and the schedules that
have been marked to run. It could be argued that this information could be put in a screen in
Ignition, but I wanted a seperate application just for admin without the entire client.
Cool Feature - Multicasting
In all the application configs (either the .config file or XML settings), there is a key
called MulticastURL. All applications either listen on or broadcast on this URL/IP combination.
By default it is 224.100.0.1:61555. The server side code looks like:
Public Class UdpServerApplication
Public Event OnReceivedRequest(ByVal ClientIP As String)
Public Event OnError(ByVal e As Exception)
Private _Port As Integer
Private _gAddress As String
Private _ServiceName As String
Private _ServiceGroup As String
Private _ServiceFriendly As String
Private _ServiceURL As String
Private _ServiceHealthURL As String
Private _ServiceApplication As String
Private _ServiceLoad As String
Private _ServiceOrder As Integer
Private _ServiceXML As String
Private _ServerSock As UdpClient = Nothing
Private _ListenThread As Thread
Private _IsListening As Boolean = False
Public Sub New(ByVal ServiceName As String, ByVal ServiceFriendly As String, _
ByVal ServiceURL As String, ByVal ServiceHealthURL As String, ByVal ServiceGroup As String, _
ByVal ServiceApplication As String, ByVal ServiceLoad As String, ByVal ServiceOrder As Integer)
Me.New(String.Empty, 0, ServiceName, ServiceFriendly, ServiceURL, ServiceHealthURL, _
ServiceGroup, ServiceApplication, ServiceLoad, ServiceOrder)
End Sub
Public Sub New(ByVal GroupAddress As String, ByVal Port As Integer, _
ByVal ServiceName As String, ByVal ServiceFriendly As String, _
ByVal ServiceURL As String, ByVal ServiceHealthURL As String, ByVal ServiceGroup As String, _
ByVal ServiceApplication As String, ByVal ServiceLoad As String, ByVal ServiceOrder As Integer)
If (Port <= 0) Then
Port = 61555
End If
If (String.IsNullOrEmpty(GroupAddress)) Then
GroupAddress = "224.100.0.1"
End If
Me._Port = Port
Me._gAddress = GroupAddress
Me._ServiceName = SecurityElement.Escape(ServiceName)
Me._ServiceFriendly = SecurityElement.Escape(ServiceFriendly)
Me._ServiceURL = SecurityElement.Escape(ServiceURL)
Me._ServiceHealthURL = SecurityElement.Escape(ServiceHealthURL)
Me._ServiceGroup = SecurityElement.Escape(ServiceGroup)
Me._ServiceApplication = SecurityElement.Escape(ServiceApplication)
Me._ServiceLoad = SecurityElement.Escape(ServiceLoad)
Me._ServiceOrder = ServiceOrder
Me._ServiceXML = BuildServiceInfo()
End Sub
Public Sub Start()
If (Me._IsListening = False) Then
Me._IsListening = True
Me._ListenThread = New Thread(AddressOf StartListener)
Me._ListenThread.Start()
End If
End Sub
Public Sub [Stop]()
Me._IsListening = False
Try
Me._ServerSock.Close()
Catch ex As Exception
End Try
End Sub
Private Sub StartListener()
Dim groupEP As IPEndPoint
Dim groupAddress As IPAddress
Try
Me._ServerSock = New UdpClient
groupAddress = IPAddress.Parse(_gAddress)
groupEP = New IPEndPoint(IPAddress.Any, _Port)
Me._ServerSock.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, True)
Me._ServerSock.Client.Bind(groupEP)
Me._ServerSock.JoinMulticastGroup(groupAddress)
While (Me._IsListening)
Dim bytes As Byte() = Nothing
Dim response As Byte() = Nothing
Dim buffer As String = String.Empty
Dim message As UdpMessage
Dim sendResponse As Boolean = False
Try
bytes = _ServerSock.Receive(groupEP)
buffer = Encoding.Unicode.GetString(bytes, 0, bytes.Length)
RaiseEvent OnReceivedRequest(groupEP.ToString())
Try
message = New UdpMessage(buffer)
Catch ex As Exception
message = Nothing
End Try
If (message IsNot Nothing) Then
If (String.IsNullOrEmpty(message.MessageFilter)) Then
sendResponse = True
Else
If (message.MessageFilter.ToLower.Contains(Me._ServiceApplication.ToLower)) Then
sendResponse = True
Else
sendResponse = False
End If
End If
Select Case message.MessageType.ToLower
Case "service.info"
response = System.Text.Encoding.Unicode.GetBytes(Me._ServiceXML)
End Select
If (sendResponse) Then
If (response IsNot Nothing) Then
Me._ServerSock.Send(response, response.Length, groupEP)
End If
End If
End If
Catch ex As Exception
End Try
End While
Catch ex As Exception
RaiseEvent OnError(ex)
Finally
If (Me._ServerSock IsNot Nothing) Then
Me._ServerSock.Close()
End If
End Try
End Sub
Private Function BuildServiceInfo() As String
Dim xBuilder As New StringBuilder
xBuilder.AppendLine("<viperXml type=""service.info.response"" version=""1.0"">")
xBuilder.AppendFormat(" <service name=""{0}"" friendly=""{1}"" url=""{2}""
healthUrl=""{3}"" group=""{4}"" application=""{5}"" load=""{6}"" order=""{7}""/>", _
Me._ServiceName, _
Me._ServiceFriendly, _
Me._ServiceURL, _
Me._ServiceHealthURL, _
Me._ServiceGroup, _
Me._ServiceApplication, _
Me._ServiceLoad, _
Me._ServiceOrder _
)
xBuilder.AppendLine("</viperXml>")
Return xBuilder.ToString
End Function
End Class
Then you have the client code:
Public Class UdpClientApplication
Public Event OnServiceFound(ByVal FoundService As ServiceItem)
Public Event OnError(ByVal e As Exception)
Private _Port As Integer
Private _gAddress As String
Private _ServiceMessage As UdpMessage
Private _ClientSock As UdpClient
Private _AppFilter As String
Private _SendThread As Thread
Private _SendTime As Integer
Private _IsSending As Boolean = False
Public Sub New(ByVal Filter As String)
Me.New(String.Empty, 0, 2, Filter)
End Sub
Public Sub New(ByVal GroupAddress As String, ByVal Port As Integer, ByVal Timeout As Integer, ByVal Filter As String)
If (Port <= 0) Then
Port = 61555
End If
If (String.IsNullOrEmpty(GroupAddress)) Then
GroupAddress = "224.100.0.1"
End If
If (Timeout <= 0) Then
Timeout = 10
ElseIf (Timeout > 30) Then
Timeout = 30
End If
Me._SendTime = Timeout
Me._AppFilter = Filter
Me._Port = Port
Me._gAddress = GroupAddress
Me._ServiceMessage = New UdpMessage
Me._ServiceMessage.MessageType = "service.info"
Me._ServiceMessage.MessageFilter = Me._AppFilter
End Sub
Public Sub Start()
If (Me._IsSending = False) Then
Me._IsSending = True
Me._SendThread = New Thread(AddressOf StartSender)
Me._SendThread.Start()
End If
End Sub
Public Sub [Stop]()
Me._IsSending = False
Try
_ClientSock.Close()
Catch ex As Exception
End Try
End Sub
Private Sub StartSender()
Dim iep As IPEndPoint
Dim sendMsg As Byte()
Dim count As Integer
Try
_ClientSock = New UdpClient
_ClientSock.EnableBroadcast = True
iep = New IPEndPoint(IPAddress.Parse(Me._gAddress), Me._Port)
sendMsg = Encoding.Unicode.GetBytes(Me._ServiceMessage.ToXML)
Dim s As New UdpState
s.Client = _ClientSock
s.EndPoint = iep
count = _SendTime * 16
For i As Integer = 0 To count
If (Me._IsSending = False) Then
Exit For
Else
_ClientSock.Send(sendMsg, sendMsg.Length, iep)
_ClientSock.BeginReceive(New AsyncCallback(AddressOf ReceiveCalback), s)
Thread.Sleep(40)
End If
Next
Catch ex As Exception
RaiseEvent OnError(ex)
Finally
_ClientSock.Close()
End Try
End Sub
Private Sub ReceiveCalback(ByVal ar As IAsyncResult)
Dim myEnv As ServiceItem
Dim uClient As UdpClient
Dim ePoint As IPEndPoint
Dim receiveBytes() As Byte
Dim receiveString As String
Try
uClient = CType((CType(ar.AsyncState, UdpState)).Client, UdpClient)
ePoint = CType((CType(ar.AsyncState, UdpState)).EndPoint, IPEndPoint)
receiveBytes = uClient.EndReceive(ar, ePoint)
receiveString = Encoding.Unicode.GetString(receiveBytes)
myEnv = GetService(receiveString)
If (myEnv IsNot Nothing) Then
RaiseEvent OnServiceFound(myEnv)
End If
Catch ex As Exception
End Try
End Sub
Private Function GetService(ByVal XMLString As String) As ServiceItem
Dim xDoc As Xml.XmlDocument
Dim svcNode As Xml.XmlNode
Dim env As ServiceItem = Nothing
Try
xDoc = New Xml.XmlDocument
xDoc.LoadXml(XMLString)
svcNode = xDoc.SelectSingleNode("/viperXml/service")
If (svcNode IsNot Nothing) Then
env = New ServiceItem
env.ServiceName = svcNode.Attributes("name").Value
env.ServiceFriendly = svcNode.Attributes("friendly").Value
env.ServiceURL = svcNode.Attributes("url").Value
env.ServiceHealthURL = svcNode.Attributes("healthUrl").Value
env.ServiceGroup = svcNode.Attributes("group").Value
env.ServiceApplication = svcNode.Attributes("application").Value
env.ServiceLoad = svcNode.Attributes("load").Value
Try
env.ServiceOrder = CInt(svcNode.Attributes("order").Value)
Catch ex As Exception
env.ServiceOrder = 0
End Try
End If
Return env
Catch ex As Exception
Return Nothing
End Try
End Function
Private Class UdpState
Private _EndPoint As IPEndPoint
Private _Client As UdpClient
Public Property EndPoint() As IPEndPoint
Get
Return Me._EndPoint
End Get
Set(ByVal value As IPEndPoint)
Me._EndPoint = value
End Set
End Property
Public Property Client() As UdpClient
Get
Return Me._Client
End Get
Set(ByVal value As UdpClient)
Me._Client = value
End Set
End Property
End Class
End Class
By using this multicasting, the client application can get information about the server application without the need
for any config files (well, apart from the server side configs). The idea was to get the client application as thin as
possible so that the client doesn't know anything about the installation. Just install and run. The code above
was developed from a variety of sources. So by using this multicasting, you can just create another environment (like DEV
or LIVE) and the client will just know about it, no reconfiguration of configs required.
The multicasting implemented works like this:
1 - You have server applications (Reactor, Fusion, Scheduler) listening for requests.
2 - A client application broadcasts on an IP/Port, requesting info from any services.
3 - The services respond back with connection information.
4 - The client then connects.
There is also filtering involved. The Ignition WinForms application only needs to know about Reactor services. So when
it sends its message, it says, I only want to know about Reactor applications. Although all server apps get the request,
only the Reactor application sends a response back. I've tested this on single computer setups, on small networks and
on large networks. Works a charm in all tested scenarios. Where it falls over is when you connect to a VPN - I've
noticed in some cases, the client doesn't get any responses back. If you have the server app on a computer with a VPN,
it really doesn't like that, but that's a very small chance a server would be on a VPN.
There is actually a seperate project, not in the Ignition realm that demonstrates how you could implement this. Parts of
it are designed for Ignition, but it's totally standalone. Check out the ViperWorks.Multicast project in the source
to see how you could implement something like this.
The Reactor Application
Very early in the development of Ignition, the Reactor server-side application was developed. This application
exists solely to perform database transactions. Sadly, the WinForms client connects straight into this service
making the framework pretty fat. If I were to redesign Ignition, I wouldn't have the client connect into Reactor,
but connect to Fusion (webservice hosting) instead. It's not entirely a bad thing, the system works, I would just
go down a different road if I were to do this again.
Basically when a transaction comes through (like an Insert), Reactor does this:
Public Function DataInsert(ByVal ClientID As System.Guid, _
ByVal FieldList As List(Of BusinessField), ByVal TableName As String, _
ByVal KeyColumns() As String) As String() Implements IReactorContract.DataInsert
Dim Client As ReactorClient = Nothing
Dim ReturnData As String()
Dim SQLCommand As StringBuilder = Nothing
Dim StartTime As Date = Now
Dim conn As ServerConnection = Nothing
Dim cCommand As String = String.Empty
Try
cCommand = String.Format("INSERT ({0})", TableName)
Client = FindClientItem(ClientID)
If (Client IsNot Nothing) Then
If (ReactorHelper.SQLCommandHistoryMax > 0) Then
SQLCommand = New StringBuilder
End If
conn = ReactorHelper.CheckoutConnection(Client, cCommand)
ReactorHelper.IncrementRequests()
StartTime = Now
Client.TimeLastAccess = Now
Client.RequestCurrent = cCommand
Client.ClientStatus = ReactorClient.eClientStatus.Busy
ReturnData = conn.Connection.Insert(FieldList, TableName, KeyColumns, SQLCommand)
Core.ReactorHelper.AddInsertTranaction(TableName, ReturnData, Client.ClientUser)
Core.ReactorHelper.LogCommand(Client, "INSERT", TableName, StartTime, Now)
Me.AddSQLCommand(SQLCommand, Client, StartTime, Now, True)
Return ReturnData
Else
Return Nothing
End If
Catch ex As Exception
Dim reactorEx As New ReactorException
Dim infoString As String
If (TypeOf ex Is Exceptions.DataTableLockException) Then
infoString = String.Format(String.Format( _
"TABLE LOCKED: The table {0} is locked from write access. Locked on {1} by {2}.", _
CType(ex, Exceptions.DataTableLockException).TableName, _
CType(ex, Exceptions.DataTableLockException).LockDate.ToString, _
CType(ex, Exceptions.DataTableLockException).LockUser _
))
Else
infoString = "INSERT FAILURE: DataInsert"
End If
Core.ReactorHelper.LogException(Client, ex)
Me.AddSQLCommand(SQLCommand, Client, StartTime, Now, False)
If (SQLCommand IsNot Nothing) Then
reactorEx = New ReactorException( _
ex.Source, _
ex.StackTrace, _
ex.Message, _
ex.ToString, _
SQLCommand.ToString _
)
Else
reactorEx = New ReactorException( _
ex.Source, _
ex.StackTrace, _
ex.Message, _
ex.ToString, _
String.Empty _
)
End If
Throw New FaultException(Of ReactorException)(reactorEx, infoString)
Return Nothing
Finally
If (conn IsNot Nothing) Then
ReactorHelper.CheckInConnection(Client, conn)
End If
If (Client IsNot Nothing) Then
ReactorHelper.DecrementRequests()
Client.TimeLastAccess = Now
Client.RequestLast = String.Format("INSERT ({0})", TableName)
Client.RequestCurrent = ""
Client.ClientStatus = ReactorClient.eClientStatus.Idle
End If
End Try
End Function
So first off, Reactor finds the client based on the passed in ClientID. It then checks out an actual DB connection.
This connection is essentially an instance of IDBInterface (see ViperWorks.Core.Data.IDBInterface). We then increment
the request count, set statistic data on the client, and then perform the Insert on the connection. After that
we log the request and then return the data. In the Finally statement, we check the connection back in. For those of
you that downloaded the source to Ignition a couple years back, Reactor didn't have this check in/out functionality.
As a result of this, Reactor wasn't very thread friendly and caused a bit of chaos in moderate load situations.
I won't go into the entire structure of Reactor here. If there is demand, I'll write another article devoted to Reactor
and how the application works in whole. To keep moving along, we'll take a quick look at the config of Reactor.
<configuration>
<application>
<!---->
<!---->
<setting key="ServiceName" value="ViperWorks.Ignition.Reactor.DEMO"/>
<setting key="ServiceDescription" value="Demo Database Service for ViperWorks Reactor."/>
<setting key="ServicePort" value="8810"/>
<setting key="ServiceURI" value="demo.console"/>
<!---->
<setting key="ApplicationName" value="DEMO.CLIENT.CONSOLE"/>
<setting key="ApplicationGroup" value="DEMO"/>
<setting key="ApplicationFriendly" value="DEMO (Default)"/>
<setting key="ApplicationLoad" value="SERVER,CLIENT"/>
<setting key="ApplicationOrder" value="0"/>
<!---->
<setting key="HealthPort" value="8000"/>
<setting key="HealthURI" value="demo.console"/>
<setting key="MulticastURL" value="224.100.0.1:61555"/>
<!---->
<!---->
<setting key="DataAssembly" value="ViperWorks.Core.Data"/>
<setting key="DataAssemblyType" value="ViperWorks.Core.Data.Access.DatabaseInterface"/>
<setting key="DataBase" value="\Data\ViperWorks.Ignition.Reactor.Access.mdb"/>
<setting key="DataServer" value="localhost"/>
<setting key="DataUsername" value=""/>
<setting key="DataPassword" value=""/>
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<setting key="LogDirectory" value="\Logs"/>
<setting key="LogType" value="EXCEPTION,SQL,FILE"/>
<!---->
<!---->
<!---->
<setting key="TempDirectory" value="\Temp"/>
<!---->
<setting key="CommandHistoryMax" value="200"/>
<!---->
<setting key="BusinessTableName" value="XSYS_BUSINESSTABLE"/>
<setting key="BusinessFieldName" value="XSYS_BUSINESSFIELD"/>
<setting key="TransactionName" value="XSYS_TRANSACTION"/>
<setting key="TransactionEnabled" value="false"/>
<!---->
<!---->
<!---->
<!---->
<!---->
<setting key="MetaRefreshInterval" value="1"/>
</application>
</configuration>
Service
The service part of the config configures what core service information. The service name
and description is what is showed in the Windows Service window. The Port is the port Reactor
listens on for WCF connections and same thing with the URI.
Application
The application part is used in an important part, the multicasting.
The application name is the unique name for the service. Application group is the group of applications.
In a real world scenario, this would be DEV, TEST, or LIVE. Friendly is just the user friendly name.
Load is the type of service. Sometimes you want to create multiple Reactors for load balancing. The Ignition
client will only display CLIENT load types not SERVER. So if you want a reactor for server use, only put
SERVER in there, and the Ignition client won't see it. Order is the order this environment should be displayed in.
Health
Each service (Reactor, Fusion, Scheduler) hosts a generic WCF contract (in the case of Reactor and Fusion, this
is in addition to the already hosted contracts) that contains health information. The Plasma tool (see above) uses
this information to provide diagnostic information to system admins.
Data
Reactor needs to know how to connect to the database. That's what this section is for. The DataAssembly is the
name of the assembly which contains your driver. For Access, MSSQL and MySQL connections this will be ViperWorks.Core.Data.
If you create your own assembly, put this in here. DataAssemblyType is the name of the class (include the entire name,
not just the class name). In the supplied config this is ViperWorks.Core.Data.Access.DatabaseInterface. Change this to
ViperWorks.Core.Data.MSSQL.DatabaseInterface for the MSSQL driver or ViperWorks.Core.Data.MySQL.DatabaseInterface for
the MySQL driver. Database is the name of the database, for MDB, this is the file path. Put a slash at the front
to use the current directory as the start. Server is the name of the DB server (use something like SERVER\INSTANCE for
instance explicit configuration). And Username/Password is the usual stuff. From memory, if you don't put this in here
for MSSQL implementation, integrated security will be used.
Logging
Logging is pretty self-explanatory, see the XML comments for more info.
Command History
This history can be used in Plasma. Ideally, don't keep this too large in production scenarios. You can use this
to see all the recent transactions being pushed through Reactor. If you want a full history, use the logging above
and write out SQL commands to a text file. Be warned this will affect performance. It's not too bad of a hit, but
it is noticeable in MDB scenarios.
DB Tables, Transaction and MetaRefresh
Although not fully implemented, Ignition can maintain a list of metadata relating to Tables and Fields. You
specify in here what the names of the tables are. BusinessTableName is used for pulling back all the tables in the system.
This was going to be used for the notification - some tables you want to see notifications for, so the service needs
to know about them. And Fields was going to be used to enforce your rules. So if a field is setup to be upper case,
Reactor would up case the data. TransactionName is the name of the transaction table - used for notifications. I've
set the included release to not use transactions. MetaRefreshInterval is how often Reactor should update its metadata.
Use -1 for a once off when the service starts, 0 to disable or any other number for the refresh interval in minutes.
Another cool feature of the server apps is that they use port sharing. This enables me or you to have all the services
using standard ports, the only thing that changes it the URI. Great if you have multiple services on a site since you
don't have to have a different port for each service.
Make sense? Just remember that Reactor is to serve up database requests. It sits over what ever database you are using
and puts a object layer over the top. The Ignition client then uses the business logic, the business logic connects into
Reactor and does its work.
The Fusion Application
Fusion came after the WinForms client was written (well, half written). Looking back, this should have been written after
Reactor. Fusion basically sits on top of Reactor. Based on the configuration you did via the WinForms client, Fusion
will host many different webservices that exposes your business logic via WCF contracts. It's really that simple. It takes
apart your code via Reflection, and puts a wrapper over top of it. So if you had a function called GetLollipops and that
returned a String, it would be turned into GetLollipops(Token As String). Every method you expose externally requires
a token. If an invalid token is specified, you can't use that method.
The token you need can be obtained from the authentication service (so http://localhost/authentication). Basically you
pass in your username and your web password (web password is on XSYS_USER table), the webservice ID and you can
get your token. Tokens expire after a set amount of time.
I've done some testing with using SSL and Fusion and yes - you can do it. It's a bit of a pain, mainly in the Windows
side of things, but it is possible so you can protect the communication channels. As a downside though, you can't host
Fusion within IIS7 or above. Fusion was designed as a standalone application to host webservices.
We'll take a quick look at the config, it's similar to Reactor, but also different:
="1.0" ="utf-8"
<configuration>
<application>
-->
-->
-->
-->
<setting key="ServiceName" value="ViperWorks.Ignition.Fusion.DEMO"/>
<setting key="ServiceDescription" value="Fusion Service for DEMO."/>
<setting key="HttpPort" value="80"/>
<setting key="NetTcpPort" value="8127"/>
<setting key="Hostname" value=""/>
<setting key="TokenTimeOut" value="20"/>
<setting key="TokenRemoval" value="60"/>
-->
-->
-->
-->
-->
<setting key="SecureMode" value="false"/>
<setting key="SecureCertificate" value="c:\mycert.pfx"/>
<setting key="SecureCertificatePassword" value="test"/>
<setting key="ForceBasicBindings" value="false"/>
-->
<setting key="HealthPort" value="8888"/>
<setting key="HealthURI" value="fusion"/>
-->
<setting key="ApplicationName" value="FUSION_DEMO"/>
<setting key="ApplicationGroup" value="DEMO"/>
<setting key="ApplicationFriendly" value="Demo Fusion"/>
<setting key="ApplicationLoad" value="SERVER"/>
<setting key="ApplicationOrder" value="0"/>
-->
-->
<setting key="ReactorURL" value="net.tcp://localhost:8810/demo.console"/>
<setting key="MulticastURL" value="224.100.0.1:61555"/>
-->
-->
-->
-->
-->
-->
<setting key="LogDirectory" value="\Logs"/>
<setting key="LogType" value="INFO,EXCEPTION,EVENT,FILE"/>
-->
<setting key="DependantOnServices" value = "ViperWorks.Ignition.Reactor.DEMO;" />
</application>
</configuration>
Service
The service part of the config configures what core service information. The service name
and description is what is showed in the Windows Service window. The HTTP port is the port that the http WCF
contract is bound to. Same story with the NetTcpPort. Set to 0 to not use the specified contract type. Host
name is the name of the host the bindings will use. This isn't always the name of the server. Say in a situation
where you are exposing the webservice externally. You might have a hostname like http://ignition.company.com. In
this case, the host name will be ignition.company.com. TokenTimeOut is when a token will be marked as inactive and
not available for further use. TokenRemoval is when all traces of that token will be removed from memory.
If you change the ports here, you must change them in the binding screen (see CORE2120 above). If you don't change
them in that screen, the Ignition client will not know what ports to use for communication when creating an instance
of the screen. By default, the bindings are setup for localhost. Change that in a proper networking scenario.
Security
SecureMode is if Fusion should be using SSL security. The related bindings will then be changed. SecureCertificate
is the location of the PFX file (read up on SSL security if you don't know what this is). And the password, well, that's
the password for the PFX file. ForceBasicBindings is for situations where you need to provide integration for non .NET
applications. For example, you can have one Fusion service serving up .NET apps, but you can have another for serving
other types of applications, perhaps in data integration scenarios. HealthPort and HealthURI is the same story as Reactor.
The port can stay the same, but the URI must be unique for every service.
Application
The application part is used in an important part, the multicasting.
The application name is the unique name for the service. Application group is the group of applications.
In a real world scenario, this would be DEV, TEST, or LIVE. Friendly is just the user friendly name.
Load is the type of service. Sometimes you want to create multiple Fusions for load balancing. The Ignition
client will only display CLIENT load types not SERVER. So if you want a reactor for server use, only put
SERVER in there, and the Ignition client won't see it. The Ignition WinForms client won't actually see the Fusion
service (it filters these services out), but it's in there for any future use.
Order is the order this environment should be displayed in.
URL's and Logging
The Reactor URL is the URL of reactor that Fusion should connect to. The multicast URL is used when multicasting.
If you change this, it is advised to change it for every single application. Logging is the same concept as Reactor.
DependantOnServices is only used when using Fusion as a service - when installing the service, the services listed
here will be the services Fusion is dependent upon (so if you stop Reactor, Fusion will stop also).
A deeper look
We're going to take a deeper look into Fusion now. Mainly because SOA is a good thing. Some enterprises don't even
have a good one, some have them but are super complicated. Fusion is somewhere in between. It's very customizeable,
has tight security and lets you get at your prized business logic via webservices. We'll take a look at basic
bindings for this example. Just so we can use the included WebServiceStudio (only included so you don't have
to go hunting if you don't have it) to test this out. You could also use the supplied tools in VS2008+
(WcfTestClient.exe in the VS directory \Common7\IDE). So if you haven't already, start Reactor, then start the
Console app in the Fusion directory (remember, the .Basic one). Replace localhost if you need to in the URLs below.
Before we get into it, each Fusion service will host an authentication webservice at the root. The reason for this is
that to perform any transaction on webservices, you need to authenticate yourself. The authentication webservice
provides a standard, generic way for your application to authenticate.
All going well, you have started Reactor and Fusion. Next thing is to open up WebServiceStudio. Then:
• Enter this URL: http://localhost:8830/authentication
• Choose the GetTokenMethod
• Enter ADMIN for username and WEB for password.
• Enter 000EABCF34AF402A865B695E0A7E2A56 as the WebserviceID (you can find this using Plasma).
• Hit Invoke. You should have a token returned back to you.
• Enter this URL: http://localhost:8830/demo/dict/dictionarybus?wsdl
• Choose the RandomWord method.
• Enter your token.
• You should now have a random word back.
That's the Fusion service explained pretty broadly, doesn't get much more complicated than that. Note that tokens
do expire, so you'll need to handle this in your applications. The majority of the testing done was on a Windows 7 machine.
I have done other testing with Windows XP and Windows 2003 (with all SP's) and have noticed that Windows XP and
WebServiceStudio can behave oddly. If you run into problems with Fusion (with basic bindings) try using the
WcfTestClient application in Visual Studio.
The Scheduler Application
Scheduler solely exists to run scheduled tasks. One example would be to run a script that performs data integration.
I won't go into much of this here, but the config file is nearly the same as Fusion, with the exception of many removed
keys and one added key called FromEmail - this is the email address emails are sent from for Scheduler progress.
Key Generation
In the Utilities directory, you'll find an application called ViperWorks.Core.KeyGenerator. You can use this tool
to generate product keys. By default Ignition comes with a key that expires in 2010 and is registered to ViperWorks
Research Limited. Hopefully this shows you one possible way of using product keys. Obviously in a commercial
scenario you wouldn't distribute the generator. To give you an idea how it works, here's the code:
Public Class ProductKeyHelper
Public Shared Function GenerateProductKey(ByVal ProductName As String, ByVal CompanyName As String, _
ByVal ExpirationDate As Date, ByVal KeyType As ProductKey.ProductKeyType, _
ByVal MajorVersion As Integer, ByVal PrivateKey As String, _
Optional ByVal ConnectionLimit As Integer = 0) As ProductKey
Dim KeyFile As ProductKey
Dim Buffer As System.Text.StringBuilder
Try
KeyFile = New ProductKey
Dim generator As New Random
Dim randomValue As Integer
randomValue = generator.Next(8, 20)
For i As Integer = 0 To randomValue
KeyFile.PublicKey += Guid.NewGuid.ToString("N").ToUpper
Next
KeyFile.ProductName = ProductName
KeyFile.CompanyName = CompanyName
KeyFile.ExpirationDate = ExpirationDate
KeyFile.ConnectionLimit = ConnectionLimit
KeyFile.KeyType = KeyType
KeyFile.MajorSoftware = MajorVersion
Try
Buffer = New System.Text.StringBuilder
Buffer.Append(ProductName)
Buffer.Append("ý")
Buffer.Append(CompanyName)
Buffer.Append("ý")
Buffer.Append(ExpirationDate.ToString("ddMMyyyy"))
Buffer.Append("ý")
Buffer.Append(ConnectionLimit)
Buffer.Append("ý")
Buffer.Append(CInt(KeyType))
Buffer.Append("ý")
Buffer.Append(CInt(MajorVersion))
Buffer.Append("ý")
Buffer.Append(KeyFile.PublicKey)
Buffer.Append("ý")
Buffer.Append(GetSerialNumber(Buffer.ToString))
Buffer.Append("ý")
KeyFile.ProductKeyValue = Encrypt(Buffer.ToString, KeyFile.PublicKey, PrivateKey)
Buffer.Append(KeyFile.ProductKeyValue)
KeyFile.SerialNumber = GetSerialNumber(Buffer.ToString)
Return KeyFile
Catch ex As Exception
Throw New Exception("Could not generate key because the product or company name is too large.", ex)
End Try
Catch ex As Exception
Throw
End Try
End Function
Private Shared Function Encrypt(ByVal PlainText As String, ByVal Key As String, ByVal SubKey As String) As String
Dim CipherProvider As Encryption.Symmetric.Provider
Dim CipherSymmetric As Encryption.Symmetric
Dim CipherData As Encryption.Data
Try
CipherProvider = Encryption.Symmetric.Provider.Rijndael
CipherSymmetric = New Encryption.Symmetric(CipherProvider)
CipherSymmetric.Key.Text = Key & SubKey
CipherData = CipherSymmetric.Encrypt(New Encryption.Data(PlainText & SubKey))
Return CipherData.ToHex
Catch ex As Exception
Return String.Empty
End Try
End Function
Private Shared Function GetSerialNumber(ByVal HashData As String) As String
Dim Buffer As StringBuilder
Dim Crypto As SHA512CryptoServiceProvider
Dim ByteData As Byte()
Dim SingleChar As Integer
Dim ValidChars As String = "0123456789ABCDEFGHJKLMNPQRTUVWXY"
Try
Buffer = New StringBuilder
Crypto = New SHA512CryptoServiceProvider
ByteData = Crypto.ComputeHash(System.Text.Encoding.ASCII.GetBytes(HashData))
For i As Integer = 0 To ByteData.Length - 1
If (ByteData(i) = 0) Then
ByteData(i) = 1
End If
Next
For i As Integer = 0 To ByteData.Length - 1
SingleChar = ByteData(i) Mod ValidChars.Length
Buffer.Append(Mid(ValidChars, SingleChar + 1, 1))
Dim pos As Integer = i + 1
If (pos Mod 4 = 0) Then
If (i <> ByteData.Length - 1) Then
Buffer.Append("-")
End If
End If
Next
Return Buffer.ToString
Catch ex As Exception
Throw
Return ""
End Try
End Function
End Class
IBM UniVerse users (now Rocket) will see a cool little character in there! Yes, this is what the ultimate
goal of five years of work. Put a character in the key generation that comes from a 70's database system.
Obviously some sarcastic tones in there. Somewhere. Observant programmers will realise that the supplied
code doesn't use SHA512CryptoServiceProvider. The reason for this is because this isn't supported (to
my knowledge on Windows XP systems). So I've used a different provider in the release code.
Cool feature - Paged Searches
A feature put in quite late was the ability to page search results. This is very common in the web world, but
you don't see it often in the WinForms world. Basically when I search in Ignition, most searches come back with the
full results. Sometimes its big. Like when you search for World Events, you get 16000+ records back. Quotes is even
more. I have done load testing on 200k records, and that took a while to come back. So paging was put in. Basically
a special command is executed on the ReactorContract which will bring back paged results.

Here's a screenshot of a paged search. Note the top right - has navigation buttons and a drop down list of the page.
Next up, we'll take a look at a search that isn't paged:

Here's a screenshot of a standard search - no paging available.
To enable paging, you really need to have a suitable table and have something indexed on there. So take a table
that stores contacts - you probably have an index on there for the surname. You'd set that up in Ignition on Search Definitions
(screen CORE2014). You'd setup a page size (like 5000 records per page) and the field you want paged. Most likely the surname.
Different database systems page in different ways. MySQL is the easiest. You basically put in LIMIT with the start
position and the amount of records you want to bring back. Nice and easy. MSSQL you need to use ROW_NUMBER(). Access,
well, Access works weird. Paged searches is asking a lot of Access, but it does work. Access you do an inner
select, an outer select and then join the two. Sort of beyond the scope of this article, but post a question
if you want to know more about how this works.
Cool Feature - Multi-Lingual Support
Another feature that was put in quite late. To give you an idea how fast Ignition can help you develop, Ignition
from the ground up was coded for the English language. It was only when I had a possible buyer for Ignition that
I thought this through. From start to finish, to enable Ignition for multi-lingual support, it took a full day.
If I didn't have the framework, this would have taken much longer. I didn't have to worry about TSQL or anything
like that, I just created the tables, envisioned the business processes and got to work. At the end of the day,
Ignition was multi-lingual. I'll admit it's not the best way, it's basically word for word replacement with order
of precedence.
Other regional settings such as currency and time are not taken into account at this point.
Cool Feature - QuickSearch
This was added quite late in the development of Ignition. Mainly because Ignition is pretty powerful for searching
but what if you had a couple screens you went to all the time? And you knew the ID's? So if you had a contacts
system, you probably would have a unique ID for the contact (say, autonumber). You just wanted to enter that
number in and go straight to the screen. QuickSearch lets you do that. We'll go through a couple examples, first
one being searching for a word.
First off, start Ignition. Then enter test in the quick search box, click on the drop down and
choose Dictionary. Click on the magnifying glass. You'll go straight to the screen and the
record will load. Go back into the box and put in up. The record will change instantly.
Using that same session, type in worldeventid in the box, change the drop down to
Fields and hit the magnifying glass. The screen will change and the record will load.
Now, type in wor. A search box will pop up with the search pre-executed, allowing
you to pick from what you want. Think of this applied to a system for contacts, real estate, food management -
the possibilities are endless.
Cool Feature - Recursive Screens and Searches
A pretty big part of the user experience is using screens. Some users only spend their entire work life in a
company using only 3 or 4 screens. You want to reuse those screens whenever you can. Take a report for example.
If you fire Ignition up and click on "Field Report" in the favourites bar. You'll get a pretty busy report,
but if you double click on a row, you'll get a popup screen. This is the same screen that is used when
you maintain modules. If you close that and expand one of the first three rows and double click that - you'll
get a different screen.
The other cool thing are searches. When you are on a screen and do a search for a record, you get some search
options. Example, if you click on "Search Definition Maintenance" on the Favourites Bar, and then click on
the Search button, and then click on "Search All Search Definitions", you get a list of results back. Double
click on any one and the record will load. Now go down to "Search Page Field". Click on the magnifying glass
(or click into the field and press F3). A search box will pop up. These search options are the same options you
get when you are on the field maintenance screen. Close that search screen.
Another cool thing is when you are in screen, you can sometimes edit another screen whilst searching for data.
Take the search example. After closing the search dialog from above, go click on the magnifying glass for the
"Search Script" field. Straight away the search will execute and you already have results. Now single click a
row and then click on the Edit button. A new screen will popup allowing you to edit the record. How cool is
that? All within the same instance. Not all search options have this ability and you need to manually set this
up via Search Definition Maintenance. The reason for this is that you shouldn't actually have the ability to
create or edit records on the fly, depending on the screen in question and the business processes surrounding it.
Cool Feature - WPF Functionality
Ignition was designed as a WinForms application. Along the way XAML came out and it's only recently that I've
tried to support XAML/WPF in Ignition. The result - WPF screens are supported, but to use them, put your
business logic in the webservice. It's the only way I could get around WCF calls from an XBAP (the ideal
goal of using WPF). Once you start getting into XBAP [applications] it's a proper pain in the butt to
communicate out of it. That alone really is an article in itself! I've only got one example and it's a pretty
poor one.
To test the WPF screen, make sure you've got Fusion running (without basic bindings) and then go to the Misc
module, then Other and then start the WPF Test screen. Click on New, go into any field, then click on Save.
You should see some text in a box. That text was retrieved from a hosted webservice. It's actually reasonably
complicated to set on of these screens up (because it's using webservices, you have to create bindings and
also configure security). It's essentially putting a XAML screen in a WinForms application and then you are
creating additional bindings for that screen. Not sure if this is the best way mind you. I have also tested
WPF screens in a prototype XBAP, and it did work. Very cool seeing the same screen in the web as you do in
WinForms world and also using the same business logic.
Quick Word on Caching
The Ignition WinForms client uses caching pretty heavily. Once you do a certain thing, the metadata is read
from the DB and then cached for faster execution next time. Not everything uses it, but table and field metadata
do and QuickSearch items do as well. If you change metadata and find the related functionality hasn't updated,
close and restart Ignition. Clicking on the Home button also clears out most metadata, but not all. In a
production sense this speeds up Ignition even faster.
Points of Interest
Points of interest, what I learnt? I learnt five years is a long time. I learnt some people go their entire lives
without experiencing life. I learnt some people end up in careers they don't want to really be in and are only
in it because circumstances makes its that way. I learnt many things. I learnt that although developing Ignition
has come with its ups and downs, I don't regret the time I spent doing it. If there are budding developers out there
thinking of doing something similar - go for it. You may come to a different conclusion than I have. You may create
an awesome product and want to sell it. If you have an idea, no matter how big, do your best. Take it in small chunks.
By the same token, five years is also a short time. Life is short - do what you want to do.
In terms of programming, I've learnt how to implement WCF, how to use WPF in a basic sense, how to host WinForms
controls and WPF controls in the same application. I've learnt how to dynamically create webservices. I've learnt how to
use multicasting. I've learnt how to create a framework.
I've also learnt how to do things the wrong way. The Ignition client application is essentially a fat client. That's because
it initially was a server/client system. Then I put in the webservices. I should have done the webservices first and the client
should just be hosting the forms.
Some bits of the documentation may be called Draco or Apollo. The system has been through a couple namespace changes.
What is not done
As complete as Ignition is, some things are not complete. Major things being:
• Schema creation - tables and fields - stuff like that.
• Importing/Exporting of functionality.
• Reporting - needs more work to make it complete.
• Reporting - XtraReports need parent report capability.
• Notifications - ability to send messages on updates to the database.
• Documentation - not all done.
• Custom URL entry on the logon screen of client - for work at home situations.
• Messaging in client - framework is done, UI is not done.
• Attachments should be stored in database.
• Record concurrency - needs to be implemented.
• Proper data encryption - right now, business logic handles encryption - should be in DAL.
I'm not saying I won't get around to these things, but it may be a while before that get implemented.
I'm currently busy with other things at the moment, and I'm putting this up on CodeProject so
other people can see how a framework could be written.
More info on License
The licenses still confuses me a bit (would never be a lawyer), but essentially I don't want people downloading the framework, rebranding it and then on-selling it as their own framework, closed source.
What I am fine with is people modifying the framework, keeping the framework open source, but - and this is a big but - you can create your own modules and then on sell your modules for profit. I guess it's a fine line between making money off the framework compared to making money off your modules that you would create.
I read as much as I could into the different licenses before I fell asleep, but the GPL was the closest I could find to this.
So put simply:
- I don't mind people modifying the framework
- I do mind people changing the framework and then selling the framework
- I don't mind people creating their own modules
- I also don't mind people selling those modules
- You can put Ignition in commercial scenarios
I hope that clears it up, I think that's quite reasonable - what I'm just trying to do is keep the open source vision clear and steady - any changes to the framework I believe should be free and available to everyone. What you do with the modules that plug into it, you can really do what you like.
If you have some really cool code you want to stay closed source, but need that code to distribute to commercial applications, you're free to do that. But if that code is required for the framework to operate, it should really be open source.
End Words
To finish, this project has been my enjoyment, my pain and my learning experience in the .NET world. I don't regret
taking this project on, and feel like the community can benefit from some of the stuff in here. It's mostly written
in VB.NET, so it should be easy to pick up and step into the code to see how she works.
My involvement with this project may become less and less over time. To start off with, I'm hosting the code on CodePlex as
a 7zip download. If there is enough interest, I'll change it to be a subversion style. So the community can get into
it and start changing it. I hope anyone who downloads it and gives it shot may learn something new.
It all depends on demand basically. If enough people start using this, the to-do list above is really required to be done
to get Ignition into a situation where it can be put into production use. So if you download it, give it a twirl, like it -
give us a yell - in my spare time I'll finish this baby off. I reckon there is about 3-4 months part-time work (like weekend)
left to complete this.
And if you read through the article (it's a big ask - it's pretty big!) and look through the code and just want
a bit of an explanation, or think it may make a good article, let me know and I'll try and write something up.
If my style of writing is not too on the wacky side!