Gunnar Peipman's ASP.NET blog

ASP.NET, C#, SharePoint, SQL Server and general software development topics.

Sponsors

News

 
 
 
DZone MVB

Links

Social

August 2012 - Posts

Hosting WCF service in Windows Service

When building Windows services we often need a way to communicate with them. The natural way to communicate to service is to send signals to it. But this is very limited communication. Usually we need more powerful communication mechanisms with services. In this posting I will show you how to use service-hosted WCF web service to communicate with Windows service.

Create Windows service

VS2010: Create Windows service

Suppose you have Windows service created and service class is named as MyWindowsService. This is new service and all we have is default code that Visual Studio generates.

Create WCF service

Add reference to System.ServiceModel assembly to Windows service project and add new interface called IMyService. This interface defines our service contracts.


[ServiceContract]

public interface IMyService

{

    [OperationContract]

    string SayHello(int value);

}

We keep this service simple so it is easy for you to follow the code. Now let’s add service implementation:


[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

public class MyService : IMyService

{

    public string SayHello(int value)

    {

        return string.Format("Hello, : {0}", value);

    }

}


With ServiceBehavior attribute we say that we need only one instance of WCF service to serve all requests. Usually this is more than enough for us.

Hosting WCF service in Windows Service

Now it’s time to host our WCF service and make it available in Windows service. Here is the code in my Windows service:


public partial class MyWindowsService : ServiceBase

{

    private ServiceHost _host;

    private MyService _server;

 

    public MyWindowsService()

    {

        InitializeComponent();

    }

 

    protected override void OnStart(string[] args)

    {

        _server = new MyService();

        _host = new ServiceHost(_server);

        _host.Open();

    }

 

    protected override void OnStop()

    {

        _host.Close();

    }

}


Our Windows service now hosts our WCF service. WCF service will be available when Windows service is started and it is taken down when Windows service stops.

Configuring WCF service

To make WCF service usable we need to configure it. Add app.config file to your Windows service project and paste the following XML there:


<system.serviceModel>

  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

  <services>

    <service name="MyWindowsService.MyService" behaviorConfiguration="def">

      <host>

        <baseAddresses>

          <add baseAddress="http://localhost:8732/MyService/"/>

        </baseAddresses>

      </host>

      <endpoint address="" binding="wsHttpBinding" contract="MyWindowsService.IMyService">

      </endpoint>

      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>

    </service>

  </services>

  <behaviors>

    <serviceBehaviors>

      <behavior name="def">

        <serviceMetadata httpGetEnabled="True"/>

        <serviceDebug includeExceptionDetailInFaults="True"/>

      </behavior>

    </serviceBehaviors>

  </behaviors>

</system.serviceModel>


Now you are ready to test your service. Install Windows service and start it. Open your browser and open the following address: http://localhost:8732/MyService/ You should see your WCF service page now.

Conclusion

WCF is not only web applications fun. You can use WCF also as self-hosted service. Windows services that lack good communication possibilities can be saved by using WCF self-hosted service as it is the best way to talk to service. We can also revert the context and say that Windows service is good host for our WCF service.

Reading OpenDocument spreadsheets using C#

Excel with its file formats is not the only spreadsheet application that is widely used. There are also users on Linux and Macs and often they are using OpenOffice and other open-source office packages that use ODF instead of OpenXML. In this post I will show you how to read Open Document spreadsheet in C#.

Importer as example

My previous post about importers showed you how to build flexible importers support to your web application. This post introduces you practical example of one of my importers. Of course, sensitive code is omitted. We start with ODS importer class and we add new methods as we go.


public class OdsImporter : ImporterBase

{

    public OdsImporter()

    {

    }

 

    public override string[] SupportedFileExtensions

    {

        get { return new[] { "ods" }; }

    }

 

    public override ImportResult Import(Stream fileStream, long companyId, short year)

    {

        string contentXml = GetContentXml(fileStream);

 

        var result = new ImportResult();

        var doc = XDocument.Parse(contentXml);

 

        var rows = doc.Descendants("{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-row").Skip(1);

 

        foreach (var row in rows)

        {

            ImportRow(row, companyId, year, result);

        }

 

        return result;

    }

}


The class given here just extends base class for importers (previous post uses interface but as I already told there you move to abstract base class when writing code for real projects).

Import method reads data from *.ods file, parses it (it is XML), finds all data rows and imports data. As you may see then first row is skipped. This is because the first row on my sheet is always headers row.

Reading ODS file

Our import method starts with getting XML from *.ods file. ODS files like OpenXml files are zipped containers that contain different files. We need content.xml as all data is kept there. To get the contents of file we use SharpZipLib library to read uploaded file as *.zip file.


private static string GetContentXml(Stream fileStream)

{

    var contentXml = "";

 

    using (var zipInputStream = new ZipInputStream(fileStream))

    {

        ZipEntry contentEntry = null;

        while ((contentEntry = zipInputStream.GetNextEntry()) != null)

        {

            if (!contentEntry.IsFile)

                continue;

            if (contentEntry.Name.ToLower() == "content.xml")

                break;

        }

 

        if (contentEntry.Name.ToLower() != "content.xml")

        {

            throw new Exception("Cannot find content.xml");

        }

 

        var bytesResult = new byte[] { };

        var bytes = new byte[2000];

        var i = 0;

 

        while ((i = zipInputStream.Read(bytes, 0, bytes.Length)) != 0)

        {

            var arrayLength = bytesResult.Length;

            Array.Resize<byte>(ref bytesResult, arrayLength + i);

            Array.Copy(bytes, 0, bytesResult, arrayLength, i);

        }

        contentXml = Encoding.UTF8.GetString(bytesResult);

    }

    return contentXml;

}


If here is content.xml file then we stop browsing the file. We read this file to memory and return it as UTF-8 format string.

Importing rows

Our last task is to import rows. We use special method for this as we have to handle some tricks here. To keep files smaller the cell count on row is not always the same. If we have more than one empty cell one after another then ODS keeps only one cell for sequential empty cells. This cell has attribute called number-columns-repeated and it’s value is set to the number of sequential empty cells. This is why we use two indexers for cells collection.


private void ImportRow(XElement row, ImportResult result)

{

    var cells = (from c in row.Descendants()

                where c.Name == "{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-cell"

                select c).ToList();

 

    var dto = new DataDto();

 

    var count = cells.Count;

    var j = -1;
 

    for (var i = 0; i < count; i++)

    {

        j++;

        var cell = cells[i];

        var attr = cell.Attribute("{urn:oasis:names:tc:opendocument:xmlns:table:1.0}number-columns-repeated");

        if (attr != null)

        {

            var numToSkip = 0;

            if (int.TryParse(attr.Value, out numToSkip))

            {

                j += numToSkip - 1;

            }

        }
 

        if (i > 30) break;

        if (j == 0)

        {

            dto.SomeProperty = cells[i].Value;

        }

        if (j == 1)

        {

            dto.SomeOtherProperty = cells[i].Value;

        }


        // some more data reading

    }
 
   
// save data

}


You can define your own class for import results and add there all problems found during data import. Your application gets the results and shows them to user.

Conclusion

Reading ODS files may seem to complex task but actually it is very easy if we need only data from those documents. We can use some zip-library to get the content file and then parse it to XML. It is not hard to go through the XML but there are some optimization tricks we have to know. The code here is safe to use in web applications as it is not using any API-s that may have special needs to server and infrastructure.

Posted: Aug 24 2012, 07:21 AM by DigiMortal | with no comments
Filed under: , ,
Adding multiple data importers support to web applications

I’m building web application for customer and there is requirement that users must be able to import data in different formats. Today we will support XLSX and ODF as import formats and some other formats are waiting. I wanted to be able to add new importers on the fly so I don’t have to deploy web application again when I add new importer or change some existing one. In this posting I will show you how to build generic importers support to your web application.

Importer interface

All importers we use must have something in common so we can easily detect them. To keep things simple I will use interface here.


public interface IMyImporter

{

    string[] SupportedFileExtensions { get; }

    ImportResult Import(Stream fileStream, string fileExtension);

}


Our interface has the following members:

  • SupportedFileExtensions – string array of file extensions that importer supports. This property helps us find out what import formats are available and which importer to use with given format.
  • Import – method that does the actual importing work. Besides file we give in as stream we also give file extension so importer can decide how to handle the file.

It is enough to get started. When building real importers I am sure you will switch over to abstract base class.

Importer class

Here is sample importer that imports data from Excel and Word documents. Importer class with no implementation details looks like this:


public class MyOpenXmlImporter : IMyImporter

{

    public string[] SupportedFileExtensions

    {

        get { return new[] { "xlsx", "docx" }; }

    }

    public ImportResult Import(Stream fileStream, string extension)

    {

        // ...

    }

}


Finding supported import formats in web application

Now we have importers created and it’s time to add them to web application. Usually we have one page or ASP.NET MVC controller where we need importers. To this page or controller we add the following method that uses reflection to find all classes that implement our IMyImporter interface.


private static string[] GetImporterFileExtensions()

{

    var types = from a in AppDomain.CurrentDomain.GetAssemblies()

                from t in a.GetTypes()

                where t.GetInterfaces().Contains(typeof(IMyImporter))

                select t;

 

    var extensions = new Collection<string>();

    foreach (var type in types)

    {

        var instance = (IMyImporter)type.InvokeMember(null,
                      
BindingFlags.CreateInstance, null, null, null);

 

        foreach (var extension in instance.SupportedFileExtensions)

        {

            if (extensions.Contains(extension))

                continue;

 

            extensions.Add(extension);

        }

    }

 

    return extensions.ToArray();

}


This code doesn’t look nice and is far from optimal but it works for us now. It is possible to improve performance of web application if we cache extensions and their corresponding types to some static dictionary. We have to fill it only once because our application is restarted when something changes in bin folder.

Finding importer by extension

When user uploads file we need to detect the extension of file and find the importer that supports given extension. We add another method to our page or controller that uses reflection to return us importer instance or null if extension is not supported.


private static IMyImporter GetImporterForExtension(string extensionToFind)

{

    var types = from a in AppDomain.CurrentDomain.GetAssemblies()

                from t in a.GetTypes()

                where t.GetInterfaces().Contains(typeof(IMyImporter))

                select t;

    foreach (var type in types)

    {

        var instance = (IMyImporter)type.InvokeMember(null,
                      
BindingFlags.CreateInstance, null, null, null);
 

        if (instance.SupportedFileExtensions.Contains(extensionToFind))

        {

            return instance;

        }

    }

 

    return null;

}


Here is example ASP.NET MVC controller action that accepts uploaded file, finds importer that can handle file and imports data. Again, this is sample code I kept minimal to better illustrate how things work.


public ActionResult Import(MyImporterModel model)

{

    var file = Request.Files[0];

    var extension = Path.GetExtension(file.FileName).ToLower();

    var importer = GetImporterForExtension(extension.Substring(1));

    var result = importer.Import(file.InputStream, extension);

    if (result.Errors.Count > 0)

    {

        foreach (var error in result.Errors)

            ModelState.AddModelError("file", error);

 

        return Import();

    }

    return RedirectToAction("Index");

}


Conclusion

That’s it. Using couple of ugly methods and one simple interface we were able to add importers support to our web application. Example code here is not perfect but it works. It is possible to cache mappings between file extensions and importer types to some static variable because changing of these mappings means that something is changed in bin folder of web application and web application is restarted in this case anyway.

Posted: Aug 23 2012, 10:15 PM by DigiMortal | with 1 comment(s)
Filed under: ,
More Posts