ILearnable .Net

April 16, 2018

Custom parsing of config in .net core

Filed under: Uncategorized — andreakn @ 11:23

On my current project we have quite a lot of stuff happening at configurable intervals. Some time ago someone thought it would be better to configure these intervals using human readable and very flexible values, so for instance the following values are all valid and parseable: “1.Hour”, “500.Milliseconds”, “30.Days”. The parsing is done through TimeSpanParser:


    public static class TimeSpanParser
    {
        private static readonly IEnumerable<Parser> Parsers = new[]
        {
            new Parser
            {
                Pattern = @"^\d+\.[mM]illiseconds?$",
                Parse = @int => TimeSpan.FromMilliseconds(@int)
            },
            new Parser
            {
                Pattern = @"^\d+\.[sS]econds?$",
                Parse = @int => TimeSpan.FromSeconds(@int)
            },
            new Parser
            {
                Pattern = @"^\d+\.[mM]inutes?$",
                Parse = @int => TimeSpan.FromMinutes(@int)
            },
            new Parser
            {
                Pattern = @"^\d+\.[hH]ours?$",
                Parse = @int => TimeSpan.FromHours(@int)
            },
            new Parser
            {
                Pattern = @"^\d+\.[dD]ays?$",
                Parse = @int => TimeSpan.FromDays(@int)
            },
            new Parser
            {
                Pattern = @"^\d+\.[wW]eeks?$",
                Parse = @int => TimeSpan.FromDays(@int*7)
            }
        };

        public static TimeSpan ToTimeSpan(this string timeSpan)
        {
            var parser = Parsers.FirstOrDefault(p => Regex.IsMatch(timeSpan, p.Pattern));

            if (parser != default(Parser))
            {
                return parser.Parse(timeSpan.IntPart());
            }

            TimeSpan parsed;
            if (TimeSpan.TryParse(timeSpan, CultureInfo.InvariantCulture, out parsed))
            {
                return parsed;
            }

            throw new NotSupportedException($@"The timespan string '{timeSpan}' is not a supported format. 
Supported formats are default TimeSpan formats and the following custom formats: 
{Parsers.Select(p => p.Pattern).Aggregate((first, second) => $"{first}, {second}")}");
        }

        public static TimeSpan? TryParseToTimeSpan(this string timeSpan)
        {
            try
            {
                return timeSpan.ToTimeSpan();
            }
            catch (NotSupportedException)
            {
                return null;
            }
        }

        public static TimeSpan TryParseToTimeSpan(this string timeSpan, TimeSpan defaultValue)
        {
            try
            {
                return timeSpan.ToTimeSpan();
            }
            catch (NotSupportedException)
            {
                return defaultValue;
            }
        }

        private static int IntPart(this string timespan)
        {
            return int.Parse(timespan.Split('.').First());
        }

        private class Parser
        {
            public string Pattern { get; set; }
            public Func<int, TimeSpan> Parse { get; set; }
        }
    }
  


 

We even have extension methods on int to give a shorthand for specifying default values like this: 30.Minutes()

var someDefault = 30.Minutes();
var someOtherDefault = 24.Hours();
 

Here is an example extension method:

 public static class TimespanExtensions
    {
        public static TimeSpan Days(this int days)
        {
            return TimeSpan.FromDays(days);
        }
        ...
    }
 

While upgrading the solution from .nat 4.6.1 to .net core 2 / .net standard 2 and rejigging the entire configuration setup from relying on ConfigurationManager / AppSettings to use the new Microsoft.Extensions.Configuration I wanted to be able to keep the human-readable Timespan specifications in our config files but have them automagically being read into our config POCOs as TimeSpan objects. After some digging and googling I found this approach which I have never even heard about before, live and learn! It all boils down to being able to decorate the default TypeConverter for TimeSpans (System.ComponentModel.TimeSpanConverter) with my own implementation. Here’s how you do it:

First make the decorator:

    public class TimeSpanConverterCustom : TimeSpanConverter
    {
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string strValue)
            {
                var ret = strValue.TryParseToTimeSpan();
                if (ret.HasValue) return ret.Value;
            }
            return base.ConvertFrom(context, culture, value);
        }

    }

 

Then make some code to replace a default TypeConverter with a custom one

    public static class CustomTypeConverters
    {
        public static TypeDescriptionProvider Register<T, TC>() where TC : TypeConverter
        {
            Attribute[] attr = new Attribute[1];
            TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(TC));
            attr[0] = vConv;
            return TypeDescriptor.AddAttributes(typeof(T), attr);
        }
    }

 

Finally register the the custom decorator on application bootstrapping:

  CustomTypeConverters.Register<TimeSpan, TimeSpanConverterCustom>(); //be able to read "30.Days" as TimeSpan

 

Change the configuration POCOs to expect a TimeSpan input

public class ApplicationConfig
{
    public TimeSpan SomeInterval { get; set; } = 24.Hours();
    ...
}
 

and specify the config in some appsettings.root.json

{
    "SomeInterval":"36.Hours",
    ...
}
 

April 2, 2014

Display control hierarchy in winforms application

Filed under: Uncategorized — andreakn @ 13:08

I recently changed projects and suddenly found myself in a MASSIVE winforms application… Now normally I have no problem navigating massive codebases, after all all you have to do is to right click and select “inspect element” in chrome to see which part of the code created this part of the GUI, right? not so in winforms…

after spending a bit of time searching for strings found nearby what I wanted to track down in the codebase I hit me: “there’s got to be a better way!!”

Luckily for me all GUI controls in the solution inherits from a common baseclass, so I was able to tweak that baseclass so that whenever you press the F12 key you enter “inspection mode” which makes any control clicked display a messagebox with its control hierarchy path all the way up to the form.
the result looks somewhat like this: Control hieararchy for clicked control

 public class TheBaseControl : SomeOtherControlBase
   {
      private bool currentlyInInspectMode;
      protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
      {
         ActivateOrDeactivateMouseHack(keyData);
         return base.ProcessCmdKey(ref msg, keyData);
      }

      private void ActivateOrDeactivateMouseHack(Keys keyData)
      {
         if (keyData.HasFlag(Keys.F12) )
         {
            if (!currentlyInInspectMode)
            {
               SetInspectModeRecursively(this, true, true);
               currentlyInInspectMode = true;
            }
         }
         else
         {
            if (currentlyInInspectMode)
            {
               SetInspectModeRecursively(this, false, true);
               currentlyInInspectMode = false;
            }
         }
      }

      private void SetInspectModeRecursively(Control ctrl, bool active, bool initialEventSource)
      {
         if (ctrl is TheBaseControl &&!initialEventSource) return;
         if (ctrl == null) return;
         if (active)
            ctrl.MouseDown += DisplayControlInfo;
         else
            ctrl.MouseDown -= DisplayControlInfo;

         foreach (var control in ctrl.Controls)
         {
            SetInspectModeRecursively(control as Control, active, false);
         }
      }

      private void DisplayControlInfo(object sender, MouseEventArgs e)
      {
         var clickedControl = FindControlAtCursor( this.ParentForm);
         var controlpath = "";
         var tip = clickedControl;
         while (tip != null)
         {
            controlpath = string.Format("[{0}:id={1}]{2}{3}", tip.GetType().FullName, tip.Name, Environment.NewLine,
               controlpath);
            tip = tip.Parent;
         }
         MessageBox.Show("You just clicked on " + Environment.NewLine+controlpath);
      }
      
      public static Control FindControlAtPoint(Control container, Point pos)
      {
         Control child;
         foreach (Control c in container.Controls)
         {
            if (c.Visible && c.Bounds.Contains(pos))
            {
               child = FindControlAtPoint(c, new Point(pos.X - c.Left, pos.Y - c.Top));
               if (child == null) return c;
               else return child;
            }
         }
         return null;
      }

      public static Control FindControlAtCursor(Form form)
      {
         Point pos = Cursor.Position;
         if (form.Bounds.Contains(pos))
            return FindControlAtPoint(form, form.PointToClient(Cursor.Position));
         return null;
      }
   }

February 20, 2014

Working with Enums

Filed under: Uncategorized — andreakn @ 15:14

Enums are like the small tokens you can use instead of real coins in shopping carts, lockers etc. They really have a very limited use, can always be replaced by something more valuable which also gets the job done, but sometimes it’s nice to have them. even if they are the bastard child of int and object.

when converting to/from different enum types and values / strings as one typically has to do when down the enum-road here’s a helper class which sweetens the taste a bit

 public class EnumMeta<TEnum>
    {
        public EnumMeta()
        {
            var type = typeof (TEnum);
            if (!type.IsEnum)
            {
                throw new ArgumentException(string.Format("The type {0} is not an Enum, which is sort of a big deal around here.", type));
            }
        }

        public TEnum Convert(object stupidValue)
        {
            try
            {
                return DoConvert((dynamic)stupidValue);
            }
            catch (Exception)
            {
                throw new ArgumentException(string.Format("One does not simply convert value '{0}' ({1}) to type {2}", stupidValue, stupidValue.GetType(), typeof(TEnum)));
            }
        }

        private static TEnum DoConvert(byte value)
        {
            return (TEnum) Enum.ToObject(typeof (TEnum), value);
        }

        private static TEnum DoConvert(int value)
        {
            return (TEnum) Enum.ToObject(typeof(TEnum), value);
        }

        private static TEnum DoConvert(string value)
        {
            return (TEnum) Enum.Parse(typeof(TEnum), value);
        }

        private static TEnum DoConvert(object value)
        {
            return DoConvert((int)value);
        }

        public IEnumerable<TEnum> GetValues()
        {
            return Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
        }
    }

making C# compare two strings the same way SQL server will

Filed under: Uncategorized — andreakn @ 15:08

This snippet has served us well

public static bool LooksLike(this string value, string other)
{
  if (value == null)
  {
    return other == null;
  }
  return other != null &&
     value.Trim().Equals(other.Trim(), StringComparison.InvariantCultureIgnoreCase);
}

Truncate a string without truncating/cutting words (if possible)

Filed under: Uncategorized — andreakn @ 15:04

heres a small snippets we’ve used to try and cut a string at a certain position, but accepting up to N extra letters if that means that we will not truncate in the middle of a word

   public static string TruncateAtWord(this string input, int length, int extraLength = 5)
        {
            var absoluteMaxLength = length + extraLength;
            if (input == null || input.Length < absoluteMaxLength)
            {
                return input;
            }
            var nextSpace = input.LastIndexOf(" ", absoluteMaxLength, StringComparison.Ordinal);
            return string.Format("{0}...", input.Substring(0, (nextSpace > 0) ? nextSpace : length).Trim());
        }

set up a new dev machine IIS sites through script

Filed under: Uncategorized — andreakn @ 14:37

here’s a powershellscript you can use to script IIS web sites (we update it whenever we add new web sites to the project so everyone is running their sites on the same ports and visual studio doesn’t complain)

param( [string]$basePath)

#Imports a module if it isn't already imported
function Using-Module
{
 param([string]$name)
 if(-not(Get-Module -name $name))
 {
 if(Get-Module -ListAvailable | Where-Object { $_.name -eq $name })
 {
 Import-Module -Name $name
 }
 }
}

#Creates a website. Overwrites if website exists.
function Create-WebSite
{
 param( [string]$name,
 [string]$port,
 [string]$physicalPath)

 Write-Host "Creating web site $name on $port with physical path $physicalPath"

 $existingWebSite = Get-Website | Where-object { $_.name -eq $name }
 if($existingWebSite)
 {
 $existingAppPool = $existingWebSite.applicationPool
 Remove-Website $name
 Remove-WebAppPool $existingAppPool
 }

 $appPoolName = $name + "_AppPool"

 New-WebAppPool $appPoolName
 #New-WebSite -Name $name -Port $port -HostHeader $name -PhysicalPath $physicalPath -ApplicationPool $appPoolName
 New-WebSite -Name $name -Port $port -PhysicalPath $physicalPath -ApplicationPool $appPoolName
}

#Modify app pool property
function Modify-AppPoolProperty
{
 param( [string]$name,
 [string]$propertyName,
 [string]$propertyValue)

 Set-ItemProperty "IIS:\AppPools\$name" -name $propertyName -value $propertyValue
}

Write-Host "Creating IIS web sites and application pools for alle web and service projects. Existing web sites and application pools will be replaced"
Write-Host "Specify base path needs to be specified as file argument."
if(!$basePath)
{
 Write-Host "Base path not specified. Syntax: CreateWebSites -basePath c:\root\of\sourcecode"
 Write-Host "where c:\root\of\sourcecode is where you checkout the code to"
 exit
}

Using-Module -name WebAdministration
Create-WebSite -name myWebSite -port 8000 -physicalPath "$basePath\src\SomeAspect\SomeSolution\SomeIisWebProject\"
Modify-AppPoolProperty -name myWebSite_AppPool -propertyName "managedRuntimeVersion" -propertyValue "v4.0"

....lather, rinse, repeat

Write-Host "Creating web sites completed"

Easily check process ID of IIS app pool

Filed under: Uncategorized — andreakn @ 14:15

Here’s a trick I learned from http://martinnormark.com/iisapp-equivalent-in-iis-7-and-beyond/

create a .bat file called iisapp.bat containing the following:

%SystemRoot%\System32\inetsrv\appcmd list wp

and put it in c:\windows\System32 on your dev/test/prod machines then you will always be able to list the IIS worker processes by just typing iisapp whereever

It’s been a year (almost) what’s up?

Filed under: Uncategorized — andreakn @ 13:45

Last blogpost is from april 2013, what have I been doing in the meantime? 

Well I’ve mostly been busy working on the same relatively huge and ugly codebase gradually prettifying it and in the process letting my customer downscale the project team from about 10 devs to 3 while still maintaining rougly the same speed through gradually changing the architecture to be more testable, isolateable and generic.

My time on the project is soon up, I might have accidentally helped my client to achieve a codebase so efficient they can now manage it without me, but that’s the way I like it 🙂

On the way I have learned a great deal, I hope I can document some of it here before I move on to bigger/better/smaller/worse things

April 22, 2013

Sql Server In Recovery

Filed under: Uncategorized — andreakn @ 10:04

So, you just did something potentially stupid.

You became impatient while Sql Server was churning away at something and took forever to do a simple small little thing which should take no time at all. 

Maybe you reset the sql server service, maybe you reset the entire machine trying to free up the hangup, 

And you reward was the dreaded “(In Recovery)” suffix to your databases name.

….. nothing works, you can’t even query the DB… And suddenly teammates or customers ask you … HOW LONG UNTIL IT IS FIXED?!!

 

Instead of answering “I don’t know” or “everything comes to he who waits” you could try to  execute this little query

—————-

DECLARE @DBName VARCHAR(64) = ‘your_database_name_here’
DECLARE @ErrorLog AS TABLE([LogDate] CHAR(24), [ProcessInfo] VARCHAR(64), [TEXT] VARCHAR(MAX))

INSERT INTO @ErrorLog
EXEC master..sp_readerrorlog 0, 1, ‘Recovery of database’, @DBName

SELECT TOP 5
[LogDate]
,SUBSTRING([TEXT], CHARINDEX(‘) is ‘, [TEXT]) + 4,CHARINDEX(‘ complete (‘, [TEXT]) – CHARINDEX(‘) is ‘, [TEXT]) – 4) AS PercentComplete
,CAST(SUBSTRING([TEXT], CHARINDEX(‘approximately’, [TEXT]) + 13,CHARINDEX(‘ seconds remain’, [TEXT]) – CHARINDEX(‘approximately’, [TEXT]) – 13) AS FLOAT)/60.0 AS MinutesRemaining
,CAST(SUBSTRING([TEXT], CHARINDEX(‘approximately’, [TEXT]) + 13,CHARINDEX(‘ seconds remain’, [TEXT]) – CHARINDEX(‘approximately’, [TEXT]) – 13) AS FLOAT)/60.0/60.0 AS HoursRemaining
,[TEXT]

FROM @ErrorLog ORDER BY [LogDate] DESC

——————

which should give you an inkling on the time remaining. (though to be quite honest it is not very accurate, the last percent seems to take a long time to resolve)

(thanks http://timlaqua.com/2009/09/determining-how-long-a-database-will-be-in-recovery-sql-server-2008/ for showing me the procedure! )

August 23, 2012

Åpent brev til de som vedlikeholder nettbank.handelsbanken.no

Filed under: Uncategorized — andreakn @ 11:41

Jeg er særdeles lite imponert over passordkravene til Handelsbankens nettbank.

Ved å hindre meg i å bruke “spesialtegn” så legger dere opp til at entropien i passordet blir langt mindre enn den kunne vært.

Jeg håper for guds skyld at dere salter og hasher passordene før dere lagrer dem. I så fall så skulle det jo ikke spille noen rolle hvilke eller hvor mange tegn jeg skriver
inn.

Den dagen dere blir hacket og passordbasen kommer på avveie, enten gjennom utro tjenere, spear fishing, en zero-day eller noe så gammelmodig og velprøvd som
sqlinjection så vil passordreglene deres gjøre brute-forcing av passordene MYE enklere for slemmingene.

Jada, jada, jada, jeg *vet* at dere har “strenge” sikkerhetsrutiner begrenser adgang til databasene og at
dere jevnlig har sikkerhets-auditing på alle systemer og at dere tar “brukernes sikkerhet på alvor”,

Tror dere kanskje ikke at LinkedIn, Dropbox, EHarmony og de andre sitene som ble hacket bare i år *ikke* tok “brukernes sikkerhet på alvor”?

Hvis du går inn på https://www.grc.com/haystack.htm så kan du se at hva forskjellen på å tillate spesialtegn gjør ift
hvor lett det er å brute-force passord hvis man sitter på både saltet og hashen, for dere lagrer vel gjerne disse to rett ved siden av hverandre i samme databaserad også?

Et eksempel på et passord som i utgangspunktet ser veldig sikkert ut er QrY8Tv9w1 en rask sjekk på tidligere nevnte side avslører at kompleksiteten på å knekke dette passordet ligger
på i området halvannen dag (dog gitt en *veldig* kraftig maskin). Hvis dere hadde tillatt “spesialtegn” kunne jeg endret det til QrY8Tv9w! (ser du utropstegnet på slutten)?
dette øker kompleksiteten til to og en halv måned.

Problemet med å være restriktiv på hvilke tegn brukere får legge inn er at dere gjør det MYE lettere for slemminger å teste stjålne passordhasher og derfor gjør dere det MYE mer
attraktivt for de samme slemmingene å få tak i dataene. Man kan nesten si at dere maler en stor målskive på nettbanken deres, slår dere på brystet
og roper til verden “BARE KOM HER OG PRØV DERE, VI ER TILSYNELATENDE IKKE SÅ GOD PÅ DETTE MED SIKKERHET”. Og jeg må være helt ærlig når jeg sier at jeg er litt ubekvem med å ha pengene mine
stående i en bank som utbasunerer dette til alle sine kunder hver gang de skal skifte passord.

Nå er jo dette selvfølgelig ikke et problem dersom ingen slemminger noensinne finner ut at dere begrenser passordentropi for sluttbrukerne deres.

Vi får håpe at ingen av dem leser dette da.

 

——-

Har du sett, Handelsbanken svarte meg samme dag som jeg henvendte meg til dem. Her følger epost-dialogen:

Fra Handelsbanken:

Hei igjen, og takk for innspill 

Sikkerheten rundt vår nettbank og våre databaser diskuteres kun internt. 
Så du får ikke noe svar på dine spørsmål/antagelser. 

Men som du vet, så holder det ikke bare med passord ved innlogging i nettbanken. 
Innloggingen krever et vite-element (passord) og et ha-element (kodebrikke). 
For deg som kunde betyr det at du må ta kontakt med vår sperretjeneste – hvis noen av disse to 
mistenkes å være på avveie. 

I de hackede tjenestene/sidene du viser til, slik som LinkedIn, Dropbox, EHarmony etc. besto sikkerheten ved innlogging 
kun av et vite-element, ha-elementet var fraværende. 

Nå var det vel de færreste av de 6,5 millioner  berørte LinkedIn-brukerne som fikk LinkedIn-profilen sin vandalisert på grunn av innbruddet. Problemet er at folk gjenbruker passord på tvers av tjenester…

Jeg prøver med en ny mer oppklarende epost til Handelsbanken:

Hei igjen, takk for raskt svar, 
 
Det gjelder ikke bare innlogging til nettbanken som sådan, den dagen databasen kommer på avveie så vil den være relativt lettere hackbar, 
dvs at passord i basen relativt lettere blir hacket, dvs at alle de kundene som bruker samme passord (f.eks min mor og hennes syklubb) overalt da vil være mer sårbare fordi hackerne lærte passordene å kjenne gjennom deres system.
 
Alle de andre stedene bruker ikke fler-faktor-autentisering så potensielt setter dere døren på gløtt for “slemmingene” mange steder på internett, selv om dere selv er beskyttet gjennom kodebrikker.
 
Mitt poeng er primært at dersom dere hasher passord så *spiller det ingen rolle* hva input er. ALLE moderne algoritmer kan operere på ALLE karakterer man kan fremprodusere på et keyboard.
 
Den eneste grunnen til å sette begrensninger i brukernes passord er dersom man ønsker å kunne opplyse kunden om passord f.eks per telefon, men jeg velger å tro at dere ikke er så langt unna gode praksiser at dere faktisk muliggjør dette for deres kundebehandlere.
 
et annet tankemoment er at dere “tvinger” brukeren inn i dårligere passordvaner.
 
Det eneste positive jeg kan tenke meg med regelsettet deres er at siden så mange andre *krever* spesialtegn, at dere i så måte “tvinger” brukeren til å ha et unikt passord for handelsbanken i stedet for å gjenbruke et som han allerede bruker på facebook.
 
Men å gjøre policyen (passord isolert sett) sin til den minst sikre på internett er en underfundig måte å oppnå slik sikkerhet på.
 
jeg benytter LastPass til mine passord og ønsker aller helst å få den til å generere sikre unike passord til meg. Handelsbankens passordregime gjør det vanskelig for meg å følge best practice som sluttbruker. 
 
Hvordan kan da deres praksis være et eksempel på en god praksis?
Igjen imponerte Handelsbanken med å svare lynraskt, dog ble jeg ikke særlig mye klokere på om mitt innspill falt for døve ører:
Hei 

Som jeg nevnte i forrige mail, sikkerheten rundt våre systemer diskuteres kun internt. 

Ihht banken policy så har jeg heller ikke anledning til å komme med kommentarer 
utover de du fikk i mitt første svar. 

—- Takk skarru ha!
Next Page »

Blog at WordPress.com.