COM Interop without referencing COM assemblies using Dynamic C#


Dynamics is a very strong yet quite under utilised feature of C# which came with C# version 4.0. The primary premise for its usage is that the “object type or data structure is not known at compile time”. In these cases the using the dynamic keyword basically tells the C# compiler to defer it evaluating the object type/ data structure to run time instead of compile time. This functionality or capability comes from another language runtime that sits on top of the CLR (Common Language Runtime) called the DLR (Dynamic Language Runtime).

Dynamics has a lot of use cases in the .Net Framework, one of which is interacting with COM components without having to actually add references to the Primary COM Interop assemblies. Below I just wanted to show a little use case where we can use dynamic to create an Excel document without referencing the following Excel Interop assembly:

  • Microsoft.Office.Interop.Excel

Now ofcourse, you would have to have Excel installed on the system where the code would run, but we will eliminate the need to reference the COM interop assemblies to our project. Also with use of dynamics one would need to have knowledge of the library since we do not get any intellisense support in Visual Studio once an object is declared as dynamic. This is solely because of the fact that the compiler does not know the type of the object until its evaluated at run time. So you would only see the base object methods on the intellisense.

My intent here is not to create a fully featured application, rather to just show the use case of how we can use dynamics to interact with COM Interop assemblies without actually having to reference them in our projects.

I am going to create a simple Console application that will launch an Excel, open a worksheet and add some information to the rows and columns. The sample code for the simple application is available on github.

I am going to create a simple Person class, the data for which we will add to Excel.

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

Create a Console application and add a reference to the following assembly

AddReferenceInteropExcel

This example opens Excel WITH the primary interop assembly reference

class Program
{
    static List<Person> persons = new List<Person>();
    static Program()
    {
        persons.Add(new Person("Frank", 25));
        persons.Add(new Person("Joe", 24));
    }
 
    static void Main(string[] args)
    {
        var excelType = new Microsoft.Office.Interop.Excel.Application();
        excelType.Visible = true;
 
        excelType.Workbooks.Add();
        Worksheet workSheet = excelType.ActiveSheet;
 
        workSheet.Cells[1, 1] = "Names";
        workSheet.Cells[1, 2] = "Age";
 
        int rowIndex = 1;
        foreach (var person in persons)
        {
            rowIndex++;
            workSheet.Cells[rowIndex, 1] = person.Name;
            workSheet.Cells[rowIndex, 2] = person.Age;
        }
    }
}

And the same example WITHOUT using the Excel interop assembly:


class Program
{
    static List<Person> persons = new List<Person>();
    static Program()
    {
        persons.Add(new Person("Frank", 25));
        persons.Add(new Person("Joe", 24));
    }

    static void Main(string[] args)
    {
        dynamic excelType = Type.GetTypeFromProgID("Excel.Application");
        excelType.Visible = true;

        excelType.Workbooks.Add();
        dynamic workSheet = excelType.ActiveSheet;

        workSheet.Cells[1, 1] = "Names";
        workSheet.Cells[1, 2] = "Age";

        int rowIndex = 1;
        foreach (var person in persons)
        {
            rowIndex++;
            workSheet.Cells[rowIndex, 1] = person.Name;
            workSheet.Cells[rowIndex, 2] = person.Age;
        }
    }
}

We get the type of the Excel application using the Type.GetTypeFromProgID into a dynamic variable. Now we can program assuming that at Runtime the variable excelType will be evaluated to Excel.Application. As long as that happens during the run time our program will run just fine. However, its noteworthy that in case the type doesn’t evaluate to Excel.Application at runtime a RunTimeBinderException will be thrown when we try to access any properties or methods on the excelType variable.

So as we can see, Dynamic C# provides a very powerful mechanism that compliments the statically defined C# bindings and can also be used to interact with other dynamic langauages like Iron Python etc without much code clutter.

Advertisements

Integrating Rakuten API with Quartz.Net for Scheduling jobs using Windows Service


Recently one of my clients started using Rakuten E-commerce market place and wanted to develop a solution to run scheduled jobs at regular intervals for accessing the Rakuten API for tasks like updating stock information, fetching orders, updating shipment status for the orders etc.
The API is fairly well documented and is REST based, simple to understand. So I am going to show how we can work with Rakuten E-commerce market place API along side Quartz.Net for building Scheduled Jobs that will run inside a windows service.

Setup:
To start using Rakuten, You must request a license to use the Rakuten Marketplace Web Services Development Platform. Contact Rakuten Support to request your license. Rakuten assigns an authentication key to you containing your encoded credentials. This value must be used in each of your HTTP Request Headers to authorize your requests. Some of the other settings that we require to access the Rakuten API are:

I have used Visual Studio 2015 to develop this Windows Service.

Creating a HTTPClient with Authorization header to make API requests:
We will be using this HttpClient to make Rest based API requests to the Rakuten API

private HttpClient GetHttpClient()
{
    var client = new HttpClient();
    var authenticationKey = ConfigurationHelper.GetValue("AuthenticationKey");
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("ESA", authenticationKey);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    return client;
}

Helper classed used:

namespace RakutenIntegrationService.Helpers
{
    public static class CronHelper
    {
        public static string GetCronExpression(double interval)
        {
            return string.Format("0 0/{0} * 1/1 * ? *", interval);
            //return "0 43 16 ? * *";
        }
    }
}

using System;
using System.Configuration;

namespace RakutenIntegrationService.Helpers
{
    public static class ConfigurationHelper
    {
        public static TResult GetValue<TResult>(string key)
        {
            var setting = ConfigurationManager.AppSettings[key];
            if (string.IsNullOrEmpty(setting))
                return default(TResult);
            var result = Convert.ChangeType(setting, typeof(TResult));
            return (TResult)result;
        }
    }
}

And then define a RestService class that contains methods to make GET and POST requests using this HttpClient

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Common.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RakutenIntegrationService.Helpers;

namespace RakutenIntegrationService.Services
{
    public class RestService : IRestService
    {
        #region Fields

        private ILog Log = LogManager.GetLogger<RestService>();

        #endregion

        #region IRestService members

        public TResult Get<TResult>(string uriString) where TResult: class
        {
            var uri = new Uri(uriString);
            using (var client = GetHttpClient())
            {
                HttpResponseMessage response = client.GetAsync(uri).Result;
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    Log.Error(response.ReasonPhrase);
                    return default(TResult);
                }
                var json = response.Content.ReadAsStringAsync().Result;
                return JsonConvert.DeserializeObject<TResult>(json, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
            }
        }

        public TResult Post<TResult, TInput>(string uriString, TInput payload = null) where TInput : class
        {
            var uri = new Uri(uriString);
            using (var client = GetHttpClient())
            {
                var jsonContent = JsonConvert.SerializeObject(payload, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver()});
                HttpResponseMessage response = client.PostAsync(uri, new StringContent(jsonContent, Encoding.UTF8, "application/json")).Result;
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    Log.Error(response.ReasonPhrase);
                    return default(TResult);
                }
                var json = response.Content.ReadAsStringAsync().Result;
                return JsonConvert.DeserializeObject<TResult>(json);
            }
        } 

        #endregion
    }
}

Configuring Quartz.Net to create Scheduled jobs for API operations
So I installed Quartz.Net using nuget package manager: https://www.nuget.org/packages/Quartz/. Afterwards I created a class called TaskScheduler that basically configures the Scheduled Jobs creation and is responsible for Running/ Stopping the Quartz scheduler

using Quartz;
using RakutenIntegrationService.Helpers;
using RakutenIntegrationService.Jobs;

namespace RakutenIntegrationService.Scheduler
{
    public class TaskScheduler : ITaskScheduler
    {
        #region Private fields

        private readonly IScheduler _scheduler;

        #endregion

        #region Constructors

        public TaskScheduler(IScheduler scheduler)
        {
            _scheduler = scheduler;
        }

        #endregion

        #region ITaskScheduler members

        public string Name
        {
            get { return this.GetType().Name; }
        }

        public void Run()
        {
            ScheduleGetOrdersJob();
            ScheduleStockUpdateJob();
            ScheduleShipmentUpdateJob();

            _scheduler.Start();
        }

        public void Stop()
        {
            if (_scheduler != null) _scheduler.Shutdown(true);
        }

        #endregion

        #region Private methods

        private void ScheduleGetOrdersJob()
        {
            var jobDetails = JobBuilder.Create<GetOrdersJob>()
                                       .WithIdentity("GetOrdersJob")
                                       .Build();
            var trigger = TriggerBuilder.Create()
                                        .StartNow()
                                        .WithCronSchedule(CronHelper.GetCronExpression(ConfigurationHelper.GetValue<double>("GetOrdersInterval")))
                                        .Build();
            _scheduler.ScheduleJob(jobDetails, trigger);
        }

        private void ScheduleStockUpdateJob()
        {
            var jobDetails = JobBuilder.Create<StockUpdateJob>()
                                       .WithIdentity("StockUpdateJob")
                                       .Build();
            var trigger = TriggerBuilder.Create()
                                        .StartNow()
                                        .WithCronSchedule(CronHelper.GetCronExpression(ConfigurationHelper.GetValue<double>("StockUpdateInterval")))
                                        .Build();
            _scheduler.ScheduleJob(jobDetails, trigger);
        }

        private void ScheduleShipmentUpdateJob()
        {
            var jobDetails = JobBuilder.Create<ShipmentUpdateJob>()
                                       .WithIdentity("ShipmentUpdateJob")
                                       .Build();
            var trigger = TriggerBuilder.Create()
                                        .StartNow()
                                        .WithCronSchedule(CronHelper.GetCronExpression(ConfigurationHelper.GetValue<double>("ShipmentUpdateInterval")))
                                        .Build();
            _scheduler.ScheduleJob(jobDetails, trigger);
        }

        #endregion
    }
}

The jobs classes can then be created to do concrete specific work. I am giving an example of the GetOrdersJob

using System.Linq;
using Quartz;
using RakutenIntegrationService.Helpers;
using RakutenIntegrationService.Models.Response;
using RakutenIntegrationService.Services;

namespace RakutenIntegrationService.Jobs
{
    public class GetOrdersJob : IJob
    {
        #region Fields

        private readonly IRestService _restService;

        #endregion

        #region Constructors

        public GetOrdersJob(IRestService restService)
        {
            _restService = restService;
        }

        #endregion

        #region IJob members

        public void Execute(IJobExecutionContext context)
        {
            var uri = string.Concat(DataServiceConstants.BaseEndpointAddress, "order/list", RequestBuilder.ConstructListOrdersRequestParams());
            var response = _restService.Get<OrderResponse>(uri);
            if (response != null && response.Orders != null && response.Orders.Any())
            {
                var ordersToProcess = response.GetOrdersToProcess();
                if (ordersToProcess != null && ordersToProcess.Any())
                    OrderProcessor.ProcessOrders(ordersToProcess);
            }
        }

        #endregion
    }
}

And then define a Windows Service class that provides methods to Start and Stop the Scheduler

using System.ServiceProcess;
using Common.Logging;
using RakutenIntegrationService.Scheduler;

namespace RakutenIntegrationService
{
    public partial class RakutenService : ServiceBase
    {
        #region Fields

        private static ILog Log = LogManager.GetLogger<RakutenService>();
        private readonly ITaskScheduler _taskScheduler;

        #endregion

        public RakutenService(ITaskScheduler taskScheduler)
        {
            InitializeComponent();
            _taskScheduler = taskScheduler;
        }

        protected override void OnStart(string[] args)
        {
            Log.Info("Starting Rakuten Scheduler service.");
            _taskScheduler.Run();
        }

        protected override void OnStop()
        {
            Log.Info("Stopping Rakuten Scheduler service.");
            _taskScheduler.Stop();
        }
    }
}

And that’s pretty much about it. You can add as much custom functionality in the individual jobs classes depending what work you want the Scheduled Job to perform.

Could not load file or assembly ‘WebGrease’ one of its dependencies. The located assembly’s manifest definition does not match the assembly reference


Usually when you would run an update-package command in Package Manager Console, you would encounter such error messages, in this instance the case is with Webgrease assembly

The reason for this error message is that the application is trying to load a different version of the assembly than what is present in the packages folder, and since there is a mismatch, its failing. A couple of things to quickly check in this scenario are:

  • Check the web.config file for assembly bindings
<dependentAssembly>
    <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
    <bindingRedirect oldVersion="1.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
</dependentAssembly>
  • Check the version of the assembly referenced in your project

Clearly you can see the mismatch in the version of the referenced assembly and the assembly binding in the web.config file. All you have to do is to update the web.config file with the correct version of the assembly for binding. so,

<dependentAssembly>
    <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
    <bindingRedirect oldVersion="1.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
</dependentAssembly>

And that should be all!