Label Cloud

Sunday, July 05, 2009

Looking for work in a tough market

For over a month, I’ve been pretty quiet. No new posts, comments, tweets, etc… The reason behind it was that I was too busy looking for a job. Yep… A month and a half ago, I was “Right Sized” by my previous employer and was places into the ranks of millions of unemployed Americans.

Being laid off is definitely a learning experience, one that I would not wish on anyone. I got a chance to rethink my ideas on job security, professional relationships and job searching. After 6 weeks, I've started a new job that is quite a bit different then my previous one. I will be doing a lot more software architecture than management (Comes with the Sr. Architect title :). I will also be doing a lot more Java and no .Net development. I will also be working out of New Jersey instead of New York City. That's a lot of changes, a lot of things I didn't quite look for originally, yet all are good. My commute is expectantly shorter, I am doing what I like, and I am expanding my technology footprint.

My experience of looking for a new job was eye opening as well. There are tons of guides to get you through the time when you are looking for work. I don’t have a guide, these are some comments from my own recent experience.

  • Ask everyone / Network – Talk to everyone you’ve had good relationship with. Friends, Recruiters (Good Recruiters), Vendors, Technology Partners, etc…
  • Use Networking Sites – LinkedIn.com works wonders. Facebook, twitter, plaxo, etc… Do not ignore the sites because you think that no one will notice your post. People do notice, and they do help!
  • Be Flexible – Don’t ignore an opportunity because its not a perfect fit. This relates to technology, organization and salary requirements. Don’t be afraid of a different technology. As a manager, specific development language matters a lot less then you think. The more flexible you are, the easier it will be for you to talk to people. You might be pleasantly surprised about the opportunity you might have turned down otherwise.
  • Be Patient – This might be the toughest one. There are tons of thoughts that go through your head. It would be great to get a response to your resume the same day – but you will probably not get it that fast.
  • Don’t burn your bridges – This is, VERY IMPORTANT. The world is much smaller then it seems. I guarantee you, that you will meet people that know someone who knows you. And every relationship you kept, will be remembered.

To those who are now looking for work, good luck. Please contact me via linked in on http://www.linkedin.com/in/tfanshteyn. If there is anything that I’ll be able to do to help, I’ll gladly do so.

For those managers, who decide on the hiring and firing. Don’t make your decisions lightly. Don’t ignore resumes – people are waiting. And don’t ever let someone go easily.

Technorati Tags: ,,


Share/Save/Bookmark

Monday, June 29, 2009

Oracle Coherence SIG - Presentation and sample code

Oracle Coherence SIG came around. Excellent event. It was twitted live, so go ahead and search : http://search.twitter.com/search?q=%23nycsig

I spend around 30 minutes talking about using Coherence with .Net. First part of the presentation was on general Coherence configuration and setup. Second part was on more advanced use of serialization wrappers and LINQ to Coherence. People asked some great questions. I guess the one important point I wanted to reiterate is that you should not be afraid of using Coherence with .Net. It is a really great product. You will definitely get a lot of benefit out of the box with it. The wrappers I’ve provided will create some overhead, but that should be acceptable to many. Wrappers and LINQ are not a solution that will solve every .Net developers problem, but should definitely get you started.

I’ve uploaded the presentation and the samples (in 4MB large zip file).

Presentation http://tfanshteyn.110mb.com/CoherencePresentation.pdf
Sample Files http://tfanshteyn.110mb.com/CoherencePresentation.zip


Share/Save/Bookmark

Wednesday, June 17, 2009

I’ll be presenting at NY Coherence SIG

I will be making a presentation at NY Coherence SIG on June 24th. Check it out.

http://coherence.oracle.com/display/CSIG/24+June+2009+-+New+York%2C+NY
http://craigblitz.typepad.com/rawpower/2009/06/announcing-summer-new-york-coherence-sig.html

Craig Blitz will be twittering live from the SIG. http://craigblitz.typepad.com/rawpower/2009/06/live-twittering-the-next-nycsig.html  Join us by searching for #nycsig on twitter.

Using Oracle Coherence in .NET Environment

Timur Fanshteyn (speaker bio) Technology Manager
Oracle Coherence can be successfully used in a Microsoft Window and .NET Environment. It can be a excellent media for connecting .Net and Java applications. It can provide .NET applications with a caching solution in front of a database server. It can even be used to provide real-time data to the desktop clients. All with minimum or possible no java coding.
The presentation will go through requirements of setting up Coherence .NET Client. Go through use cases and point out some limitations. We will focus on general data caching, object serialization (through POF), event notifications and LINQ to coherence.

Outline

  • Oracle Coherence for .Net Environment
      • Basic Setup and Configuration
  • POF Serialization
      • Serialization
      • Cross platform data transfer
  • Invocations, Eventing and Notifications
      • Using Coherence as messaging bus
      • Moving data to the desktop
  • Advanced .NET Integration
      • Attributes base serialization
      • Serializing Metadata
      • Linq to Coherence
Technorati Tags: ,


Share/Save/Bookmark

Tuesday, April 07, 2009

Linq to Coherence + Attributes + MetaData = Cool

First, we got the Linq Provider for Oracle Coherence

Second, we got attribute based serialization

Third, Metadata in the serialization stream

Once we put all that together, what we get is a set of very clean way of storing and querying data in Oracle Coherence.

A Coherence Linq provider now supports passing a CoherenceQueryTranslator as a parameter. I am providing a MetadataCoherenceQueryTranslator that uses getProperty method to access property originally serialized by the Generic Serializer. Here’s almost all relative .Net Code:

Person Class

[POFSerializableObject(StoreMetadata=true)]
    public class Person// : IPortableObject
    {
        [POFSerializableMember(Order=0,WriteAsType=POFWriteAsTypeEnum.Int16)]
        public int ID { get; set; }
        [POFSerializableMember(Order=1)]
        public string FirstName { get; set; }
        [POFSerializableMember(Order = 2)]
        public string LastName { get; set; }
        [POFSerializableMember(Order = 3)]
        public string Address { get; set; }
        [POFSerializableMember(Order = 4)]
        public string Title { get; set; }
        public Person()
        {
        }
    }
Add object function
INamedCache cache = CacheFactory.GetCache("dist-Person");
for (int i = 0; i < 1000; i++)
{
cache.Add(i, new Person()
{
   ID = i,
   FirstName = string.Format("First Name {0}", i),
   LastName = string.Format("LastName {0}", i),
   Address = string.Format("Address {0}" , Guid.NewGuid()) ,
   Title = i % 2  == 1 ? "Mr" : "Mrs"
   });
}

Query using Linq Query:

CoherenceQuery<Person> coherenceData =
 new CoherenceQuery<Person>(
	 new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person"), 
		 new MetadataCoherenceQueryTranslator()));
string likeClause = "%8";
var people = from person in coherenceData
			 where
				(person.FirstName.Like("Test")
				 || person.LastName.Like(likeClause))
				 && person.Title == "Mrs"
			 select new { person.Title, person.ID, person.LastName };
IFilter filter = ((ICoherenceQueryable)people).Filter;
dataGridView1.DataSource = people.ToArray();

Internally, MetadataCoherenceQueryTranslator, will convert the linq query into a filter and execute the query against the Java POFGenericObject


Share/Save/Bookmark

Monday, April 06, 2009

99.9% pure .NET Coherence (100 % No Java Code Required)

Its not 100% .NET Coherence, since a developer is required to modify configuration files on the java service to specify the generic POF Serializer.

So to start: The .NET Class:

 

    [POFSerializableObject(StoreMetadata=true)]
    public class Person
    {
        [POFSerializableMember(Order=0,WriteAsType=POFWriteAsTypeEnum.Int16)]
        public int ID { get; set; }
        [POFSerializableMember(Order=1)]
        public string FirstName { get; set; }
        [POFSerializableMember(Order = 2)]
        public string LastName { get; set; }
        [POFSerializableMember(Order = 3)]
        public string Address { get; set; }
        [POFSerializableMember(Order = 4)]
        public string Title { get; set; }
        public Person()
        {
        }
        
    }

Note the StoreMetadata=true argument. When StoreMetadata is specified, Generic serializer will first first write a string array of property names. On server side, we must specify a Generic Java serializer. This is needed to be able to store objects for filtering. Here’s an interesting note. Unless filters are invoked, the object WILL NOT be deserialized on the java side. That means, Put, Get, GetAll calls without a filter, do not require Metadata to be written into the cache. Now. To specify Java Generic Serializer. Distributed Cache Configuration:

      <distributed-scheme>
      <scheme-name>dist-default</scheme-name>
      <serializer>
		<class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
		<init-params>
		  <init-param>
			<param-type>string</param-type>
			<param-value>custom-types-pof-config.xml</param-value>
		  </init-param>
		</init-params>
	  </serializer>
      <backing-map-scheme>
        <local-scheme/>
      </backing-map-scheme>
      <autostart>true</autostart>
    </distributed-scheme>

custom-types-pof-config.xml

<pof-config>
  <user-type-list>
    <!-- include all "standard" Coherence POF user types -->
    <include>coherence-pof-config.xml</include>
    <!-- include all application POF user types -->
    <user-type>
      <type-id>1001</type-id>
      <class-name>com.Coherence.Contrib.POF.POFGenericObject</class-name>
      <serializer>
        <class-name>com.Coherence.Contrib.POF.POFGenericSerializer</class-name>
        <init-params>
           <init-param>
             <param-type>int</param-type>
             <param-value>{type-id}</param-value>
           </init-param>
           <init-param>
            <param-type>boolean</param-type>
             <param-name>LoadMetadata</param-name>
             <param-value>true</param-value>
           </init-param>
         </init-params>        
	  </serializer>
    </user-type>
  </user-type-list>
</pof-config>

For now, you must specify a user-type for each .Net object. On the java side, the server will be using the POFGeneicSerializer, and all values in the object array indexed by the property names. A generic getProperty method is implemented to allow filtering on any property that was used in the serialization. Property evaluation is happening on the server, so only filtered data is returned.

Here’s a simple loop to add an object into the cache

INamedCache cache = CacheFactory.GetCache("dist-Person");
for (int i = 0; i < 1000; i++)
{
    cache.Add(i, new Person()
    {
       ID = i,
       FirstName = string.Format("First Name {0}", i),
       LastName = string.Format("LastName {0}", i),

       Address = string.Format("Address {0}" , Guid.NewGuid()) ,
       Title = i % 2  == 1 ? "Mr" : "Mrs"
    });
}

A cool side effect, data can be accessed from .Net and from Java code in the same fashion.


Share/Save/Bookmark

Attributes based Coherence POF Serializer

As much as I love Oracle Coherence, it is a very much a Java product. .Net has lots of cool tricks that can be used during programming, however, they are not implemented in Coherence .Net

Many .Net developers are used to using attributes to define object serialization. A Coherence Generic serializer allows a developer to specify POF Serialization using object attributes as well. Here’s a simple example

    [POFSerializableObject()]
    public class Person// : IPortableObject
    {
        [POFSerializableMember(Order=0,WriteAsType=POFWriteAsTypeEnum.Int16)]
        public int ID { get; set; }
        [POFSerializableMember(Order=1)]
        public string FirstName { get; set; }
        [POFSerializableMember(Order = 2)]
        public string LastName { get; set; }
        [POFSerializableMember(Order = 3)]
        public string Address { get; set; }
        [POFSerializableMember(Order = 4)]
        public string Title { get; set; }
        public Person()
        {
        }
        //#region IPortableObject Members
        //public void ReadExternal(IPofReader reader)
        //{
        //    ID = reader.ReadInt32(0);
        //    FirstName = reader.ReadString(1);
        //    LastName = reader.ReadString(2);
        //    Address = reader.ReadString(3);
        //    Title = reader.ReadString(4);
        //}
        //public void WriteExternal(IPofWriter writer)
        //{
        //    writer.WriteInt32(0, ID);
        //    writer.WriteString(1, FirstName);
        //    writer.WriteString(2, LastName);
        //    writer.WriteString(3, Address);
        //    writer.WriteString(4, Title);
        //}
        //#endregion
    }

To force Coherence to use the serialization attribute, use the the Coherence Generic Serializer in the POF Configuration File

    <user-type>
      <type-id>1001</type-id>
      <class-name>CoherenceSample.Person, CoherenceSample</class-name>
      <serializer>
        <class-name>Coherence.Contrib.POFGenericSerializer, Coherence.Contrib</class-name>
      </serializer>
    </user-type>

Some important points to mention:

  • Object must include POFSerializableObject attribute
  • Generic Serializer will serialize Properties and Members
  • Objects MUST NOT implement IPortableObject. Doing so will force Coherence to use ReadExternal and WriteExternal functions instead of a custom serializer
  • Order parameter of the attribute is optional. The order of serialization is Order, Alphabetical Ascending. Meaning. Multiple attributes can have the same order argument, and will be serialized in alphabetical order
  • WriteAsType parameter is optional and is usually derived based on the source type.
  • Serializer uses Converter.Convert() to convert between object types.
  • Most (but not all) main types are implemented. Check out the source code for specifics
  • Hardcoding a serializer still provides better performance due to extra boxing and object conversion performed by the serializer.

Please check out the latest code on Google Code: http://code.google.com/p/linqtocoherence

Technorati Tags: ,,,


Share/Save/Bookmark

Friday, April 03, 2009

Glad to have my IPhone back

My IPhone didn’t work this morning, so I wound up using a loaner Blackberry. Now… I’ve used a blackberry for over 5 years before my IPhone, and the interface didn’t change much. However, switching back was definitely an eye opener. Let’s just say I am glad my IPhone is back in business.

What I’ve learned:

Pro IPhone

  • I can type just as fast on the IPhone as I can on the blackberry. Since the IPhone “keys” are actually bigger, I even like IPhone typing more then blackberry typing.
  • I miss all the additional apps that I’ve installed on my IPhone. And they are not available on the blackberry

Pro Blackberry (I really wish IPhone had these)

  • Battery Life – It keeps going, and going, and going. Last time I charged it was 2 days ago. And it still has 2 out of 5 battery bars left
  • Blinking red light tells me I have new emails. I can see that from across the room.
  • EMail Shortcuts (T – Top / B – Bottom / U – Unread email)
  • Some additional email functionality – Mark all as read. Delete All Older EMail (from blackberry)
  • Quiet mode – The IPhone has only Sound and / Vibrate. Blackberry has multiple settings.
  • Brick Breaker – Spent 45 minutes playing it on the bus. I miss it.

At the end, I am glad that I have my IPhone back. Blackberry is a great business device, but a mediocre consumer accessory. I wish IPhone gets a better batter life and a few EMail / Calendar improvements.

Technorati Tags: ,,


Share/Save/Bookmark

Thursday, March 26, 2009

Windows Server 2008 R2 is 64bit only

Learned something new from Mark’s blog: Windows Server 2008 R2 is 64bit ONLY( The news is not new, but I only learned it tonight.) It will still support 32 bit applications, but only via OPTIONAL WoW64 functionality that is not added by default. I’ve heard about Microsoft stopping 32bit OS development a while back, but seeing this actually done…. This is BIG.

Technorati Tags: ,,


Share/Save/Bookmark

Subversion 1.6 is released

Subversion 1.6 (And so is TortoiseSVN 1.6 and Subclipse 1.6) is released.

New Functionality includes

TortoiseSVN includes a completely rewritten Revision Graph

Get it while its hot www.tigris.org


Share/Save/Bookmark

Thursday, March 19, 2009

Internet Explorer 8 is released – and I am using it

IE8 is now released and I’ve just upgraded my home laptop to it (I try not to mess around with betas on my main machine) So far so good. Speed of the browser is definitely something to brag about. I’ve also liked accelerators that come with IE. When you select a block of text with a mouse, a menu pops up that allows you to blog, email, search and even translate. Very useful functionality that can be extended by third-parties.

Download the new Internet Explorer at http://www.microsoft.com/windows/internet-explorer/default.aspx

Technorati Tags: ,


Share/Save/Bookmark

Wednesday, March 18, 2009

Linq Provider for Oracle Coherence – Pt 2

In previous post I published about a Linq for Oracle Coherence. Linq makes it very convenient to add functionality to a provider, however creating a functionally reach and complete provider is a very complicated process.

Latest functionality I’ve added was ability to use coherence extractors in the query. Linq does not allow you to change its keywords and syntax, however, all functions are converted to linq expressions and are passed to the ExpressionVisitor for evaluation. First thing is to create an extended function to provide the functionality

public static T Extractor<T>(this object obj, string Name)
{
    throw new NotSupportedException();
}
public static T ChainedExtractor<T>(this object obj, string Name)
{
    throw new NotSupportedException();
}

The functions themselves do not have any functionality and are used solely to be converted to a linq expression. I’ve created them as Generic functions to be able to strongly type against extracted data

In the QueryTranslator class VisitMethodCall() function, we add functionality to evaluate the expression

if (m.Method.DeclaringType == typeof(LinqFunctions))
{
    if (m.Method.Name == "Extractor")
    {
        this.Visit(m.Arguments[1]);
        string right = (string)globalFilter;
        globalFilter = new Tangosol.Util.Extractor.ReflectionExtractor(right);
        return m;
    }
    else if (m.Method.Name == "ChainedExtractor")
    {
        this.Visit(m.Arguments[1]);
        string right = (string)globalFilter;
        globalFilter = new Tangosol.Util.Extractor.ChainedExtractor(right);
        return m;
    }
}

m.Arguments[0] has the reference to the parent object. m.Arguments[0] contains the argument. This allows us to create a Coherence Extractor that is used in the filters higher in the expression tree.


A Note: I hope that the project gets some visibility and comments. Oracle Coherence is an excellent product with great .NET potential. Check out the project on google code: http://code.google.com/p/linqtocoherence


Share/Save/Bookmark

Tuesday, March 17, 2009

Linq provider for Oracle Coherence (Linq to Coherence)

I am very impressed with Coherence from Oracle. Coherence provides a distributed in-memory cache and processing fabric. However it is a lot more then just a cache. It can be used for everything from messaging to cross platform communication medium. There is too much to talk say about it, so read more information at Oracle: http://www.oracle.com/technology/products/coherence/index.html

Coherence works very nicely with .Net however, in the days of Linq, I wanted to write a Linq provider for it. My code is based largely on the Linq provider documentation on MSDN (http://msdn.microsoft.com/en-us/library/bb546158.aspx) and excellent series on creating a linq provider by Matt Warren (http://blogs.msdn.com/mattwar/pages/linq-links.aspx)

I am using Google Code to host the project under Artistic License. Please check out the full source code at http://code.google.com/p/linqtocoherence/.

Below is a rundown on two main classes. The main part of the code that deals with Coherence is in two classes CoherenceQueryProvider and CoherenceQueryTranslator.

CoherenceQueryProvider accepts a connection to the INamedCache – a reference to coherence cache that will be queried.

public class CoherenceQueryProvider  : IQueryProvider
{
   public INamedCache Cache { get; set; }
   public CoherenceQueryProvider ()
    {
    }

   public CoherenceQueryProvider(INamedCache cache)
   {
       Cache = cache;
   }

In the Execute method, CoherenceQueryProvider translates the Where clause to a Coherence Filter and executes the filter against the Cache objects to return array of values.

public object Execute(Expression expression)
{
  if (Cache == null)
      throw new InvalidOperationException("Cache is not properly set");

  // Find the call to Where() and get the lambda expression predicate.
  InnermostWhereFinder whereFinder = new InnermostWhereFinder();
  MethodCallExpression whereExpression = whereFinder.GetInnermostWhere(expression);
  LambdaExpression lambdaExpression = (LambdaExpression)((UnaryExpression)(whereExpression.Arguments[1])).Operand;

  // Send the lambda expression through the partial evaluator.
  lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);

  IFilter filter = new CoherenceQueryTranslator().Translate(lambdaExpression);

  object[] data = Cache.GetValues(filter);
  Type elementType = TypeSystem.GetElementType(expression.Type);
  return data;
}


CoherenceQueryTranslater uses the visitor pattern to convert the Linq Expression from the where clause to Coherence Filter. Coherence filters are nested to converting one to the other is relatively simple

protected override Expression VisitBinary(BinaryExpression b)
{
  this.Visit(b.Left);
  object lastGlobal1 = globalFilter;
  this.Visit(b.Right);
  object lastGlobal2 = globalFilter;
  switch (b.NodeType)
  {
      case ExpressionType.AndAlso:
          globalFilter = new AndFilter((IFilter) lastGlobal1, (IFilter)lastGlobal2);
          break;
      case ExpressionType.OrElse:
          globalFilter = new OrFilter((IFilter) lastGlobal1, (IFilter)lastGlobal2);
          break;

There is a lot more code in the classes to handle other filters, but a lot of it is pretty repetitive. The work on the linq provider is not done and I still have to implement some of the coherence functionality. Full code and usage sample is available on google code http://code.google.com/p/linqtocoherence/

Check it out and post your comments / suggestions.


Share/Save/Bookmark

Sunday, March 15, 2009

Embrace Change

We as developers spend our careers creating tools, innovating, making things better through technology. However, as a developer we also constantly deal with users who are refusing to change. Examples are everywhere

  • Doctors refusing to replace their filing cabinets with record keeping software. I’ve heard of this just this weekend from my wife.
  • People “Hate” new look and feel of software because it looks different (Office 2007 is a great example. I’ve also heard “hate it” comments about new Facebook look)
  • Users refusing to use new procedures and software because “old way works just fine”

The world is changing all the time. Technology accelerates this change and we can either fight it, or embrace it and make it work for us. I say – Embrace Change
ECbaseball_800x600


Share/Save/Bookmark

Wednesday, March 04, 2009

noop.nl - Top 50 New Software Development Books and other lists

I generally don’t link to other blog entries since that doesn’t add that much value to people. However, this post is not regarding the specific blog entry. Jurgen has an excellent blog dedicated to software development and management of development teams.

He also created excellent lists of TOP *EVERYTHING*. The last one published is Top 50 New Software Development Books. Other lists are at http://www.noop.nl/top-lists/

This is one of the blogs I would definitely recommend subscribing to.

Technorati Tags: ,,


Share/Save/Bookmark

Wednesday, February 11, 2009

Production Debugging a Memory Leak

I wrote before about not believing in regular system reboots. One of the services we wrote had a serious memory leak and process size grew over 1GB within 2 days requiring us to perform regular service restarts. This is not something that we were able to replicate in development or QA environment so I’ve decided to do some production debugging.

I love reading the blog of Tess Ferrandez on low level .NET Debugging. http://blogs.msdn.com/tess. The has a series walk trough sessions one of them is on Memory Leaks http://blogs.msdn.com/tess/archive/2008/03/25/net-debugging-demos-lab-7-memory-leak.aspx

I can’t really provide the original code for our service, but I was able to replicate the basic leak in a sample app, and below are steps to find out what it is.

Sample (on skydrive.live.com)

LeakyCache.zip-download


Sample Setup: Open LeakyCache.zip  Compile it if you want, or just run the included executable. Click “Leak” to leak memory.

LeakyApp

  1. Download Debugging Tools for Windows form Microsoft and install it on the server that is running the problem application.
  2. Copy SOS.DLL from “C:\Windows\Microsoft.NET\Framework\v2.0.50727” to “c:\Program Files\Debugging Tools for Windows (x86)” to get access to debugging library for .NET 2.0
  3. Execute ADScript to take a memory dump of the LeakyCache application
    "c:\Program Files\Debugging Tools for Windows (x86)\adplus.vbs" -hang -pn LeakyCache.exe -o c:\temp\LeakDump

LeakyAppDump

  1. Start WinDbg
    "c:\Program Files\Debugging Tools for Windows (x86)"\windbg
  2. From the File Menu, select “Open Dump File” and open the created dump file from C:\temp\LeakDump\
  3. Load SOS debugging using command
    .load SOS

Now the fun begins :)

  1. Run !dumpheap –stat
     DumpHeap
    What you’ll see is that the most memory is used by data type is System.String (53MB) and Dictionary+Entry (22MB). Also notice that there are more then 1 million string entries. Most of them are very small (<55 bytes average).
  2. To see the entries. Use command  (Press CTRL+BREAK to stop the flow) to see the list of addresses.
    !dumpheap -type System.String -max 100
    !do 022b8978 
    DO
    Substitute the address of one of the items instead of the 022b8978
    I underlines a Text String that you can see. In my experience of debugging my apps, based on the data, I can tell what is stored, and probably have some ideas about where that data is generated or should it be cleaned.
  3. Run !gcroot [Reference] to see exactly what class is holding a reference to the object
    GCRoot

A walkthrough like this will not necessary solve a problem in the application, but it can point out to a possible issue in the application that can be solved. To me, a memory leak is not a problem that should be ignored, but is a bug that can be fixed.

Note: Huge thanks to Tess for the wonderful blog http://blogs.msdn.com/tess

Technorati Tags: ,,


Share/Save/Bookmark