.NET

Consuming REST services in .NET

December 17, 2012 .NET, REST, WCF 5 comments

There are few different ways of consuming REST services in .NET

  1. Plain .NET HTTP request
  2. WCF mechanisms
  3. HttpClient
  4. Other libraries

In this post I would like to demonstrate how you can consume REST in C#.

Before we proceed to examples, let’s choose some very simple public REST API to consume. I stopped at this one: http://timezonedb.com/api. It is very simple service providing current local time for requested time zone. After registering you will get some key to access api. In this blog post we will use “YOUR_API_KEY”.

Here is sample response of the service we chose to consume:

<?xml version="1.0" encoding="UTF-8"?>
<result>
    <status>OK</status>
    <message></message>
    <countryCode>AU</countryCode>
    <zoneName>Australia/Melbourne</zoneName>
    <abbreviation>EST</abbreviation>
    <gmtOffset>39600</gmtOffset>
    <dst>1</dst>
    <timestamp>1321217345</timestamp>
</result>

 

Whichever approach we take, we would need some entities corresponding to the API’s response.

For XML response use cases I prepared following entity:

    [XmlRoot("result")]
    public class TimezoneDbInfo
    {
        [XmlElement("status")]
        public string Status { get; set; }
        [XmlElement("message")]
        public string Message { get; set; }
        [XmlElement("countryCode")]
        public string CountryCode { get; set; }
        [XmlElement("zoneName")]
        public string ZoneName { get; set; }
        [XmlElement("abbreviation")]
        public string Abbreviation { get; set; }
        [XmlElement("gmtOffset")]
        public int GmtOffset { get; set; }
        [XmlElement("dst")]
        public int Dst { get; set; }
        [XmlElement("timestamp")]
        public int Timestamp { get; set; }
    }

For JSON use cases we will use the following:

    [DataContract(Name = "result")]
    public class TimezoneDbInfoJson
    {
        [DataMember(Name = "status")]
        public string Status { get; set; }
        [DataMember(Name = "message")]
        public string Message { get; set; }
        [DataMember(Name = "countryCode")]
        public string CountryCode { get; set; }
        [DataMember(Name = "zoneName")]
        public string ZoneName { get; set; }
        [DataMember(Name = "abbreviation")]
        public string Abbreviation { get; set; }
        [DataMember(Name = "gmtOffset")]
        public int GmtOffset { get; set; }
        [DataMember(Name = "dst")]
        public int Dst { get; set; }
        [DataMember(Name = "timestamp")]
        public int Timestamp { get; set; }
    }

So let’s start!

Option 1: Plain .NET HTTP request

Here is how you can consume REST using WebRequest:

var urlTemplate = "http://api.timezonedb.com/?zone={0}&key={1}";
var url = string.Format(urlTemplate, "Europe/Kiev", "YOUR_API_KEY");

var request = WebRequest.Create(url);
var response = request.GetResponse();
var s = new XmlSerializer(typeof(TimezoneDbInfo));
var timezoneDbInfo = (TimezoneDbInfo)s.Deserialize(response.GetResponseStream());

And if you would need to consume same data but in JSON format, you can come up with following code:

var urlTemplate = "http://api.timezonedb.com/?zone={0}&key={1}&format=json";
var url = string.Format(urlTemplate, "Europe/Kiev", "YOUR_API_KEY");

var webClient = new WebClient();
byte[] downloadedRawResponse = webClient.DownloadData(url);

var stream = new MemoryStream(downloadedRawResponse);
var s = new DataContractJsonSerializer(typeof(TimezoneDbInfoJson));
var timezoneDbInfo = (TimezoneDbInfoJson)s.ReadObject(stream);

Either way, using WebRequest or WebClient allows for some lower level of flexibility like controlling timeouts, headers and many other things. For example:

request.Timeout = 30000;
request.Headers.Add("Accept-Language", "en-US");

Some of the disadvantages of above approach are manual building of a request URL and manual deserialization of a response. Many of you might not like the above code, thus let’s switch to WCF approach, which looks more elegant and robust at the first glance.

Option 2: WCF mechanisms

Not a secret that we can create REST services with WCF. Effectively this means that we can also create client for other REST services. For this all we need is just to create proxy as if it was our own service. Since, we cannot get wsdl with metadata for our service we have to create proxies manually.

To start we put some WCF configuration in place:

<system.serviceModel>
  <bindings>
    <webHttpBinding>
      <binding name="webHttpBindingCustom"
                receiveTimeout="00:01:01" sendTimeout="00:01:01">
        <security mode="None"/>
      </binding>
    </webHttpBinding>
  </bindings>
  <client>
    <endpoint address="http://api.timezonedb.com/"
              binding="webHttpBinding"
              bindingConfiguration="webHttpBindingCustom"
              behaviorConfiguration="tzBehavior"
              contract="ConsumingRestInNet.ITimezoneDb"
              name="TimeZoneDbREST" />
  </client>
  <behaviors>
    <endpointBehaviors>
      <behavior name="tzBehavior">
        <webHttp/>
      </behavior>
    </endpointBehaviors>
  </behaviors>
</system.serviceModel>

We already have some configuration flexibility which comes with WCF. Now, let’s define service contract:

[ServiceContract]
public interface ITimezoneDb
{
    [OperationContract]
    [XmlSerializerFormat]
    [WebGet(UriTemplate = "?zone={zone}&key={key}")]
    TimezoneDbInfo GetTimezoneInfo(string zone, string key);

    [OperationContract]
    [WebGet(UriTemplate = "?zone={zone}&key={key}&format=json")]
    TimezoneDbInfoJson GetTimezoneInfoJson(string zone, string key);
}

(In above method we can avoid having 2 methods by orchestrating our TimezoneDbInfo entity with DataContract in addition to XmlRoot and by having 3rd param).

XML consumption:

var channelFactory = new ChannelFactory<ITimezoneDb>("TimeZoneDbREST");
var channel = channelFactory.CreateChannel();
var timezoneDbInfo = channel.GetTimezoneInfo("Europe/Kiev", "YOUR_API_KEY");

JSON consumption:

var channelFactory = new ChannelFactory<ITimezoneDb>("TimeZoneDbREST");
var channel = channelFactory.CreateChannel();
var timezoneDbInfo = channel.GetTimezoneInfoJson("Europe/Kiev", "YOUR_API_KEY");

You can also go further and implement your own Client class:

public class TimezoneDbClient : ClientBase<ITimezoneDb>, ITimezoneDb
{
    public TimezoneDbClient(string endpointConfigurationName)
        : base(endpointConfigurationName)
    {
    }
    public TimezoneDbInfo GetTimezoneInfo(string zone, string key)
    {
        return base.Channel.GetTimezoneInfo(zone, key);
    }
    public TimezoneDbInfoJson GetTimezoneInfoJson(string zone, string key)
    {
        return base.Channel.GetTimezoneInfoJson(zone, key);
    }
}

Now consumptions will look like piece of cake:

var client = new TimezoneDbClient("TimeZoneDbREST");
var timezoneDbInfo = client.GetTimezoneInfoJson("Europe/Kiev", "YOUR_API_KEY");

Well, of course! All nasty code went to other classes and the config.

Option 3: HttpClient

It would be a perfect ending for a blog post. But it is not. On one hand using WCF provides us with great abstraction over service, but on the other hand it is just overhead for doing simple things. Also some people think that coupling generated by WCF defeats the point of REST.

Here is extract of a great answer on SO about what exactly is RESTful programming:

Really, what it’s about is using the true potential of HTTP. The protocol is oriented around verbs and resources. The two verbs in mainstream usage is GET and POST, which I think everyone will recognize. However, the HTTP standard defines several other such as PUT and DELETE. These verbs are then applied to resources.

To leverage all of this potential Microsoft has built System.Net.Http.HttpClient.

HttpClient is inside of namespace System.Net.Http but nuget package is called Microsoft.Net.Http. So don’t get confused. If you add the package it will simply reference System.Net.Http in your project. Nuget page says:

“This package provides a programming interface for modern HTTP applications. This package includes HttpClient for sending requests over HTTP, as well as HttpRequestMessage and HttpResponseMessage for processing HTTP messages.”

Here is how we can use HttpClient:

var urlTemplate = "http://api.timezonedb.com/?zone={0}&key={1}&format=json";
var url = string.Format(urlTemplate, "Europe/Kiev", "YOUR_API_KEY");

var httpClient = new HttpClient();
var streamTask = httpClient.GetStreamAsync(url);

var s = new DataContractJsonSerializer(typeof(TimezoneDbInfoJson));
var timezoneDbInfo = (TimezoneDbInfoJson)s.ReadObject(streamTask.Result);

Code above looks pretty much the same as when we used plain WebRequest. But HttpClient is much more advanced and adopted for consuming REST services. For example, it has methods: GetAsync and PostAsync. Reading and sending custom headers is much more simplified. Here is great post with many examples of using HttpClient: http://www.bizcoder.com/index.php/2012/01/09/httpclient-it-lives-and-it-is-glorious/

Option 4: Other libraries

So, are there any other possibilities to consume REST in .NET?

Yes. There are dozen of libraries out there implemented exactly for that, though I’m sure that a lot of them cannot be considered as good candidates.

[Edit 2015-Sep-2016] People often mention Restsharp and Hammock libraries.

Links

 

Hope this post comes handy for you!


5 comments


Application Fabric Cache – an easy, but solid start

July 4, 2012 .NET, DistributedCache, Errors, HowTo, Opinion 1 comment

I would like to share some experiences of working with Microsoft AppFabric Cache for Windows Server.

AppFabricCache is distributed cache solution from Microsoft. It has very simple API and would take you 10-20 minutes to start playing with. As I worked with it for about one month I would say that product itself is very good, but probably not enough mature. There are couple of common problems and I would like to share those. But before let’s get started!

If distributed cache concept is something new for you, don’t be scary. It is quite simple. For example, you’ve got some data in your database, and you have web service on top of it which is frequently accessed. Under high load you would decide to run many web instances of your project to scale up. But at some point of time database will become your bottleneck, so as solution you will add caching mechanism on the back-end of those services. You would want same cached objects to be available on each of the web instances for that you might want to copy them to different servers for short latency & high availability. So that’s it, you came up with distributed cache solution. Instead of writing your own you can leverage one of the existing.

Install

You can easily download AppFabricCache from here. Or install it with Web Platform Installer.

Installation process is straight forward. If you installing it to just try, I wouldn’t even go for SQL server provider, but rather use XML provider and choose some local shared folder for it. (Provider is underlying persistent storage as far as I understand it.)

After installation you should get additional PowerShell console called “Caching Administration Windows PowerShell”.

So you can start your cache using: “Start-CacheCluster” command.

Alternatively you can install AppFabric Caching Amin Tool from CodePlex, which would allow you easily do lot of things though the UI. It will show PowerShell output, so you can learn commands from there as well.

image

Usually you would want to create named cache. I created NamedCacheForBlog, as can be seen above.

Simple Application

Let’s now create simple application. You would need to add couple of references:

image

Add some configuration to your app/web.config

  <section name="dataCacheClient" type="Microsoft.ApplicationServer.Caching.DataCacheClientSection, Microsoft.ApplicationServer.Caching.Core" allowLocation="true" allowDefinition="Everywhere"/>
  
<!-- and then somewhere in configuration... -->
  
  <dataCacheClient requestTimeout="5000" channelOpenTimeout="10000" maxConnectionsToServer="20">
    <localCache isEnabled="true" sync="TimeoutBased" ttlValue="300" objectCount="10000"/>
    <hosts>
      <!--Local app fabric cache-->
      <host name="localhost" cachePort="22233"/>
      <!-- In real world it could be something like this:
      <host name="service1" cachePort="22233"/>
      <host name="service2" cachePort="22233"/>
      <host name="service3" cachePort="22233"/>
      -->
    </hosts>
    <transportProperties connectionBufferSize="131072" maxBufferPoolSize="268435456"
                       maxBufferSize="134217728" maxOutputDelay="2" channelInitializationTimeout="60000"
                       receiveTimeout="600000"/>
  </dataCacheClient>

Note, that above configuration is not the minimal one, but rather more realistic and sensible. If you are about to use AppFabric Cache in production I definitely recommend you to read this MSDN page carefully.

Now you need to get DataCache object and use it. Minimalistic, but wrong, way of doing it would be:

public DataCache GetDataCacheMinimalistic()
{
    var factory = new DataCacheFactory();
    return factory.GetCache("NamedCacheForBlog");
}

Above code would read configuration from config and return you DataCache object.

Using DataCache is extremely easy:

object blogPostGoesToCache;
string blogPostId;
dataCache.Add(blogPostId, blogPostGoesToCache);
var blogPostFromCache = dataCache.Get(blogPostId);
object updatedBlogPost;
dataCache.Put(blogPostId, updatedBlogPost);

DataCache Wrapper/Utility

In real world you would probably write some wrapper over DataCache or create some Utility class. There are couple of reasons for this. First of all DataCacheFactory instance creation is very expensive, so it is better to keep one. Another obvious reason is much more flexibility over what you can do in case of failures and in general. And this is very important. Turns out that AppFabricCache is not extremely stable and can be easily impacted. One of the workarounds is to write some “re-try” mechanism, so if your wrapping method fails you retry (immediately or after X ms).

Here is how I would write initialization code:

private DataCacheFactory _dataCacheFactory;
private DataCache _dataCache;
private DataCache DataCache
{
    get
    {
        if (_dataCache == null)
        {
            InitDataCache();
        }
        return _dataCache;
    }
    set
    {
        _dataCache = value;
    }
}

private bool InitDataCache()
{
    try
    {
        // We try to avoid creating many DataCacheFactory-ies
        if (_dataCacheFactory == null)
        {
            // Disable tracing to avoid informational/verbose messages
            DataCacheClientLogManager.ChangeLogLevel(TraceLevel.Off);
            // Use configuration from the application configuration file
            _dataCacheFactory = new DataCacheFactory();
        }

        DataCache = _dataCacheFactory.GetCache("NamedCacheForBlog");

        return true;
    }
    catch (DataCacheException)
    {
        _dataCache = null;
        throw;
    }
}

DataCache property is not exposed, instead it is used in wrapping methods:

public void Put(string key, object value, TimeSpan ttl)
{
    try
    {
        DataCache.Put(key, value, ttl);
    }
    catch (DataCacheException ex)
    {
        ReTryDataCacheOperation(() => DataCache.Put(key, value, ttl), ex);
    }
}

ReTryDataCacheOperation performs retry logic I mentioned before:

private object ReTryDataCacheOperation(Func<object> dataCacheOperation, DataCacheException prevException)
{
    try
    {
        // We add retry, as it may happen,
        // that AppFabric cache is temporary unavailable:
        // See: http://msdn.microsoft.com/en-us/library/ff637716.aspx
        // Maybe adding more checks like: prevException.ErrorCode == DataCacheErrorCode.RetryLater

        // This ensures that once we access prop DataCache, new client will be generated
        _dataCache = null;

        Thread.Sleep(100);
        var result = dataCacheOperation.Invoke();

        //We can add some logging here, notifying that retry succeeded
        return result;
    }
    catch (DataCacheException)
    {
        _dataCache = null;
        throw;
    }
}

You can go further and improve retry logic to allow for many retries and different intervals between retries and then put all that stuff into configuration.

RetryLater

So, why the hell all this retry logic is needed?

Well, when you open MSDN page for AppFabric Common Exceptions be sure RetryLater is the most common one. To know what exactly happened you need to verify ErrorCode.

So far I’ve see this sub-errors of the RetryLater:

There was a contention on the store. – This one is quite frequent one. Could happen when someone is playing some concurrent mess with cache. Problem is that any client can affect the whole cluster.

The connection was terminated, possibly due to server or network problems or serialized Object size is greater than MaxBufferSize on server. Result of the request is unknown. – This usually has nothing to do with object size. Even if configuration is correct and you save small objects you can still get this error. Retry mechanism is good for this one.

One or more specified cache servers are unavailable, which could be caused by busy network or servers. – Have no idea how frequent this one could be, but it can happen.

No specific SubStatus. – Amazing one!

Conclusion

AppFabricCache is very nice distributed cache solution from Microsoft. It has a lot of features. Of course not described here, as you can read it elsewhere, say here. But to be able to go live with it you should be ready for AppFabricCache not being extremely stable & reliable, so you better put some retry mechanisms in place.

To be honest if I was one to make decision if to use this dcache, I would go for another one. But who knows, maybe other are not much better… I’ve never tried other distributed caches.

Links

Thank you, and hope this is of some help.


1 comment


Autofac: resolve dependencies later in code & pass your parameters

March 22, 2012 .NET, Autofac, C#, IoC 3 comments

Today I played a bit with Autofac, and I learned few simple but nice tricks. This blog post is for my personal notes. If you haven’t used Autofac or know little about IoC it might be of interest for you.
Below we have fun setup. I call it “fun”, because it is very explicit and easy to understand. Reading though it makes it clear what dependencies are:
builder
    .Register(b => new F())
    .As<IF>();

builder
    .Register(b => new E())
    .As<IE>();

builder
    .Register(b => new D1())
    .Named<ID>(SomeD1Name);

builder
    .Register(b => new D2())
    .Named<ID>(SomeD2Name);

builder
    .Register((b,prms) => new C(b.Resolve<IE>(), prms.TypedAs<IF>()))
    .Named<IC>(CName);

builder
    .Register((b,prms) => new B(b.Resolve<IF>(), b.ResolveNamed<IC>(CName, prms)))
    .As<IB>();

builder
    .Register(b => new A(b.Resolve<IE>(), 
        b.ResolveNamed<ID>(SomeD1Name),
        b.ResolveNamed<ID>(SomeD2Name)))
    .As<IA>(AName);

builder
    .Register(b => new Service(b.Resolve<IComponentContext>(),
        b.Resolve<IA>(), b.Resolve<IB>()))
    .As<IService>();

And that’s it.

I would like to pay your attention to few things. One of them is “prms.TypedAs”, which is great way to inject parameters for your construction later on. Another thing is passing IComponentContext to the Service instance. This two things altogether will allow you to resolve dependencies inside of your Service class, just like below (assuming you have field _componentContext):
_componentContext.Resolve<IB>(new TypedParameter(typeof(IF), new F1()));

As you understood this will create us instance of B, by passing into its constructor instance of F and instance of C, created basing on E and another implementation of IF, which is F1 (not one registered at very beginning). Isn’t it nice?
For serious reading on Autofac please refer to its web site:


3 comments


Working with FTP for the first time? Quick setup & quick C# code.

March 21, 2012 .NET, C#, QuickTip No comments

Recently I had some FTP work to do. Nothing special, but in case you need quick guide on setting up FTP and writing access code in .NET you might find this interesting. Also you know where to find it in case you need it later.

I will define simple task and we will solve it!

Task:

Imagine we have external FTP server, where some vendor puts many files. Of course they provided us with credentials. We want to connect to server and then parse some files from the whole list of files. Also for testing purposes we are going to mock external service with our own local.

Setup FTP:

1) Enable FPT in Windows features.

image

2) Go to IIS –> Sites –> “Add FPT Site…”. You would need to specify some folder.

3) As for our task we want to mock some system. Following setup might be good:

  • Binding with all assigned host names and port 21
  • No SSL
  • Allow for Anonymous and Basic Authentication
  • Add Read permissions for All Users and Anonymous

You should see something like this:

image

image

You will be able to access FTP locally without any issues and need to provide credentials.

4) Go to User Accounts –> Advanced –> Advanced –> New User… Create user you would like use when connecting to FTP.

image

5) Go to IIS -> your FTP site –> Basic Settings –> Connect as… –> Specific User. And enter same user again.

image

We added this user because we need to imitate situation in which our code and FTP have different credentials.

Access code:

To get list of files on server (using WebRequest):

public List<string> FetchFilesList()
{
    var request = WebRequest.Create(FtpServerUri);
    request.Method = WebRequestMethods.Ftp.ListDirectory;

    request.Credentials = new NetworkCredential(UserName, UserPassword);

    using (var response = (FtpWebResponse)request.GetResponse())
    {
        var responseStream = response.GetResponseStream();

        using (var reader = new StreamReader(responseStream))
        {
            var fileNamesString = reader.ReadToEnd();
            var fileNames = fileNamesString.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

            return fileNames.ToList();
        }
    }
}

To fetch some file contents as XDocument (using WebClient):

public XDocument FetchFile(string fileName)
{
    var client = new WebClient();
    client.Credentials = new NetworkCredential(UserName, UserPassword);

    var fileUri = new Uri(FtpServerUri, fileName);
    var downloadedXml = client.DownloadString(fileUri);
    return XDocument.Parse(downloadedXml);
}

I don’t think those two chucks of code need lot of explanations. As you can see with WebClient there is less code, but this way you cannot specify request ftp method.

Hope this overview is quick and not too much noisy.

NOTE: I’m not professional administrator, so my FTP setup may be somewhatwrong, but it satisfied needs of task described in the beginning of blog post.

In any case here are some links:


No comments


Custom configuration: collection without “add” plus CDATA inside

March 20, 2012 .NET, C#, QuickTip 2 comments

This blog post might look like any other standard blog posts, answering question, which can be googled and found on stackoverflow. But it isn’t. You see… it composes couple of interesting things you might need for you custom configuration. Also it is not congested with explanations. I’m adding this as quick reference for myself, so I don’t spend my time googling a lot to find answers. Also if you just starting with custom configuration and don’t want to read MSDN pages, please refer to my earlier blog post on basics here.

Let’s get back to topic:

We want section in our app/web.config with collection which will be able to contain elements without ugly “add” tag and also have CDATA inside. See configuration:

    <Feeds defaultPollingInterval="00:10:00">
      <Feed>
        <![CDATA[http://www.andriybuday.com/getXmlFeed.aspx?someParam=A&somethingElse=B]]>
      </Feed>
      <Feed pollingInterval="00:05:00">
        <![CDATA[http://www.andriybuday.com/getXmlFeed.aspx?someParam=C&somethingElse=D]]>
      </Feed>
    </Feeds>

So as you can see in collection of elements there is custom name “Feed”, which is awesome. Also notice that URL contains weird characters (not for us, but for XML), so we surround URL into CDATA. Those feeds are fake of course.

To make all this happen we need few things:

  1. Override CollectionType property for our collection, and set type to BasicMap
  2. Override ElementName property for our collection, and return preferred name
  3. Override DeserializeElement method for element inside collection. Here you need to manually fetch your attributes, like I do for poollingInterval and read contents of CDATA. Please refer to source code below to see how this is done as it is bit tricky. For example because of the nature of the XmlReader you need to read attributes first and then proceed to contents.

Source code below (interesting pieces are in bold):

[ConfigurationCollection(typeof(FeedConfigElement))]
public class FeedsConfigElementCollection : ConfigurationElementCollection
{
    [ConfigurationProperty("defaultPollingInterval", DefaultValue = "00:10:00")]
    public string DefaultPollingInterval
    {
        get
        {
            return (string)base["defaultPollingInterval"];
        }
    }
    protected override ConfigurationElement CreateNewElement()
    {
        return new FeedConfigElement();
    }
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((FeedConfigElement)(element)).Url;
    }

    // In order to avoid standard keyword "add"
    // we override ElementName and set CollectionType to BasicMap
    protected override string ElementName
    {
        get { return "Feed"; }
    }

    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.BasicMap; }
    }
    public FeedConfigElement this[int index]
    {
        get
        {
            return (FeedConfigElement)BaseGet(index);
        }
    }
}

public class FeedConfigElement : ConfigurationElement
{
    public string Url { get; private set; }

    public string PollingInterval { get; private set; }

    // To get value from the CDATA we need to overrride this method
    protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
    {
        PollingInterval = reader.GetAttribute("pollingInterval") ?? "00:00:00";

        // Also for some unknown reason for CDATA ReadElementContentAsString returns 
        // a lot of spaces before and after the actual string, so we Trim it
        Url = reader.ReadElementContentAsString().Trim();
    }
}

Hope this gives quick answers to some of you. It took me good portion of time to find all this things, because for some odd reason it wasn’t so much easy to find.

Some links:


2 comments


log4net versions deployment issue

October 6, 2010 .NET, Deployment 13 comments

So few days ago I faced with issue of 3rd party references.

My original question on stackoverflow:

What is the best approach to use 3rd party that uses another version of other 3rd party (log4net) already used in the system?

  • Currently we use log4net of version 1.2.10.0 and we should start using some 3rd party components developed by other team.
  • Mentioned component references log4net of version 1.2.9.0.
  • All binaries are deployed into one folder.
I’m sure that we cannot rebuild our sources with 1.2.9.0 version,
because there are too many other dependencies and will require lot of
efforts. Are there any other approaches to solve this issue? I’m NOT
looking for too sophisticated that have something to do with CLR
assemblies loading, but would hear them with great pleasure. I’m looking
for the simplest approaches. I guess someone has encountered the same
issue.

I got (as for now) two answers and I would like to try them out.

So I created 3 projects, one references log4net of version of 1.2.10.0 and another references 1.2.9.0. Both of them are referenced in client console application, which also references one of the log4net assemblies. In client application I’m trying to execute code that requires log4net in both of the assemblies.

Below is projects layout:

When I execute my code I’m getting error:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly ‘log4net, Version=1.2.9.0, Culture=neutral, PublicKeyToken=b32731d11ce58905’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: ‘log4net, Version=1.2.9.0, Culture=neutral, PublicKeyToken=b32731d11ce58905’
   at ProjReferencesLog4Net._1._2._9._0.ClassA..ctor()
   at ConsoleAppReferencesLog4Net1._2._10._0_andBothAssemblies.Program.Main(String[] args) in
  ….

In order to resolve this I tried suggestion one by one…

Suggestion number 1

Redirecting Assembly Versions

Accordingly to MSDN there is possibility to redirect code execution to assembly with higher version, just with using following configuration:

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
         <assemblyIdentity name="log4net"
                           publicKeyToken="b32731d11ce58905"
                           culture="neutral" />
         <bindingRedirect oldVersion="1.2.9.0"
                          newVersion="1.2.10.0"/>
       </dependentAssembly>
      </assemblyBinding>
   </runtime>
</configuration>

I’ve tried it and it did not work. Reason is that we cannot do redirection between assemblies with different PublicKeyToken-s. log4net 1.2.9.0 has “b32731d11ce58905” and log4net 1.2.10.0 has “1b44e1d426115821”. Also see this stackoverflow question.

Suggestion number 2

Use GAC. So when I install those two assemblies into GAC:

In this case code works, but suggestion doesn’t work for us, since we do not want to gac assemblies we use.

Other suggestions

So I’ve been thinking about another approach.
Approaches that require rebuilding our code with different version of log4net are not suitable for us. At least for now.
Another thing about which I’ve been thinking is to load those assemblies into different application domain or host 3rd party that uses 1.2.9.0 under different WinService. Both of these are cumbersome solutions and I like to avoid them.

YOUR SUGGESTION

If you have some ideas, could you please let me know!

[EDITED 7 Oct, 2010 11PM]

FUNNY-HAPPY-END OF THIS STORY

Do you know what is the most interesting about all of this? It is how it has finished. We contacted those guys, who developed component we now should use. They gave us know, that they were encountering issues with updating on-the-fly configuration file for log4net 1.2.10.0. By their words, new version of log4net is not capable of doing this. So they sent as simple application that demonstrates this, and indeed, after updating config when app is running, 1.2.10.0 did not catch up new configuration, but 1.2.9.0 was working just fine. This surprised me very much, so I went to this download page and downloaded latest binaries. When I tried it got working!!! Actually I guess that they simply used version of log4net buit with references to .net framework 1.1, and we should use one built with .net 2.0 (Yeah! Actually if you would download you will see.)

After all of this, they created new sub-release of their sources especially for us and they were able to fix some minor bug. Great news! Unexpected end of story! :)


13 comments


.NET Remoting Quickly

August 11, 2010 .NET, C#, HowTo No comments

As you may know recently I got junior to mentor him. In order to understand his capabilities and knowledge I asked him to do couple of things, like explain me one Design Pattern he knows, explain SCRUM and write the simplest .NET Remoting. So that was yesterday and today I verified that he failed with .NET Remoting, but it doesn’t mean that he is bad. He just need learn googling art more. I asked that for next day, and gave him stored procedure to write. Hope he will be smart enough to finish it till I come tomorrow from my English classes.

.NET Remoting

To ensure that I’m not asshole that asks people to do what I cannot do, I decided to write it by my own and see how long will it take for me. It took me 23 minutes. Hm… too much, but I should complain at VS about “Add Reference” dialog.

So here we have three projects in Visual Studio: one for Server, one for Client and of course Proxy class shared between client and server.

Shared proxy class ChatSender in ChatProxy assembly:

    public class ChatSender : MarshalByRefObject
    {
        public void SendMessage(string sender, string message)
        {
            Console.WriteLine(string.Format(“{0}: {1}”, sender, message));
        }
    }

Server (ChatServer):

    class Program
    {
        static void Main(string[] args)
        {
            var channel = new TcpServerChannel(7777);
            ChannelServices.RegisterChannel(channel, true);
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(ChatSender),
                “ChatSender”, WellKnownObjectMode.Singleton );
            Console.WriteLine(“Server is started… Press ENTER to exit”);
            Console.ReadLine();
        }
    }

Client (ChatClient assembly):

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(“Client is started…”);
            ChannelServices.RegisterChannel(new TcpClientChannel(), true);
            var chatSender =
                (ChatSender)Activator.GetObject(typeof(ChatSender), “tcp://localhost:7777/ChatSender”);
            string message;
            while ((message = Console.ReadLine()) != string.Empty)
            {
                chatSender.SendMessage(“Andriy”, message);   
            }
        }
    }

My results


No comments