Tuesday, February 19, 2008

.Net 2.0 and calling dynamic Web Services

It is really easy to use a web service in .Net 2.0. You just add a "Web Reference" to you project and point it to your WSDL URI. A proxy class is generated that provides you with methods that line up with the methods available from your web service.

OK, but I want this to be dynamic: I want to deploy this to my production environment which uses a different web service address, without recompiling it.

Easy, you just make sure that your web reference has the "dynamic" property set and an app.config will be added to your project. Take a look at it. Now you can change the access point via the app.config file. So you just deploy a different app.config file in production. If you are using DLLs and COM interop you can have a lot of fun trying to get the config files loading (see here). If needs be, you can roll your own config file and set the access point URL manually before invoking the web service via the ".URL" method on the web proxy.

OK, but I want this to be really dynamic: I don't know the WSDL at compile-time, all I know is the method signature; I want to provide the WSDL at run-time.

It starts to get a bit ugly now. But if you know the method signature, eg:

    public string HelloWorld(string inArg)


then you can define an interface class which you can use in your code at compile time. Then, at run-time, you can look up the WSDL, generate assembly source code from it, compile the assembly and have that assembly implement your interface. Google is your friend here. If found a few examples but this implementation of a WebServiceProxyFactory is pretty darned useful. Note that it does take time to build and compile this proxy assembly - you probably won't be wanting to do this every time, so you may have to implement some kind of proxy-caching.

Hmmm, OK, but I want this to be really, REALLY dynamic: I don't even know the method signature at design time - I want to examine the WSDL and work out which one to use.

Yeah right! Maybe you can get the user to write the code? But seriously, there are times when you might want to call a web service that hasn't even been designed yet - maybe you are deploying your app to a number of clients and allowing them to hook in their own web services calls? It is time to read your SOAP manual. You're going to have to grab the WSDL, interpret it, find the method you want to call, build a soap envelope with your values inserted in the right places, and make the web service call. A quick google of HttpWebRequest and SOAP should get you on the right track. This is no trivial task!

Wednesday, February 13, 2008

How to read an app.config setting for your DLL from your dll.config file

It turns out you can manually read the app.config settings for your DLL, using the ConfigurationManager.OpenMappedExeConfiguration. This is pretty darned ugly, but it works. It will look in the location of the currently executing assembly (ie. you dll's location) for the file "{dllname}.config". You still need to manually deploy the config file to this location - the setup package only deploys the EXE config file.

To get the code working, first add a reference to the .Net "ConfigurationManager" to your project, then add "using System.ConfigurationManager" to your class.

public string getOpenedConfigValue(string keyName)
{
string codebase =
System.Reflection.Assembly.GetExecutingAssembly().
CodeBase;
Uri p = new Uri(codebase);
string localPath = p.LocalPath;
string executingFilename =
System.IO.Path.
GetFileNameWithoutExtension(localPath);

string sectionGroupName = "applicationSettings";
string sectionName =
executingFilename + ".Properties.Settings";

string configName = localPath + ".config";
ExeConfigurationFileMap fileMap =
new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = configName;
Configuration config =
ConfigurationManager.OpenMappedExeConfiguration(
fileMap,ConfigurationUserLevel.None);
ConfigurationSectionGroup group =
config.GetSectionGroup(sectionGroupName);
ClientSettingsSection section =
(ClientSettingsSection)group.Sections[sectionName];
SettingElement elem = section.Settings.Get(keyName);

if (elem == null)
return "";
else
return elem.Value.ValueXml.InnerText.Trim();
}

App.config in C# with VS2005 - Part II: DLL within an EXE

In part I I looked at using the app.config from an EXE. In part II I'm going to look at using app.config from a DLL that is called by an EXE.

So, create a DLL project (mine is called "TestAppConfigDll") and add it to your EXE project. Use the Settings designer again to add your app.config file to your DLL (right-clicking on the project, selecting "properties" and then choosing the "settings" tab). I'm going to add an "Application" setting called "MyDllVal". Again we get a pretty similar looking app.config file in our project:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section
name="TestAppConfigDll.Properties.Settings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<TestAppConfigDll.Properties.Settings>
<setting name="MyDllVal" serializeAs="String">
<value>18.2</value>
</setting>
</TestAppConfigDll.Properties.Settings>
</applicationSettings>
</configuration>


And again, we can access the value in code using:

string myDllVal = 
TestAppConfigDll.Properties.Settings.Default.MyDllVal;


No surprise to find that the config file in our bin folder (..\TestAppConfigDll\bin\Debug) is prefixed by the dll name, "TestAppConfigDll.dll.config" in my case. BUT looking in the bin for the EXE project (which references the dll and contains a copy of my dll file), there is no dll.config file, just the original exe.config. And the exe.config has no "MyDllVal" property. If I run the EXE it picks up the original value for "MyDllVal" but I have no way to modify it! It knows the original compile-time value because it has been hard-coded into the "Settings.Designer.cs" file that was generated when you entered your value via the Settings designer. Here is the code if you are interested (my original value was "18.2"):


[global::System.Configuration.
ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.
DebuggerNonUserCodeAttribute()]
[global::System.Configuration.
DefaultSettingValueAttribute("18.2")]
public string MyDllVal {
get {
return ((string)(this["MyDllVal"]));
}
}


So, what if I add a dll.config ("TestAppConfigDll.dll.config") under the EXE bin folder - will it read it on start-up? Nope - it doesn't read it.

So, maybe it is looking in the exe.config for my setting. What if I merge my "MyDllVal" setting into the exe.config file - will it read it on start-up? Wooeee! That works! So I had to take two chunks of xml from the dll.config to the exe.config; (i) the "section", and (ii) the "{dllname}.Properties.Settings". The resulting exe.config looks like this:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section
name="TestAppConfig.Properties.Settings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
<section
name="TestAppConfigDll.Properties.Settings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<TestAppConfig.Properties.Settings>
<setting name="MyVal" serializeAs="String">
<value>17</value>
</setting>
</TestAppConfig.Properties.Settings>
<TestAppConfigDll.Properties.Settings>
<setting name="MyDllVal" serializeAs="String">
<value>18.00 combo</value>
</setting>
</TestAppConfigDll.Properties.Settings>
</applicationSettings>
</configuration>


So the upshot is, if the DLL is called from an EXE, then the DLL will try to get its app.config settings from the EXE's app.config file. Even if I remove the EXE's app.config, the DLL does not use the DLL's app.config file.

Man, this is annoying! Why have they done it this way? Maybe it is so that shared DLLs in the GAC can have different config settings for different EXEs. But even then, couldn't they have used the EXE config settings first, and if none, then use the DLL config settings?

So where will it look for app.config if I have a .Net DLL in the GAC that is begin called from a VB6 exe via COM interop? Probably nowhere I guess.

The solution seems to be to use a custom config file for your DLL app settings. However my DLL is using a web-service and the .Net web service tool puts the web service URI into, you guessed it, the app.settings file for the DLL. I want access to the app.config so I can change the web service URI to point to a different web service. Again, I can get around it by setting the web service URI manually in the code, but I'm starting to fight the framework rather than use it!

Newline in a .Net textbox (C#)

Always forget this one - to put a new line in a .Net textbox in C#, use the "\r\n" string (after you've set the textbox to multiline of course). "\t" is tab but that is easy to remember!

App.config in C# with VS2005 - Part I: EXE

Using the app.config has been giving me a few headaches. And googling it has revealed many others have the same kind of problems! So here is an overview of how it works:

From a Windows form project (mine is called TestAppConfig), you can add the app.config setting by right-clicking on the project, selecting "properties" and then choosing the "settings" tab. Add an "Application" setting into the settings designer (eg "MyVal") and you will find an "app.config" file has been added to your project. It will contain something like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section
name="TestAppConfig.Properties.Settings"
type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<TestAppConfig.Properties.Settings>
<setting name="MyVal" serializeAs="String">
<value>15</value>
</setting>
</TestAppConfig.Properties.Settings>
</applicationSettings>
</configuration>


From your code, you can access this setting like this:

string myval = 
TestAppConfig.Properties.Settings.Default.MyVal;


If you take where the code has been built (eg. ..\TestAppConfig\bin\Debug), you'll see that the app.config has been created using the name of your exe, in my case TestAppConfig.exe.config. If you run the exe from this folder it will use the value from this config file. If you change the value in the config it will be picked up the next time that the application is started - it does not pick it up changes immediately!


If you need to get the changed value picked up at run-time, you can use the reset method:

TestAppConfig.Properties.Settings.Default.Reset();

Wednesday, February 6, 2008

Distributing a .Net DLL for use by VB6

The trick is getting the .Net assembly installed into the GAC on your 500+ remote machines. You can do this manually by copying the file into c:\windows\assembly but this won't work via remote scripting. You can use the "gacutil /i {assembly.dll}" but the GacUtil.exe is no longer distributed with the .Net framework (since 2.0) so you have the have the SDK or Visual Studio installed on the client machine - no thanks!

The easiest way is to create an MSI to install the DLL. In fact, this will also copy it and register it, so it saves a few steps; plus you get the uninstall stuff with it. To do this:

1. In your project, add a new "Setup" wizard. Choose "Windows application".

2. Select the "Primary Output" for your DLL project.

3. By default this will install the DLL in your application folder. To install in the GAC, right-click the "File System" in the setup project and choose "Add Special Folder-Global Assembly Cache Folder". Then right click in the new folder and add the primary output of your DLL project.

4. Make sure you set the "Product name" and "Company name" in the properties of the setup project. These are used to build the path to you application folder where the DLL will be copied:
[ProgramFilesFolder][Manufacturer]\[ProductName]


4. Build the Setup project and you'll find a {assemblyname}.msi file sitting in the ".../release/bin" path under your project.

5. Deploy the MSI to your remote machine and run it. Hopefully you have some kind of application deployment tool that can run MSI installs!

Monday, February 4, 2008

Steps to make your .Net DLL useable from VB6

In order to use a .Net DLL from VB6 I had to take the steps below. This assumes that you have already created a .Net dll with a public method. Before you begin, in VS2005, build your project and then go into the "Project-References" box in your VB6 application. At this stage you will not be able to see your .Net DLL.

Step 1. Make it COM-visible.

Right click on the project In VS2005 and select "Properties". Under the "Application Tab" click the "Make assembly COM-Visible" checkbox. Then click on the "Build" tab and check the "Register for COM interop" checkbox in the "Output" section. Save.

If you try and build the project now you will find that you can reference the project in VB6, and can instantiate it in code, but you cannot view the methods in the Object Browser or get VB6 IntelliSense in the methods. You can call the method and get your result back though.

Step 2. Implement an interface.

Your class should implement an interface. Convert your class from this:

    public class MyClass
{

public String testInOut(String sIn)
{
return ret = "Returning [" + sIn + "] at " + System.DateTime.Now.ToString();
}
}


to this:

    public interface IMyClass
{
String testInOut(String sIn);
}

public class MyClass : IMyClass
{

public String testInOut(String sIn)
{
return ret = "Returning [" + sIn + "] at " + System.DateTime.Now.ToString();
}
}


Or, even easier, select the class and method definition and right-lick, choosing "Refactor-Extract Interface..." and check the method in the box that pops up. This will generate an interface into a new file and modify your class to implement it.

Step 3. Expose your methods.

Get .Net to expose your methods to VB6. Do this by adding the [ClassInterface(ClassInterfaceType.AutoDual)] attribute to the public class, and importing the InteropServices library:

...
using System.Runtime.InteropServices;

namespace MyNamespace
{
[ClassInterface(ClassInterfaceType.AutoDual)]
public class MyClass : MyProject.IMyClass
{


If you rebuild your .Net dll now, you should be able to see the methods in the VB6 Object Browser. Now we need to make sure we can deploy it remotely. Follow these additional steps.

Step 4. Control your GUIDs.

Add GUIDs to both the Interface and the public class. To get a GUID, click "Tools-Create GUID" and choose option 4 "Registry Format". Click "New GUID" and then "Copy". Paste as a "Guid" attribute into your interface, stripping out the curly brackets. Then generate a new GUID and paste an attribute into your public class. The GUID against the public class is the key one because it is the one that is looked up in the registry to determine which DLL your program will use. Specifying the GUIDs in these attributes ensure that the same GUID is used when you compile your build.

    [Guid("3A7E8E37-3B6B-4cda-9A47-EBD0D1D11812")]
interface IMyClass


and

    [ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("87E9EBBD-CE79-4336-BB7F-F070483C442C")]
public class MyClass : MyProject.IMyClass


Step 5. Sign the assembly with a strong name.

Go into the "Project-Properties-Signing" tab and select "Sign Assembly" and choose the "string name" combo entry.

Now we are read to deploy this remotely. Follow the steps below on the remote machine:

Step 6. Install into the GAC.

Copy the file to the remote machine (which already has the .Net framework installed) into some directory {myfolder}. Then copy it into the c:\windows\assembly folder. This is the location of the GAC (Global Assembly Cache). .Net applications on this machine will now be able to use it. However VB6 will still not be able to find it.

Step 7. Register the DLL.

Register the DLL with COM via the regasm tool:

    regasm c:\{myfolder}\MyProject.dll


The regasm tool is installed as part of the .Net framework and can be found here:

    C:\WINDOWS\Microsoft.NET\Framework\v2.0.5072


This will register the type library. If you try to use the COM object via VB6 it will pick up the version from the GAC. The version you have copied to {myfolder} can be deleted if required. And that is it!