Categories
Uncategorized

WCF and Sharing Ports

WCF can become a tricky beast to configure once you start hosting multiple services on the same port not to mention hosting it inside a Windows service. I ran into a few challenges in getting things up and running. Hopefully this short article might save someone a lot of time and effort if you find yourself in a similar situation.

Sharing ports among WCF services can become an issue when dealing with production environments. Many examples that you find use a different port for each WCF service that is created. This approach works fine for prototyping and in academia. However your IT guy will probably break into a diatribe about security and maintainability when you want to deploy the service. This is especially true if you are deploying to a client’s network. So let’s save the IT guy unnecessary stress and share a port. I encountered a couple of issues while tackling this problem.

The first obstacle was hosting multiple services inside a Windows service. I really prefer hosting WCF in IIS, but that is another discussion. Suffice it to say that the due to design choices beyond my control and prior to my tenure on the project, WCF services were hosted in a Windows service. I created a simple Windows service to act as a host. Here is the code called by the Service Control Manager when the Windows Service is started and stopped.

    public partial class WcfServiceHost : ServiceBase
    {
        private readonly List<ServiceHost> _myServiceHosts = new List<ServiceHost>();

        public WcfServiceHost()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            OpenServiceHost<HelloService>();
            OpenServiceHost<GoodbyeService>();
        }

        protected override void OnStop()
        {
            CloseServiceHosts();
        }

        private void OpenServiceHost<T>()
        {
            var serviceHost = new ServiceHost(typeof(T));
            serviceHost.Open();
            _myServiceHosts.Add(serviceHost);
        }

        private void CloseServiceHosts()
        {
            foreach (var serviceHost in _myServiceHosts)
            {
                if (serviceHost.State == CommunicationState.Opened)
                {
                    serviceHost.Close();
                }
            }
        }
    }

The code is pretty straightforward. The OnStart(string[] args) and OnStop() methods for the Windows service give us the opportunity to open and close the WCF services. I threw in a generic method to spin up the ServiceHost instances for various types. Next let’s tackle to configuration.

The second problem that I encountered was getting the configuration correct. A lot of example out there for hosting multiple services in a single Windows service were all code based. This is not helpful when your WCF is setup via configuration. For this example I created the server configuration manually. No wizards or designers were used. Here is the configuration for the server.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <system.serviceModel>
    <services>
      <service name="WcfWindowsServiceHostExample.HelloService" behaviorConfiguration="exampleBehavior">
        <endpoint address="http://localhost:8888/HelloService" binding="basicHttpBinding" contract="WcfWindowsServiceHostExample.IHelloService">
          <!--identity>
            <dns value="localhost" />
          </identity-->
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8888/HelloService" />
          </baseAddresses>
        </host>
      </service>
      <service name="WcfWindowsServiceHostExample.GoodbyeService" behaviorConfiguration="exampleBehavior">
        <endpoint address="http://localhost:8888/GoodbyeService" binding="basicHttpBinding" contract="WcfWindowsServiceHostExample.IGoodbyeService">
          <!--identity>
            <dns value="localhost" />
          </identity-->
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8888/GoodbyeService" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="exampleBehavior">
          <!-- To avoid disclosing metadata information, 
          set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
          <!-- To receive exception details in faults for debugging purposes, 
          set the value below to true.  Set to false before deployment 
          to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Here are a few pitfalls to avoid. Make sure your base address is defined for the mex endpoint. Remember to include the serviceMetadata element with the httpGetEnabled attribute set to true. These items are required in order for the service to be discoverable by your client application. To Test the WCF services out, I created a simple console application and added a service reference to generate the proxies. You can download the entire project here.

Categories
Uncategorized

Printing SSRS Reports with XPS

Printing in .NET can present some technological challenges. This is particularly true when you are trying to print from the context of a Windows service or an ASP .NET application or service. Now throw SQL Server Reporting Services (SSRS) into the mix and you have a real debacle. Fortunately there is a reliable solution using the XPS Printing API.

I found several helpful examples on the internet but not exactly what I needed for printing SSRS reports via XPS. Thanks to Aspose for their XPS server side printing article and this MSDN post describing how to add images to XPS documents. The information was very helpful in getting started.

There were several challenges I faced in accomplishing my goal. The first was that much of the documentation I found online for XPS presented solutions for creating a physical XPS file on disk. I needed to generate and print the document without creating any physical file. Another challenge was getting the page content from SSRS.

The SSRS ReportExecution2005.Render method can be used to render SSRS reports programmatically. In versions previous to SSRS 2008R2 when rendering to an image, the StreamIds out parameters provided the required IDs to render each page as a stream. Many examples on the internet use this method of rendering the pages of a report programmatically. Unfortunately this does not work in newer versions of SSRS. I came across an article on CodePlex that provided a solution to this issue. Now I will jump into the code.

The prerequisites for this sample are Visual Studio 2010 or later, Visual Studio 2008 SP1 / Business Intelligence Development Studio (BIDS) 2008R2, AdventureWorks 2008R2 database, and AdventureWorks Sample Reports 2008R2. You should use VS 2008 SP1 / BIDS to deploy the datasource and the Product Catalog SQL2008R2 report to your report server. You can also manually deploy the RDL file and create a datasource using the SSRS Report Manager. Note you may need to manually edit the RDL file to provide the appropriate SQL Server connection string which matches your shared datasource. I created three static utility classes to demonstrate the three steps used to retrieve and print the reports. The entire sample project can be downloaded here.

The first step in the process is to get a collection of streams from SSRS that represent the pages in the reports. This is accomplished from the code below from sending an HTTP request to SSRS. There are a couple of key items here. One is the use of the rs:PersistStreams=True / rs:GetNextStream=True query parameter. These parameters tell reporting services to use persistent streams for the first and subsequent pages of the report. Each stream contains the image data for a single page. The second thing to take note of is rc:OutputFormat=png parameter. This parameter tell SSRS to render the pages as png images.

        public static IEnumerable<Stream> GetReportStreams(string reportServerUrl, string reportPath, string snapshotId = null)
        {
            var reportStreams = new List<Stream>();
            var cookies = new CookieContainer();
            string requestUri;
            if (!string.IsNullOrEmpty(snapshotId))
            {
                requestUri =
                    string.Format(
                        "{0}?{1}&rs:SnapshotId={2}&rs:Command=Render&rs:Format=IMAGE&rs:PersistStreams=True&rc:OutputFormat=png",
                        reportServerUrl, reportPath, snapshotId);
            }
            else
            {
                requestUri =
                    string.Format(
                        "{0}?{1}&rs:Command=Render&rs:Format=IMAGE&rs:PersistStreams=True&rc:OutputFormat=png",
                        reportServerUrl, reportPath);
            }
            var request = WebRequest.Create(requestUri);
            ((HttpWebRequest) request).CookieContainer = cookies;

            while (true)
            {
                //Use the currently logged in user
                request.UseDefaultCredentials = true;

                //Get the web response for the request object.
                var response = request.GetResponse();

                //Read the stream.
                var memoryStream = new MemoryStream();
                var byteCount = 0;
                var bytes = new byte[1024];
                var responseStream = response.GetResponseStream();
                do
                {
                    if (responseStream != null)
                        byteCount = responseStream.Read(bytes, 0, bytes.Length);
                    memoryStream.Write(bytes, 0, byteCount);
                } while (byteCount > 0);

                //Reset the memory stream position to the beginning
                memoryStream.Seek(0, SeekOrigin.Begin);

                //An empty stream signals the end of the pages so break out of the while loop
                if (memoryStream.Length == 0)
                {
                    break;
                }

                //Add the current pages to the collection of page streams
                reportStreams.Add(memoryStream);

                //Request the next page.
                if (!string.IsNullOrEmpty(snapshotId))
                {
                    requestUri =
                        string.Format(
                            "{0}?{1}&rs:SnapshotId={2}&rs:Command=Render&rs:Format=IMAGE&rs:GetNextStream=True&rc:OutputFormat=png",
                            reportServerUrl, reportPath, snapshotId);
                }
                else
                {
                    requestUri =
                        string.Format(
                            "{0}?{1}&rs:Command=Render&rs:Format=IMAGE&rs:GetNextStream=True&rc:OutputFormat=png",
                            reportServerUrl, reportPath);
                }
                request = WebRequest.Create(requestUri);
                ((HttpWebRequest) request).CookieContainer = cookies;
            }
            return reportStreams;
        }

 

The second step of the process is to create an XPS document as a memory stream and insert the images as individual pages. A couple of things worth noting here are that the image size is retrieved from the data. This determines the page size. Reports could be designed for 8.5 x 11 or for some other paper size like A4. The document itself is constructed in markup according to the XPS specifications. I found the following code as the minimal amount needed to accomplish my goals. Note the specification defines specific extensible Application Markup Language (XAML) constructs and appropriate attributes required to construct the XPS document.

        internal static void SendToXpsPrinter(IEnumerable<Stream> streams, string printerName, string printJobName)
        {
            using (var memoryStream = new MemoryStream())
            {
                var documentUri = new Uri("pack://document.xps");
                var streamPackage = Package.Open(memoryStream, FileMode.Create, FileAccess.ReadWrite);
                PackageStore.AddPackage(documentUri, streamPackage);

                var xpsDoc = new XpsDocument(streamPackage, CompressionOption.NotCompressed, documentUri.AbsoluteUri);
                var docSeqWriter = xpsDoc.AddFixedDocumentSequence();
                var docWriter = docSeqWriter.AddFixedDocument();
                foreach (var stream in streams)
                {
                    var pageWriter = docWriter.AddFixedPage();
                    var imageUri = AddImage(pageWriter, stream);

                    //Calculate image size for the png to determine page size
                    var imageSizeBuffer = new byte[32];
                    stream.Read(imageSizeBuffer, 0, 32);
                    const int WOffset = 16;
                    const int HOffset = 20;
                    var width =
                        Convert.ToDouble(
                            BitConverter.ToInt32(
                                new[]
                                    {
                                        imageSizeBuffer[WOffset + 3], imageSizeBuffer[WOffset + 2],
                                        imageSizeBuffer[WOffset + 1], imageSizeBuffer[WOffset + 0]
                                    }, 0));
                    var height =
                        Convert.ToDouble(
                            BitConverter.ToInt32(
                                new[]
                                    {
                                        imageSizeBuffer[HOffset + 3], imageSizeBuffer[HOffset + 2],
                                        imageSizeBuffer[HOffset + 1], imageSizeBuffer[HOffset + 0]
                                    }, 0));

                    //Write the XAML
                    var xmlWriter = pageWriter.XmlWriter;
                    xmlWriter.WriteStartElement("FixedPage");
                    xmlWriter.WriteAttributeString("xmlns", "http://schemas.microsoft.com/xps/2005/06");
                    xmlWriter.WriteAttributeString("xmlns:x",
                                                    "http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key");
                    xmlWriter.WriteAttributeString("xml:lang", "en-US");
                    xmlWriter.WriteAttributeString("Height", height.ToString(CultureInfo.InvariantCulture));
                    xmlWriter.WriteAttributeString("Width", width.ToString(CultureInfo.InvariantCulture));
                    xmlWriter.WriteStartElement("Canvas");
                    xmlWriter.WriteStartElement("Path");
                    xmlWriter.WriteAttributeString("Data",
                        string.Format("M 0,0 L {0},0 {1},{2} 0,{3} z",
                            width.ToString(CultureInfo.InvariantCulture),
                            width.ToString(CultureInfo.InvariantCulture),
                            height.ToString(CultureInfo.InvariantCulture),
                            height.ToString(CultureInfo.InvariantCulture)));
                    xmlWriter.WriteStartElement("Path.Fill");
                    xmlWriter.WriteStartElement("ImageBrush");
                    xmlWriter.WriteAttributeString("ViewboxUnits", "Absolute");
                    xmlWriter.WriteAttributeString("ViewportUnits", "Absolute");
                    xmlWriter.WriteAttributeString("Viewbox",
                        string.Format("0,0,{0},{1}",
                            width.ToString(CultureInfo.InvariantCulture),
                            height.ToString(CultureInfo.InvariantCulture)));
                    xmlWriter.WriteAttributeString("Viewport",
                        string.Format("0,0,{0},{1}",
                            width.ToString(CultureInfo.InvariantCulture),
                            height.ToString(CultureInfo.InvariantCulture)));
                    xmlWriter.WriteAttributeString("ImageSource", imageUri);
                    xmlWriter.WriteEndElement();
                    xmlWriter.WriteEndElement();
                    xmlWriter.WriteEndElement();
                    xmlWriter.WriteEndElement();
                    xmlWriter.WriteEndElement();
                    pageWriter.Commit();
                }
                //Add any print tickets to both the page and the document sequence
                var ticket = new PrintTicket {PageOrientation = PageOrientation.Portrait};
                docWriter.PrintTicket = ticket;
                docWriter.Commit();
                docSeqWriter.PrintTicket = ticket;
                docSeqWriter.Commit();
                xpsDoc.Close();

                //Reset the stream to the beginning
                memoryStream.Position = 0;
                XpsPrint.Print(memoryStream, printerName, printJobName, true);
            }
        }

        private static string AddImage(IXpsFixedPageWriter pageWriter, Stream imageStream)
        {
            var image = pageWriter.AddImage(XpsImageType.PngImageType);
            CopyStream(imageStream, image.GetStream());
            image.Commit();
            return image.Uri.ToString();
        }

        private static void CopyStream(Stream sourceStream, Stream destinationStream)
        {
            const int BufferSize = 1024*1024;
            var buffer = new byte[BufferSize]; //1MB
            var bytesRemaining = sourceStream.Length;
            var writeDpi = false;
            while (bytesRemaining > 0)
            {
                var copyLength = (bytesRemaining > BufferSize) ? BufferSize : bytesRemaining;
                sourceStream.Read(buffer, 0, (int) copyLength);
                if (!writeDpi && bytesRemaining > 76)
                {
                    writeDpi = true;
                    buffer[70] = buffer[74] = 0x00;
                    buffer[71] = buffer[75] = 0x00;
                    buffer[72] = buffer[76] = 0x0E;
                    buffer[73] = buffer[77] = 0xC3;
                }
                destinationStream.Write(buffer, 0, (int) copyLength);
                bytesRemaining -= copyLength;
            }
            sourceStream.Position = 0;
        }

 

I replace the Physical pixel dimensions (pHYs chunk) in the CopyStream method. Updating this value to 3779 (0x0EC3 hex) for both the X and Y axis values ensures the image is rendered at 96 dots per inch (DPI) resolution. The unit specifier (byte[78]) of 1 signifies meters. If meters is specified (which it is) then a conversion factor of 0.0254 meters per inch is used to calculate the value. The calculation works out to 96 DPI / 0.0254 = 3779.

                    buffer[70] = buffer[74] = 0x00;
                    buffer[71] = buffer[75] = 0x00;
                    buffer[72] = buffer[76] = 0x0E;
                    buffer[73] = buffer[77] = 0xC3;

 

 

The final step of the process is to print the document through the XPS Print API. A few .NET methods act as calling wrappers for the unmanaged API calls via PInvoke.

        public static void Print(Stream stream, string printerName, string jobName, bool isWait)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }
            if (printerName == null)
            {
                throw new ArgumentNullException("printerName");
            }

            //Create an event that we will wait on until the job is complete.
            var completionEvent = CreateEvent(IntPtr.Zero, true, false, null);
            if (completionEvent == IntPtr.Zero)
            {
                throw new Win32Exception();
            }

            try
            {
                IXpsPrintJob job;
                IXpsPrintJobStream jobStream;
                StartJob(printerName, jobName, completionEvent, out job, out jobStream);

                CopyJob(stream, job, jobStream);

                if (isWait)
                {
                    WaitForJob(completionEvent);
                    CheckJobStatus(job);
                }
            }
            finally
            {
                if (completionEvent != IntPtr.Zero)
                {
                    CloseHandle(completionEvent);
                }
            }
        }

        private static void StartJob(string printerName, string jobName, IntPtr completionEvent, out IXpsPrintJob job,
                                     out IXpsPrintJobStream jobStream)
        {
            var result = StartXpsPrintJob(printerName, jobName, null, IntPtr.Zero, completionEvent,
                                          null, 0, out job, out jobStream, IntPtr.Zero);
            if (result != 0)
            {
                throw new Win32Exception(result);
            }
        }

        private static void CopyJob(Stream stream, IXpsPrintJob job, IXpsPrintJobStream jobStream)
        {
            try
            {
                var buff = new byte[4096];
                while (true)
                {
                    var read = (uint) stream.Read(buff, 0, buff.Length);
                    if (read == 0)
                    {
                        break;
                    }

                    uint written;
                    jobStream.Write(buff, read, out written);

                    if (read != written)
                    {
                        throw new Exception("Failed to copy data to the print job stream.");
                    }
                }

                //Indicate that the entire document has been copied.
                jobStream.Close();
            }
            catch (Exception)
            {
                //Cancel the job if we had any trouble submitting it.
                job.Cancel();
                throw;
            }
        }

        private static void WaitForJob(IntPtr completionEvent)
        {
            const int INFINITE = -1;
            switch (WaitForSingleObject(completionEvent, INFINITE))
            {
                case WAIT_RESULT.WAIT_OBJECT_0:
                    //Expected result, do nothing.
                    break;
                case WAIT_RESULT.WAIT_FAILED:
                    throw new Win32Exception();
                default:
                    throw new Exception("Unexpected result when waiting for the print job.");
            }
        }

        private static void CheckJobStatus(IXpsPrintJob job)
        {
            XPS_JOB_STATUS jobStatus;
            job.GetJobStatus(out jobStatus);
            switch (jobStatus.completion)
            {
                case XPS_JOB_COMPLETION.XPS_JOB_COMPLETED:
                    //Expected result, do nothing.
                    break;
                case XPS_JOB_COMPLETION.XPS_JOB_FAILED:
                    throw new Win32Exception(jobStatus.jobStatus);
                default:
                    throw new Exception("Unexpected print job status.");
            }
        }

        [DllImport("XpsPrint.dll", EntryPoint = "StartXpsPrintJob")]
        private static extern int StartXpsPrintJob(
            [MarshalAs(UnmanagedType.LPWStr)] String printerName,
            [MarshalAs(UnmanagedType.LPWStr)] String jobName,
            [MarshalAs(UnmanagedType.LPWStr)] String outputFileName,
            IntPtr progressEvent, // HANDLE
            IntPtr completionEvent, // HANDLE
            [MarshalAs(UnmanagedType.LPArray)] byte[] printablePagesOn,
            UInt32 printablePagesOnCount,
            out IXpsPrintJob xpsPrintJob,
            out IXpsPrintJobStream documentStream,
            IntPtr printTicketStream);

        // This is actually "out IXpsPrintJobStream", but we don't use it and just want to pass null, hence IntPtr.

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);

        [DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern WAIT_RESULT WaitForSingleObject(IntPtr handle, Int32 milliseconds);

        [DllImport("Kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);
    }

    [Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D")] // This is IID of ISequenatialSteam.
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IXpsPrintJobStream
    {
        // ISequentualStream methods.
        void Read([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbRead);
        void Write([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbWritten);
        // IXpsPrintJobStream methods.
        void Close();
    }

    [Guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IXpsPrintJob
    {
        void Cancel();
        void GetJobStatus(out XPS_JOB_STATUS jobStatus);
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct XPS_JOB_STATUS
    {
        public UInt32 jobId;
        public Int32 currentDocument;
        public Int32 currentPage;
        public Int32 currentPageTotal;
        public XPS_JOB_COMPLETION completion;
        public Int32 jobStatus; // UInt32
    };

    internal enum XPS_JOB_COMPLETION
    {
        XPS_JOB_IN_PROGRESS = 0,
        XPS_JOB_COMPLETED = 1,
        XPS_JOB_CANCELLED = 2,
        XPS_JOB_FAILED = 3
    }

    internal enum WAIT_RESULT
    {
        WAIT_OBJECT_0 = 0,
        WAIT_ABANDONED = 0x80,
        WAIT_TIMEOUT = 0x102,
        WAIT_FAILED = -1 // 0xFFFFFFFF
    }
        XPS_JOB_COMPLETED = 1,
        XPS_JOB_CANCELLED = 2,
        XPS_JOB_FAILED = 3
    }

    internal enum WAIT_RESULT
    {
        WAIT_OBJECT_0 = 0,
        WAIT_ABANDONED = 0x80,
        WAIT_TIMEOUT = 0x102,
        WAIT_FAILED = -1 // 0xFFFFFFFF
    }

 

Overall this was a great learning experience. XPS printing allows for a unified client / server printing solution when dealing with Windows services and ASP .NET applications and services.

Categories
Powershell

Invoke Powershell from Powershell Script

Recently I have been working in Powershell to harness both the automation and development aspects of the platform. I have used Powershell in the past in a limited capacity for automation tasks like build and deployment. I was in unfamiliar territory with my current need to harness its development capabilities. Fortunately a coworker of mine, Doug Finke, is a Powershell Most Valuable Professional (MVP). He was able to provide insight into some of the more sophisticated aspects of Powershell.

My experience has been challenging but rewarding with a few pitfalls along the way. The first obstacle I encountered centered around the use of the Start-Process cmdlet. It can be fairly simple and straightforward to use from the command line but proved to be a bit unwieldy for me to use in the context of a Powershell script.

I found starting an instance of Powershell to execute a script from within another Powershell script fairly difficult. The information I found online was a mixture of fact and fallacy. Weeding through it to find a solution was time-consuming and painful. Hopefully my experience will inform you and expedite your journey. The two pain points revolved around invoking the Powershell executable to run a script from within another script and secondly passing arguments to the script. After a bit of research I figured out the first piece of the puzzle.

Invoking Powershell to execute a script requires the ampersand (&) character. This part was straightforward to implement without too much effort. With a little bit of research and effort I was able to execute a simple script.

##SimpleInvokeScript.ps1 Write-Host "Hello World!"; Read-Host "Press <Enter> key to exit"

##Start the process from the command line or another script Start-Process powershell.exe '& C:\Scripts\SimpleInvokeScript.ps1'

After conquering the first obstacle I needed to expand my solution to pass arguments to the script. This proved to be much more difficult. Previously I had found the -ArgumentList command line switch documentation. I knew this was necessary to provide the input arguments. Unfortunately there was not a good example in the help documents to base my script on. After trying many different variations I was finally able to achieve my goal.

##ParameterizedInvokeScript.ps1 param([string]$argOne, [string]$argTwo, [int]$argThree) Write-Host "ArgumentOne: $argOne, ArgumentTwo: $argTwo, ArgumentThree: $argThree" Read-Host "Press <Enter> key to exit"

##Start the process from the command line or another script passing arguments Start-Process powershell.exe -ArgumentList '& C:\Scripts\ParameterizedInvokeScript.ps1', One, Two, 3

There are nuances associated with passing command-line arguments. In the example above the items in the argument list are comma-separated. The punctuation is required for the command to execute successfully. The position of arguments is also very important. Additional Powershell command line switches need to be placed before the -ArgumentList parameters. Notice the -NoExit switch also needs to be single quoted and comma-separated here.

Start-Process powershell.exe -ArgumentList '-NoExit ', '& C:\Scripts\ParameterizedInvokeScript.ps1', One, Two, 3

Additional Powershell switches can be included preceding the invocation operator and script path. Notice in this example that the first string argument contains spaces and requires surrounding a single quoted (‘) string with double quotes (“) to allow for the white space.

Start-Process powershell.exe -ArgumentList '-NoExit & C:\Scripts\ParameterizedInvokeScript.ps1', "'One With Spaces'", Two, 3

The command line can also be built up dynamically at runtime. Multiple variables can be used to construct the arguments.

##InitiateParameterizedInvokeScript.ps1 ##Use variable(s) to dynamically build up the command line content $scriptPath = 'C:\Scripts\ParameterizedInvokeScript.ps1' $commandLine = "-NoExit & $scriptPath 'One With Spaces' Two 3" ##Start the process building the command line arguments dynamically Start-Process powershell.exe -ArgumentList $commandLine

Notice in the above example when building up the command line dynamically with string variables the commas are omitted. White space within the string separates the arguments and for a string argument with white space single quotes (‘) are required. I hope these examples have been helpful and your experience with the Powershell Start-Process cmdlet is more productive.

Categories
C# .NET Managed Extensibility Framework

Dynamic DataContractSerializer KnownTypes with MEF

Have you ever used the .NET DataContractSerializer? Do you need to serialize types defined at runtime? Well then keep reading. There might be a simple solution to your problem. There are several ways of providing type information to the serializer.

The first method is the type provided to the serializer’s constructor. A second method is through a configuration file. This is particularly useful in the context of using Windows Communication Foundation (WCF) services particularly with third-party components. A third method is providing type information through the serializer’s KnownTypes property. This example is a variation on the last approach. The standard way of providing some known type information looks like this.

var serializer = new DataContractSerializer(typeof(TypeOne), new List<Type>(new[] { typeof(TypeTwo), typeof(TypeThree) });)

This standard approach provides a collection of known types when constructing the serializer. This works in many cases but what if you don’t know the types at design time? How about if they are defined in an assembly that is not referenced? The configuration file approach could solve this problem. What if you are using the serializer outside of WCF? Fortunately there is a simple solution. The Managed Extensibility Framework (MEF) comes to the rescue.

MEF facilitates loose coupling and runtime discovery of components. It warrants a post of its own but here is a high-level overview. MEF utilizes a CompositionContainer to expose exported types. Types are imported either through a constructor decorated with an [ImportingConstructor] attribute or members decorated with an [Import] attribute.

First I create an interface with a single collection property to store one or more types.

public interface IPersistable { IEnumerable<Type> Types { get; } }

Next I implemented the interface in the class I wanted to serialize.

[Export(typeof(IPersistable))] [DataContract] public class Order : IPersistable { [DataMember] public int Id { get; set; } [DataMember] public string ItemName { get; set; } [DataMember] public decimal Price { get; set; } public IEnumerable<Type> Types { get { return new[] { typeof(Order) }; } } public override string ToString() { return string.Format("Order - ID:{0} Item:{1} Price:${2}", Id, ItemName, Price); } }

Alternately a class that implements the interface and provides a collection of multiple types can be used. Either approach works fine. The last step is for the class that uses the serializer to import the type collections by using the [ImportMany] attribute.

public class DataPersistence { private readonly DataContractSerializer _serializer; [ImportingConstructor] public DataPersistence([ImportMany] IEnumerable<IPersistable> knownTypes) { if (knownTypes == null) throw new ArgumentNullException("knownTypes"); var types = knownTypes.SelectMany(t => t.Types); _serializer = new DataContractSerializer(typeof(object), types); } }

This is a simple way to dynamically provide type information to the DataContractSerializer. You can download the complete sample here.

Categories
C# .NET

C# Generic Object Factory

I recently was searching for some ideas for creating an object factory to centralize the creation of various class instances. I came across several interesting articles on the topic related to C# and .NET development. One in particular on The Code Project site caught my attention. I would like to thank Rob Hemstede for his Generic Object Factory article which was my inspiration for this post.

This one was my initial inspiration for this post  but they all lacked one thing. They were specific to individual types or families of objects.

One way of implementing a generic object factory in .NET is to use the Activator class. This can be accomplished with something like the following:

var type = Type.GetType("Namespace.Class, Assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); var instance = Activator.CreateInstance(type);

I worked with this approach on a previous .NET 2.0 WinForms project. It leveraged the User Interface Process (UIP) Application Block from Microsoft’s Patterns and Practices site. The UIP block instantiated the views using a model-view-controller (MVC) pattern. This worked well and was robust. Although with this object factory I did not have the requirement of defining customized views at runtime through configuration. With that in mind I decided to take another approach in the design.

One thing I noticed with the various .NET examples I found was that each factory was specific to a single type or family of types. I wanted something that could be used to create various types of objects. In the past I had accomplished this type of functionality in C++ using function pointers. This approach led me to the following ObjectFactory class.

public class ObjectFactory { private SortedList<string, object> _factories = new SortedList<string, object>(); public void RegisterFactory<TR>(Func<TR> factory) { Add<TR>(factory); } public void RegisterFactory<T1, TR>(Func<T1, TR> factory) { Add<TR>(factory); } private void Add<TR>(object factory) { if (!_factories.ContainsKey(typeof(TR).FullName)) _factories.Add(typeof(TR).FullName, factory); } public TR Create<TR>() where TR : new() { return new TR(); } public TR Create<T1, TR>(T1 t1) { object factory; if (_factories.TryGetValue(typeof(TR).FullName, out factory)) ((Func<T1, TR>)factory).Invoke(t1); return default(TR); } }

Here is a simple example that demonstrates the usage of the ObjectFactory.

class Program { static void Main(string[] args) { var factories = new ObjectFactory(); factories.RegisterFactory<int, ITestObject>(id => new TestObject(id)); var testObj = factories.Create<int, ITestObject>(1); Console.WriteLine(@"ITestObject instance created with Id=" + testObj.Id); Console.ReadLine(); } }