Wednesday, October 18, 2006

Releasing COM components


Clean up after the use of com components ALLWAYS! Don't wait for the garbage collection to take action, free that memory asap. Ensure that the objects are released in a specific order.

To release use the Marshal.FinalReleaseComObject and pass the relevant RCW.

COM uses reference counting to determine when objects finally get released. RCW's cause the underlying COM object to hold onto the reference even when the object variable goes out of scope! The reference will be released only when the GC disposes of the RCW, thus you cannot control when or in what order COM objects are released from memory.

If there are several references held using Marshal.ReleaseComObject will need to be called several times. If you want to release them all in one go then use Marshal.FinalReleaseComObject this will set the reference count straight to zero.
Consume COM components in .NET


If theres a PIA (primary interop assembly) then use it otherwise generate a RCW (tuntime callable wrapper). Use the Type Library Importer (Tlbimp.exe) to do this.

RCW is a special .NET proxy class that sits between the .NET code and the COM component. It handles everything including the marshaling of data types, using COM interfaces and handling COM events.

For using RCW we have the following options:

1. Obtain it from the author of the original COM component. In microsoft offices case the RCW is created from a PIA provided by the publisher.
2. Generate the RCW via Tlbimp.exe or Visual Studio .NET. The later is simple in that we just reference the COM object in the project, the rest is done for you!
3. Create your own manually via types in the System.Runtime.InteropServices namespace. Very tedious and complicated.

Using Tlbimp.exe is simple [ Tlbimp ComComponent.dll ]

Use a PIA where possible, there more likely to work as expected because there created by the original component publisher. They also might include additional .NET refinements or enhancements.

If a PIA is registered for a dll then it will be used in preference to the creation of an RCW when referenced in VS.NET Studio.

Office has a PIA which you can download from http://msdn.microsoft.com/downloads/list/office.asp
Impersonating windows users


This is a very common requirement for applications.

Use the System.Security.Principal.WindowsIdentity to impersonate the user you want then use this object in the Impersonate method of the WindowsIdentity object


Sample code:

using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;

//need to ensure that the thread has the permissions to do the impersonation
//this is at the namespace/class level, before the namespace preferably
[assembly:SecurityPermission(SecurityAction.RequestMinimum, UnmanagedCode=true, ControlPrincipal=true)]


//windows logon form
//this is within the class (after the class tags)
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
[DllImport("advapi32.dll"), SetLastError=true, CharSet=CharSet.Unicode)]
static extern bool LogonUser(string userName, string domain, string password, int logonType, int logonProvider, ref IntPtr accessToken);

IntPtr accessToken = IntPtr.Zero;

bool success = LogonUser("username",".","password",LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref accessToken);

if(!success){
--Marshal.GetLastWin32Error();
}else{
--WindowsIdentity identity = new WindowsIdentity(accessToken);
--WindowsImpersonationContext impContext = identity.Impersonate();
--impContxt.Undo();
}
Current user is a member of a specific windows
group


Authenticated applications normally need this type of functionality, what we need to do to implement this functionality is ..

1. Obtain a System.Security.Principal.WindowsIdentity object of the current user via the WindowsIdentity.GetCurrent.

2. Then create a System.Security.Principal.WindowsPrincipal class using the WindowsIdentity class

3. Then call the method IsInRole of the WindowsPrincipal object..


Sample code:

using System.Security.Principal;

WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);

//args is an arraylist of roles to include in the search
string[] args = {'domain\role1','domain\role2'}
foreach(string role in args){
--principal.IsInRole(role);
}
Using TCP to communicate


So you want to communicate to another computer via your local TCP network?

One of the computers needs to be listening (System.Net.Sockets.TcpListener), this is oftne refered to as the server.

Another computer needs to be broadcasting (System.Net.Sockets.TcpClient), often refered to as the client.

If a connection is established between the server and the client then they can communicate via the System.Net.Sockets.NetworkStream class.

TCP is reliable, connection oriented protocol. Has built in flow control, sequencing and error handling.

SERVER SAMPLE CODE

TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"),8000);
listener.Start();
using(TcpClient client=listener.AcceptTcpClient()){
--NetworkStream stream = client.GetStream();
--using(BinaryWriter w = new BinaryWriter(stream)){
----using(BinaryReader r = new BinaryReader(stream)){
------ if(r.ReadString() == "Hello"){
--------w.Write("OK");
--------while(r.ReadString()!="Bye"){}
------}
----}
--}
}

listener.Stop();



CLIENT SAMPLE CODE

The client port is assigned dynamically by default (it is chosen dynamically at runtime from the available ports)

using System.IO;
using System.Net;
using System.Net.Sockets;


TcpClient client = new TcpClient();
client.Connect(IPAddress.Parse("127.0.0.1"),8000);
NetworkStream stream = client.GetStream();
--using(BinaryWriter w = new BinaryWriter(stream)){
----using(BinaryReader r = new BinaryReader(stream)){
------w.Write("Hello");
------if(r.ReadString()=="OK"){
--------w.Write("Bye");
------}
----}
--}

client.Close();
Sending emails using SMTP


Ok this is a common requirement that all my projects need. I use the SmtpClient and MailMessage in the System.Net.Mail namespace.

In .NET1.0 these classes were found in System.Net.Mail namespace from the System.Web.dll.

In .NET2.0 these classes are now found in the System.dll assembly, which simply extends the functionality found from .NET1.0.

To specify default settings for the SmtpClient use the section of the machine or application configuration files.

When sending messages you use the SmtpClient.Send or SmtpClient.SendAsync. No explanation needed here...


Sample code:

using System.Net;
using System.Net.Mail;

SmtpClient client = new SmtpClient("mail.server.address.com",25);
client.Credentials = new NetworkCredential("user@company.com","password");

using (MailMessage msg = new MailMessage()){
--msg.From = "";
--msg.Subject = "";
--msg.Body = "";
--msg.Attachments.Add( new Attachment(@"..\..\file.zip", "application/zip"));
--msg.To.Add(new MailAddress("LiquidBoy@RipThatPage.com"));
--client.Send(msg);
}
HTML page retrieval from a site requireing
Authentication


One of the major pieces of functionality i almost allways need is the ability to retrieve a file from a website where the website needs credentials for the purpose of authentication.

As part of the System.Net.WebRequest object configure the WebRequest.Credentials and WebRequest.Certificates properties with the necessary authentication information.

Based on the websites authentication approach we need to do different things to set these properties.

1) Basic/Digest, transmit a username and password by manually creating System.Net.NetworkCredential object and assigning it to the WebRequest.Credentials. Digest you may also supply the domain name.

2) Windows Integrated same approach as above or use the current logged in user from System.Net.CredentialCache.DefaultCredentials

3) Client Certificates rquired, load from a file using System.Security.Cryptography.X509Certificates.X509Certificate2 and add it to the HTTPWebRequest.ClientCertificates collection.

4) You can load an X.509 certificate from a certificate store using the System.Security.Cryptography.X509Certificates.X509Store found in the System.Security.dll. [this was introduced in .NET 2.0]. You can search the store X509Store.Certificates.Find or present the user with a list to choose from via the SelectFromCollection method on System.Security.Cryptography.X509Certificates.X509Certificate2UI.


Sample code


using System.Net;
using System.Security.Cryptography.X509Certificates;

//BASIC Authentication
WebRequest requestA = WebRequest.Create("http://www.RipThatPage.com");
requestA.Credentials = new NetworkCredential("username", "password");
requestB.PreAuthenticate = true;

//WINDOWS Authentication
WebRequest requestB = WebRequest.Create("http://www.RipThatPage.com);
requestB.Credentials = CredentialCache.DefaultCredentials;
requestB.PreAuthenticate = true;

//CLIENT CERTIFICATE
HttpWebRequest requestC = (HttpWebRequest)WebRequest.Create("http://www.RipThatPage.com);
X509Certificate cert1 = X509Certificate.CreateFromCertFile(@"..\..\TestCert.cer");
requestC.ClientCertificates.Add(cert1);
Using FTP or HTTP to download data


If you have the need to download data via FTP or HTTP then this little snippet is your solution.

Use the System.Net.WebClient to achieve this. The URI determines whether a file (file://) , FTP (FTP://) or HTTP (HTTP:// or HTTPS://) scheme is to be used.

For every method on this class there is an equivalent Async version (added in .net 2.0). This allows you download data asynchronously as a background task using its own thread from the thread pool.

The WebClient object can only handle one async download at a time. This is not suitable for downloading many files concurrently (you could use multiple WebClient objects). Cancel the current download using CancelAsync.

Methods:

OpenRead - returns a System.IO.Stream
OpenReadAsync - async downloading use the handler OpenReadCompleted

DownloadData - returns a ByteArray
DownloadDataAsync - use handler DownloadDataCompleted

DownloadFile - Saves to a local file
DownloadFileAsync - use handler DownloadFileCompleted

DownloadString - returns a String ( introduced in .net 2.0)
DownloadStringAsync - use handler DownloadStringCompleted

** you can also upload files using this class (OpenWrite, UploadData, UploadFile, UploadString) and the Async versions of each.

** if you have a proxy then you will need extra code to code around it. You will need to validate against the proxy before using the webclient. [code to follow in another topic]

Sample code:

using System.IO;
using System.Net;
using System.Text.RegularExpressions;

string remoteUri = "http://www.RipThatPage.com";
WebClient client = new WebClient();
string str = client.DownloadString(remoteUri);
Checking Network Connectivity


During my application i need to know if the network goes offline or is online.

How i do this is by using the handlers NetworkAddressChanged and NetworkAvailabilityChanged events implemented by the System.Net.NetworkInformation.NetworkChange class.

This NetworkChange class was introduced in .NET framework 2.0. It allows us to build logic in our applications that detect network changes and network availability.

Sample Code:

using System.Net.NetworkInformation;

//handlers
NetworkChange.NetworkAvailabilityChanged += NetworkAvailabilityChanged;
NetworkChange.NetworkAddressChanged += NetworkAddressChanged;

private static void NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e){
--if(e.IsAvailable){
----//network is available
--} else{
----//network is down
--}
}

private static void NetworkAddressChanged(object sender, EventArgs e){
--foreach(NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()){
----//get the ip address using the property UnicastAddresses.Address
--}
}
Getting Local Network Information


So you want information regarding the local network adaptors and the local machines network configuration, use the static method System.Net.NetworkInformation.GetAllNetworkInterfaces. This returns objects of type NetworkInterface. The properties of the NetworkInterface object will unravel a rich set of information regarding network configuration and network statistics.

Sample Code:

using System.Net.NetworkInformation;


--if(NetworkInterface.GetIsNetworkAvailable()){

----NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();

----foreach(NetworkInterface ni in interfaces){
------string NetworkName = ni.Name;
----}

--}