I am working against 3rd level party assembly in my current web application. I need to send US address information to this assembly and to retrieve an answer whether this address is exist or not. This assembly requires validation against X.509 certificate (to ensure that only permited client could use the 3rd level's services), which is installed on the server that runs the application (in dev environment this is my local PC).
More details about it here.
The problem: In order to authenticate against this certificate, the process that runs the application need to 'hold' sufficient credentials in order to get an access to the certificate and to do the authentication. Here comes our problem; when trying to access this certificate through the asp.net application, we run into a problem - It's impossible, because the process that runs the web application is ASPNET and doesn't has the needed credentials in order to authenticate the certificate and get the info from the 3rd level.
Suggested solutions:
- Credentials. Read the credentials from the web.config (username, password and domain) and impersonate the user using these credentials. This will 'save' the impersonated user all over the impersonation context (System.Security.Principal.WindowsImpersonationContext) and the authenicate action against the certificate will be done using this credentials. One more important thing, to ensure this data protected, encrypt it before puting it into the web.config.
- I thought about IIS Application Pool. This is a great feature that came up in IIS 6.0, which enables you the ability of creating one or more applications and allows us to configure a level of isolation between different Web applications. You can set the identity of an application pool which will be the account under which the application pool's worker process runs. So I thought to set it over there, but I had one big problem, an IIS 5 was installed on the production server and it is not a dedicated server. (More details about application pool here).
- Host .NET component in COM+. This is the third solution and the best for me at the current circumstances; Because I am working with a several applications (assemblies) I want to host the component that validates the user against the 3rd level party, this will give me a unified behavoir for all the applications while doing this action (Instead of setting these properties in web.config file of each web application we want to use {solution 1, remember?}). In other words, I'll set the username and password on the COM+ component just once in order to grant the process that runs this component the right and sufficient credentials. .NET provides a way to host your .NET components inside COM+ environment. All the functionality you need to write a COM+ aware component in .NET can be found in System.EnterpriseServices namespace.
So how we do it (hosting .NET assembly in COM+)?
Take a look on this code:
using System;
using System.Collections.Generic;
using System.Text;
using System.EnterpriseServices;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace ComPlusTest
{
[Transaction(TransactionOption.Required),
ObjectPooling(MinPoolSize=2, MaxPoolSize=5, CreationTimeout=20000),
ComVisible(true)]
public class TestClass : ServicedComponent
{
protected override void Activate()
{
base.Activate();
DoSomeAction(Action activate)
}
protected override void Deactivate()
{
base.Deactivate();
DoSomeAction(Action deactivate)
}
protected override bool CanBePooled()
{
DoSomeAction(Action pooled)
return base.CanBePooled();
}
public void ValidateAddress(string address)
{
try
{
// Do the validation against the 3rd party
ContextUtil.SetComplete();
}
catch(Exception ex)
{
// Handle exception
ContextUtil.SetAbort();
}
}
[AutoComplete()]
public void JustAction()
{
DoSomeAction(Action simpleAction);
}
private void DoSomeAction(Action act)
{
// Do the action
}
}
}
Lets dissect it:
- Firstable you can see that the class is derived from ServicesComponent (which sits in the System.EnterpriseServices namespace). I marked our TestClass with some attributes. The first one in Transaction; The values for this attribute are same as in traditional VB/VC++ development i.e. Required, RequiresNew, Supported etc. MinPoolSize and MaxPoolSize specifies values for minimum and maximum object instances. The ComVisible attribute must be set to true to give the accessibility of an individual managed type or member, or of all types within an assembly, to COM (I spent lots of time trying to figure out some exceptions that I had while overriding the ServicesComponent class).
- the class is marked to require a transaction each method will execute in a transaction (existing or new). Once the ValidateAddress has been executed we need to either commit or rollback the transaction. This is done via static methods of ContextUtil class. The method SetComplete is used to commit a transaction where as SetAbort is used to rollback a transaction.
- Just for example, I defined a methid called JustAction. This method is marked with an attribute AutoComplete which means that once the method execution is over the transaction is automatically committed (equivalent to ContextUtil.SetComplete). In case of any error the transaction will be rolled back (equivalent to ContextUtil.SetAbort).
- Overrided Activate, Deactivate and CanBePooled methods are just for testing (in order to observe the flow behavior).
Now, you have to sign your assembly with a strong name and to add the following attributes to the AssemblyInfo class of your project:
[assembly: ApplicationName("ComPlusTest")]
[assembly: ApplicationActivation(ActivationOption.Library)]
[assembly: AssemblyKeyFileAttribute("ComPlusKey.pfx")]