Posts with tag: C#

If you have used Random() in C# to any extent before you probably know it isen't really that random, and can produce quite predictable results. Especially when it's being used in threads or parallel loops. I had such a scenario recently where I needed to have a better random number generator that even when used on different threads or from different instances still produced random numbers.

So how do you get a better random number than what Random() can provide?
Easy answer is you use the RNGCryptoServiceProvider. The purpose of this class is to create cryptographically strong random data. Perfect for the task at hand.

Here is the code I created for generating random integers between a given interval. I'm using the RNGCryptoServiceProvider to produce 4 bytes of random data that I then convert to an integer. The produced integer is somewhere between -int.Max and int.Max. So I reduce the size of the number by the proportion of the size of the range wanted as compared to the full range of an Int32 and then take the absolute value of that.

public int GetRealRandom(int min = 0, int max = int.MaxValue)
{
    if (max <= min)
        throw new ArgumentOutOfRangeException("max paramater must be greater than min paramater");

    int result = 0;
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        byte[] data = new byte[4];
        rng.GetBytes(data);
        int value = BitConverter.ToInt32(data, 0);
        result = value;

        var proportion = (max - min + 0d) / int.MaxValue;
        result = Math.Abs((int) Math.Round((result*proportion)));
        result += min;
    }
    return result;
}

And to validate it you can use this Unit Test to check the result. Note that this test might fail if you dont do enough iterations to produce a big enough data set to test it on.

[TestMethod]
public void GenerateRealRandom()
{
    var min = 0;
    var max = 1000;

    var pn = new List<int>();
    foreach (var r in Enumerable.Range(0, 100000))
    {
        var number = GetRealRandom(min, max);
        pn.Add(number);
        Assert.IsTrue(number <= max && number >= min);
    }

    var average = pn.Average();
    var minVal = pn.Min();
    var maxVal = pn.Max();
    var middle = (max - min) / 2;

    Assert.IsTrue(average < middle + 1 && average > middle - 1);
    Assert.IsTrue(minVal <= min + 1);
    Assert.IsTrue(maxVal >= max - 1);
}


While creating a unit tests I tried to find code to generate Swedish personnumbers but couldent really find any copy paste pieces of code. So to remedy that here is a method which will generate a valid Swedish "Personnummer"

I tried to get it down to a single line but this is as close as I got before realising I have better things to do. If someone can make this into a single line of code I'd love to see that in a comment ;)

public string GeneratePersonNumber()
{
    // Create first numbers
    var pNum = (
                        (DateTime.UtcNow.AddYears(-10 - (new Random()).Next(0, 50))).ToString("yyyyMMdd")
                        + (new Random()).Next(100, 999).ToString()
                    );

    // get the control value sum
    var total = string.Join(string.Empty,
                        pNum.Substring(2, pNum.Length - 2)
                            .Select((value, i) =>
                                ((int)(value - '0')) * (2 - (i % 2)))
                            ).Sum(c => c - '0');

    // add the control value to the number
    return (pNum += (Math.Ceiling(total / 10d) *10) - total);
}

 

Update: Ok so I coulden't let it go and spent a few more minutes trying golf the code down in size. This is the shortest I managed, it's 189 characters long. Not very readable but it does generate a valid personnummer of a person between the ages of 10 and 60. Still coulden't manage to make it in one line of code though.

public string GenereatePersonNumber()
{
    var p = DateTime.Now.AddDays(-3650-new Random().Next(18250)).ToString("yyyyMMddfff");
    return p + (10 - string.Join("", p.Select((v, i) => i > 1 ? (v - '0') * (2 - i % 2):0)).Sum(c => c - '0') % 10);
}

Here are 3 sample personnummer/social security numbers generated by the above code: 

  1. 199307242652
  2. 197512275202
  3. 197611112835

 

Update 2: As per the request in the comment here is a version of the code where you can set the age of the person whom you are want the personnumber to be generated for. Also included the unit test I used as this code is a little trickier.

public string GenereatePersonNumber(int age)
{
    var r = new Random(DateTime.Now.Millisecond);
    var p = (new DateTime(DateTime.Now.AddYears(-age).Year, 1, 1, 0, 0, 1, DateTime.Now.Millisecond, DateTimeKind.Local))
                   .AddDays(1 + r.Next(0, (int) (DateTime.Now - new DateTime(DateTime.Now.Year, 1, 1)).TotalDays)).ToString("yyyyMMddfff");
    var ctr = (10 - string.Join("", p.Select((v, i) => i > 1 ? (v - 48) * (2 - i % 2) : 0)).Sum(c => c - 48) % 10);
    ctr = ctr == 10 ? 0 : ctr;
    return (p + ctr);
}

Also including the unit test for this code if you want to validate that it actully produces propper values.

[TestMethod]
public void GeneratePersonNummerTest()
{
    var pn = new List<string>();
    Parallel.ForEach(Enumerable.Range(0, 1000), x =>
    {
        foreach (var r in Enumerable.Range(0, 80))
        {
            var number = GenereatePersonNumber(r);
   
            var birthdate = new DateTime(int.Parse(number.Substring(0, 4)), int.Parse(number.Substring(4, 2)), int.Parse(number.Substring(6, 2)));
            var today = DateTime.Now;
            var age = today.Year - birthdate.Year;
            if (birthdate > today.AddYears(-age)) age--;
   
            pn.Add(number);

            Assert.IsTrue(age == r);
        }
    });
}

Some tags (in Swedish) which might help people searching for this: Generera personnummer med C# kod. Personnummer, Generera, Svenskt personnummer, Skapa personnummer för test, fejka personnummer. Generera personnummer i C#.


Format Currency in C#

2017-01-04 17:00 

Two methods for formatting currency in C#. 

The purpose being that you can throw a string containging a number in it and get out a nicely formatted string containing something you can present to a user.

Example of output.

var amountParsed = FormatCurrency("3123", "USD", true, "Not Known");
> 3 123 USD

var amountParsed = FormatCurrency("31.3", "USD", true, "Not Known");
> 31.30 USD

var amountParsed = FormatCurrency("12343", "USD", false, "Not Known");
> 12 343 USD

var amountParsed = FormatCurrency("XXX", "USD", false, "Not Known");
> Not Known

 

Code:

private static string FormatCurrency(string decimalStringValue, string currency, bool includeDecimals, string emptyPlaceHolder)
{
    if (string.IsNullOrEmpty(decimalStringValue) == false)
    {
        decimal decResult = 0;
        try
        {
            if (decimal.TryParse(decimalStringValue.Trim(), out decResult))
            {
                string formatted = FormatCurrency(decResult, currency, includeDecimals);
                return formatted;
            }
        }
        catch (Exception) {}
    }

    return emptyPlaceHolder;

private static string FormatCurrency(decimal decimalValue, string currency, bool includeDecimals)
{
    if (string.IsNullOrEmpty(currency))
        currency = "SEK";
 
    var decimalTemp = Math.Round(decimalValue, 2);
    var nfi = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
    nfi.NumberGroupSeparator = " ";
    string formatted = decimalTemp.ToString("#,0.00", nfi);
    if (includeDecimals == false)
    {
        if (formatted.LastIndexOf('.') >= 0)
            formatted = formatted.Substring(0, formatted.LastIndexOf('.'));
    }
 
    formatted = formatted.Trim() + " " + currency;
    return formatted.Trim();
}


I think most people whom use the generated service context for Microsoft CRM quickly note that it does not use the full fledged entity framework and as such does not support all queries. One of these limitations that I usually hit on is that I cant use contains in a Lambda query.

Ie. This does not work and fails with this error: Invalid 'where' condition. An entity member is invoking an invalid property or method.

private List<Xrm.Account> GetAccountsFromOrgNoList(string[] orgNos)
{
    return serviceContext.Xrm.AccountSet
        .Where(x => orgNos.Contains(x.lgc_Organizationnumber))  // <-- contains is invalid
        .Select( x=> x)
        .ToList();
}

The work around for this is to use Query Expressions. Because they can do this, although not as nicely. The below code does the same as the above code with the added exception of actually working.

private List<Xrm.Account> GetAccountsFromOrgNoList(string[] orgNos)
{
    var qe = new QueryExpression(Xrm.Account.EntityLogicalName);
    qe.ColumnSet = new ColumnSet("accountid", "name", "cst_organizationnumber"); // can use new ColumnSet(true); if you just want all columns instead of specifying specif ones
    qe.Criteria.AddCondition("cst_organizationnumber", ConditionOperator.In, orgNos.Cast<Object>().ToArray());
    qe.Distinct = true;

    var results = serviceContext.Instance.RetrieveMultiple(qe).Entities
        .Select(x => x.ToEntity<Xrm.Account>())
        .ToList();

    return results;
}

This is a piece of code I tend to need every few months but always need a few minutes to find. So I'm just going to put it on here for mine and others future reference.

The code returns a KeyValuePair list of all options in a CRM optionset.

public static List<KeyValuePair<string, int>> GetOptionSets(string entityName, string attributeName, IOrganizationService service)
{
    string AttributeName = attributeName;
    string EntityLogicalName = entityName;
    RetrieveEntityRequest retrieveDetails = new RetrieveEntityRequest
    {
        EntityFilters = EntityFilters.All,
        LogicalName = EntityLogicalName
    };
    RetrieveEntityResponse retrieveEntityResponseObj = (RetrieveEntityResponse)service.Execute(retrieveDetails);
    Microsoft.Xrm.Sdk.Metadata.EntityMetadata metadata = retrieveEntityResponseObj.EntityMetadata;
    Microsoft.Xrm.Sdk.Metadata.PicklistAttributeMetadata picklistMetadata = metadata.Attributes.FirstOrDefault(attribute => String.Equals(attribute.LogicalName, attributeName, StringComparison.OrdinalIgnoreCase)) as Microsoft.Xrm.Sdk.Metadata.PicklistAttributeMetadata;
    Microsoft.Xrm.Sdk.Metadata.OptionSetMetadata options = picklistMetadata.OptionSet;
    var ret = options.Options
        .Where(x => x.Label != null && x.Label.UserLocalizedLabel != null && x.Label.UserLocalizedLabel.Label != null && x.Value.HasValue)
        .Select(x => new KeyValuePair<string, int>(x.Label.UserLocalizedLabel.Label, x.Value.Value))
        .ToList();

    return ret;
}

To use it call it as such:

GetOptionSets(Account.EntityLogicalName, "cst_sttributeWithOptionset", serviceContext.Instance);