Sunday, October 9, 2011

WCF Rest Links

Here are some links that I've used to find information about working with REST services. I've deviated from these as I've learned things, however these are great starting points.

The Web Consumer from our talk on App Harbor (warning: as this has no security I cannot guarantee that someone hasn't posted text that may be offensive to others).


An eariler entry in this blog covered setting up the first service (using stored procedures). It is a good starting point, however is very incomplete and I highly recommend making the push to move towards Linq-To-SQL or entities like we discussed in the session today.

I hope you all enjoyed the talk and found the session useful.


Here are a couple of snippets of code to refresh. From the Service Interop:


public class ServiceInterop
{
IResponseBehavior _rb;

public ServiceInterop(IResponseBehavior rb)
{
_rb = rb;
}

public T QueryServiceForDataContract<T>(string url, string uri)
{
if (typeof(DataContractResponseBehavior<T>).Equals(_rb.GetType()))
{
return queryService(url, uri, null, null, null, true);
}
else
{
throw new Exception("Type did not match for generic get data contract method in ServiceAdapter.ServiceInterop");
}
}


public dynamic QueryServiceDefault(string url, string uri)
{
return queryService(url, uri, null, null, null, false);
}


public dynamic QueryServiceUsingQueryStringVariables(string url, string uri, HttpQueryString hqs)
{
return queryService(url, uri, hqs, null, null, false);
}



public dynamic QueryServiceUsingXElement(string url, string uri, XElement xElm)
{
return queryService(url, uri, null, xElm, null, false);
}


public dynamic QueryServiceUsingXDocument(string url, string uri, XDocument xDoc)
{
return queryService(url, uri, null, null, xDoc, false);
}


private dynamic queryService(string url, string uri, HttpQueryString hqs, XElement xElm, XDocument xDoc, bool IsDataContract)
{
using (HttpClient client = new HttpClient(url))
{
if (null != hqs)
{
using (HttpResponseMessage res = client.Get(new Uri(uri, UriKind.Relative), hqs))
{
res.EnsureStatusIsSuccessful();
return _rb.getResponse(res);
}

}
else if (null != xElm)
{
client.DefaultHeaders.Add("Content-Type", "application/xml; charset=utf-8");
using (HttpResponseMessage res = client.Post(uri, HttpContent.Create(xElm.ToString(SaveOptions.DisableFormatting))))
{
res.EnsureStatusIsSuccessful();
return _rb.getResponse(res);
}
}
else if (null != xDoc)
{
client.DefaultHeaders.Add("Content-Type", "application/xml; charset=utf-8");
using (HttpResponseMessage res = client.Post(uri, HttpContent.Create(xDoc.ToString(SaveOptions.DisableFormatting))))
{
res.EnsureStatusIsSuccessful();
return _rb.getResponse(res);
}
}
else if (IsDataContract)
{
client.DefaultHeaders.Add("Content-Type", "application/xml; charset=utf-8");
using (HttpResponseMessage res = client.Get(new Uri(uri, UriKind.Relative)))
{
//ensure response is successful
res.EnsureStatusIsSuccessful();
return _rb.getResponse(res);
}
}
else
{
using (HttpResponseMessage res = client.Get(new Uri(uri, UriKind.Relative)))
{
res.EnsureStatusIsSuccessful();
return _rb.getResponse(res);
}
}
}
}
}

And from the Response Behaviors. Use these classes to build the layer that is used to send requests to the service:

//Response Behaviors
namespace ServiceAdapter
{
public interface IResponseBehavior
{
dynamic getResponse(HttpResponseMessage res);
}

///
/// Return response as Dataset
///

public class DatasetResponseBehavior : IResponseBehavior
{
public dynamic getResponse(HttpResponseMessage res)
{
StreamReader sr = new StreamReader(res.Content.ReadAsStream());
DataSet ds = new DataSet();
ds.ReadXml(sr);
return ds;
}
}

///
/// Return response as String
///

public class ScalarStringResponseBehavior : IResponseBehavior
{
public dynamic getResponse(HttpResponseMessage res)
{
//ReadAsXelement requires Extensions reference.
XElement elm = res.Content.ReadAsXElement();
return elm.Value;
}
}

///
/// Return response as a Boolean
///

public class ScalarBooleanResponseBehavior : IResponseBehavior
{
public dynamic getResponse(HttpResponseMessage res)
{
//ReadAsXelement requires Extensions reference.
XElement elm = res.Content.ReadAsXElement();
string v = elm.Value;
if (v.ToUpper().Equals("TRUE"))
{
return true;
}
else
{
return false;
}
}
}

///
/// Return response as an integer
///

public class ScalarIntResponseBehavior : IResponseBehavior
{
public dynamic getResponse(HttpResponseMessage res)
{
//ReadAsXelement requires Extensions reference.
XElement elm = res.Content.ReadAsXElement();
string v = elm.Value;
return Convert.ToInt32(v);
}
}

///
/// Return response read as a specific data contract type.
///

public class DataContractResponseBehavior<T> : IResponseBehavior
{
public dynamic getResponse(HttpResponseMessage res)
{
return res.Content.ReadAsDataContract<T>();
}
}
}

And finally, a sample of how we queried to the route in the Crud Utility. This should be enough to fill in the blanks from setup to the part where the service is querying the database, which is reviewed in the previous post. Obviously, the pubs database has different objects so if you wanted to use that database a new Datacontract object would need to be created for each type you want to serialize.


public DataContracts.ErrorDataList GetAllErrors()
{
string serviceURI = "xml/errors";
IResponseBehavior rb = new DataContractResponseBehavior();
ServiceInterop si = new ServiceInterop(rb);
return si.QueryServiceForDataContract<DataContracts.ErrorDataList>(this.BaseServiceURL,serviceURI);
}


Also, I've just realized that the original post never had any invokes to send data to the service, so here is an example from insert:

//from the interface class that defined methods:
//admin/errors/InsertError
[WebInvoke(Method = "POST", UriTemplate = "admin/errors/InsertError")]
[OperationContract]
bool InsertError(ErrorServiceInterop.DataContracts.ErrorData ed);
//...
//And the class code for invoke...
/// Insert a new error
public bool InsertError(ErrorServiceInterop.DataContracts.ErrorData ed)
{
ErrorTrackingLog etl = new ErrorTrackingLog
{
//ID = ed.ErrorLogID -- no. this is identity.
ApplicationID = ed.ApplicationID
,userName = ed.UserName
,className = ed.ClassName
,methodName = ed.MethodName
,errorTime = ed.ErrorTime
,errorMessage = ed.ErrorMessage
,errorTrace = ed.ErrorTrace
};
_context.ErrorTrackingLogs.InsertOnSubmit(etl);
_context.SubmitChanges();
return true;
}


Also, here is a method from the JSON enabled service we built at the very end:

//json/errors/{appid}
[OperationContract]
[WebGet(UriTemplate = "json/errors/{appid}")]
ErrorServiceInterop.DataContracts.ErrorDataList GetErrorsByApplicationID(string appid)
{
int appID = Convert.ToInt32(appid);
ErrorServiceInterop.DataContracts.ErrorDataList edl = new ErrorServiceInterop.DataContracts.ErrorDataList();
List<ErrorServiceInterop.DataContracts.ErrorData> errors = new List<ErrorServiceInterop.DataContracts.ErrorData>();

var appErrors = from x in _context.ErrorTrackingLogs where x.ApplicationID == appID select x;
foreach (ErrorTrackingLog etl in appErrors)
{
errors.Add(GetEDContractFromEDContext(etl));
}
edl.Errors = errors;
return edl;
}


Hopefully I'll get something more formal out here soon! Enjoy!

Saturday, October 8, 2011

TCCC 11 Talks

I've decided to quickly post a couple of materials that I'll be using in my talks at TCCC11.

In an effort to make sure that everyone is aware of what will and won't be covered in the WCF Rest talk, I've created an outline (No slides here, just code). This will be a nice compliment to the Web API talk that was given yesterday, as it's not the same thing, but has some similar underlying technology. The outline is available for download here:
Intro to WCF Rest Outline

And for the design patterns talk, if you would like to review the slides, you may access them here:
Intro to Design Patterns Slides

If you come to the talks, I hope you find them informative and entertaining.

Friday, January 14, 2011

WCF Rest Services: Part 1 -- Getting Started

In this blog post, we'll walk through building a WCF Rest service in C# 4.0, from setup to deployment. Without further adieu, let's begin!

To get started, it's assumed you're working in .Net 4.0 and that you have some experience with C#.net programming. Open your visual studio IDE, select Tools --> Extension Manager --> Online Gallery --> Templates --> WCF --> WCF REST Service Template 40(CS) [at the time of this posting, the template is not available in the Express Edition]. Install the template (which will give it a Green Check):



After installing the template start a new project by selecting Web --> WCF REST Service Application. Create the project in a logical location on your drive and name the project PublisherService:



The default project will show an object called "Sample Item" and will also give examples of ways to interact with the object using the service. This is a great thing to know and study, but for our purposes, we will be removing this and starting fresh with our own objects and code. Delete the SampleItem.cs class and in the Service1.cs class, remove all code inside of the class, and the comments around the top of the class:



Rename the service to AcmePublisherData and then change the file name to match (AcmePublisherData.cs). Note: it is very important that after renaming the service file, the Global.asax.cs file must be modified to allow the service to startup. Since we're using a "file-less" service, the route to the service must be registered. In the future we may find a need to customize the service host factory by writing our own version, but for now we'll continue with the default version. Modify the RegisterRoutes() method to resolve to the AcmePublisherData type and also remove the "Service1" from the default path (this is the root of the URI, so it can be customized, but we're going to eliminate it for now). The code should now look like this:

private void RegisterRoutes()
{
// Edit the base address of Service1 by replacing the "Service1" string below
RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(AcmePublisherData)));
}


At this point we'll take a short break from the project to make sure that everything is ready to go. The rest of the entry will assume that you are running and have access to a SQL2008 Server with a database called 'pubs' that contains pre-written stored procedures. Additionally, you must have already built your own connection class to deal with your specific connection to the database. This entry will not be covering the various nuances for that. In my code, I've written a simple database library that wraps the EnterpriseLibrary available from Microsoft. My service will include the project 'majorguidance.database' which will be leveraged for all database connections. Therefore, all data calls will be up to your system to implement, and the service will work once your data layer is completed. I may cover building a data layer in another post, but not at this time. If you would like to have the scripts for the database so you can create your own version, you may download them here.

Back to the project we'll create three simple get methods to get different sets of data. In REST architecture, we leverage the URL in order to get specific sets of data. There are many ways to do this, and it will be up to you on what makes the most sense for your implementation and your URL path formatting. For our projects, we'll have the ability to get all publishers, to get one publisher by an ID, to get all authors, a specific author by ID, and all titles by author. We'll cover sending data in the next part of this series, and the very next part will cover consuming this data from an ASP.Net WebPage.

To start on the very first service method, then, we'll create a method in our AcmePublisherData service called "Publishers()." Since the web type will tell us the operation, we don't need to preface this with "get," however you may do this if you wish. The method name is actually irrelevant, as we'll never use this in code (when we build the consumer webpage next time, this will become more clear). There are two important attributes to add to our method. The first will determine the web method type (POST/GET/PUT/DELETE) and the second will just be the OperationContract attribute.

Before adding the WebGet/WebInvoke tag to our method, we should determine a suitable URL path schema. For our system, we'll use the following:
  • PublisherData/Publishers -- to get all publishers

  • PublisherData/Publishers/{id} -- to get a specific publisher

  • PublisherData/Authors -- to get all authors

  • PublisherData/Authors/{id} -- to get author by ID

  • PublisherData/Titles?authorID={authorID} -- gets all titles by the author id

You may have noticed the last schema is different. This is just to show that it is possible to send parameters directly in the uri or they may be named.

Whenever we are doing a simple GET, we'll only use the WebGet attribute, and give it the official URI to the path we'll be sending to access the service. This will be done in a format such as:

[WebGet (UriTemplate="asdf" [, RequestFormat = WebMessageFormat.Xml]
[, ResponseFormat = WebMessageFormat.Xml] [, BodyStyle = WebMessageBodyStyle.Bare])]
//note: the items in [, ... ] above are optional parameters that can be set!


For our project, building the first method gives us the following code:

[WebGet (UriTemplate="PublisherData/Publishers")]
[OperationContract]
public DataSet Publishers()
{
//...
}

Please note that we're returning a DataSet here. DataSets only work with .Net projects, so if you need to make the project universal for consumption by Java or another language, you'll want to avoid using datasets. Use something like a generic list or build your own custom objects that can be serialized for transmission.

The next step is to write the code to access the database. This will mostly be on your shoulders, but I'll show my code that uses the EnterpriseLibrary to call a SPROC as required:

[WebGet (UriTemplate="PublisherData/Publishers")]
[OperationContract]
public DataSet Publishers()
{
try
{
//put your database logic here to access the database
string spName = "dbo.s_getAllPublishers";
//dbPubs is ultimately a decorated Enterprise Library Database object
return dbPubs.ExecuteDataset(spName);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}

Again, my code will not work in your environment, so you must put your database handling in place and call the correct stored procedure as shown. It's finally time to see if it's all working! Pressing F5 and running the project spins up the browser:




Unfortunately, this really isn't useful. The URI contains the port that is currently being used, and if you want you can change that in the project properties, but for now there is no need to change it. Later we'll publish to IIS and the port will be irrelevant. Without a service file, though, what do we do?

REST services have a handy help file that goes along with the service automagically. Therefore, all we need to do is add "/help" to the URI and the service will suddenly display an overview, including relevant URL information and a list of available methods. This new page should look as follows:




Notice the layout, as it shows the URI, the Method type, and the escription URL. Clicking on "GET" will lead us to the page that shows the specifics of the method:




Copy and paste the service method URL into a browser tab and hit enter, and the page will then display the data as returned. If you are using IE9, you may see an error that says:
XML document must have a top level element. Error processing resource 'http://localhost:51785/PublisherData/Publishers'.

If you see this error, hit the compatibility mode icon near the URL path at the top and then hit it again and the XML will display (NOTE: chrome and firefox will only show the data, not the XML):





Add the next method to get the Publisher by ID. There are a few things of note:
  • The URI variable {id} must match the variable in the method name ..string id..
  • The parameter in the method MUST be of type string
  • Do not try to pass complex types this way, as they will fail (we'll cover this later).


[WebGet(UriTemplate = "PublisherData/Publishers/{id}")]
[OperationContract]
public DataSet PublisherByID(string id)
{
try
{
string spName = "dbo.s_getPublisherByID";
SqlParameter[] parms = new SqlParameter[1];
parms[0] = new SqlParameter("@pubID", Convert.ToInt32(id));
return dbPubs.ExecuteDataset(spName, parms);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}

Running the service and going to the help page reveals yet another quirk: "Where is my new method!" It's there, but the help page seems to be cached, so hitting f5 on the help page will list your new method. Remember this each time you add a new method or it could be frustrating!

Enter a valid PublisherID into the URL and, viola, here is the specific publisher information (try pubID 9999):





Create the remaining methods in a similar fashion (code will follow at the end). Running the help page will list all five methods, and each will be accessible as before. Try entering data in the query string for the different methods, especially to see the difference in the titles by author vs. the author by ID.





Finally, it's time to deploy. Create a new Application pool for WCFRest Services running the 4.0 environment, and create a new application for our new service in a logical location on your computer (either virtual or direct) and then publish the service to the correct folder location. At that point, you should be able to browse directly to the path without running the project. When we get to the client, we'll discuss how to debug the service after deployment, as well as how to directly run the project in the VS IDE and debug from that location (there are multiple ways to do this).
Here is a shot of the titles by author method running from IIS directly. Make sure to use a very good structure for your URL, because you'll want to make this easy to remember and deploy across multiple servers:





And here is the final code from the service:

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class AcmePublisherData
{
[WebGet (UriTemplate="PublisherData/Publishers")]
[OperationContract]
public DataSet Publishers()
{
try
{
//your database logic here...
string spName = "dbo.s_getAllPublishers";
return dbPubs.ExecuteDataset(spName);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}

[WebGet(UriTemplate = "PublisherData/Publishers/{id}")]
[OperationContract]
public DataSet PublisherByID(string id)
{
try
{
//your database logic here...
string spName = "dbo.s_getPublisherByID";
SqlParameter[] parms = new SqlParameter[1];
parms[0] = new SqlParameter("@pubID", Convert.ToInt32(id));
return dbPubs.ExecuteDataset(spName, parms);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}

[WebGet(UriTemplate = "PublisherData/Authors")]
[OperationContract]
public DataSet Authors()
{
try
{
//your database logic here...
string spName = "dbo.s_getAllAuthors";
return dbPubs.ExecuteDataset(spName);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}

[WebGet(UriTemplate = "PublisherData/Authors/{id}")]
[OperationContract]
public DataSet AuthorByID(string id)
{
try
{
//your database logic here...
string spName = "dbo.s_getAuthorByID";
SqlParameter[] parms = new SqlParameter[1];
parms[0] = new SqlParameter("@authorID", id);
return dbPubs.ExecuteDataset(spName, parms);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}

[WebGet(UriTemplate = "PublisherData/Titles?authorID={authorID}")]
[OperationContract]
public DataSet TitlesByAuthor(string authorID)
{
try
{
//your database logic here...
string spName = "dbo.s_getTitlesByAuthor";
SqlParameter[] parms = new SqlParameter[1];
parms[0] = new SqlParameter("@authorID", authorID);
return dbPubs.ExecuteDataset(spName, parms);
}
catch (System.Exception ex)
{
//YOUR ERROR HANDLING SCHEMA HERE.
throw new NotImplementedException("Error handling needed");
}
}
}


Next time, we'll talk about how to consume the data from an asp.net WebPage. In the third session we'll discuss creating objects and sending data to the service for updating and inserting to the database.

Please send any feedback to Brian

Tuesday, March 24, 2009

Invalid Cross Thread Operations -- How To run class code in a thread and write to the form

If you've ever worked with windows forms, one of the most annoying things that you can ever do is write some massive process and then push go, only to 'freeze' your form in place or lock it on the screen. If nothing else, it will turn completely white and you can't see anything!

The solution to this problem, of course, is to run your code in a thread. This allows you to continue working with the form as the code runs asynchronously in the background. But what happens when you need your thread to alter information on the form, such as change the value of a control on the form for display to the user? You get a nice friendly error message that says:

Cross-thread operation not valid: Control 'blahblahblah' accessed from a thread other than the thread it was created on.

To illustrate, I've built a little project I've dubbed as ThreadSmart. It's a simple windows application which contains a main form and a class. The form is just called frmMain and the class is ActionClass. In reality, the class and form could be anything that you would need for a real purpose. In this demo, I'm just going to show the interaction between the class and the form.

To start, I've dropped a couple of objects on the form. The texts are txtCurrentState and txtMessages. The progressbar is pb1, and the button is btnRunActionClass.



To create a way to communicate between our class and our form, let's add a delegate for messaging which sends a message and a value for updating the main form with a message box and a value for the progress bar. I will blog more about using delegates later but for now I'll just put this one in the program.cs class inside the namespace, making it global for the entire project. Here is my program.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace ThreadSmart
{
//Public global delegate called Progress, can be called from any class within the namespace:
public delegate void Progress(double progressValue, string progressMessage);

static class Program
{
///
/// The main entry point for the application.
///

[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
}
}
}

The action class is pretty basic for the purpose of this demo. It has one event which is fired to subcribers from the one main method that could be called. In the main method, I've just looped for a count of 100 and then written a random message for every loop. I sleep in the loop to make it appear to take longer. Here is the ActionClass.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ThreadSmart
{
class ActionClass
{
//create the delegated event:
public event Progress ActionProgress;

///
/// Default Constructor:
///

public ActionClass()
{
//TODO: Write constructor code here
}

///
/// Main Action simulates a public method from the class
/// which is a long-running process.
///

public void MainAction()
{
//simulate a long-running process:
for (int i = 0; i < 100; i++)
{
//sleep for a bit...
System.Threading.Thread.Sleep(10000);
//report progress...
ReportProgress(i, String.Format("Some Important Message: {0}", myRandomString(12)));
}

return;
}

//random string function to create a random string for the progress report
//(modified from c-sharp corner http://www.c-sharpcorner.com/UploadFile/mahesh/RandomNumber11232005010428AM/RandomNumber.aspx ):
private string myRandomString(int size)
{
StringBuilder sb = new StringBuilder();
Random rndm = new Random();
char ch;

for (int i = 0; i < size; i++)
{
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * rndm.NextDouble() + 65)));
sb.Append(ch);
}

return sb.ToString();
}

//create the event handler in the class to fire the event:
private void ReportProgress(double value, string message)
{
if (ActionProgress != null)
{
//fire the event if there is a subscriber:
ActionProgress(value, message);
}
}
}
}

Before launching with a thread, you would write the basic code to subcribe to and handle the event and just update the progress bar and message text using the delegated reporting event. Here is what the frmMain code looks like before we start using the thread:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ThreadSmart
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}

///
/// Run the long running process...
///

private void btnRunActionClass_Click(object sender, EventArgs e)
{
//create a new action class object:
ActionClass myAC = new ActionClass();

//add the handler for the actionclass progress event:
myAC.ActionProgress += new Progress(ThreadSmartReportProgress);

//run the long-running process:
myAC.MainAction();
}

///
/// Handle the report event from the class:
///

private void ThreadSmartReportProgress(double value, string message)
{
//set the message
txtMessages.Text = message;

//set the progressbar
if (value <= 100)
{
pb1.Value = (int)value;
}
else
{
pb1.Value = (int)(value % 100);
}

//refresh the form:
this.Refresh();
}
}
}


Here we realize the need for a thread, so add it in:

First, add the threading directive:

using System.Threading;

Next, change the object to be modal instead of local to the button press event:

public partial class frmMain : Form
{
//modal declaration of the action class for use in the form:
ActionClass m_myAC;
...

///
/// Run the long running process...
///

private void btnRunActionClass_Click(object sender, EventArgs e)
{
//create a new instance of the action class object:
m_myAC = new ActionClass();

//add the handler for the actionclass progress event:
m_myAC.ActionProgress += new Progress(ThreadSmartReportProgress);

//Create a thread to launch the class in
Thread ACThread = new Thread(new ThreadStart(DoMainAction));

//launch the process to convert the file in a thread:
ACThread.Start();
}

///
/// Run the action in a thread
///

private void DoMainAction()
{
//run the long-running process:
m_myAC.MainAction();
}
//...
}


Now when the program is run, the threading error occurs:



So this is where the magic needs to happen. To fix it, I need to create local delegates for the event to fire, so that the code will be invokable by the thread. Since there are three controls ultimately being affected in this demo, I will create three events:

public partial class frmMain : Form
{
//modal declaration of the action class for use in the form:
ActionClass m_myAC;

//local delegates to set local controls from a caller:
delegate void SetPBValue(double pbValue);
delegate void SetTXTProgressMessage(string pMessage);
delegate void SetTXTCurrentState(string message);
//...
}

And then write the functions to handle the updates when invoked by a caller (I put mine under the original handler function ThreadSmartReportProgress:

//HERE are the local instance functions which will actually set the values
//they are INVOKED in the event capture from the class delegated raised event:
private void SetPBValueCallback(double value)
{
pb1.Value = (int)value;
}
private void SetTextValueCallback(string message)
{
txtMessages.Text = message;
}
private void SetStateTextValueCallback(string message)
{
txtCurrentState.Text = message;
}

Finally, alter the original code in the ThreadSmartReportProgress Function to handle the invoking of the new delegates by the thread:

///
/// Handle the report event from the class:
///

private void ThreadSmartReportProgress(double value, string message)
{
//since the progressbar is local to the form, it is UNSAFE to let an external thread
//modify it. For that reason -- have to use the InvokeRequired attribute to handle
//the modification of the thread:
if (this.pb1.InvokeRequired)
{
//create a new instance of the delegate, point to the local function to set the value:
SetPBValue pbv = new SetPBValue(SetPBValueCallback);
//make sure pb value doesn't go over the value of 100:
if (value > 100)
{
value = value % 100;
}
//invoke the delegate pointing at the local function with the passed on value:
this.Invoke(pbv, new object[] { value });
}

//handle textbox same as for PB1
if (this.txtMessages.InvokeRequired)
{
//same as for PB1
SetTXTProgressMessage txtpm = new SetTXTProgressMessage(SetTextValueCallback);
this.Invoke(txtpm, new object[] { message });
}
}


To make the final project nice and pretty, add the code to set displays on startup and also the code to fire the event from the thread when it is complete. Beware, I did not code here for the fact that you could press the button over and over again, if you want to worry about that, go ahead and make your program thread-safe as well as thread-smart.

After all is said and done, here is the main form code in frmMain.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadSmart
{
public partial class frmMain : Form
{
//modal declaration of the action class for use in the form:
ActionClass m_myAC;

//local delegates to set local controls from a caller:
delegate void SetPBValue(double pbValue);
delegate void SetTXTProgressMessage(string pMessage);
delegate void SetTXTCurrentState(string message);

public frmMain()
{
InitializeComponent();
}
private void frmMain_Load(object sender, EventArgs e)
{
txtCurrentState.Text = "Waiting for action...";
}

///
/// Run the long running process...
///

private void btnRunActionClass_Click(object sender, EventArgs e)
{
//create a new instance of the action class object:
m_myAC = new ActionClass();

//add the handler for the actionclass progress event:
m_myAC.ActionProgress += new Progress(ThreadSmartReportProgress);

//Create a thread to launch the class in
Thread ACThread = new Thread(new ThreadStart(DoMainAction));

//reset the displays
txtCurrentState.Text = "Running ...";
pb1.Value = 0;
//launch the process to convert the file in a thread:
ACThread.Start();
}

///
/// Run the action in a thread
///

private void DoMainAction()
{
//run the long-running process:
m_myAC.MainAction();
if (this.txtCurrentState.InvokeRequired)
{
//same as for PB1/txtMessages
SetTXTCurrentState txtState = new SetTXTCurrentState(SetStateTextValueCallback);
this.Invoke(txtState, new object[] { "Thread Completed!" });
SetPBValue pbv = new SetPBValue(SetPBValueCallback);
this.Invoke(pbv, new object[] { 100 });
}
}

///
/// Handle the report event from the class:
///

private void ThreadSmartReportProgress(double value, string message)
{
//since the progressbar is local to the form, it is UNSAFE to let an external thread
//modify it. For that reason -- have to use the InvokeRequired attribute to handle
//the modification of the thread:
if (this.pb1.InvokeRequired)
{
//create a new instance of the delegate, point to the local function to set the value:
SetPBValue pbv = new SetPBValue(SetPBValueCallback);
//make sure pb value doesn't go over the value of 100:
if (value > 100)
{
value = value % 100;
}
//invoke the delegate pointing at the local function with the passed on value:
this.Invoke(pbv, new object[] { value });
}

//handle textbox same as for PB1
if (this.txtMessages.InvokeRequired)
{
//same as for PB1
SetTXTProgressMessage txtpm = new SetTXTProgressMessage(SetTextValueCallback);
this.Invoke(txtpm, new object[] { message });
}
}

//HERE are the local instance functions which will actually set the values
//they are INVOKED in the event capture from the class delegated raised event:
private void SetPBValueCallback(double value)
{
pb1.Value = (int)value;
}
private void SetTextValueCallback(string message)
{
txtMessages.Text = message;
}
private void SetStateTextValueCallback(string message)
{
txtCurrentState.Text = message;
}
}
}

I hope this will help someone. I know that it will be nice for me to be able to refer to this again in the future. Feel free to comment if there are errors or things you think I could have done better.

Monday, March 16, 2009

The purpose of this blog

This first post is not going to contain anything other than a brief blah blah from me. This blog is going to be my landing place for posting things that I learn or things I want to share, or just plain cool things if I ever happen to find any while I'm programming.

I might also post some random thoughts here about coding, or else I could post some general things so I can use this as my own reference going forward. Maybe someone else will find it useful too, who knows. Hopefully it will shape up over the next few years into a pretty cool repository.

One final note is that the topics are not going to be presented in any particular order, just as I feel like writing about them. Someday maybe I'll organize it, but don't hold your breath.