MTOM with Axis2 version 1.3#

A step-by-step guide to writing a web service which supports MTOM and a web service client to call it. The example assumes a Windows environment and Eclipse as the development environment.

Setup the Development Environment

1. Download Tomcat version 5.5 from http://tomcat.apache.org/download-55.cgi

2. Set the JAVA_HOME environment variable to the Java JDK directory. Note that you have to set the variable to the JDK and not the JRE install folder.

3. Run startup.bat from the Tomcat distribution bin directory to make sure Tomcat will run. If all is good, run shutdown.bat from the bin directory to stop the Tomcat server.

4. Download Axis2 version 1.3 (http://ws.apache.org/axis2/download/1_3/download.cgi)

a. Download the Standard Distribution and the WAR distribution.

5. Copy the axis2.war file from the WAR distribution in step 4 to the webapps folder under Tomcat. Start Tomcat by running startup.bat. If Tomcat was able to consume the war file, then you should be able to browse to http://localhost:8080/axis2/ to see the Axis2 web application default page.

6. Upon startup, Tomcat unpacked the axis2.war file. Have a look at the axis2.xml configuration file within axis2\WEB-INF\conf\ folder.

7. The axis2.xml file contains the administrator username and password for axis2 administration. By default the username and password to admin and axis2, respectively. You can modify this by changing the values and restarting Tomcat.

8. Point your browser to the axis2 web application and choose the Administration link. http://localhost:8080/axis2/axis2-admin/login. Type in the username and password to access the admin console.

9. So far we have Tomcat and Axis2 up and running. To make this exercise less painful, we should also download TCPMon so we can view the SOAP message we create. Download TCPMon from: http://ws.apache.org/commons/tcpmon/download.cgi. This utility is very cool and easy to use. Before you start TCPMon (by running tcpmon.bat from the build), have a look at the user guide. http://wso2.org/project/wsas/java/2.1/docs/tools/tcpmonguide.html

Create MTOM Enabled Web Service

1. Create a new Java Project in Eclipse. Name the project TestMTOM.

2. Add the axis2 jar files to the classpath. The axis2 jar files are within the lib directory of the axis2 standard distribution.

3. Create a new class named TestService within a package named com.test. Paste the following for the class implementation.

package com.test;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import javax.activation.DataHandler;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMText;

public class TestService

{

private static final String OUTPUT_FILE = "C:\\HOLD\\att.pdf";

public void receiveMTOM(OMElement element) throws Exception

{

System.out.println("received request...");

OMText binaryNode = (OMText) (element.getFirstElement()).getFirstOMChild();

binaryNode.setOptimize(true);

DataHandler dh = (DataHandler) binaryNode.getDataHandler();

InputStream is = dh.getInputStream();

byte[] buf = readFully(is);

writeOutput(buf);

System.out.println("done writing output file.");

}

private static void writeOutput(byte[] buf)throws IOException

{

File of = new File(OUTPUT_FILE);

if(of.exists())

{

of.delete();

}

of.createNewFile();

setContents(new File(OUTPUT_FILE),buf);

}

private static void setContents(File aFile, byte[] aContents) throws FileNotFoundException, IOException

{

if (aFile == null)

{

throw new IllegalArgumentException("File should not be null.");

}

if (!aFile.exists())

{

throw new FileNotFoundException("File does not exist: " + aFile);

}

if (!aFile.isFile())

{

throw new IllegalArgumentException("Should not be a directory: " + aFile);

}

if (!aFile.canWrite())

{

throw new IllegalArgumentException("File cannot be written: " + aFile);

}

/**

* declared here only to make visible to finally clause;

* generic reference

*/

OutputStream output = null;

try

{

output = new FileOutputStream(aFile);

output.write(aContents);

}

finally

{

/**

* flush and close both "output" and its underlying FileWriter

*/

if (output != null)

output.close();

}

}

private static byte[] readFully(InputStream is) throws IOException

{

int size = 10000;

/**

* Offset - how much we've read

*/

int off = 0;

int got;

byte[] ret = new byte[size];

try

{

while (true)

{

got = is.read(ret, off, size - off);

/**

* End of stream

*/

if (got == -1)

break;

off += got;

if (off == size)

{

/**

* If we've read to the end of our buffer,

* enlarge it.

*/

size *= 2;

byte[] tmp = new byte[size];

System.arraycopy(ret, 0, tmp, 0, off);

ret = tmp;

}

}

}

finally

{

if(is!=null)is.close();

}

/**

* If we've got a bigger buffer than we need, resize it

*/

if (off != size)

{

byte[] tmp = new byte[off];

System.arraycopy(ret, 0, tmp, 0, off);

ret = tmp;

}

return ret;

}

}

The receiveMTOM() method expects an OMElement, which then contains the binary file. The method simply pulls the binary file out of the SOAP message and saves it to the local file system. The file is identified by the OUTPUT_FILE static variable. Note that we have to

binaryNode.setOptimize(true);

prior to calling the getDataHandler() method.

4. The service implementation is done. Now we have to create a service.xml file. To do that, first create a folder under the root of the project and name it META-INF. Within this folder, create an xml file and name it services.xml. Paste the following into that file.

<service name="TestService">

<description>

This service is to get the running Axis version

</description>

<parameter name="ServiceClass">com.test.TestService</parameter>

<parameter name="enableMTOM">true</parameter>

<operation name="receiveMTOM">

<messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />

</operation>

</service>

To enable MTOM, we have to add a parameter for the service called enableMTOM and set its value to true. Note that MTOM is disabled by default.

5. The next thing we have to do is package the service as a jar file but with a "aar" extension. You can do this several ways, but the easiest approach is to just use Eclipse's Export functionality.

6. Once you have the aar file, copy it under the axis2 deployment within Tomcat. This would be at TOMCAT_INSTALL\webapps\axis2\WEB-INF\services\

7. Restart Tomcat.

Create MTOM enabled Web Service Client

1. Before we can call the service, we need a proxy to the web service. Assuming Tomcat is running, browse to the following URL. http://localhost:8080/axis2/services/TestService?wsdl

2. To create the proxy, save the WSDL file to the bin directory of the Axis2 installation. Name the file TestService.wsdl. Note that within this directory, you'll also have the WSDL2Java.bat file that we need to use to create the web service proxy.

3. After you save the WSDL file, open a command prompt and execute

WSDL2JAVA -uri TestService.wsdl

This should create the proxy files with a folder name src.

4. Now we need to create a java project within Eclipse for the web service client. Create a new Project and name it TestMTOMClient.

5. Add the Axis2 jar files to the classpath.

6. Copy the src folder from step 3 under the TestMTOMClient project and refresh the project in Eclipse.

7. Now create a class to execute the web service. Name the class TestClient and paste the following in its place.

package com.test;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import junit.framework.TestCase;

import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axiom.om.OMText;

import org.apache.axis2.Constants;

import org.apache.axis2.util.Base64;

import com.test.TestServiceStub.ReceiveMTOM;

 

public class TestClient extends TestCase

{

private static final String EPR = "http://localhost:9080/axis2/services/TestService";

private static final String INPUT_FILE = "C:\\HOLD\\small.pdf";

public static void main(String[] args) throws Exception

{

}

public void testMTOM()throws Exception

{

TestServiceStub stub = new TestServiceStub(EPR);

stub._getServiceClient().getOptions().setProperty(

Constants.Configuration.ENABLE_MTOM, Constants.VALUE_TRUE);

/**

* set timeout

*/

stub._getServiceClient().getOptions().setTimeOutInMilliSeconds(10000);

String base64String = Base64.encode(readFully(INPUT_FILE));

ReceiveMTOM rmtom = new ReceiveMTOM();

OMFactory fac = OMAbstractFactory.getOMFactory();

OMText binaryNode =fac.createOMText(base64String,"application/pdf",true);

OMNamespace omNs = fac.createOMNamespace("http://test.com", "ns0");

OMElement method = fac.createOMElement("receiveMTOM", omNs);

method.addChild(binaryNode);

rmtom.setElement(method);

stub.receiveMTOM(rmtom);

System.out.println("done calling service...");

}

}

The testMTOM() method creates a stub and sets the enableMTOM property. It then reads the a file and then does a base64 encoded on the file's contents. Finally, it calls the receiveMTOM() metod on the stub. Note that to call the web service, we need a sample file. The class above assumes there is a file named "small.pdf" at c:\hold\. So create a folder named hold in the root of the c:\ drive and copy a pdf file in that directory. Name the file small.pdf.

9. Before you call the service, start TCPMon so we can view the SOAP message that the client sends to the service so you can verify that the SOAP Message is really an MTOM enabled SOAP request. Configurre TCPMon so that the listen port is 9080 and the listener's Target port is 8080. Assuming that Tomcat is running on port 8080. Also realize that the client above is setup to call the service on 9080 so as long as we configure TCPMon correctly we should be okay. If you are not using TCPMon, change the EPR variable in the client class so that it points to port 8080 or whatever port your service is running on.

10. Assuming you did everything correctly, you should be able to send some MTOM SOAP messages to the service. When the service receives a request, it save the binary file to c:\hold\att.pdf.

 

References

1. Download Tomcat: http://tomcat.apache.org/download-55.cgi

2. Download Axis2 version 1.3: http://ws.apache.org/axis2/download/1_3/download.cgi

3. Download TCPMonitor: http://ws.apache.org/commons/tcpmon/download.cgi

4. Using TCPMonitor: http://wso2.org/project/wsas/java/2.1/docs/tools/tcpmonguide.html

5. A very good article on MTOM with Axis2: http://ws.apache.org/axis2/1_1_1/mtom-guide.html

6. Apache MTOM User Guide: http://ws.apache.org/axis2/1_0/mtom-guide.html

11/9/2007 3:22:14 PM (Eastern Standard Time, UTC-05:00) #    Comments [4]  |  Trackback

 

WS-Security, Axis2 and Rampart#

One of the things that I don’t like about Java is that the community is not using the same foundational libraries. For example, there is not a standard J2EE application server. Instead there is a specification and hundreds of vendors. As a result, everyone seems to be using a different implementation of a specification, and thus finding answers to questions is a lot harder because the user community is spread across many many implementations.

Recently, I had to call a web service which was protected by WS-Security UsernameToken. I was able to quickly call the service with Axis 1.1 and WSS4J but when I tried to use Axis2 and Rampart I ran into exception after exception. After several days of struggling, I was eventually able to create a web service client that created the proper soap message.

This blog contains a step by step procedure to create a web service client to a web service which is protected by ws-security UsernameToken.

I am assuming that you have already deployed the web service which is protected by ws-security and that you simply want to call the service using Axis2 version 1.3 and Rampart 1.3. Here are the steps:

1) Download Axis2 version 1.3 from http://ws.apache.org/axis2/

2) Download Rampart 1.3 from http://ws.apache.org/axis2/modules/index.html

3) Add Axis2 JARs to your client application classpath--all JARs from the Axis2 distribution lib directory.

3) Add the Rampart jars to your client application classpath--all jars from the Rampart distribution lib directory.

4) Create a proxy to the web service that is protected by ws-security--using the WSDL2Java tool or batch file.

5) Create an Axis2 client-configuration file which engages rampart and which has the ws-security parameter. Place this file within your client application project. For example, the standard is to create a folder called "conf" for this file.

Here is an example file:

 

<axisconfig name="AxisJava2.0">

<!-- engage rampart -->

<module ref="rampart" />

 

<!-- ws-security parameters: UsernameToken and PasswordText -->

<parameter name="OutflowSecurity">

<action>

<items>UsernameToken</items>

<user>myusername</user>

<passwordCallbackClass>com.mycomp.test.PWCBHandler</passwordCallbackClass>

<passwordType>PasswordText</passwordType>

</action>

</parameter>

<!-- ================================================= -->

<!-- Parameters -->

<!-- ================================================= -->

<parameter name="hotdeployment">true</parameter>

<parameter name="hotupdate">false</parameter>

<parameter name="enableMTOM">false</parameter>

<parameter name="enableSwA">false</parameter>

<!--Uncomment if you want to enable file caching for attachments -->

<!--parameter name="cacheAttachments">true</parameter>

<parameter name="attachmentDIR"></parameter>

<parameter name="sizeThreshold">4000</parameter-->

<!--This will give out the timout of the configuration contexts, in seconds-->

<parameter name="ConfigContextTimeoutInterval">30</parameter>

<!--During a fault, stacktrace can be sent with the fault message. The following flag will control -->

<!--that behaviour.-->

<parameter name="sendStacktraceDetailsWithFaults">false</parameter>

<parameter name="DrillDownToRootCauseForFaultReason">false</parameter>

<parameter name="userName">admin</parameter>

<parameter name="password">axis2</parameter>

<!--Set the flag to true if you want to enable transport level session mangment-->

<parameter name="manageTransportSession">false</parameter>

<!-- Following parameter will completely disable REST handling in Axis2-->

<parameter name="disableREST" locked="true">false</parameter>

<!-- ================================================= -->

<!-- Message Receivers -->

<!-- ================================================= -->

<!--This is the Deafult Message Receiver for the system , if you want to have MessageReceivers for -->

<!--all the other MEP implement it and add the correct entry to here , so that you can refer from-->

<!--any operation -->

<!--Note : You can ovride this for particular service by adding the same element with your requirement-->

<messageReceivers>

<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"

class="org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver"/>

<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"

class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>

</messageReceivers>

<!-- ================================================= -->

<!-- Message Formatter -->

<!-- ================================================= -->

<!--Following content type to message formatter mapping can be used to implement support for different message -->

<!--format serialization in Axis2. These message formats are expected to be resolved based on the content type. -->

<messageFormatters>

<messageFormatter contentType="application/x-www-form-urlencoded"

class="org.apache.axis2.transport.http.XFormURLEncodedFormatter"/>

<messageFormatter contentType="application/xml"

class="org.apache.axis2.transport.http.ApplicationXMLFormatter"/>

<messageFormatter contentType="text/xml"

class="org.apache.axis2.transport.http.ApplicationXMLFormatter"/>

<messageFormatter contentType="application/echo+xml"

class="org.apache.axis2.transport.http.ApplicationXMLFormatter"/>

</messageFormatters>

<!-- ================================================= -->

<!-- Transport Ins -->

<!-- ================================================= -->

<transportReceiver name="http"

class="org.apache.axis2.transport.http.SimpleHTTPServer">

<parameter name="port">6060</parameter>

</transportReceiver>

<!--Uncomment this and configure as appropriate for JMS transport support, after setting up your JMS environment (e.g. ActiveMQ)

<transportReceiver name="jms" class="org.apache.axis2.transport.jms.JMSListener">

<parameter name="myTopicConnectionFactory">

<parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter>

<parameter name="java.naming.provider.url">tcp://localhost:61616</parameter>

<parameter name="transport.jms.ConnectionFactoryJNDIName">TopicConnectionFactory</parameter>

</parameter>

<parameter name="myQueueConnectionFactory">

<parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter>

<parameter name="java.naming.provider.url">tcp://localhost:61616</parameter>

<parameter name="transport.jms.ConnectionFactoryJNDIName">QueueConnectionFactory</parameter>

</parameter>

<parameter name="default">

<parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter>

<parameter name="java.naming.provider.url">tcp://localhost:61616</parameter>

<parameter name="transport.jms.ConnectionFactoryJNDIName">QueueConnectionFactory</parameter>

</parameter>

</transportReceiver>-->

<!-- ================================================= -->

<!-- Transport Outs -->

<!-- ================================================= -->

<transportSender name="tcp"

class="org.apache.axis2.transport.tcp.TCPTransportSender"/>

<transportSender name="local"

class="org.apache.axis2.transport.local.LocalTransportSender"/>

<transportSender name="http"

class="org.apache.axis2.transport.http.CommonsHTTPTransportSender">

<parameter name="PROTOCOL">HTTP/1.1</parameter>

<parameter name="Transfer-Encoding">chunked</parameter>

<!-- If following is set to 'true', optional action part of the Content-Type will not be added to the SOAP 1.2 messages -->

<!-- <parameter name="OmitSOAP12Action">true</parameter> -->

</transportSender>

<transportSender name="https"

class="org.apache.axis2.transport.http.CommonsHTTPTransportSender">

<parameter name="PROTOCOL">HTTP/1.1</parameter>

<parameter name="Transfer-Encoding">chunked</parameter>

</transportSender>

<!-- ================================================= -->

<!-- Global Modules -->

<!-- ================================================= -->

<!-- Comment this to disable Addressing -->

<module ref="addressing"/>

<!--Configuring module , providing parameters for modules whether they refer or not-->

<!--<moduleConfig name="addressing">-->

<!--<parameter name="addressingPara">N/A</parameter>-->

<!--</moduleConfig>-->

<!-- ================================================= -->

<!-- Clustering -->

<!-- ================================================= -->

<!-- Configure and uncomment following for preparing Axis2 to a clustered environment -->

<!--

<cluster class="org.apache.axis2.cluster.tribes.TribesClusterManager">

<parameter name="param1">value1</parameter>

</cluster>

-->

<!-- ================================================= -->

<!-- Phases -->

<!-- ================================================= -->

<phaseOrder type="InFlow">

<!-- System predefined phases -->

<phase name="Transport">

<handler name="RequestURIBasedDispatcher"

class="org.apache.axis2.dispatchers.RequestURIBasedDispatcher">

<order phase="Transport"/>

</handler>

<handler name="SOAPActionBasedDispatcher"

class="org.apache.axis2.dispatchers.SOAPActionBasedDispatcher">

<order phase="Transport"/>

</handler>

</phase>

<phase name="Addressing">

<handler name="AddressingBasedDispatcher"

class="org.apache.axis2.dispatchers.AddressingBasedDispatcher">

<order phase="Addressing"/>

</handler>

</phase>

<phase name="Security"/>

<phase name="PreDispatch"/>

<phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase">

<handler name="RequestURIBasedDispatcher"

class="org.apache.axis2.dispatchers.RequestURIBasedDispatcher"/>

<handler name="SOAPActionBasedDispatcher"

class="org.apache.axis2.dispatchers.SOAPActionBasedDispatcher"/>

<handler name="RequestURIOperationDispatcher"

class="org.apache.axis2.dispatchers.RequestURIOperationDispatcher"/>

<handler name="SOAPMessageBodyBasedDispatcher"

class="org.apache.axis2.dispatchers.SOAPMessageBodyBasedDispatcher"/>

<handler name="HTTPLocationBasedDispatcher"

class="org.apache.axis2.dispatchers.HTTPLocationBasedDispatcher"/>

</phase>

<phase name="RMPhase"/>

<!-- System predefined phases -->

<!-- After Postdispatch phase module author or service author can add any phase he want -->

<phase name="OperationInPhase"/>

<phase name="soapmonitorPhase"/>

</phaseOrder>

<phaseOrder type="OutFlow">

<!-- user can add his own phases to this area -->

<phase name="soapmonitorPhase"/>

<phase name="OperationOutPhase"/>

<!--system predefined phase-->

<!--these phase will run irrespective of the service-->

<phase name="RMPhase"/>

<phase name="PolicyDetermination"/>

<phase name="MessageOut"/>

<phase name="Security"/>

</phaseOrder>

<phaseOrder type="InFaultFlow">

<phase name="Addressing">

<handler name="AddressingBasedDispatcher"

class="org.apache.axis2.dispatchers.AddressingBasedDispatcher">

<order phase="Addressing"/>

</handler>

</phase>

<phase name="Security"/>

<phase name="PreDispatch"/>

<phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase">

<handler name="RequestURIBasedDispatcher"

class="org.apache.axis2.dispatchers.RequestURIBasedDispatcher"/>

<handler name="SOAPActionBasedDispatcher"

class="org.apache.axis2.dispatchers.SOAPActionBasedDispatcher"/>

<handler name="RequestURIOperationDispatcher"

class="org.apache.axis2.dispatchers.RequestURIOperationDispatcher"/>

<handler name="SOAPMessageBodyBasedDispatcher"

class="org.apache.axis2.dispatchers.SOAPMessageBodyBasedDispatcher"/>

<handler name="HTTPLocationBasedDispatcher"

class="org.apache.axis2.dispatchers.HTTPLocationBasedDispatcher"/>

</phase>

<phase name="RMPhase"/>

<!-- user can add his own phases to this area -->

<phase name="OperationInFaultPhase"/>

<phase name="soapmonitorPhase"/>

</phaseOrder>

<phaseOrder type="OutFaultFlow">

<!-- user can add his own phases to this area -->

<phase name="soapmonitorPhase"/>

<phase name="OperationOutFaultPhase"/>

<phase name="RMPhase"/>

<phase name="PolicyDetermination"/>

<phase name="MessageOut"/>

</phaseOrder>

</axisconfig>

 

The configuration file above is a typical client config file with the rampart and ws-security configuration.

6) When calling the web service through the web service stub, you'll have to give Axis the path to the Axis configuration file above. Moreover, for Axis to engage Rampart, you'll have to give Axis the path to the Axis respository folder. The repository folder needs to have a subdirectory called "modules" and within that directory, the Rampart 1.3 "mar" file has to exist. Therefore, create a folder somewhere within your project called "repository". Then create a subdirectory called "modules". Finally, copy the "rampart-1.3.mar" file from the Rampart distributable.

7) Now you are ready to make a service call. First create an instance of a custom configuration passing in the repository path and the axis configuration file path.

ConfigurationContext cxt =

ConfigurationContextFactory.createConfigurationContextFromFileSystem("c:\\folderpath\\repository","c:\\path_to_axis_config_file.xml");

8) Create a stub with the custom axis configuration instance.

HelloServiceStub stub = new HelloServiceStub(ctx,"http://localhost:8080/services/HelloService");

HelloServiceStub.Echo echoReq = new HelloServiceStub.Echo();

System.out.println("calling service...");

HelloServiceStub.EchoResponse resp = stub.echo(echoReq);

System.out.println("done calling service...");

The key to the whole process was ConfigurationContextFactory.createConfigurationContextFromFileSystem. When calling this method, you need to make sure you pass the correct repository path and a proper Axis2 client configuration file. The path to the repository has to have a sub directory called "modules" and that has to contain the rampart "mar" file. If this is not correct, then Axis2 will not be able to engage Rampart.

10/9/2007 7:00:29 AM (Eastern Standard Time, UTC-05:00) #    Comments [4]  |  Trackback

 

How to add a start menu icon for your ClickOnce deployed application using MSBuild?#
If you have an automated build and deployment process using MSBuild, then you likely use the GenerateApplicationManifest and GenerateDeploymentManifest tasks to create the ClickOnce application and deployment manifest, respectively. If you are using these tasks, you may have wondered how you can set an icon (under the start menu) for your application. To have an icon set, you have to do several things:
 
1) Package the icon with the deployment. The easiest way to do this is to add an icon file to your main project--that ensure that it gets deployed with the application.
2) After you add the icon file, right click on the file in Visual Studio, and set the Build Action to Content.
3) At deployment time, set the IconFile attribute on GenerateApplicationManifest to the icon file name (e.g., myapp.ico), you added in step (1).
 
The above will add an iconFile attribute to the assembly description element, e.g., <description iconFile="myapp.ico" />, in the application manifest file (e.g., myapp.exe.manifest). When ClickOnce sees this attribute set, it will look in the ClickOnce deployment for the file and create a Start Menu Icon for your application.
 
An interesting thing to note is that the iconFile attribute is set in the application manifest and not in the deployment manifest. The reason for this is to allow you to change your application icons from one deployment to the next.
 
 
If you are deploying your ClickOnce application using Visual Studio and need to set the Start Mneu icon, do steps (1) and (2), and then go to the Project | Properties and choose the Application tab. Under the Resources group box, set the application icon to the icon you added in step (1). ClickOnce should take care of the rest. You can find out more on this at http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=170920&SiteID=1 .
10/11/2006 8:20:56 AM (Eastern Standard Time, UTC-05:00) #    Comments [4]  |  Trackback

 

Application Block Internals: Demystifying the Object Builder#

Demystifying the ObjectBuilder

 

In my last post, I gave an introduction to ObjectBuilder. In this post, I take ObjectBuilder apart and talk about all of the hairy details.

 

ObjectBuilder is used in the Enterprise Library, the CAB, and the Mobile framework MS just released. If you have to extend any of the above frameworks/libraries in a non-trivial way, you are going to need to understand what is going on under the covers. Under the covers of the above frameworks/libraries is ObjectBuilder--the dependency injection framework.

 

Unfortunately, there is not any documentation for the ObjectBuilder, so learning is a real nightmare. I was able to break it by going over the unittests that ship with the source code.

 

Fundamental Classes in Object Builder

 

Strategy and Policies - OB is based upon policies and strategies. Strategies are chained (i.e. OB implements the Chain of Responsability pattern on strategies) and get registered for a build stage.  Strategies use policies to figure out how to build an object. Policies are registered with OB for types. A policy is defined for types (objects in OB are defined by the type and ID).

Locator - locators in OB are used to find registered objects. When an object is created, it gets registered with the locator (see CreationStrategy.RegisterObject).

LifetimeContainer - objects managed by the object builder can have a lifetime associated with them. The thing that determines how long an object stays around is the container that the object is associated with. LifetimeContainers in OB, maintain a list of objects. When the container is disposed off, the objects in it are also disposed.

BuilderContext- an object that defines the context for the build-up and tear-down of an object. BuilderContext holds the strategies, policies and locator for the given build-up or tear-down. It also provides a method to iterate the chain of strategies (see IBuilderContext and BuilderContext).

 

OB By Example

 

/**********Create A Singleton*************/

public void CreateASingleton()
{
    // we need a locator, a strategy chain, and a list of policies.
    Locator locator = new Locator();
    BuilderStrategyChain strategyChain = new BuilderStrategyChain();
    PolicyList policies = new PolicyList();
    // in order to build a singleton, we have to
    // have a SingletonStrategy. The singleton strategy
    // in turn uses a SingletonPolicy.
    // add a SingletonStrategy to the strategy chain
    strategyChain.Add(new SingletonStrategy());
    // add a CreationStrategy to the strategy chain
    strategyChain.Add(new CreationStrategy());
    // SingletonStrategy requires a SingletonPolicy
    policies.Set<ISingletonPolicy>(new SingletonPolicy(true), typeof(MyObject), null);
    // we also need a creation policy
    policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
    // in order to make singletons, we need a lifetime container in the locator
    locator.Add(typeof(ILifetimeContainer), new LifetimeContainer());
    // create the object
    BuilderContext cxt = new BuilderContext(strategyChain, locator, policies);
    // in order to properly track singletons, we have to give the instance an ID.
    object myObj = strategyChain.Head.BuildUp(cxt, typeof(MyObject), "MyObject_Singleton", null);
    object myObj2 = strategyChain.Head.BuildUp(cxt, typeof(MyObject), "MyObject_Singleton", null);
    if (myObj == myObj2)
    {
        // Got singleton in myObj2
        int J = 0;
    }
}

 

The example above demonstrates creating singleton objects with the OB. Most of the work simply sets up using the OB. For example, in order to create an object, we need to have a build context. A build context requires a locator, a strategy chain, and a policy list. There are several pieces that enable singletons, however. For example, utimately a strategy is the thing that will create an object in OB. Strategies rely on policies to determine how to create an object. To create a singleton, we have to have a SingletonStrategy in the strategy chain. The singleton strategy looks for a SingletonPolicy, registered for the object being created. Recall that policies are setup for types.

 

There are a few design aspects of OB that we have to understand. OB uses something called a Locator. A locator knows how to find registered objects. Locators can be nested (i.e., a locator can have a parent). Locators make use of something called a LifetimeContainer. LifetimeContainer puts a boundary around the lifetime of a created object. When a locator is asked to find an object it can look in the current locator and/or it's parent locator (if one exists).

 

It's important to know that when searching for singletons, OB looks only at the current locator--the parent is not searched. The SingletonStrategy class in OB is shown below for reinforcement of this.

 

public class SingletonStrategy : BuilderStrategy
 {
    public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
    {

       DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey(typeToBuild, idToBuild);

 

       if (context.Locator != null && context.Locator.Contains(key, SearchMode.Local))
       {
            TraceBuildUp(context, typeToBuild, idToBuild, "");
             return context.Locator.Get(key);
       }

 

       return base.BuildUp(context, typeToBuild, existing, idToBuild);
    }
 }

 

As shown, the SingletonStrategy checks to see if the build context has a locator,and then asks the locator to find the object using a SearchMode.Local. This tells the locator not to look in the parent locator for the object. This has obvious usage implications--if you don't create your singletons with the correct locator, then you'll end up breaking the singleton (TODO: more on this.).

 

Also note that we have put a CreationStrategy in the chain of strategies for our singleton object. The SingletonStrategy ensures that once we have an object, that object is returned on subseqent build-up requests. The CreationStrategy is needed to build-up the object the first time. Since the CreationStrategy is the strategy that builds the object, this strategy also registers the object with the locator/container so that it can be pulled out the next time. We can see this by looking at the CreationStrategy.

 

public class CreationStrategy : BuilderStrategy
{
  public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   if (existing != null)
    BuildUpExistingObject(context, typeToBuild, existing, idToBuild);
   else
    existing = BuildUpNewObject(context, typeToBuild, existing, idToBuild);

 

   return base.BuildUp(context, typeToBuild, existing, idToBuild);
  }

 

  private void BuildUpExistingObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   RegisterObject(context, typeToBuild, existing, idToBuild);
  }

 

  [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]
  private object BuildUpNewObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   ICreationPolicy policy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);

 

   if (policy == null)
   {
    if (idToBuild == null)
     throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
      Properties.Resources.MissingPolicyUnnamed, typeToBuild));
    else
     throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
      Properties.Resources.MissingPolicyNamed, typeToBuild, idToBuild));
   }

 

   try
   {
    existing = FormatterServices.GetSafeUninitializedObject(typeToBuild);
   }
   catch (MemberAccessException exception)
   {
    throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Properties.Resources.CannotCreateInstanceOfType, typeToBuild), exception);
   }

 

   RegisterObject(context, typeToBuild, existing, idToBuild);
   InitializeObject(context, existing, idToBuild, policy);
   return existing;
  }

 

  private void RegisterObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   if (context.Locator != null)
   {
    ILifetimeContainer lifetime = context.Locator.Get<ILifetimeContainer>(typeof(ILifetimeContainer), SearchMode.Local);

 

    if (lifetime != null)
    {
     ISingletonPolicy singletonPolicy = context.Policies.Get<ISingletonPolicy>(typeToBuild, idToBuild);

 

     if (singletonPolicy != null && singletonPolicy.IsSingleton)
     {
      context.Locator.Add(new DependencyResolutionLocatorKey(typeToBuild, idToBuild), existing);
      lifetime.Add(existing);

 

      if (TraceEnabled(context))
       TraceBuildUp(context, typeToBuild, idToBuild, Properties.Resources.SingletonRegistered);
     }
    }
   }
  }
  //...more methods ....
}

 

As shown above, the BuildUp method checks to see if it has to build-up a new object or an existing one. In our case, we are building up an object for the firsttime. In this case, the BuildUpNewObject method is called. This method looks on the build context for a ICreationPolicy for the required object. If it doesn't find one, it throws an exception--in other words, you have to have a policy registered for the object that tells OB how to create the object. In our case, we are using the DefaultCreationPolicy. If there is a ICreationPolicy, then the method builds a bare-bones object (one that is not initialized) using 

 

existing = FormatterServices.GetSafeUninitializedObject(typeToBuild);

 

and then calls RegisterObject(). This is where the singleton magic happens. As you can see from the code snippet above, the CreationStrategy looks for a lifetime container on the local locator and if one exists, it looks for a singleton policy registered for the required object. If it finds the singleton policy, it adds a key for the newly build object to the lifetime container.

 

Thus, we can conclude that in order to create singleton objects with OB, we need to have a SingletonStrategy and a SingletonPolicy. There are two aspects to creating a singleton:

 

  1. When creating an object, you have to register it.
  2. When creating an object, you have to check to see if one already exists.

The singleton policy, ensures that the object gets registered with the local locator's lifetime container and the singleton strategy ensures that the registered object gets returned on subsequent build-up operations.

 

OB is a glorified object factory and DI framework. To support DI, OB ships with some strategies and policies. Lets investigate these now. [TODO: Correct this intro into DI via Properties]

 

Injecting into Properties

 

DI in OB is achived at build-up. When you ask OB to build-up an object, OB runs a chain of strategies. The idea is to inject dependencies into objects at build-up. To do that, you put a strategy object, an object that knows how to do the proper injection, into the chain of strategies. One of the strategies that knows how to inject dependencies is the PropertySetterStrategy. An example will help.

 

public void PropertyInjectionExample()
{
 Locator locator = new Locator();
 LifetimeContainer container = new LifetimeContainer();
 locator.Add(typeof(ILifetimeContainer), container);
 // strategies ...
 BuilderStrategyChain chain = new BuilderStrategyChain();
 chain.Add(new CreationStrategy());
 chain.Add(new PropertySetterStrategy());
 // policies...
 PolicyList policies = new PolicyList();
 // Property setter policy for MyDAOObject's ConnectionString
 PropertySetterPolicy psp = new PropertySetterPolicy();
 // add a property for the ConnectionString property
 psp.Properties.Add("ConnectionString", new PropertySetterInfo("ConnectionString",new ValueParameter<string>("the connection string value would be here")));
 policies.Set<IPropertySetterPolicy>(psp, typeof(MyDAOObject), null);
 policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
 // create build conetxt...
 BuilderContext cxt = new BuilderContext(chain, locator, policies);
 // build the object
 MyDAOObject obj = chain.Head.BuildUp(cxt, typeof(MyDAOObject), null, null) as MyDAOObject;
}

 

class MyDAOObject
{
    private string connectionString;
    public string ConnectionString
    {
        get
        {
            return connectionString;
        }
        set
        {
            connectionString = value;
        }
    }
    public MyDAOObject():this(null) { }
    public MyDAOObject(string conStr)
    {
        connectionString = conStr;
    }
}

 

In our introduction, we mentioned that DI is achieved by injecting dependencies into properties or via constructors. The PropertySetterStrategy, as the name suggests, injects dependencies using properties. In order to get properties injected into your objects, you have to add a PropertySetterStrategy instance to the strategy chain (as shown above). The BuildUp method of this strategy is shown below.

 

public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
 if (existing != null)
  InjectProperties(context, existing, idToBuild);

 

 return base.BuildUp(context, typeToBuild, existing, idToBuild);
}

 

As you can see, the BuildUp method ensures that the object has been created and then it calls the InjectProperties method. Note the implication here--you have to make sure you have a strategy that creates the object ahead of the PropertySetterStrategy in the strategy chain. Otherwise, the strategy is effectively skipped because the base class's BuildUp is called to call the next strategy in the chain. Now lets have a look at the InjectProperties method (see below).

 

private void InjectProperties(IBuilderContext context, object obj, string id)
{
 if (obj == null)
  return;

 

 Type type = obj.GetType();
 IPropertySetterPolicy policy = context.Policies.Get<IPropertySetterPolicy>(type, id);

 

 if (policy == null)
  return;

 

 foreach (IPropertySetterInfo propSetterInfo in policy.Properties.Values)
 {
  PropertyInfo propInfo = propSetterInfo.SelectProperty(context, type, id);

 

  if (propInfo != null)
  {
   if (propInfo.CanWrite)
   {
    object value = propSetterInfo.GetValue(context, type, id, propInfo);

 

    if( value != null )
     Guard.TypeIsAssignableFromType(propInfo.PropertyType, value.GetType(), obj.GetType());

 

    if (TraceEnabled(context))
     TraceBuildUp(context, type, id, Properties.Resources.CallingProperty, propInfo.Name, propInfo.PropertyType.Name);

 

    propInfo.SetValue(obj, value, null);
   }
   else
   {
    throw new ArgumentException(String.Format(
     CultureInfo.CurrentCulture,
     Properties.Resources.CannotInjectReadOnlyProperty,
     type, propInfo.Name));
   }
  }
 }
}

 

The InjectProperties method checks to see if an object exists and then looks for an implementation of IPropertySetterPolicy registered for the type. If it finds an IPropertySetterPolicy for the type, it iterates over the list properties that need to be injected. The list of properties that need to be injected and the values that need to be injected into these properties are defined by IPropertySetterPolicy.

 

public interface IPropertySetterPolicy : IBuilderPolicy
{
 Dictionary<string, IPropertySetterInfo> Properties { get; }
}

 

Every property that needs to be injected has to have an entry into this dictionary. Every property has a string key and an IPropertySetterInfo instance that defines the property and the value for that property. Therefore, in our example above, when we create the PropertySetterPolicy for MyObject, we added the following for the ConnectionString property.

 

psp.Properties.Add("ConnectionString", new PropertySetterInfo("ConnectionString",new ValueParameter<string>("the connection string value would be here")));

 

The Add method adds an entry with the key "ConnectionString" and a new PropertySetterInfo for the ConnectionString property. If we go back to the InjectProperties method, we can see that the method iterates over the list of IPropertySetterInfo objects. For each property configured for injection, it calls the SelectProperty method on the IPropertySetterInfo object. The default implementation of this interface (PropertySetterInfo) looks at the type to see if it has a property with given name and if it finds one, it returns a PropertyInfo for that type. Note that when we created the PropertySetterInfo object for the property, we gave it the name of the property along with the ValueParameter instance in the constructor of PropertySetterInfo. The IPropertySetterInfo interface defines the SelectProperty method, mentioned above, and the GetValue method (see below). The GetValue method returns the value that has to be injected into the property.

 

public interface IPropertySetterInfo
{
 object GetValue(IBuilderContext context, Type type, string id, PropertyInfo propInfo);

 

 PropertyInfo SelectProperty(IBuilderContext context, Type type, string id);
}

 

After the InjectProperty method gets the PropertyInfo object for the property, it checks to see if the property can be written to (i.e., if it has a setter), and then calls the GetValue method on the IPropertySetterInfo implementation. The default implementation of this interface calls the GetValue method on the IParameter. In our example, we passed in an ValueParameter instance, which is an implementation of IParameter that stores the value in the class and returns it when needed. Once the value for the property is obtained, the InjectProperties method checks to make sure that the value of the property can be assigned to the property and then it calls PropertyInfo.SetValue.

 

That's the details on DI via properties. With this much detail, it is easy to lose focus on the big picture, so lets understand the design of this aspect of the OB. The figure below dipicts the design of DI via properties in OB.

 

As shown, there are really three core interfaces involved in the design, IPropertySetterPolicy, IPropertySetterStrategy, and IPropertySetterInfo. The strategy class looks for a registered IPropertySetterPolicy at build-up for a given type. If the policy is registered for the type, the strategy iterates the properties that require injection. Every injection property is represented by an IPropertySetterInfo. IPropertySetterInfo encapsulates two things about the property: what the property is and how to get a value for the property. OB provides an implementation of this interface, PropertySetterInfo, that operates on an IParameter abstraction (see PropertySetterInfo class below).

 

public class PropertySetterInfo : IPropertySetterInfo
{
 string name = null;
 PropertyInfo prop = null;
 IParameter value = null;

 

 public PropertySetterInfo(string name, IParameter value)
 {
  this.name = name;
  this.value = value;
 }

 

 public PropertySetterInfo(PropertyInfo propInfo, IParameter value)
 {
  this.prop = propInfo;
  this.value = value;
 }

 

 public PropertyInfo SelectProperty(IBuilderContext context, Type type, string id)
 {
  if (prop != null)
   return prop;

 

  return type.GetProperty(name);
 }

 

 public object GetValue(IBuilderContext context, Type type, string id, PropertyInfo propInfo)
 {
  return value.GetValue(context);
 }
}

 

As shown, at a minimum, you need to know two things to get a value injected into a property: 1) the name of the property and 2) how to get the value for the property. IParameter provides an abstraction to obtaining, and customizing, how the PropertySetterStrategy obtains the value to assign to the property. In our example earlier, for example, we used a ValueParameter extension that stored the value of the ConnectionString in the class and returned it when the strategy asked for it. The design of DI via properties also shows that OB provides a number of other IParameter extensions. For example, the LookupParameter implementation of IParameter looks in the build-context to find the value of the parameter. Here is an example using LookupParameter.

 

public void LookupParameterExample()
{
    Locator locator = new Locator();
    LifetimeContainer container = new LifetimeContainer();
    locator.Add(typeof(ILifetimeContainer), container);
    BuilderStrategyChain chain = new BuilderStrategyChain();
    PolicyList policies = new PolicyList();
    // put the connection string in the context
    locator.Add("ConnectionString", "connection string goes here");
    // add strategies
    chain.Add(new CreationStrategy());
    chain.Add(new PropertySetterStrategy());
    // add policies
    policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
    PropertySetterPolicy psp = new PropertySetterPolicy();
    // configure the property value to be pulled from the
    // build context
    psp.Properties.Add("CS",new PropertySetterInfo("ConnectionString",new LookupParameter("ConnectionString")));
    // set the policy for the type MyDAOObject
    policies.Set<IPropertySetterPolicy>(psp, typeof(MyDAOObject), null);
    // create build context
    BuilderContext cxt = new BuilderContext(chain, locator, policies);
    // build-up object
    MyDAOObject obj = chain.Head.BuildUp(cxt, typeof(MyDAOObject), null, null) as MyDAOObject;
}

 

First notice that we add an item to the locator named "ConnectionString". Second, when we add a property registration to the PropertySetterPolicy, we use a LookupParameter. Finally, when we create the LookupParameter, we give the key to use when looking up the parameter's value.

 

Now lets talk about DI via constructors.

 

Injecting into Constructors

 

Similar to how we inject into properties, we can inject into constructors.

 

Locator locator = new Locator();
// add connection string to the locator
locator.Add("ConnectionString", "connection string goes here...");
LifetimeContainer container = new LifetimeContainer();
locator.Add(typeof(ILifetimeContainer), container);
BuilderStrategyChain chain = new BuilderStrategyChain();
PolicyList policies = new PolicyList();
// add strategies
chain.Add(new CreationStrategy());
// add policies
ConstructorPolicy cp = new ConstructorPolicy();
// use a lookup parameter to get the connection string from
// the local locator
cp.AddParameter(new LookupParameter("ConnectionString"));
policies.Set<ICreationPolicy>(cp, typeof(MyDAOObject), null);
BuilderContext cxt = new BuilderContext(chain, locator, policies);
// buildup
MyDAOObject obj = chain.Head.BuildUp(cxt, typeof(MyDAOObject), null, null) as MyDAOObject;

 

The goal of this excercise is to inject the connection string into MyDAOObject via the constructor at build-up. Recall from our definition of MyDAOObject above that the class defined two constructors: a zero argument constructor and one that took a string parameter (i.e. the connection string). Our objective is to have the connection string picked up from the locator and injected into the object when the object is build-up. To do that, we first do the usual stuff. We create a locator, lifetime container, a strategy chain and a policy list. We add strategies and set policies and then do build-up. The difference, as always, is in what strategies and policies we're using. One way to have injection occur, for constructors, is to use a CreationStrategy combined with a ConstructorPolicy. The ConstructorPolicy class is an ICreationPolicy that determines the constructor to call based on the parameters added to its policy. In our scenario, for example, we've added the connection string parameter using a LookupParameter. Recall that when the CreationStrategy.BuildUp is called, it gets the ICreationPolicy for the type and the calls SelectConstructor. When this method is called on the ConstructorPolicy, it reflects on the type and finds the constructor that matches the list of parameters added to its parameter list (see SelectConstructor below).

 

public ConstructorInfo SelectConstructor(IBuilderContext context, Type type, string id)
{
 if (constructor != null)
  return constructor;

 

 List<Type> types = new List<Type>();

 

 foreach (IParameter parm in parameters)
  types.Add(parm.GetParameterType(context));

 

 return type.GetConstructor(types.ToArray());
}

 

After the correct constructor is selected, CreationStrategy then asks the ICreationPolicy to get the parameters for the constructor. Similarly, ConstructorPolicy then looks at the list of parameters added to it, and creates an object array of values passed for the parameters. The method is shown below.

 

public object[] GetParameters(IBuilderContext context, Type type, string id, ConstructorInfo constructor)
{
 List<object> results = new List<object>();

 

 foreach (IParameter parm in parameters)
  results.Add(parm.GetValue(context));

 

 return results.ToArray();
}

 

Recall that LookupParameter.GetValue looks on the locator to get the value.

 

That's the fundementals of injected dependencies via properties and constructor parameters. In the next section, we'll extend this discussion and talk about DI using the Dependency attribute. As you'll see, you can decorate your objects properties and constructor parameters with [Dependency] and have them injected.

 

Injection into Methods by Method Execution

 

Thus far you've seen that OB can inject dependencies via properties and constructors. OB can also inject depdencies into methods by executing methods at build-up. The example below demonstrates this.

 

public class MyDAOObject
{
        private string username, password;
        ...
        public void Credentials(string userName, string passWord)
        {
            this.username = userName;
            this.password = passWord;
        }
}

 

public void ExecuteMethodExample()
{
    Locator locator = new Locator();
    LifetimeContainer container = new LifetimeContainer();
    locator.Add(typeof(ILifetimeContainer), container);
    BuilderStrategyChain chain = new BuilderStrategyChain();
    PolicyList policies = new PolicyList();
    // add strategies
    chain.Add(new CreationStrategy());
    chain.Add(new MethodExecutionStrategy());
    // add policies
    policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
    // add a method policy for MyDAOObject
    MethodPolicy mp = new MethodPolicy();
    IMethodCallInfo methodInfo = new MethodCallInfo("Credentials", new object[] {"myUserName","myPassword" });
    mp.Methods.Add("credentials", methodInfo);
    policies.Set<IMethodPolicy>(mp, typeof(MyDAOObject), null);
    // create context
    BuilderContext cxt = new BuilderContext(chain, locator, policies);
    // build up
    MyDAOObject obj = chain.Head.BuildUp(cxt, typeof(MyDAOObject), null, null) as MyDAOObject;
}

 

 

As you can see, we've added another method, Credentials, to our MyDAOObject class. The method takes two string parameters: the username and password. The purpose of the example is to build an instance of MyDAOObject and then execute the Credentials method on it. To do that, we ask use the MethodExecutionStrategy along with a IMethodPolicy implementation.

 

The example, by now, is self explanatory--add a MethodExecutionStrategy after the CreationStrategy and then setup a IMethodPolicy to register the Credentials method, with the specific parameters, on the MyDAOObject and then call BuildUp.

 

This is probably a good time to stop and make a point about OB's build-up process. If you think of dependency injection with properties and constructors and then compare that to dependency injection via method execution, you can see that there is a difference in semantics. This leads to several questions.

 

Why would you ever want to inject dependencies via methods? Isn't enough to just inject via properties and/or constructors? It seems to make more sense if you go with properties/constructors. For example, it makes perfect sense to inject the dependencies of an object when you create the object (via constructors). Setting properties on the object can be considered an extension of constructing an object, so its okay to inject dependencies after you call the constructor. But injecting the dependencies of an object by calling a method on it, seems a bit dirty. Why? Is it because an object may not have public properties or a visible constructor? For example, we may have a static class that only defines a static method. The answer to why OB supports DI via method execution has to do with the intended use of the build-up process.

 

Objects in OB can be build-up more than once. In fact, objects in OB are build-up repeatedly. This doesn't mean that objects are "re-instantiated". It just means that objects are run through the chain of strategies again and again. If you look at one of the definitions of IBuilder.BuildUp, we can see hints of this.

 

  object BuildUp(IReadWriteLocator locator, Type typeToBuild, string idToBuild, object existing,
       params PolicyList[] transientPolicies);

 

As shown, one of the parameters to BuildUp is an existing (created) instance of the object that needs to be build-up. If an object already exists, then OB runs the stratetgy chain on the existing object. This enables some interesting solutions to be built on top of OB. The CAB, for example, has built several interesting solutions on the principal that we can execute methods as part of the build-up process. CAB has implemented a publish-subscribe pattern and a command pattern on top of OB. The fact that OB can execute methods, and injection dependencies doing so, enables these features to be built. We'll talk about CAB's extensions later. For now, know that if it were not for these facilities in OB, then OB would simply be an object factory.

 

Metadata Driven OB

 

The examples we've covered thus far demonstrate the basics of OB. The real-world applications and application blocks built using OB make use of the metadata-driven facilities provided by OB, which build upon the strategies we've talked about thus far. Metadata-driven OB is driven by decorating objects with attributes. When you ask OB for an object, OB inspects the metadata on the object, hooks up policies, injects dependencies and then returns an instance. All of this happens at runtime when you build-up an object. At the heart of all of this is a strategy base class named ReflectionStrategy, which is extended by the three reflection based classes PropertyReflectionStrategy, ConstructorReflectionStrategy, and MethodReflectionStrategy, and a host of attribute classes. Namely, CreateNew, Dependency, InjectionConstructor, InjectionMethod, and InjectionParameter (see figure below).

 

Lets get started with an example using ConstructorReflectionStrategy and [Dependency].

 

public class MyDAOObject2
{
    private string connectionString = null;
    public MyDAOObject2([Dependency(Name = "ConnectionString", SearchMode = SearchMode.Local, NotPresentBehavior = NotPresentBehavior.Throw)]string connectionString)
    {
        this.connectionString = connectionString;
    }
}
public void ConstructorInjectionExample()
{
Locator locator = new Locator();
LifetimeContainer container = new LifetimeContainer();
locator.Add(typeof(ILifetimeContainer), container);
// add connection string to the context
locator.Add(new DependencyResolutionLocatorKey(typeof(string), "ConnectionString"), "connection string goes here");
BuilderStrategyChain chain = new BuilderStrategyChain();
PolicyList policies = new PolicyList();
// add strategies
chain.Add(new ConstructorReflectionStrategy());
chain.Add(new CreationStrategy());
// add policies
policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
// create context
BuilderContext cxt = new BuilderContext(chain, locator, policies);
MyDAOObject2 obj2 = chain.Head.BuildUp(cxt, typeof(MyDAOObject2), null, null) as MyDAOObject2;
}

 

As shown, we have a class named MyDAOObject2, which has a constructor that takes the connection string. The interesting thing here is that we have decorated the constructor parameter with the Dependency attribute. This effectively tells OB that the class has a dependency on the defined parameter and to inject the parameter at build-up. Note that we have supplied some values to several properties defined on the Dependency attribute class for the connection string parameter. Specifically, we have supplied the Name, SearchMode and NotPresentBehavior property values. When OB has to inject the dependency, it looks at these values to determine where to look for the value and what to do if the value cannot be found. In our example, we are telling OB to look in the current locator for the connection string value and if it cannot find the value, then throw an exception.

 

As usual, we create a locator, a strategy chain and a policy list. Note that we add the connection string to the locator using DependencyResolutionLocatorKey. In order for OB to interpret the [Dependency], we put an instance of ConstructorReflectionStrategy into the strategy chain. As always, strategies use polices, and so we set the default creation policy to DefaultCreationPolicy. When we call BuildUp, OB resolves the dependency and creates the object correctly. So where is the magic happening? Where does OB pick the Dependency attribute and at what point, and how is the value for the dependency obtained?

 

Most of the magic happens in the base class of ConstructorReflectionStrategy: ReflectionStrategy. The essentials of ReflectionStrategy is shown below.

 

public abstract class ReflectionStrategy<TMemberInfo> : BuilderStrategy
{
    protected abstract IEnumerable<IReflectionMemberInfo<TMemberInfo>> GetMembers(IBuilderContext context, Type typeToBuild, object existing, string idToBuild);

 

    protected abstract void AddParametersToPolicy(IBuilderContext context, Type typeToBuild, string idToBuild,
            IReflectionMemberInfo<TMemberInfo> member, IEnumerable<IParameter> parameters);

 

    protected abstract bool MemberRequiresProcessing(IReflectionMemberInfo<TMemberInfo> member);

 

  public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   foreach (IReflectionMemberInfo<TMemberInfo> member in GetMembers(context, typeToBuild, existing, idToBuild))
   {
    if (MemberRequiresProcessing(member))
    {
     IEnumerable<IParameter> parameters = GenerateIParametersFromParameterInfos(member.GetParameters());
     AddParametersToPolicy(context, typeToBuild, idToBuild, member, parameters);
    }
   }

 

   return base.BuildUp(context, typeToBuild, existing, idToBuild);
  }
  ...
}

 

ReflectionStrategy is the base class for metadata-driven injection processors. ReflectionStrategy defines three protected abstract methods that are used to define the template for consuming attributes on properties, constructors, and methods.

 

The BuildUp method in ReflectionStrategy defines the template for consuming injection attributes. Derived classes don't implement BuildUp--their job is to just provide implementation for th abstract methods. The BuildUp method calls the GetMembers abstract method to get the list of members that have to be considered for injection attributes and then iterate over each member, asking the derived class if the member needs to be processed. If so, then policies are added to the context for the injection parameters.

 

Lets now take a deeper look at the extension of ReflectionStrategy in turn. We'll start with ConstructorReflectionStrategy.

 

Dependency Injection using ConstructorReflectionStrategy

 

In our ConstructorReflectionStrategy example, we decorated the constructor parameter of MyDAOObject2 with [Dependency]. Lets see how the dependency is resolved and injected into the constructor.

 

As mentioned earlier, the process of resolving injection parameters is templatized by ReflectionStrategy.BuildUp. Thus, we have to look at the implementation of the three abstract methods in ConstructorReflectionStrategy to see what happens when injection attributes are placed on constructor parameters. ConstructorReflectionStrategy is shown below.

 

 public class ConstructorReflectionStrategy : ReflectionStrategy<ConstructorInfo>
 {
  protected override IEnumerable<IReflectionMemberInfo<ConstructorInfo>> GetMembers(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   List<IReflectionMemberInfo<ConstructorInfo>> result = new List<IReflectionMemberInfo<ConstructorInfo>>();
   ICreationPolicy existingPolicy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);

 

   if (existing == null && (existingPolicy == null || existingPolicy is DefaultCreationPolicy))
   {
    ConstructorInfo injectionCtor = null;
    ConstructorInfo[] ctors = typeToBuild.GetConstructors();

 

    if (ctors.Length == 1)
     injectionCtor = ctors[0];
    else
    {
     foreach (ConstructorInfo ctor in ctors)
     {
      if (Attribute.IsDefined(ctor, typeof(InjectionConstructorAttribute)))
      {
       // Multiple decorated constructors aren't valid
       if (injectionCtor != null)
        throw new InvalidAttributeException();

 

       injectionCtor = ctor;
      }
     }
    }

 

    if (injectionCtor != null)
     result.Add(new ReflectionMemberInfo<ConstructorInfo>(injectionCtor));
   }

 

   return result;
  }
    protected override void AddParametersToPolicy(IBuilderContext context, Type typeToBuild, string idToBuild, IReflectionMemberInfo<ConstructorInfo> member, IEnumerable<IParameter> parameters)
  {
   ConstructorPolicy policy = new ConstructorPolicy();

 

   foreach (IParameter parameter in parameters)
    policy.AddParameter(parameter);

 

   context.Policies.Set<ICreationPolicy>(policy, typeToBuild, idToBuild);
  }
  protected override bool MemberRequiresProcessing(IReflectionMemberInfo<ConstructorInfo> member)
  {
   return true;
  }
}

 

Recall that GetMembers has to return an IEnumerable for the list of members, for the type, that need injection attribute processing. ConstructorReflectionStrategy has to return the constructor that has [InjectionConstructor] defined. This attribute is used to tell ConstructorReflectionStrategy, which constructor to use for attribute-based dependency injection, if you have more than one constructor defined. Notice that we didn't put this attribute on our constructor definition of MyDAOObject2, which means that [InjectionConstructor] is optional if only one constructor has injection attributes. The GetMembers method for ConstructorReflectionStrategy checks to see if only one constructor is defined, and if so, it takes that constructor as the injection constructor. If more than one constructor is defined, it looks for the [InjectionConstructor]. Note that multiple constructors decorated with [InjectionConstructor] is invalid.

 

So ConstructorReflectionStrategy.GetMembers, returns a ReflectionMemberInfo for the [InjectionConstructor]. ReflectionMemberInfo, is a wrapper for items that need injection attribute processing. In the case of ConstructorReflectionStrategy, this is a wrapper around the constructor that has [InjectionConstructor]. We'll see a bit later, how ReflectionMemberInfo also wraps properties and methods.

 

After ReflectionStrategy get the list of members that need injection attribute processing, the next thing it has to do is add appropriate policies to the context for each parameter. Recall from our earlier discussion where we injected a dependency using ConstructorPolicy. Using ConstructorPolicy and a ValueParameter, we manually established where and how to resolve the dependency. Using ConstructorReflectionStrategy and [Dependecy], effectively takes this manual step our of the picture because ConstructorReflectionStrategy creates a ConstructorPolicy and adds an IParameter for each parameter for you. Note that AddParametersToPolicy iterates over the list of parameters and then adds that to the ConstructorPolicy. After which, it sets the ICreationPolicy, for the type, to the created constructor policy. Since this is a ConstructorReflectionStrategy, the type that needs to be build has not been constructed when this code executes. Which means that if the client is using the defualt creation strategy (CreationStrategy), and if the default creation policy (DefaultCreationPolicy) was set (which is the case in our ConstructorReflectionStrategy example above), that policy gets overriden by the newly created ConstructorPolicy. So, when CreationStrategy.InitializeObject runs, the correct constructor is selected and the proper values are passed into the constructor.

 

Now lets see how this is done with properties are decorated with attributes.

 

Dependency Injection using PropertyReflectionStrategy

 

 

PropertyReflectionStrategy extends ReflectionStrategy to support injection attributes on properties. The example below demonstrates using this extension.

 

public class MyDAOObject3
{
    private string connectionString;
   
    [Dependency(Name = "ConnectionString", SearchMode = SearchMode.Local,
        NotPresentBehavior = NotPresentBehavior.Throw)]
    public string ConnectionString
    {
        get
        {
            return connectionString;
        }
        set
        {
            connectionString = value;
        }
    }
}
public void PropertyReflectionStrategyExample()
{
    Locator locator = new Locator();
    LifetimeContainer container = new LifetimeContainer();
    locator.Add(typeof(ILifetimeContainer), container);
    // add connection string to locator
    locator.Add(new DependencyResolutionLocatorKey(typeof(string), "ConnectionString"), "connection string goes here");
    BuilderStrategyChain chain = new BuilderStrategyChain();
    PolicyList policies = new PolicyList();
    // add strategies
    chain.Add(new PropertyReflectionStrategy());
    chain.Add(new CreationStrategy());
    chain.Add(new PropertySetterStrategy());
    // add policies
    policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
    // create context
    BuilderContext cxt = new BuilderContext(chain, locator, policies);
    // do buildUp
    MyDAOObject3 obj = chain.Head.BuildUp(cxt, typeof(MyDAOObject3), null, null) as MyDAOObject3;
}

 

In the code snippet above, we've created a class named MyDAOObject3 which has a single property named ConnectionString. This property is decorated with the same [Dependency] that was previously set on the constructor of MyDAOObject2. The obvious idea is to have OB inject this propety at build-up. To have OB interpret the metadata on the ConnectionString property, we have to add a few strategies to the strategy chain. In the last section, all we had was a ConstructorReflectionStrategy. Here, we see two new strategies: PropertyReflectionStrategy and PropertySetterStrategy. Note that the usual CreationStrategy sit in between these two. Why? In the section we had a ConstructorReflectionStrategy precede CreationStrategy because the strategy needed to resolve which constructor to call, prior to instantiating the object. With properties, however, we have to wait for the object to be instantiated prior to calling properties. Thus, we use the PropertyReflectionStrategy to [TODO] and the PropertySetterStrategy to assign property values.

 

As before, the magic lies in the PropertyReflectionStrategy implementations of the three abstract methods defined in ReflectionStrategy (see below).

 

public class PropertyReflectionStrategy : ReflectionStrategy<PropertyInfo>
 {
  protected override IEnumerable<IReflectionMemberInfo<PropertyInfo>> GetMembers(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   foreach (PropertyInfo propInfo in typeToBuild.GetProperties())
    yield return new PropertyReflectionMemberInfo(propInfo);
  }
    protected override void AddParametersToPolicy(IBuilderContext context, Type typeToBuild, string idToBuild, IReflectionMemberInfo<PropertyInfo> member, IEnumerable<IParameter> parameters)
  {
   PropertySetterPolicy result = context.Policies.Get<IPropertySetterPolicy>(typeToBuild, idToBuild) as PropertySetterPolicy;

 

   if (result == null)
   {
    result = new PropertySetterPolicy();
    context.Policies.Set<IPropertySetterPolicy>(result, typeToBuild, idToBuild);
   }

 

   foreach (IParameter parameter in parameters)
    if (!result.Properties.ContainsKey(member.Name))
     result.Properties.Add(member.Name, new PropertySetterInfo(member.MemberInfo, parameter));
  }
  protected override bool MemberRequiresProcessing(IReflectionMemberInfo<PropertyInfo> member)
  {
   return (member.GetCustomAttributes(typeof(ParameterAttribute), true).Length > 0);
  }
  ...
}

 

The GetMembers method is now returning an IEnumerable for the list of properties on the type. Recall from ReflectionStrategy.BuildUp that as each member is iterated, a call is made to MemberRequiresProcessing to see if that property needs to be processed. Since GetMembers returns all of the properties, MemberRequiresProcessing is used to ensure that the property being considered has at least one custom property which extends [Parameter]. From the defintion of MyDAOObject3, we know that ConnectionString is decorated with [Dependeny], and [Dependency]does extend [Parameter].

 

After the property is determined to be processable, ReflectionStrategy.BuildUp calls AddParametersToPolicy to add the property to the policy. In ConstructorReflectionStrategy.AddParametersToPolicy we saw that a new ConstructorPolicy was created for the constructor and the parameters for the constructor were added to that policy and the policy was set on the context for the type. Here, the method is looking for the appropriate policy to be on the context. Why? Recall that ConstructorReflectionStrategy enforced that only one constructor could have the [InjectionConstructor] decoration. Here, more than one property can have an extension of [Parameter] and because you have one policy for a given policy interface, for a type, we can create the policy ahead of time and set it prior to build-up. Note that if the PropertySetterPolicy is not on the context, which is our case, the method creates one and adds it to the context.

 

After all of the properties have been preprocessed, the object is created, and then PropertySetterStrategy is executed to do the actual injection of values into the properties.

 

Dependency Injection using MethodReflectionStrategy

 

MethodReflectionStrategy is also an extension of ReflectionStrategy. This extension strategy is used to support calling methods which have been docorated with an [InjectionMethod] and have [Dependency] parameters. The code snippet below demonstrates this.

 

public class MyDAOObject3
{
      private string connectionString;
      private string username, password;

 

      [Dependency(Name = "ConnectionString", SearchMode = SearchMode.Local,
          NotPresentBehavior = NotPresentBehavior.Throw)]
      public string ConnectionString
      {
          get
          {
              return connectionString;
          }
          set
          {
              connectionString = value;
          }
      }
      [InjectionMethod]
      public void Credentials([Dependency(Name = "DBUserName", SearchMode = SearchMode.Local,
          NotPresentBehavior = NotPresentBehavior.Throw)]string userName,
          [Dependency(Name = "DBPassword", SearchMode = SearchMode.Local,
          NotPresentBehavior = NotPresentBehavior.Throw)]string password)
      {
          this.username = userName;
          this.password = password;
      }
}

 

public void MethodReflectionStrategyExample()
{
    Locator locator = new Locator();
    LifetimeContainer container = new LifetimeContainer();
    locator.Add(typeof(ILifetimeContainer), container);
    // add username and password to the locator
    locator.Add(new DependencyResolutionLocatorKey(typeof(string), "DBUserName"), "sa");
    locator.Add(new DependencyResolutionLocatorKey(typeof(string), "DBPassword"), "fairoza");
    BuilderStrategyChain chain = new BuilderStrategyChain();
    PolicyList policies = new PolicyList();
    // add strategies
    chain.Add(new MethodReflectionStrategy());
    chain.Add(new CreationStrategy());
    chain.Add(new MethodExecutionStrategy());
    // add policies
    policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
    // create context and buildup
    BuilderContext cxt = new BuilderContext(chain, locator, policies);
    MyDAOObject3 obj = chain.Head.BuildUp(cxt, typeof(MyDAOObject3), null, null) as MyDAOObject3;
}

 

You can see that we've added a method to MyDAOObject3 which is decorated with [InjectionMethod]. You can also see that the two parameters to the method are decorated with the [Dependency]. As far as build-up goes, the pattern looks very similar to our previous example--we have two method related strategies sandwiching the CreationStrategy. MethodReflectionStrategy is responsible for identifying the methods on the type that have the [InjectionMethod] decoration (or a derived class of InjectionMethod). MethodExecutionStrategy is responsible for executing the methods with proper values for the method parameters. MethodReflectionStrategy is shown below (we've already discussed MethodExecutionStrategy).

 

public class MethodReflectionStrategy : ReflectionStrategy<MethodInfo>
{
  protected override IEnumerable<IReflectionMemberInfo<MethodInfo>> GetMembers(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
  {
   foreach (MethodInfo method in typeToBuild.GetMethods())
    yield return new ReflectionMemberInfo<MethodInfo>(method);
  }
  protected override void AddParametersToPolicy(IBuilderContext context, Type typeToBuild, string idToBuild,       IReflectionMemberInfo<MethodInfo> member, IEnumerable<IParameter> parameters)
  {
   MethodPolicy result = context.Policies.Get<IMethodPolicy>(typeToBuild, idToBuild) as MethodPolicy;

 

   if (result == null)
   {
    result = new MethodPolicy();
    context.Policies.Set<IMethodPolicy>(result, typeToBuild, idToBuild);
   }

 

   result.Methods.Add(member.Name, new MethodCallInfo(member.MemberInfo, parameters));
  }
  protected override bool MemberRequiresProcessing(IReflectionMemberInfo<MethodInfo> member)
  {
   return (member.GetCustomAttributes(typeof(InjectionMethodAttribute), true).Length > 0);
  }
}

 

9/7/2006 12:13:52 PM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

ObjectBuilder-Dependency Injection Framework#

Some Notes on Object Builder

 

Over the past few weeks I have been looking over the ObjectBuilder (Dependecy Injection) framework. I have taken a bunch of notes and will start to write some blog entries on it. This is the first....so an introduction is in order.

 

Introduction to Object Builder

 

Most software developers today understand classes and object-oriented programming. One of the basic pillars of OO is to encapsulate concepts in objects. Thus, when writing OO based software, you end up with (potentially) lots of classes. A common problem that arises with having a bunch of classes/components is how do you decouple some of the major components of your software so that you can maintain them independently. The solution is to use a loose coupling design. Dependency Injection (DI) is a design pattern that promotes loose coupling. Specifically, DI addresses the following:

  1. If you have a collection of classes/components/services that rely on each other, then what's the best way to hook these objects together, so that you minimize coupling?
  2. If you have objects that depend on each other, then how do you resolve the order in which these objects/components/services are created?
  3. If you have objects that depend on other objects, then how do objects find their dependencies?

In order for an implementation to support a mechanism where objects declare their dependencies and somehow their dependencies are made available, while taking into account that the dependency itself may also have dependencies, requires a container for objects. So DI is a pattern that requires a container. The container maintains the created objects. When an object says it has a dependency on another object, the DI implementation looks in it's container for the object and if it exists, it uses the object, otherwise, it creates it and puts it in the container (note that this is a simplified example).

 

The benefits of using a dependency injection framework are:

  1. The dependencies of your components are loosely coupled. By using DI you say that you depend on a component, and it's up to the DI container to deliver that component to you. Your component has no knowledge of how the dependency was created or where the implementation lives.
  2. Because your loosely coupled to your dependencies, you can swap implementations of your components.

Object builder is a framework that can be used to implement a DI system. OB is based upon policies and strategies. Strategies are chained and get registered for a build stage.  Strategies use policies. Policies are registered with OB for types. A policy is defined for types (objects in OB are defined by the type and ID). Some examples of policies in OB include:

 

ITypeMappingPolicy

ICreationPolicy

ISingletonPolicy

 

 

With OB you can define default policies. You can, for example, say that all objects with a given ID have to be a singleton.

 

// create a new builder container

Builder builder = new Builder();

// tell the builder that all of the objects in the container should be a singleton.

builder.Policies.SetDefault<ISingletonPolicy>(new SingletonPolicy(true));

 

Note that the ID doesn't have to be unique because an object in OB is defined by an ID and the type of the object. Thus, you can have many objects with the same ID.

 

There are several ways you can have a dependent service injected into an object. You can do it by requiring classes to write a constructor with the list of their dependencies. You can provided setter properties for your dependencies. And you can do for using attributes (i.e., decorate your class with your dependencies). OB supports all of these. As an example, you can define a property on a class with a dependency and then use the PropertySetterPolicy to set the dependency when the object is created. You can also define a constructor on class with it's dependencies and then use a CreationPolicy to have the dependency injected. Here are examples of how you can to this.

 

1) Injection using a property

 

// create the PropertySetterPolicy

PropertySetterPolicy propPolicy = new PropertySetterPolicy();

// define the property that needs to be set--the dependency

propPolicy.Properties.Add("IConfigurationService",new PropertySetterInfo("IConfigurationService"),typeof(IConfigurationService),"MyConfigurationService",typeof(ConfigurationServiceImpl),NotPresentBehavior.CreateNew,SearchMode.Local)));

// add the policy for the object

builder.Policies.Set<IPropertySetterPolicy>(propPolicy,typeof(IEmailService), "MyEmailServiceID");

 

The above code creates a new PropertySetterPolicy and tells the dependency inject to inject the IConfigurationService into the IEmailService by calling it's IConfigurationService property. When the instance of IEmailService is created by the builder, it will run through it's policies and when the PropertySetterPolicy is executed, it will set the IConfigurationService property and thus inject the dependency. Note that by saying NotPresentBehavior.CreateNew tells the builder what to do if there is not an instance of IConfigurationService when the propery needs to be injected. In this case, it is saying to create a new one. Realize that if you have multiple instances of IEmailService, they will all get the same instance of IConfigurationService.

 

2) Injection using a constructor

 

// injection via constructor is implemented by ICreationPolicy.

ICreationPolicy createPolicy = new ConstructorPolicy(new ValueParameter<FileInfo>(new FileInfo("c:\\EmailGroupsFile.txt"));

builder.Policies.Set<ICreationPolicy>(createPolicy,typeof(EmailServiceImpl),"MyEmailServiceID");

 

The above code tells the builder to inject the path to the list of email groups (via the FileInfo) when you create an object of type EmailServiceImpl and ID=MyEmailServiceID.

 

We'll see alot more on this in later posts.

9/7/2006 11:58:22 AM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

#
A few days ago I was doing a demo on ClickOnce and was asked a few questions that I felt others could benefit from. The first one was with respect to the folders/files generated by Visual Studio when you do deployments and the other one worth mentioning deals with desktop icons and Clickonce.
 
When I publish my application using VS 2005, it puts each deployment in a separate folder. In addition, it creates a [APPLICATION]_[VERSION].application file for each version. Moreover, there is a [APPLICATION].application file too. What is the reasoning behind all of this? Especially, the [APPLICATION]_[VERSION].application files?
 
When you publish an application using VS 2005, VS creates a deployment folder and names it based on your application name and version. The deployment folder contains everything about a particular version. VS also creates a deployment manifest based on the application and version (e.g., MyApp_1_0_0_0.application). Moreover, the firsttime you publish your application, VS creates deployment manifest (e.g., MyApp.application) that is independent of any version. The idea behind these files is to achieve server side rollback. The scenario is for you to do a deployment and send your users the link to the MyApp.application file. This deployment manifest refers to the latest version of the deployment. If you need to do a rollback on the server side for any reason, you can simply delete the MyApp.application and rename the latest version to MyApp.application.
 
Desktop Shortcut and ClickOnce
 
ClickOnce does not support the automatic creation of desktop shortcuts. Microsoft actually says that in their research they found that users don't like their desktop cluttered with icons and so they decided not to support desktop shortcuts with ClickOnce. If you have played around with ClickOnce to even a small degree then you know that ClickOnce also has an API that you can use to customize/supplement your ClickOnce deployment. If your users start asking for a desktop shortcut and you start to look for a way to achieve this, you'll likely end up using the APIs to figure out if the application is running for the firsttime (after a ClickOnce deployment) and if so, then execute some code that will determine the path to the application executable and then create a desktop shortcut. There are a few pitfalls that you have to consider before you jump into using desktop shortcuts with ClickOnce.
 
a) Desktop shortcuts requires use of some COM APIs and thus your application needs to have Full-Trust. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/e6ac108b-15f6-4a54-891f-589e8b687ace.asp or http://www.knowdotnet.com/articles/createshortcutondesktop.html . If your application does not have full trust, you'll get a SecurityException.
 
b) If your application does not have Full-Trust, then you might be eager to tell your users to just drag the Program Files shortcut for your application onto their desktop and then use that when launching the application. I highly recommend against this and here's why. Lets say you deploy version 1 of your application and a user drags the shortcut from Program Files onto the desktop. You then publish version 2 of your application. When the user goes to launch the application using the shortcut he created, he'll get a message that the application has an update and he will update and everything will be fine. The next day when he comes in and executes the shortcut again, he'll get a message that the application has an update and he will have to update again. This will continue, until there is version 3. When version 3 comes out, the ClickOnce runtime will delete version 1 (ClickOnce maintaines only 2 versions of an application per user). When the user executes the shortcut again, he'll get a message that the shortcut points to a file that doesn't exist. The bottom line is that when the user drags the shortcut to the desktop on version 1, you have no way of updating the target of the shortcut when an update happens--ClickOnce updates the shortcut when you do an update so everything works with the Program Files shortcut.
 
c) Another problem with desktop shortcuts is that you have no way of getting rid of them when your application is uninstalled.
6/20/2006 3:28:45 PM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

Forcing Application Updates#

Forcing ClickOnce Updates

One of the big selling points of ClickOnce is automatic updates. One of the common questions I get with regard to updates is "How can I force an update on the user?"

There are three things to know with respect to forcing updates on users:

1) If your application is an online application, your users will always run the latest version; online applications get downloaded everytime the application is accessed. Thus, with online applications, you get forced-updates by default.

2) If your application is an installed application, you can force updates by using the MinimumRequiredVersion attribute. If you publish your application using Visual Studio, you can set this property from the Updates Dialog.

3) The last thing to note is that if your application is an installed application (and you have not set the MinimumRequiredVersion attribute) ClickOnce will prompt the user with an "Update Available" dialog ONLY if the user launches the application from the Start Menu shortcut. That is, if an application is an installed application and the user launches the application from a URL, ClickOnce forces the update.

 

3/21/2006 2:40:48 PM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

URL Parameter Passing with a ClickOnce Application#

ClickOnce applications can have arguments passed to it via URL paramters (similar to how we pass paramters to web pages). An example URL looks like:

http://mycompany.com/myapp.application?username=user&pass=pwd

If you are thinking of using this feature, there are a couple of things to keep in mind.

1) URL Paramters are passed to your application only if your application is launched from a URL. This means that if you have an installed application and the user launches the application from the Start Menu shortcut, your application will not get URL paramters.

2) This feature works well for online applications because online applications are always launched using the URL (i.e., online applications are not installed applications and thus do not get Start Menu shortcuts).

3) URL Parameter passing is not enabled by default. In Visual Studio 2005, you can enable it by checking the "Allow URL parameters to be passed to this application" checkbox. You can get to this by clicking on the "Options..." button from the Publish tab. If you are using the MAGE tool, you can do this when you create a deployment manifest by editing the "Deployment Options" UI. If you are using MSBuild to generate your ClickOnce application, you need to set the TrustUrlParameters
property on the GenerateDeploymentManifest task.

4) Here is an example of a class that lets you get to the URL parameters collection of your ClickOnce application. Note that the application has to be deployed via ClickOnce for the class to return the URL parameters.

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

public sealed class DeploymentHelper
{
    private static Dictionary<string, string> urlParms;

    static DeploymentHelper()
    {
        try
        {
            Uri appUri = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.ActivationUri;
            if (appUri != null)
            {
                URLParameters = GetQueryStringParametersCollection();
            }
        }
        catch (Exception) { }
    }

    public static Dictionary<string, string> URLParameters
    {
        get
        {
            return urlParms;
        }
        private set
        {
            urlParms = value;
        }
    }
   
    private static Dictionary<string, string> GetQueryStringParametersCollection()
    {
        Dictionary<string, string> nameValueTable = new Dictionary<string, string>();

        if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
        {
            string url = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.ActivationUri.AbsoluteUri;
            string queryString = (new Uri(url)).Query;
            string[] nameValuePairs = queryString.Split('&');
            bool firstVar = true;
            foreach (string pair in nameValuePairs)
            {
                string[] vars = pair.Split('=');
                if (firstVar)
                {
                    firstVar = false;
                    if (vars[0].Contains("?"))
                    {
                        vars = new string[] { vars[0].Trim('?'), vars[1] };
                    }
                }
                if (!nameValueTable.ContainsKey(vars[0]))
                {
                    nameValueTable.Add(vars[0], vars[1]);
                }
            }
        }

        return (nameValueTable);
    }
}

You can get to the URL parameters by using the static property URLParameters:

Dictionary<string,string> urlParms = DeploymentHelper.URLParameters;

Note that the core of the class is the static GetQueryStringParametersCollection() method. I copied most of this method from MSDN, however, the MSDN version has a bug in it so and I had to modify it.


 

3/21/2006 9:32:41 AM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

Setting up CruiseControl.NET with MSBuild#

Just setup CruiseControl.NET (CC.NET) on a fresh install of Windows 2003 server. It went pretty seamless.

For now, we are only doing continuous integration, but very soon we are going to set it up for NUnit and FxCop too. If you plan on using it, here are the steps I followed. Note that we are using Subversion for our source control and that the box had nothing on it.

1. Download and install Subversion (http://subversion.tigris.org/project_packages.html).
2. Create a working directory and get latest from trunk.
3. Download CC.NET (http://confluence.public.thoughtworks.org/display/CCNET/Welcome+to+CruiseControl.NET).
4. Download CC.NET Tray (http://confluence.public.thoughtworks.org/display/CCNET/CCTray).
5. Install CC.NET.
6. Modify CC.NET Server configuration file to monitor your source control working directory and to call MSBuild.
7. Modify CC.NET web dashboard configuration file to show MSBuild output.

CC.NET can provide build updates via email notification or to a utility that you can install on your desktop (CCTray). We decided to go this route rather than have CC.NET email us. The cool thing about the tray utility is that it sits in your system tray and the icon will change when a build starts and the result of the build.

If you have never used CC.NET or the Java version, note that CC.NET has a server application that can be run as an NT Service (named CruiseControl.NET Server). You can configure the service to start automatically via the service control MMC. CC.NET also has a web dashboard that is used to view build outputs. The dashboard application gets installed under Program Files\CruiseControl.NET\webdashboard\.  If you use configure CC.NET to call MSBuild, then you'll want to also add an entry to the dashboard.config file to include a link to the MSBuild output.

Most of the setup of CC.NET was easy, the only problem I had was that I working off of an old configuration file and that one was not configured for MSBuild. Here is the server configuration file (ccnet.config), followed by the dashboard config file.

<!-- ************************************************* -->
<cruisecontrol>
 <project name="YourAppName">
  <!-- after a change is detected, wait 10 mins and then kick off the build-->
  <schedule type="schedule" sleepSeconds="600" />
  <!--set the sourcecontrol type to subversion and point to the subversion exe-->
  <sourcecontrol type="svn">
   <executable>C:\Program Files\Subversion\bin\svn.exe</executable>
   <workingDirectory>D:\CruiseControl\yourapp\build\checkout</workingDirectory>
   <trunkUrl>svn://servername/Projects/yourapp/trunk/Source</trunkUrl>
   <autoGetSource>true</autoGetSource>
   <username>sayed</username>
   <password>password</password>
  </sourcecontrol>
  <tasks>
   <!-- Configure MSBuild to compile the updated files -->
   <msbuild>
    <executable>C:\WINNT\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable>
    <workingDirectory>D:\CruiseControl\YOURAPP\build\checkout\Solutions\YourSolution</workingDirectory>
    <projectFile>YourSolutionFileName.sln</projectFile>
    <buildArgs>/noconsolelogger /p:Configuration=Debug</buildArgs>
    <targets></targets>
    <timeout>15</timeout>
    <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,ThoughtWorks.CruiseControl.MsBuild.dll</logger>
   </msbuild>
  </tasks>
  <!--Publishers will be done after the build has completed-->
  <publishers>
   <xmllogger>
    <logDir>d:\cruisecontrol\yourapp\logs</logDir>
   </xmllogger>
   <email from="alerts@sayedhashimi.com" mailhost="yoursmtp.yourcompany.com" includeDetails="TRUE">
    <projectUrl>http://yourservername/ccnet</projectUrl>
    <users>
     <user name="Sayed" group="developers" address="sayed@sayedhashimi.com"/>
    </users>
    <groups>
     <group name="developers" notification="always">
    </groups>
   </email>
  </publishers>
  <modificationDelaySeconds>10</modificationDelaySeconds>
 </project>

</cruisecontrol>
<!-- ************************************************* -->

Here is the dashboard.config file I am using:

<!-- ************************************************* -->
<?xml version="1.0" encoding="utf-8" ?>
<dashboard>
 <remoteServices>
  <servers>
   <!-- Update this list to include all the servers you want to connect to. NB - each server name must be unique -->
   <server name="local" url="tcp://localhost:21234/CruiseManager.rem" />
  </servers>
 </remoteServices>
 <plugins>
  <farmPlugins>
   <farmReportFarmPlugin />
   <cctrayDownloadPlugin />
  </farmPlugins>
  <serverPlugins>
   <serverReportServerPlugin />
   <serverLogServerPlugin />
   <serverInformationServerPlugin />
  </serverPlugins>
  <projectPlugins>
   <projectReportProjectPlugin />
   <latestBuildReportProjectPlugin />
   <viewAllBuildsProjectPlugin />
  </projectPlugins>
  <buildPlugins>
   <buildReportBuildPlugin>
    <xslFileNames>
     <xslFile>xsl\header.xsl</xslFile>
     <xslFile>xsl\modifications.xsl</xslFile>
     <xslFile>xsl\compile.xsl</xslFile>
     <xslFile>xsl\unittests.xsl</xslFile>
     <xslFile>xsl\MsTestSummary.xsl</xslFile>
     <xslFile>xsl\fxcop-summary.xsl</xslFile>
     <xslFile>xsl\NCoverSummary.xsl</xslFile>
     <xslFile>xsl\SimianSummary.xsl</xslFile>
    </xslFileNames>
   </buildReportBuildPlugin>
   <buildLogBuildPlugin />
   <xslReportBuildPlugin description="MSBuild Output" actionName="MSBuildOutputBuildPlugin" xslFileName="xsl\msbuild.xsl" />
   <xslReportBuildPlugin description="NUnit Details" actionName="NUnitDetailsBuildReport" xslFileName="xsl\tests.xsl" />
   <xslReportBuildPlugin description="NUnit Timings" actionName="NUnitTimingsBuildReport" xslFileName="xsl\timing.xsl" />
   <xslReportBuildPlugin description="FxCop Report" actionName="FxCopBuildReport" xslFileName="xsl\FxCopReport.xsl" />
   <!--
   <xslReportBuildPlugin description="NAnt Output" actionName="NAntOutputBuildReport" xslFileName="xsl\Nant.xsl" />
   <xslReportBuildPlugin description="NAnt Timings" actionName="NAntTimingsBuildReport" xslFileName="xsl\NantTiming.xsl" />
   <xslReportBuildPlugin description="NCover Report" actionName="NCoverBuildReport" xslFileName="xsl\NCover.xsl" />
   <xslReportBuildPlugin description="Simian Report" actionName="SimianBuildReport" xslFileName="xsl\SimianReport.xsl"/>
   -->
   <!-- This is an example of using Project-specific build plugins
   <xslReportBuildPlugin description="My Report" actionName="MyReport" xslFileName="xsl\MyReport.xsl">
    <includedProjects>
     <projectName>My Project</projectName>
    </includedProjects>
   </xslReportBuildPlugin>
   <xslReportBuildPlugin description="My Other Report" actionName="MyOtherReport" xslFileName="xsl\MyOtherReport.xsl">
    <excludedProjects>
     <projectName>My Project</projectName>
    </excludedProjects>
   </xslReportBuildPlugin>
   -->
  </buildPlugins>
 </plugins>
</dashboard>
<!-- ************************************************* -->

2/8/2006 11:46:22 AM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

ClickOnce Updates of Data Files#

In my previous post, I talked about how ClickOnce udpates applications but forgot to mention something specific to data files. ClickOnce deployed applications might use files that need to be migrated from one version to another. For example, an access database. ClickOnce supports this but with respect to downloading updates, data files are always downloaded (and need to be synchronized with the version on the client). ClickOnce doesn't even look at the hash when it sees that a file is marked as a data file.

2/8/2006 8:33:01 AM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

ClickOnce Application Management#

I was recently asked a question concerning how the publisher of a ClickOnce application can force removal of an assembly after an update. Here is the question:

 

Questions: If my application no longer needs an assembly, does ClickOnce ensure that the file is deleted when I do an update?

 

Answer: ClickOnce installs applications within the %userprofile% folder. At any time, ClickOnce will have up to two versions of an application for a given user. When ClickOnce has to do an update, it's smart enough to only download files that have changed, and not copy files that have been deleted. ClickOnce does updates using the files listed in the application manifest file, on the server, and the files that are in the existing application folder. So image for a second that we have an application with three files: an exe (myapp.exe), a supporting assembly (mysupport.dll) and another supporting assembly named "anothersupport.dll". Lets now assume that we have deployed version 1.0 to our clients.

 

After deploying version 1.0, we decide that mysupport.dll is no longer needed because there is a major security flaw in it. In fact, we decide to purchase the same functionality from a 3rd party that doesn't have any security issues. What happens when we do an update? Does the assembly with the security flaw stay on the user's machine?

 

The answer is yes, but only until you do another update. We said earlier that ClickOnce maintain up to two version of an application for a given user. When you update your application, ClickOnce looks at the files you have listed in the application manifest and the files in the existing application folder on the client, to decide what to download and what not to download. ClickOnce does this to ensure that it doesn't download a file that was not modified. When ClickOnce sees that a file entry exists in the application manifest and does not exist in the existing application directory on the client, it knows that the file was added to the application and so it downloads the file. If it sees that a file is in the application manifest and is also in the existing application directory and the files have the same hash, then it just copies the file from the existing application directory to the new application directory. If a file exists in the application directory that is not in the application manifest, then ClickOnce leaves that file in the existing application directory. The reason it leaves the file is for rollback purposes. ClickOnce, supports "rollback to previous version" so it keeps the existing application just in case the user decides to go back to the old application. Can you get rid of the file manually? Technically you could, but the ClickOnce app store is not meant to be tampered with--ClickOnce manages the app store.

2/7/2006 8:17:41 AM (Eastern Standard Time, UTC-05:00) #    Comments [3]  |  Trackback

 

All content © 2009, Sayed Y. Hashimi