Malisa Ncube - C# and .NET Delights

Mumblings about the software development using Microsoft technologies.

  Home  |   Contact  |   Syndication    |   Login
  22 Posts | 0 Stories | 36 Comments | 0 Trackbacks

News

Twitter












Tag Cloud


Archives

Post Categories

Tuesday, May 26, 2009 #

This is the first posting on MEF, in which I will be explaining some things that I discovered while playing around with MEF. I should say that all the postings on my blog, including this subject do not represent my employer or any other organization, they are merely my mumblings based on my work that I do and whatever in find interesting and would like to share. Use it at your own risk, but it works on my machine.

clip_image002

To start with I would like you to read about the architecture of the MEF framework from the MEF Codeplex site.

In my MEF101, I present an example which shows how you can import composable parts from your assemble and let MEF compose and satisfy imports and enable you to come up with a simple interface made from different parts.

I created the IPlugin interface which I use in my exports.

/// <summary>

/// IPlugin Interface

/// Used by all the composable parts which will be used as plugins

/// </summary>

public interface IPlugin

{

}


 

All rectangle items visible are simple UserControls which implement the IPlugin contract. Notice that I have placed an [Export] attribute. This informs MEF that during composition that if there is something which needs [Imports] IPlugin, then the MEF will create it and satisfy that import.

/// <summary>

/// Plugin1

/// Example Plugin

/// </summary>

[Export(typeof(IPlugin))]

public partial class Plugin1 : UserControl, IPlugin

{

public Plugin1()
{
InitializeComponent();
}
}

 

All the plugins in this example have this same structure. I just changed the background color in the designer to so that we can tell that they are different.

The Main form has the following code.

 

/// <summary>
/// Form1
/// The main form. We need to have the [Export] attribute on it because on the Program.cs
/// we compose it using MEF and get its instance as an ExportedObject.
/// </summary>
[Export]
public partial class Form1 : Form
{
/// <summary>
/// Plugins
/// These imports will be satisfied by instances of the IPlugin 's
/// which in this case is the user controls with different colors.
/// </summary>
[Import]
public IEnumerable<IPlugin> Plugins;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
// We loop through all the instances of the plugins an add them to the tableLayoutPanel
// and because we know that all of them are UserControls we can cast the as Control.
foreach (var plugin in Plugins)
{
(plugin as Control).Dock = DockStyle.Fill;
tableLayoutPanel1.Controls.Add(plugin as Control);
}
}
}

The Program.cs has the following code.

 

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

var form1 = Compose();
Application.Run(form1);
}

public static Form1 Compose()
{
//Create a catalog - in this case we want to import the composable parts which exist in this
//assembly, so we will use the AssemblyCatalog against the current assembly.
//
//It is also possible to import assemblies from external assemblies using the DirectoryCatalog.
//Furthermore you can combine a number of catalogs into an AggregateCatalog.
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

//Create the Container - then pass the catalog into the container
var container = new CompositionContainer(catalog);

//Return the instance of Form1.
return container.GetExportedObject<Form1>();
}
}

And that’s all. Look at how we create the instance of Form1 class and use the Application class to run it.

You can download the source here

 


clip_image002

1. Introduction

Have you ever been in a situation where the users are unable to explain how an error was displayed on their computer and moreover describe the details of the error message? Well… I wonder why I asked when I already knew the answer you’ll say.

A few weeks ago, the some users started testing the system; I realized our method of tracking bugs was not nearly good. We had an excel spreadsheet which had bug details and how we can reproduce the bug. We had problems with that each tester was now using their own version of a bug tracking spreadsheet, since excel does not allow shared access, so I downloaded the opensource Bugtracker.NET from codeplex to see if it could be of help, since we do not have  the Team Foundation Server.

2. Trapping unhandled exceptions

The first thing to do is to catch all unhandled exceptions, and to do that you added an event handler in the Application.

Program.cs

static class Program
{

static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
....
....
Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
....
....
}


static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
Bug bug = Bug.Create();
string screenshotFile = bug.CaptureScreenShot();

bool sendScreenShot = false;
bool bugSent = false;
TaskDialog exceptionDialog = new TaskDialog();
exceptionDialog.MainIcon = TaskDialogIcon.Error;
exceptionDialog.MainInstruction = e.Exception.Message;
exceptionDialog.UseCommandLinks = false;
exceptionDialog.WindowTitle = "Error Occured!";
exceptionDialog.Content = string.Format("Unhandled Exception: {0}\nSource: {1}\nObject: {2}", e.Exception.Message, e.Exception.Source, e.Exception.TargetSite.ToString());
exceptionDialog.ExpandedByDefault = false;
exceptionDialog.ExpandedInformation = e.Exception.StackTrace;

exceptionDialog.VerificationText = "Do you want to send screenshot?";
exceptionDialog.VerificationFlagChecked = true;
exceptionDialog.FooterIcon = TaskDialogIcon.Information;

TaskDialogButton sendButton = new TaskDialogButton();
sendButton.ButtonId = 101;
sendButton.ButtonText = "Send";

TaskDialogButton cancelButton = new TaskDialogButton();
cancelButton.ButtonId = 102;
cancelButton.ButtonText = "Don't Send";

exceptionDialog.Buttons = new TaskDialogButton[] { sendButton, cancelButton };
int result = exceptionDialog.Show(null, out sendScreenShot);

if (result == sendButton.ButtonId)
{
bug.BugUrl = "http://server:88/insert_bug.aspx";
bug.MailText = string.Format("Unhandled Exception: {0}\nSource: {1}\nObject: {2}\nTrace: {3}",
e.Exception.Message, e.Exception.Source, e.Exception.TargetSite.ToString(), e.Exception.StackTrace);
bug.ProjectId = "ICEA.NET";
bug.UserName = "tester";
bug.Password = "tester";
bug.Subject = "User: [" + WindowsIdentity.GetCurrent().Name + "] "+ e.Exception.Message;

if (sendScreenShot)
bugSent = bug.WithAttachment(screenshotFile)
.ComposeMail()
.SendBugReport();
else
bugSent = bug.ComposeMail()
.SendBugReport();

if (bugSent)
MessageBox.Show("Details of error have sent to development team! Thanks.", "Message Sent", MessageBoxButtons.OK, MessageBoxIcon.Information);
else
MessageBox.Show("Could not send error details to development team!", "Message Failure", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}


}

The above code snippet will ensure that if an exception is unhandled then the method Application_ThreadException will be executed and the exception details are passed as an argument.

3. Capturing the screenshot

This is a very important part, because the screenshot will be attached to the email which is then sent to the server, so we can know what the user was doing when the exception occurred. The image is then converted to base64 format. This is simply changing the image to text so we can send it over HTTP.


public string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format)
{
using (MemoryStream ms = new MemoryStream())
{
// Convert Image to byte[]
image.Save(ms, format);
byte[] imageBytes = ms.ToArray();

// Convert byte[] to Base64 String
string base64String = Convert.ToBase64String(imageBytes);
return base64String;
}
}

private string CaptureImage(Point SourcePoint, Point DestinationPoint, Rectangle SelectionRectangle)
{
using (Bitmap bitmap = new Bitmap(SelectionRectangle.Width, SelectionRectangle.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(SourcePoint, DestinationPoint, SelectionRectangle.Size);
}
//return the string which represent the image
return ImageToBase64(bitmap, ImageFormat.Jpeg);
}
}

public string CaptureScreenShot()
{
Rectangle bounds = Screen.GetBounds(Screen.GetBounds(Point.Empty));
return CaptureImage(Point.Empty, Point.Empty, bounds);
}

4. Composing the Bug Mail

public Bug ComposeMail()
{
mail = "&username=" + HttpUtility.UrlEncode(_UserName) +
"&password=" + HttpUtility.UrlEncode(_Password) +
"&from=" + HttpUtility.UrlEncode(_UserName) +
"&short_desc=" + HttpUtility.UrlEncode(_Subject) +
"&projectid=" + HttpUtility.UrlEncode(_ProjectId);

if (_Attachment != string.Empty)
mail += "&message=" + HttpUtility.UrlEncode(_Attachment);
else
mail += "&message=" + HttpUtility.UrlEncode(_MailText);

return this;
}

5. Making the Web Request (HTTP)

Thanks to fiddler, I was able to analyse the details of the contents that are posted to the Bugtracker.NET. The tool downloadable with BugTracker.NET called bt2312 is written in C++. I translated part of it to C# and luckily .NET has very interesting libraries which allow you to perform most of the things out of the box.

clip_image006

Fiddler enables you to inspect your web requests and that was helpful, because after i inspected the web requests from bt2312 i was able to replicate the behaviour.

clip_image008

The labels above indicate the steps that have been made during the web request.

1. The application sends the data using HTTP.

2. The data sent (as text).

3. The response.

public bool SendBugReport()
{
bool Success = false;
ASCIIEncoding encoding = new ASCIIEncoding();


byte[] buffer = encoding.GetBytes(mail);

// Prepare web request...
HttpWebRequest myRequest =
(HttpWebRequest)WebRequest.Create(_BugUrl);

// We use POST ( we can also use GET )

myRequest.Method = "POST";

// Set the content type to a FORM
myRequest.ContentType = "application/x-www-form-urlencoded";
myRequest.UserAgent = Application.ProductName;
// Get length of content
myRequest.ContentLength = buffer.Length;

// Get request stream
Stream newStream = myRequest.GetRequestStream();

// Send the data.
newStream.Write(buffer, 0, buffer.Length);

// Close stream
newStream.Close();


// Assign the response object of 'HttpWebRequest' to a 'HttpWebResponse' variable.
HttpWebResponse myHttpWebResponse = (HttpWebResponse)myRequest.GetResponse();

// Display the contents of the page to the console.
Stream streamResponse = myHttpWebResponse.GetResponseStream();

// Get stream object
StreamReader streamRead = new StreamReader(streamResponse);
Char[] readBuffer = new Char[256];

// Read from buffer
int count = streamRead.Read(readBuffer, 0, 256);

while (count > 0)
{
// get string
String resultData = new String(readBuffer, 0, count);
// Write the data
Success = resultData.Contains("OK:");
Console.WriteLine(resultData);
// Read from buffer
count = streamRead.Read(readBuffer, 0, 256);

}
// Release the response object resources.
streamRead.Close();
streamResponse.Close();

// Close response
myHttpWebResponse.Close();

return Success;
}


I have attached the Bug.cs class which contains all the code for creating a Bug report and sending it to and BugTracker.NET server. Apologies, i have not put a lot of comments in my code, if i revise this post i will probably do something about it.

Download Bug.cs