Introduction
In my last project, I experienced that there is no software to test performance on mobiles for NET applications. The ones working on WinForms or ASP.NET sites have a high performance cost because they hook everything they can. So I decided to try and create a new one based on injection... but Reflection Studio quickly becomes a bit more. It's a programmer tool around all my work with Reflection, database, code generation, WPF, and so on.
Reflection Studio is hosted on http://reflectionstudio.codeplex.com/. It is completely written in C# under the .NET/WPF platform. I recently moved to Visual Studio 2010 and NET 4. Please have a look at the CodePlex project because it is huge to describe everything in detail. Given below is a screenshot of the application:
Contents
As the subject is quite big, I will (try to) write this article in several parts:
- Part 1 - Introduction: Architecture and design
- Part 2 - User interface: Themes, dialogs, controls, external libraries
- Part 3 - Assembly: Schema, provider and parsing, threads and parallelism, controls
- Part 4 - Database: Schema, providers and plug-ins, controls
- Part 5 - Code generation: Template, engine, controls
- Part 6 - Performance: Injection, capture, and reports
- Part 7 - The project - Putting it all together
Part 1 - Architecture and Design
1.1 The Modules
Reflection Studio contains several core modules:
- Assembly parsing as a base for performance injection and to make diagrams
- Performance injection to get traces back from the execution of any NET assemblies, etc.
- Code generation from templates and database, etc.
- Database schema discovering, query, quality control, diagrams, etc.
1.2 Solution and Assemblies
As a short introduction to the project, below is the assembly diagram, with the project assemblies in blue:
The external ones:
- Moco.Cecil from Mono project - for assembly injection
- WPFToolkit from CodePlex - for more controls
- Fluent from CodePlex - excellent ribbon control library
- AvalonDock from CodePlex - get Visual Studio like interface with docking, panels, toolbox, and tabbed documents
- ICSharpCode.AvalonEdit - a highlighted syntax editor in WPF
- WPFVisifire.Charts from CodePlex - for chart creation
- ...
Internally, we have these main assemblies:
- ReflectionStudio
- The main UI with everything specific to it
- ReflectionStudio.Controls
- All generic UI related elements
- ReflectionStudio.Core
- All business/core and helpers not related to UI or controls
- ReflectionStudio.Core.Database.xxxxProvider
- Plug-in assembly for SQL schema discovery and more
- ReflectionStudio.Spy
- The runtime that makes performance traces
- ReflectionStudio.Tools
- It's a utility for testing skin, colors, and controls
Class Dependencies
The whole class dependency diagram generated by Visual Studio is quiet unreadable, but it shows the starting complexity of the project:
To resume, the architecture is quiet simple. I avoid complex patterns, and classes/functions are always constructed on the same principle: simple as possible, with exception and trace management. You will see in detail in the other part of this article some "touchy" points like the database providers, assembly parser, injection, or the code generation engine.
1.3 Tracing and Events
The idea was to develop a tool that suits the developer and me during development, but also a good way to update the interface and share events between all components. I don't like to write basic trace in my code, because it's useful when a problem that is not easy occurs but is hard to exploit... so I decided my traces need to be visible in the user interface. It is based on the following model.
1.3.1 Model: EventDispatcher and Tracer
1.3.2 Tracer Class
Allows to trace all development steps through classical functions like in the Microsoft trace or debug system: Error, Info, Verbose. I generally use a template like the following, and it's very useful when writing code and testing, as you can see the trace directly in the UI:
public bool Open( string fileName )
{
Tracer.Verbose("ProjectService:Open", "START");
try
{
Current = new ProjectEntity(fileName);
return LoadProject();
}
catch (Exception err)
{
Tracer.Error("ProjectService.Open", err);
return false;
}
finally
{
Tracer.Verbose("ProjectService:Open", "END");
}
}
The main function of the Tracer
class creates a message info, sends it to the Microsoft Trace system (if configured), and then sends it as a message (if it matches the workspace settings).
private static void Send(MessageEventType typ, string from, string message)
{
MessageInfo info = new MessageInfo(typ, from, message);
Trace.TraceInformation(info.ToString());
if (info.Type <= WorkspaceService.Instance.Entity.LogLevel)
EventDispatcher.Instance.RaiseMessage(info);
}
1.3.3 Configuration
I use a function like the next one to configure the Microsoft trace system. This allows me to delete the log on each start and to activate it depending on the software settings - call it in AppLoad
:
private void TraceConfiguration()
{
try
{
string logPath = Path.Combine(PathHelper.ApplicationPath,
"ReflectionStudio.exe.log");
if (File.Exists(logPath))
File.Delete(logPath);
if (ReflectionStudio.Properties.Settings.Default.UseTraceListener)
{
System.Diagnostics.Trace.AutoFlush = true;
System.Diagnostics.Trace.IndentSize = 2;
System.Diagnostics.TraceListenerCollection listeners =
System.Diagnostics.Trace.Listeners;
listeners.Add(new
System.Diagnostics.TextWriterTraceListener(logPath, "LOG"));
}
}
catch (Exception error)
{
Tracer.Error("Reflection Studio.TraceConfiguration", error);
}
}
1.3.4 EventDispatcher Class
This one serves in the background to the Tracer
class with the basic messaging system. It can raise three types of events (all of them send a message as the base information):
Message
: Pure text information, completed with Type, Where, and When, ....
Status
: With start or stop, allows to update the progress in the status bar
Project
: More customized and business oriented, allows to signal events like opened, closed, saved...
Here is an example of usage to fire project and status event by using the EventDispatcher
class, but also used by the Tracer
in try
/catch
management.
private bool LoadProject()
{
try
{
EventDispatcher.Instance.RaiseStatus(
Resources.CORE_LOADING, StatusEventType.StartProgress);
EventDispatcher.Instance.RaiseProject(Current, ProjectEventType.Opening);
Current = new ProjectDAC().Load(Current.ProjectFilePath);
if (Current != null)
{
Refresh();
EventDispatcher.Instance.RaiseProject(Current, ProjectEventType.Opened);
return true;
}
else
{
EventDispatcher.Instance.RaiseStatus(Resources.CORE_LOADING_ERROR,
StatusEventType.StopProgress);
return false;
}
}
catch (Exception err)
{
Tracer.Error("ProjectService.LoadProject", err);
return false;
}
finally
{
EventDispatcher.Instance.RaiseStatus(Resources.CORE_PROJECT_LOADED,
StatusEventType.StopProgress);
}
}
1.3.5 The Log Toolbox
To complete all this, I had to write a control to display this in the UI. I do not invent the wheel and take Visual Studio as a good sample. So I create a LogToolBox
user control plugged in an Avalon content. It was nice to have a converter to change the message type into an error icon!
1.3.6 Connecting
And for all this to work, you just have to plug the event handlers all together like I do in the main window creation:
EventDispatcher.Instance.OnProjectChange +=
new EventHandler<ProjectEventArgs>(OnProjectChange);
EventDispatcher.Instance.OnStatusChange +=
new EventHandler<StatusEventArgs>(this.MainStatusBar.OnStatusChange);
EventDispatcher.Instance.OnMessage +=
new EventHandler<MessageEventArgs>(this.LogToolbox.OnMessage);
1.4 Helpers
1.4.1 Serializing
The core assembly contains a helper class to serialize or deserialize any type of object to binary or XML format. It is as simple as:
entity = (WorkspaceEntity)SerializeHelper.Deserialize(WorkspaceFile,
typeof(WorkspaceEntity), false);
result = SerializeHelper.Serialize(WorkspaceFile, entity, false);
1.4.2 Various Helpers
The core assembly also contains:
UrlSave
and AsyncUrlSave
to save the response of any HTTP GET
ByteHelper
and CecilHelper
ResourceHelper
: Read embedded resources
ProcessHelper
: Launch navigator for a web URI
WorkerBase
to manage a BackgroundWorker
for multithreading
1.5 Documentation
All the code need to be documented in English as I activate the XML documentation. This allows Sandcastle and Sandcastle Help File Builder to generate the technical project help file in the new 2010 Help Viewer format. This is available in the source, and can be generated and installed by building RSHelp.shfbproj in the solution folder <ReflectionStudio\Documents\Help>. As it is too big, it will not be included in the source code or release.
Additionally, I try to keep up to date the user manual and technical documentation for each release. You can find them in the <Help> folder as a doc or xps document, or through the application in the main ribbon tab that displays the help document illustrated below:
1.6 Installation
The setup and related project have been created since beta 0.2. But we can have lot of changes; please do not forget to remove any previous installation, and we do not guarantee the project reversibility between versions before first release.
Everything is installed in the Reflection Studio folder. There are no specific needs and no modifications needed on the computer.
Conclusion / Feedback
See you in the next article of this series. Do not hesitate to give me feedback about it either on CodePlex or CodeProject. As the team is growing, I hope that we are getting faster, and do not hesitate to join us!
Article / Software History
- Initial release - Version BETA 0.2
- Initial version that contains "nearly" everything, and is available for download on Part 1 or CodePlex
- Version BETA 0.3
- Update on skin/color management and the Database module
- Part 4 - Database module - is published