About the author

Miron Abramson
Me
Software Engineer, Senior Developer at Rima, and .NET addicted for long time.

Recent comments

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008

Creative Commons License

Blog Flux Directory
Technology Blogs - Blog Top Sites

High performance css minifier

It is known that minifying the JavaScript and CSS files can really reduce the files size and improve the general site performance. Lot of sites (BlogEngine.NET among them)  do it on run-time and not it the build time. At this point, I want to recommend the JavaScript minifier JSMIN by Douglas Crockford. It does the job very good, and by far, much faster than all the RegularExpression \ Replace minifiers. Because in my Compression project MbCompression I do the minifying on run-time, I decided to use jsmin minifier.

The Css minifier

All the Css minifiers I found are using Regular Expression\ Replace to remove the unneeded characters. This is working fine, but have a realy bad performance, special on run-time. The speed is slow (special using the Regular Expression), and another important thing is, every Replace creates a very big string in the memory! (Strings are immutable, remember?), so if you have several Replaces, it creates several big strings in the server memory!

To improve performance, I took  JSMIN idea, that are not using any strings in the memory, and perform the minifying much faster, and created a CSS minifier that produce a small CSS file, much faster, and with much less memory overhead.

This minifier was tested on several CSS files. Feel free to download it and use it. If there is any file that not been minify correctly, send it to me, and I will try to improve the minifier.

CssMinifier.cs (9.59 kb)

Currently rated 5.0 by 5 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: C# | Client side | Performance
Posted by Miron on Sunday, June 29, 2008 4:48 AM
Permalink | Comments (5) | Post RSSRSS comment feed

Create your own new Type and use it on run-time (C#)

It is not something you will use on daily bases. It doesn't have good performance. But one day, you will have to use it so it's good to know that it is possible. As you can understand from the title, I'm talking about creating a new type with fields and properties (it can also have methods), create instance/s from it and use it. It will not be 'type safe', of course, and you will be able to use it - read and set its values only with reflection. but, all the DataBindingControls (GridView, FormView etc...) are binding the data using reflection, so they will be happy to bind and use your new created objects from your own type you created on run-time.

Lets cut the crap and jump into the code:

Let's say you got an xml from a webservice. and you want to create an object from it. Something like that:

You receive an xml like this:

<root>
    <column name="Name">Miron</column>
    <column name="LastName">Abramson</column>
    <column name="Blog">www.blog.mironabramson.com</column>
</root> 

You want to make a 'match' type that looks like this:

public class MyType
{
    public string Name{ get; set; }
    public string LastName{ get; set; }
    public string Blog{ get; set; }
}

and than create an object from that type and fill it with your data. The prolem is that you don't know what will be the values of the xml attributes and fields. So you need to create the object on run-time:

private object CreateOurNewObject()
{
    string _xml = "<root>" +
        "<column name=\"Name\">Miron</column>" +
        "<column name=\"LastName\">Abramson</column>" +
        "<column name=\"Blog\">www.blog.mironabramson.com</column>" +
        "</root>";

    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(_xml);

    // create a dynamic assembly and module
    AssemblyName assemblyName = new AssemblyName();
    assemblyName.Name = "tmpAssembly";
    AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
    ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule");

    // create a new type builder
    TypeBuilder typeBuilder = module.DefineType("BindableRowCellCollection", TypeAttributes.Public | TypeAttributes.Class);

    // Loop over the attributes that will be used as the properties names in out new type
    foreach (XmlNode node in xmlDoc.SelectSingleNode("root").ChildNodes)
    {
        string propertyName = node.Attributes["name"].Value;

        // Generate a private field
        FieldBuilder field = typeBuilder.DefineField("_" + propertyName, typeof(string), FieldAttributes.Private);
        // Generate a public property
        PropertyBuilder property =
            typeBuilder.DefineProperty(propertyName,
                             PropertyAttributes.None,
                             typeof(string),
                             new Type[] { typeof(string) });

        // The property set and property get methods require a special set of attributes:

        MethodAttributes GetSetAttr =
            MethodAttributes.Public |
            MethodAttributes.HideBySig;

        // Define the "get" accessor method for current private field.
        MethodBuilder currGetPropMthdBldr =
            typeBuilder.DefineMethod("get_value",
                                       GetSetAttr,
                                       typeof(string),
                                       Type.EmptyTypes);

        // Intermediate Language stuff...
        ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
        currGetIL.Emit(OpCodes.Ldarg_0);
        currGetIL.Emit(OpCodes.Ldfld, field);
        currGetIL.Emit(OpCodes.Ret);

        // Define the "set" accessor method for current private field.
        MethodBuilder currSetPropMthdBldr =
            typeBuilder.DefineMethod("set_value",
                                       GetSetAttr,
                                       null,
                                       new Type[] { typeof(string) });

        // Again some Intermediate Language stuff...
        ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
        currSetIL.Emit(OpCodes.Ldarg_0);
        currSetIL.Emit(OpCodes.Ldarg_1);
        currSetIL.Emit(OpCodes.Stfld, field);
        currSetIL.Emit(OpCodes.Ret);

        // Last, we must map the two methods created above to our PropertyBuilder to
        // their corresponding behaviors, "get" and "set" respectively.
        property.SetGetMethod(currGetPropMthdBldr);
        property.SetSetMethod(currSetPropMthdBldr);
    }

    // Generate our type
    Type generetedType = typeBuilder.CreateType();

    // Now we have our type. Let's create an instance from it:
    object generetedObject = Activator.CreateInstance(generetedType);

    // Loop over all the generated properties, and assign the values from our XML:
    PropertyInfo[] properties = generetedType.GetProperties();

    int propertiesCounter = 0;

    // Loop over the values that we will assign to the properties
    foreach (XmlNode node in xmlDoc.SelectSingleNode("root").ChildNodes)
    {
        string value = node.InnerText;
        properties[propertiesCounter].SetValue(generetedObject, value, null);
        propertiesCounter++;
    }
  
    //Yoopy ! Return our new genereted object.
    return generetedObject;
}

Mazal Tov!!!

We create our type and instance from it on run-time !!!

In the file bellow, there is full working exmple of the code

 

MyTypeRunTime.zip (4.15 kb)

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by Miron on Monday, June 09, 2008 10:43 AM
Permalink | Comments (1) | Post RSSRSS comment feed

Prevent from the user to submit any 'not legal' input, using client side validation

We are all know that client validations are good way to validate input, of cource you must re validate in server side also, but the client side will save some wasted post backs. Usually, we validate each input separate (writing manually script, or a validator control). What if you have a rule that is for all inputs in the page, or in the site? for example, you want to prevent the user to submit any input that contains 'fuc*' , 'shi*' or what ever words that are not 'legal' for you, or input that is not valid for the ASP.NET such '<'or  '/>' ?

I wrote some JavaScript code, that validate all the TextBox and Textarea controls in the page, and prevent it to be submitted if one (or more) of the input/s is in your 'not legal words' list. What the script actualy does, is checking the values of all Textboxes in the page, and if one input value contains one of the 'bad' words that defined in the 'MachingWords' variable, it will pop an alert, and will cancel the submit.  Here we go:

 // List of 'bad' words.  Append here all the words you want from the user to submit as an input, seperates by '|' char.

var MachingWords = "<|/>|fuck|shit|ass";


function FormValidationController()
{
    this.IsValid = true;
    this._notValidValuesCollection = [];
    this._notValidValuesHash = new Array();
    this.AddNotValidValue = function(value){   
        if( !this._notValidValuesHash[value] ){
            this._notValidValuesHash[value] = 1;
            this._notValidValuesCollection.push(value);
        }
    }
    this.PopMessage = function() {
        var wordsList = '';
        for( var i = 0 ; i < this._notValidValuesCollection.length ; i++ ) {
            wordsList = wordsList.length < 1 ? "'" + this._notValidValuesCollection[i] + "'" : wordsList + ", '" + this._notValidValuesCollection[i] + "'";
        }
        var notValidMessage = "The input{0} {1} {2} not valid.";
        alert( String.Format(notValidMessage,(this._notValidValuesCollection.length > 1 ? "s" : ""),(wordsList),(this._notValidValuesCollection.length > 1 ? "are" : "is")) );
    }
    this.Validate = function(){
        this.IsValid = true;
        this._notValidValuesCollection = [];
        this._notValidValuesHash = new Array();
        var inputElementsCollection = document.getElementsByTagName("INPUT");
        if( inputElementsCollection ){
            for (var index=0; index<inputElementsCollection.length; index++) {
                var el=inputElementsCollection[index];
                if( el.type.toLowerCase() == 'text' )
                    this.ValidateInput(el);
            }
        }
        var textareaElementsCollection = document.getElementsByTagName("TEXTAREA");
        if( textareaElementsCollection ){
            for (var index=0; index<textareaElementsCollection.length; index++) {
                var el=textareaElementsCollection[index];
                this.ValidateInput(el);
            }
        }
    }
    this.ValidateInput = function (element) {
        if( element ){
            if( !this.IsValidString(element.value) ) {
                this.AddNotValidValue(element.value);
                if( this.IsValid ){
                    this.IsValid = false;
                    element.focus();
                    element.select();
                }
            }
        }
    }
}

FormValidationController.RegularExpression = new RegExp(MachingWords,"i","g");
// Validate the current input
FormValidationController.prototype.IsValidString = function(strInput)
{   
    if( !strInput || typeof( strInput ) == 'string' ){
          if( strInput.length < 1 )
            return true;
    }
    if( FormValidationController.RegularExpression.test ( strInput ) )
        return false;
    return true;
}
var c = new FormValidationController();

//
// String.Format implementation
//
String.Format = function(format,args){
    var result = format;
    for(var i = 1 ; i < arguments.length ; i++) {
        result = result.replace(new RegExp( '\\{' + (i-1) + '\\}', 'g' ),arguments[i]);
    }
    return result;
}

And the method that using it and excuting the validation:

 //
//  Excute the validation
//
function ValidatePage() {
    c.Validate();
    if( !c.IsValid ) {
           c.PopMessage();
           return false;
      }
    return true;
}

 The implementation is very simple. Add the script to your page, and add to  the OnLoad page event  the following line, and that's it.

ClientScript.RegisterOnSubmitStatement(this.GetType(), "ValidatePage", "return ValidatePage();");

Javascript file with the code available here: 

FormValidation.js (3.23 kb)

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | Client side
Posted by Miron on Saturday, May 17, 2008 9:53 AM
Permalink | Comments (1) | Post RSSRSS comment feed

Sorting a collection using Linq and 'SortExpression' string

Already happened to you that you had a collection of object from type 'X' with some properties, and you had to sort it one time by property 'ID', and another time by property 'Name' ? You wished that you can sort it by just using a 'Sort Expression' ? If still not, I'm sure this moment will arrive sooner or later. Let me save you some time and an headache.

This is how it can be done: 

 public static IEnumerable<T> Sort<T>(this IEnumerable<T> source, string sortExpression)
{
    string[] sortParts = sortExpression.Split(' ');
    var param = Expression.Parameter(typeof(T), string.Empty);
    try
    {
        var property = Expression.Property(param, sortParts[0]);
        var sortLambda = Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);

        if (sortParts.Length > 1 && sortParts[1].Equals("desc", StringComparison.OrdinalIgnoreCase))
        {
            return source.AsQueryable<T>().OrderByDescending<T, object>(sortLambda);
        }
        return source.AsQueryable<T>().OrderBy<T, object>(sortLambda);
    }
    catch (ArgumentException)
    {
        return source;
    }
}

Just drop it in a static class, and you will be able to sort any collection that implement the interface IEnumerable.

Lets say you have a class 'User':

public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
}

and a List<User> collection: users. You can sort it however you want:

IEnumerable<User> sortedUsersIEnumerable = users.Sort<User>("ID desc"); 

Or

List<User> sortedUsersList = users.Sort<User>("Name").ToList();

I really think this extension should be 'built-in' part of the 'Linq'. 

Extensions.cs (1.08 kb)

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | C# | Server side
Posted by Miron on Wednesday, May 07, 2008 1:59 PM
Permalink | Comments (1) | Post RSSRSS comment feed

Some basic but useful C# methods implemented in JavaScript

While I was working on a full Ajax interface based on pure ajax calls and not on any framework,  I implemented some basic and simple methods that we use all the time in the server code, and just not exist in javascript. It's not big deal, but since I wrote them, I use them all the time.

Even it is very simple, I decided to share it here. Hope it will same other coders few minutes:

Cache object:

//
//  Cach object implementation
//
var Cache = new function()
{
    var _cache = new Array();
    this.Insert = function ( key, value ){
        _cache[key] = value;
        }
    this.Get = function ( key ){
        return _cache[key];
        }
    this.Contains = function ( key ){
        if( !_cache[key] || _cache[key] == null ) return false;
        else return true;
        }
};

The use of the cache is as simple as it is in the server side, and it is realy useful to save some calls to the server:

 Cache.Insert("myKey","MyValue");
 if( Cache.Contains("myKey") ) alert( "Yeee the value of my key is in the cache:" + Cache.Get("myKey") );  

String.Format(string,params)  &  String.IsNullOrEmpty(string) :

//
// String.Format implementation
//
String.Format = function(format,args){
    var result = format;
    for(var i = 1 ; i < arguments.length ; i++) {
        result = result.replace(new RegExp( '\\{' + (i-1) + '\\}', 'g' ),arguments[i]);
    }
    return result;
}

//
// String.IsNullOrEmpty implementation
//
String.IsNullOrEmpty = function(value){
    if(value){
        if( typeof( value ) == 'string' ){
             if( value.length > 0 )
                return false;
        }
       if( value != null )
           return false;
    }
    return true;
}

Again, the use is the same as server side:

alert( String.Format("Hello {0}. Yes, hello {0} again. My name is {1}","world","Miron") );
if( String.IsNullOrEmpty('') ) alert('Empty string');

StartsWith(string suffix,bool ignoreCase)EndsWith(string suffix,bool ignoreCase)  and Trim() :

//
// string.StartWith implementation
//
String.prototype.StartsWith = function(prefix,ignoreCase) {
    if( !prefix ) return false;
    if( prefix.length > this.length ) return false;
    if( ignoreCase ) {
        if( ignoreCase == true ) {
            return (this.substr(0, prefix.length).toUpperCase() == prefix.toUpperCase());
        }
    }
    return (this.substr(0, prefix.length) === prefix);
}

 //
// string.EndsWith implementation
//
String.prototype.EndsWith = function(suffix,ignoreCase) {
    if( !suffix ) return false;
    if( suffix.length > this.length ) return false;
    if( ignoreCase ) {
        if( ignoreCase == true ) {
            return (this.substr(this.length - suffix.length).toUpperCase() == suffix.toUpperCase());
        }
    }
    return (this.substr(this.length - suffix.length) === suffix);
}

 //
// string.Trim implementation
//
String.prototype.Trim = function() {
    return this.replace(/^\s+|\s+$/g, '');
}

The last three are working on an istance of a string:

var test = "Hello Words ";
test = test.Trim();
var end = test.EndsWith("ds",true);
var begin = test.BeginsWith("rr",false);

All the code can be downloaded here: 

Utils.js (1.94 kb)

Currently rated 4.7 by 3 people

  • Currently 4.666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: Client side
Posted by Miron on Sunday, May 04, 2008 4:43 PM
Permalink | Comments (0) | Post RSSRSS comment feed

What's going on...

It's around 3 weeks I haven't write anything in my blog. Actually I have some nice things in my mind I want to post about, but I really run out of time.

I am going to leave my current job, and going to work in a new company CapitalIQ. I'm really looking forward for this change. CapitalIQ is a really big company, and I'm going to face new challenges.

Now, the last project I'm working on is a really nice and useful Anti-Spam filter based on Paul Graham's Naive Bayesian Spam Filter algorithm. I Implemented the improved algorithm and added some nice features such 'white/black' lists 'phishing/scam' detecting and made it 'self learning' system that you can actually train it, and it will be improved every time you train it. The results are very very impressive. after some days of learning, from 1014 spams emails and 50 real emails in a weekend, the system detected 1008 spams as spam, and all the real emails as real. I think it could be very useful system, and I'm thinking of uploading it into Codeplex as an open source project.

I have some more nice things to post about, but I have too much things to do and think about, so I will wait for more 'relax time' to post it. I hope all the new changes will be ok and without any problems, and I hope I will have the time to post new and nice things soon.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by Miron on Sunday, April 20, 2008 9:34 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Uniform Distribution algorithm in C#

Background
Where I work, one of the serviced we give to our clients is Medical articles.With the time, we have tones of articles. Millions. All PDF files, and all are in the same folder.

The Problem
It became impossible to open the folder with Windows explorer, trying to search, copy or move files. I had to find a way to reorder it, and divide it into 1000 folders that every folder will have around the same number of files, and when a request for a specific file will come, I will be able to know in witch folder it is.

The Solution 
I contacted my brother Ari (his site is outdated) for help. He have PhD from the "Electrical Engineering department" at the Technion institute.  He is the smartest guy I have known. So, that is his Algorithm, I just implemented it in C#. It's not too complicate, but it does the job perfect.

The algorithm gets a string and maps it into the set 0-999 (or any given range) with uniform distribution.

/// <summary>
/// Get the position in the range of the specified string
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public int GetPosition(string input)
{
    if (string.IsNullOrEmpty(input))
    {
        return 0;
    }

    input = hasher.ComputeHash(input).Replace("-",string.Empty);
    double Sum = 0.0;
    int Aj;
    double Tj;
    double Pj;

    for (int j = 0; j < input.Length; j++)
    {
        Aj = (int)input[j];
        Tj = (Math.PI * (1 + j%5) / 2);
        Pj = Math.Pow(Aj,Tj);
        Sum += Math.Round((Math.Ceiling(Pj) - Pj) * Range) * Tj;
    }
    return ((int)(Sum % Range)) + LowerValue;
}

To use it, just create an object from type Mapper, and call the 'GetPosition' method for any needed string:

Mapper aMap = new Mapper(RANGE);
int position = aMap .GetPosition(input)); 

The Results 

I run the algorithm few times with random strings 10 chars long and map them into some ranges of 'folders'. Here are the results:

1,000,000 random strings into range of 1000: the folder with the maximum # of files contained 1156 files, and the minimum  contained 886 files.
1,000,000 random strings into range of 100: the folder with the maximum # of files contained 10,699 files, and the minimum  contained 8800 files.
1,000,000 random strings into range of 10: the folder with the maximum # of files contained 106,873 files, and the minimum  contained 98,747 files.

Very nice results. Maybe not perfect, but certainly good enough for this kind of purpose!

Special thanks to my brother Ari for his help.

Source code:

AriMap.zip (2.34 kb)

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: C#
Posted by Miron on Wednesday, April 02, 2008 8:58 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Extensions can make us free from testing against null

The concept of the Extensions in the last frameworks is great. They are extremely easy to write and use, give us infinit options to add needed methods we just wished to have in the framework,  and the thing that most catched my eyes is the fact that the extensions are "attached" to an instance of an object, but can be excute with a 'null' object. It is kind of weird.

Excuting the following code will thow an exception (of course):

string s;
s = null;
s = s.Replace("bla", "alb");

But, if we add the following extension:

public static string EReplace(this string s, string oldValue, string newValue,
    bool ignoreCase)
{
    if (s == null)
        return s;

    if (ignoreCase)
    {
         return Regex.Replace(s, oldValue, newValue, RegexOptions.IgnoreCase);
    }
    return s.Replace(oldValue, newValue);

 and try to excute the code:

string s;
s = null;
s = s.EReplace("bla", "alb",true);

the code will run without any exception.

Conclusion:

Using extensions and validate input, will make us free from testing against null any object before excuting a method, and this way, avoid errors that can be throw exceptions on runtime environment.

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: C#
Posted by Miron on Saturday, March 22, 2008 5:32 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Really quick "Downloads" page

I guess it's the same for all of us. We are the 'computers guy' of the family. When anyone of the close/far family/friends have a problem with the  computer, or need any software - the first call he do, is for us. The 'computers guy'. The technical part is a little problematic, but the 'Do you have an Anti-Virus' for me? 'With what application I can open RAR files?' is the easy part.

Since I have an hosting account, I just created a folder, droped in it this Default.aspx (1.33 kb) I did, droped in it all the (legal) software I have, and when somebody ask me for an application, I just give him the link for that folder. From time to time, when I have a new app, I just upload it to this folder. No need to change any code. The Default.aspx will list it by itself.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:
Categories: ASP.NET | General
Posted by Miron on Tuesday, March 18, 2008 1:46 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Another version for the missing method: Enum.TryParse (in C#)

The 'TryParse' methods for all types are very useful and I'm using them all the time. It's very surprising that Microsoft didn't include in the Framework a method that can be very useful: Enum.TryParse. A lot of coders find themself writing from time to time  'parsing' methods for their enums. Something like that:

public enum ImageType
{
    Jpg,
    Gif,
    Png
}

public static ImageType ParseImagetype(string typeName)
{
    typeName = typeName.ToLower();
    switch (typeName)
    {
        case "Gif":
            return ImageType.Gif;
        case "png":
            return ImageType.Png;
        default:
        case "jpg":
            return ImageType.Jpg;
    }
}...

 Thats work fine, but you need to write such 'parsing' method for each enum you have.  The Enum class have it's own 'parsing' method (that luckly have 'IgnoreCase' flag), but not a TryParse method. The commonly fix around is to put the Enum.Parse method inside Try & Catch, what is, of cource, give bad performance in case of failure. The Enum class have also a method 'IsDefined' that return an indication if a value is exists in the enum. unfortunately, this method doesn't have an  'IgnoreCase' flag.

So, trying to put all this 'knowledge' together, I wrote my own generic version for 'Enum.TryParse' method that is also ignore case and not using try & catch:

public static bool EnumTryParse<T>(string strType,out T result)
{
    string strTypeFixed = strType.Replace(' ', '_');
    if (Enum.IsDefined(typeof(T), strTypeFixed))
    {
        result = (T)Enum.Parse(typeof(T), strTypeFixed, true);
        return true;
    }
    else
    {
        foreach (string value in Enum.GetNames(typeof(T)))
        {
            if (value.Equals(strTypeFixed, StringComparison.OrdinalIgnoreCase))
            {
                result = (T)Enum.Parse(typeof(T), value);
                return true;
            }
        }
        result = default(T);
        return false;
    }
}

The line 'string strTypeFixed = strType.Replace(' ', '_');' is because I was getting the data from third party WebService that send the enum strings with spaces, what is not allowed in enum, so my enums had '_' instead of  spaces.

To parse a ImageType (from the example above) just use it like this:

ImageType type;
if (Utils.EnumTryParse<ImageType>(typeName, out type))
{
    return type;
}
return ImageType.Jpg;

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:
Categories: C#
Posted by Miron on Saturday, March 08, 2008 8:54 AM
Permalink | Comments (8) | Post RSSRSS comment feed