Abou Chleih

{the magic lies between the brackets}

Menü Schließen

Kategorie: C# (Seite 2 von 4)

Beiträge zur objektorientierten typsicheren Programmiersprache C# in Verbindung mit dem .NET-Framework von Microsoft

[C#/.NET] Eigene Erweiterungen für den Windows Explorer mit SharpShell erstellen

In meinem letzten Beitrag habe ich beschrieben, wie man mit Hilfe einer Microsoft Library eigene Dateiattribute hinzufügen kann.
Auf Grund der Tatsache, dass der Windows Explorer nur die Standardattribute anzeigt (siehe Grafik), müssen wir zur Anzeige der eigenen Attribute entweder

  • ein eigenes Programm entwickeln (Handhabe umständlich)
    oder
  • eine Erweiterung für den Windows Explorer erstellen.

Standardattribute:
Standardattribute_Details

Und um letzteren Punkt handelt dieser Beitrag.

Achtung: Die Library verwendet .NET Framework 4, d.h. eine Erstellung ist nur mit dieser .NET Version möglich und einer IDE, welche diese unterstüzt (VS2010 und höher)

Um zu beginnen, benötigen wir die Library, mit welcher eine Extension erstellt werden kann. Diese findet ihr entweder auf unserer Downloadseite (SharpShell Project) oder auf der Projektpage.

Wir erstellen nun ein neues Projekt vom Typ „Windows Forms-Steuerelementenbibliothek“ (man kann auch einfach eine Klassenbibliothek erstellen) und fügen eine Referenz auf die DLL ein.

Rechtsklick auf das Projekt -> Verweis hinzufügen… ->Durchsuchen -> Durchsuchen -> Datei auswählen.

Nun können wir mit dem eigentlichen Coden beginnen.
SharpShell bietet einige Extensions an, unter Anderem sogenannte Shell Property Sheets/Pages (Erweiterung der Eigenschaftsseite einer Datei/eines Ordners), auf die wir hier näher eingehen.

Zuerst beginnen wir mit dem Sheet, welches alle Pages hält.
Wir erstellen also eine Klasse (ich nenne sie hier einfach mal Sheet) und lassen sie von der abstrakten SharpPropertySheet-Klasse erben. Diese Klasse hält alle nötigen Funktion, welche noch ausgecodet werden müssen.
Die Methoden sind die folgenden:

protected abstract bool CanShowSheet();
protected abstract IEnumerable CreatePages();

Erstere Methode gibt an, ob die Seite angezeigt werden soll.
Letztere gibt die Pages zurück, welche angezeigt werden sollen.

protected override bool CanShowSheet()
{
            return SelectedItemPaths.Count() == 1; //Sobald mindestens eine Datei angewählt, soll die Seite angezeigt werden
}

protected override IEnumerable CreatePages()
{
            CustomPage page = new CustomPage(); //Erstelle eine Page oder mehrere
            return new[] { page }; //und schreibe sie in das Array, welches zurückgegeben wird.
} 

Nun existiert die Klasse CustomPage noch nicht, wir müssen sie erst erstellen.
Da wir ein „Windows Forms-Steuerelementenbibliothek“-Projekt erstellt haben, haben wir schon eine von der IDE vordefinierte Klasse.
Diese nutzen wir nun und benennen wir in „CustomPage“.
Anschließend lassen wir sie von der SharpPropertyPage-Klasse im Namespace „SharpShell.SharpPropertySheet erben.
In den Konstruktor der Klasse schreiben wir einfach mal den Page-Titel:

public CustomPage()
{
     InitializeComponent();
     PageTitle = "Custom Properties";      //Definiert den Page-Titel
}

Diese Klasse hat einige virtual Methoden, wovon wir eine zwingend benötigen:

 public virtual void OnPageInitialised(SharpPropertySheet parent);

Diese Funktion wird aufgerufen, sobald die Seite initialisiert wurde, also ähnlich der Funktion

Form.Load();

Hier bauen wir unsere Logik ein, bspw. können wir hier eine MessageBox aufrufen, welche den Pfad der markierten Datei zurückgibt, bzw. den Pfad der ersten markierten Datei.

    MessageBox.Show(parent.SelectedItemPaths.First().ToString()); 

Oder wir können die CustomProperties, welche wir gesetzt haben auslesen.
Dazu einfach die DSO-Library einbinden und die CustomProperties auslesen.
Ich habe dazu eine ListView eingebettet und fülle diese mit dem Key Bezeichner und dem Wert des Properties:

 public override void OnPageInitialised(SharpPropertySheet parent)
        {
            filePath = parent.SelectedItemPaths.First(); //Pfad der Datei speichern
            OleDocumentProperties myFile = new DSOFile.OleDocumentProperties();
            myFile.Open(@filePath, false, DSOFile.dsoFileOpenOptions.dsoOptionDefault);
            int ctr = 1;
            foreach (DSOFile.CustomProperty property in myFile.CustomProperties)
            {
                ListViewItem key = new ListViewItem(property.Name);
                key.SubItems.Add(property.get_Value());
                lstVw_keys.Items.Add(key);
                ctr++;
            }
            myFile.Close(true);
        } 

Voila, schon ist man eigentlich fertig.
Nun müssen wir allerdings noch ein paar Parameter definieren.
Wir gehen zurück in die Sheet-Klasse und definieren einmal, wie die Clients mit dem verwalteten Code umgehen:

     [ComVisible(true)] 

Nun müssen wir noch definieren, welche Dateien überhaupt betroffen sind:

 [COMServerAssociation(AssociationType.ClassOfExtension,".txt",".css",".js")] 

In diesem Fall also Dateien mit den Endungen .txt, .css und .js.

Jetzt können wir die DLL compilen und schließlich per regAsm einbinden (sehr kompliziert und nervenaufreibend).
Alternativ kann man auch den ServerManager, welcher Teil der SharpShell-Tools ist, benutzen.
Ich zitiere hier jetzt einfach mal meinen Stackoverflow-Beitrag:

I had the same problem while using regasm.exe.
Furthermore there are many things to mention when registering an assembly through regasm.
For example you have to use the x64/x86 version of the regasm.exe, depending on your system.

x64: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regAsm.exe
x86: C:\Windows\Microsoft.NET\Framework\v4.0.30319\regAsm.exe
After having so many problems, I switched to the ServerManager.exe, which is part of the SharpShell Tools. It can be downloaded on the project page.
The usage is quite easy:

  • Load the DLL with „Load server…“
  • Click on „Install Server (xYZ)“
  • And after that on „Register Server (xYZ)“

Restart the Windows Explorer and you should be done (not necessarily needed).

Das Ergebnis sieht in etwa so aus:

CustomPropertiesTab_Pic

Nun seid ihr fertig und könnt eure CustomProperties beliebig hinzufügen, löschen und anzeigen lassen.

Ein Beispielprojekt findet ihr hier: CustomPropertyTab

[WP8] PDF-Datei aus dem Web laden und per App anzeigen

Eine PDF-Datei aus dem Web herunterzuladen und anzuzeigen ist relativ einfach.
Folgender Methoden laden die Webseite, laden die PDF-Datei herunter, speichern diese in den Isolated Storage und starten die externe App mit dem Pfad der Datei.

Folgende Links geben weitere Informationen zu folgendem Code:

WebClient pdfDownloader = null;
string LastFileName = ""; //Speichert den Dateinamen der zuletzt gesicherten Datei

private void StartPDFDownload()
{
    pdfDownloader = new WebClient(); //prevents that the OpenReadCompleted-Event is called multiple times
    pdfDownloader.OpenReadCompleted += DownloadPDF; //Create an event handler
    pdfDownloader.OpenReadAsync(new Uri("Your URL as string with HTTP://")); //Start to read the website
}

async void DownloadPDF(object sender, OpenReadCompletedEventArgs e)
{
    byte[] buffer = new byte[e.Result.Length]; //Gets the byte length of the pdf file
    await e.Result.ReadAsync(buffer, 0, buffer.Length); //Waits until the rad is completed (Async doesn't block the GUI Thread)

    using (IsolatedStorageFile ISFile = IsolatedStorageFile.GetUserStoreForApplication())
    {
        try
        {
            LastFileName = "tempPDF" + DateTime.Now.Ticks + ".pdf";
            using (IsolatedStorageFileStream ISFileStream = ISFile.CreateFile(LastFileName))
            {
                await ISFileStream.WriteAsync(buffer, 0, buffer.Length);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message + Environment.NewLine + ex.HResult,
                ex.Source, MessageBoxButton.OK);
            //Catch errors regarding the creation of file
        }
    }
    OpenPDFFile();
}

private async void OpenPDFFile()
{
    StorageFolder ISFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    try
    {
        IStorageFile ISFile = await ISFolder.GetFileAsync(LastFileName);
        await Windows.System.Launcher.LaunchFileAsync(ISFile);
            //http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj206987%28v=vs.105%29.aspx
    }
    catch (Exception ex)
    {
        //Catch unknown errors while getting the file
        //or opening the app to display it
    }
}

[C#] XML in Applikation einlesen

Um schnell mal eine XML-Datei in die Applikation einzulesen, bedarf es dank des .NET-Frameworks nicht viel Arbeit.

Ich nutze dazu die XDocument-Klasse mit welcher es möglich ist XML zu parsen und zu schreiben.

Mein XML hatte folgende Form:

<?xml version="1.0" encoding="utf-8" ?>
<Phonenumbers>
  <phonenumber>
    <Name>Test</Name>
    <Number>12345678</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test2</Name>
    <Number>11111111</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test3</Name>
    <Number>222222222</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test4</Name>
    <Number>333333333</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test5</Name>
    <Number>44444444444</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test6</Name>
    <Number>55555555555</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test7</Name>
    <Number>66666666666</Number>
  </phonenumber>
  <phonenumber>
    <Name>Test8</Name>
    <Number>77777777777</Number>
  </phonenumber>
</Phonenumbers>

In die Applikation wird das XML mit folgendem Code geladen:

string xml = System.IO.File.ReadAllText(PfadZurXMLDatei);
XDocument xmlReader =  XDocument.Parse(xml);
var List = from element in xmlReader.Descendants("phonenumber")
		select new
		{
			name = (string)element.Element("Name"),
			number = (string)element.Element("Number"),
			//Weitere Elemente lesen
		};
foreach (var item in List)
{
	listBox1.Items.Add(item.name);
	listBox2.Items.Add(item.number);
}

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #4 – ActiveDirectory

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #1 – Plain
[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #2 – Verschlüsselung und SecureString
[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #3 – MySQL und Hash

ActiveDir_auth
Bei Active Directory (ab Windows Server 2008 Active Directory Domain Services) handelt es sich um einen Verzeichnisdienst (englisch directory service) von Microsoft. Sie speichert Benutzerinformationen(Benutzernamen, Kennwort sowie Gruppen) zentral auf einer Datenbank und stellt diese den Clients im Netzwerk zur Verfügung. Dadurch ist es, im Gegensatz zu einer Arbeitsgruppe, möglich die Benutzerdaten auf jeden Rechner in der Domäne zu verteilen. Somit ist es nicht mehr nötig jedes Profil auf jedem Rechner einzurichten, was eine enorme Zeitersparnis ergibt.

In vielen Firmen wird Active Directory genutzt und eine Authentifizierung ist  mit C# in Verbindung mit dem .NET-Framework schnell geschrieben.
Ich werden hier beschreiben, wie sich ein Benutzer durch Eingabe seines Benutzernamens und Kennworts authentifizieren kann, sowie die Überprüfung, ob sich der Benutzer in einer Gruppe befindet.

Authentifizierung über Benutzernamen und Kennwort:

Zuerst widmen wir uns der Authentifizierung an einer Domäne mittels Benutzername und Passwort.
Mit folgendem Code beziehen wir die aktuell verwendete Domäne aus dem System.

System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;

Die einfachste Variante ergibt sich mit der Verwendung von PrincipalContext-Klasse, welche sich im Namespace System.DirectoryServices.AccountManagement befindet

public static bool? AuthenticateUserWithAD(string Domain, string Username, string Password)
{
	try
	{
		using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
		{
			//Überprüft die übergebenen Anmeldedaten mit dem angegeben ContextType (ApplicationDirectory, Domain oder Machine)
			return pc.ValidateCredentials(Username, Password); //Gibt true oder false zurück
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, ex.Source);
		return null;
	}
}

Wenn man nicht auf PrincipalContext zurückgreifen möchte, so kann man den Benutzer auch mittels DirectoryEntry aus dem Namespace System.DirectoryServices authentifizieren.

Dazu verwendet man folgenden Methode:

private static readonly int ERROR_DS_NO_SUCH_OBJECT = -2147016656; //Fehlercode für LDAP_NO_SUCH_OBJECT 0x80072030 | http://msdn.microsoft.com/en-us/library/aa746528(v=vs.85).aspx
public static DirectoryEntry GetDirectoryEntry(string Domain, string username, string password)
{
	DirectoryEntry dirEntry = new DirectoryEntry();
	dirEntry.Path = "LDAP://" + Domain;
	dirEntry.Username = username;
	dirEntry.Password = password;

	try
	{
		if (dirEntry.NativeObject == null)
		{
			// Kein ActiveDir-Objekt gefunden
			dirEntry = null;
		}
	}
	catch (COMException ex)
	{
		if (ex.ErrorCode == ERROR_DS_NO_SUCH_OBJECT)
		{
			MessageBox.Show("Es wurde kein Objekt, welches mit den Eingaben übereinstimmt gefunden", "Fehler");
		}
		else
		{
			MessageBox.Show(ex.ErrorCode.ToString() + "\r\n" + ex.Message);
		}
		dirEntry.Close();
		dirEntry = null;
	}
	return dirEntry;
}

Um nun einen Wahrheitswert (Boolean) zurückzugeben, muss man prüfen, ob das DirectoryEntry-Objekt gefüllt oder null ist.
Dazu verwendet man folgende Methode, welche wir später dann auch über unsere Klasse aufrufen.

public static bool AuthenticateUser(string Domain, string username, string password)
{
	return GetDirectoryEntry(Domain, username, password) != null;
}

Falls man nur wissen möchte, ob der Benutzer existiert, so kann man ebenfalls PrincipalContext verwenden, jedoch diesmal in Verbindung mit der statischen Methode FindByIdentity der UserPrincipal-Klasse

public static bool CheckIfUserExists(string Domain, string Username)
{
	using (var pc = new PrincipalContext(ContextType.Domain, Domain))
	{
		using (var User = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, Username))
		{
			return User != null;
		}
	}
}

Überprüfung, ob sich Benutzer in Gruppe befindet:

Nun möchte man natürlich nicht immer den Benutzer authentifizieren oder prüfen, ob dieser existiert, sondern spezielle Funktionen einem gewissen Kreis von Personen bereitstellen.

Dazu bietet ActiveDirectory s.g. Gruppen

Eine Gruppe besteht aus Benutzer- und Computerkonten, Kontakten und anderen Gruppen, die als eine Einheit verwaltet werden können. Benutzer und Computer, die zu einer bestimmten Gruppe gehören, werden als Gruppenmitglieder bezeichnet.
Die Verwendung von Gruppen vereinfacht die Verwaltung, indem vielen Konten in einem Schritt einheitliche Berechtigungen und Rechte zugewiesen werden können, anstatt jedem Konto einzeln Berechtigungen und Rechte zuweisen zu müssen.

Für die Prüfung, ob der Benutzer Mitglied der Gruppe ist, gibt es zwei Möglichkeiten.

  1. Unter Verwendung von DirectoryEntry
  2. Benutzung WindowsIdentity

Zuerst unter Verwendung von DirectoryEntry

public static bool? CheckIfUserIsInGroupWithDirectoryEntry(string Domain, string Username, string Password, string Group)
{
	if (Username == "" || Password == "")
	{
		return false;
	}

	try
	{
		DirectoryEntry entry = new DirectoryEntry("LDAP://" + Domain, Username, Password);
		DirectorySearcher mySearcher = new DirectorySearcher(entry);
		mySearcher.Filter = "(&(objectClass=user)(|(cn=" + Username + ")(sAMAccountName=" + Username + ")))";
		SearchResult result = mySearcher.FindOne();

		foreach (string GroupPath in result.Properties["memberOf"])
		{
			if (GroupPath.Contains(Group))
			{
				return true;
			}
		}
	}
	catch (DirectoryServicesCOMException DirSvrComEx)
	{
		MessageBox.Show(DirSvrComEx.Message);
		return null;
	}
	return false;
}

Und die Alternative mit WindowsIdentity:

public static bool? CheckIfUserIsInGroup(string Username, string Groupname)
{
	try
	{
		using (var identity = new WindowsIdentity(Username))
		{
			var principal = new WindowsPrincipal(identity);
			return principal.IsInRole(Groupname);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		return null;
	}
}

Möchte man den aktuellen Benutzer verwenden (und somit keine Eingabe der Login-Informationen erfordern), so kann man die Methode wie folgt anpassen

public static bool? CheckIfCurrentUserIsInGroup(string Groupname)
{
	try
	{
		IntPtr accountToken = WindowsIdentity.GetCurrent().Token;
		using (var identity = new WindowsIdentity(accountToken))
		{
			var principal = new WindowsPrincipal(identity);
			return principal.IsInRole(Groupname);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		return null;
	}
}

Zu guter Letzt das Demo-Projekt:

20130811_Verweis-Manager - OleDBTest_000034

ActiveDirectory_Auth.zip

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #3 – MySQL und Hash

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #1 – Plain
[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #2 – Verschlüsselung und SecureString

Eine Authentifizierung mittels MySQL ist ziemlich einfach und schnell geschrieben.

Wir müssen lediglich den .NET Connector für MySQL herunterladen, den Verweis auf die DLL hinzufügen und folgenden Namespace mittels

using MySql.Data.MySqlClient;

einbinden.

Nun benötigen wir ein Objekt der MySQLConnection, welches den ConnectionString beinhaltete und eine Verbindung zum MySQL-Server bereitstellt.

Ich erstelle ein Field vom Typ MySQLConnection (also eine Variable, auf welche alle Methoden innerhalb der Klasse zugreifen können), welches jedoch noch nicht initialisiert wurde.

MySqlConnection connection = null;

In der öffentlichen Methode setupConnection() erstelle ich den ConnectionString (mit den Parametern) und initialisiere den MySQLConnector mit dem ConnectionString.

public void setupConnection(string server, string database, string user, string password)
{
     string myConnectionString = "SERVER=" + server + ";" +
     "DATABASE=" + database + ";" +
     "UID=" + user + ";" +
     "PASSWORD=" + password + ";";
     connection = new MySqlConnection(myConnectionString);
}

Nun erstelle ich eine weitere öffentliche Methode, welche zur Authentifizierung dient.

public bool? Authenticate(string username, string password, string table) //Das Fragezeichen (?) nach einem Typ gibt an, dass der <a href="http://msdn.microsoft.com/de-de/library/1t3y8s4s(v=vs.90).aspx" target="_blank">Typ NULL-Werte zulässt (nullable)</a>
{
	try
	{
		MySqlCommand command = connection.CreateCommand();
		command.CommandText = "SELECT * FROM "+ table +" WHERE Username=?Username"; //Erstellt die parametrisierte Abfrage (Tabellen können nicht parameterisiert werden)
		command.Parameters.AddWithValue("?Username", username); //Weißt dem Parameter einen Wert zu
		MySqlDataReader Reader;
		connection.Open(); //Öffnet die Verbindung zum Server
		Reader = command.ExecuteReader(); Führt die Abfrage auf dem Server aus
		while (Reader.Read()) //Solange noch Daten verfügbar sind
		{
			if (Reader[1].ToString() == username) //Prüfe ob der Wert der ZWEITEN(!) Spalte mit dem übergebenen Usernamen übereinstimmt
			{ //Falls dies der Fall ist,
				if (Reader[2].ToString() == password)//prüfe ob der Wert der dritten Spalte mit dem Passwort übereinstimmt
				{//Sollte dem so sein,
					connection.Close(); //so schließe die Verbindung
					return true; //und geben den Wahrheitswert "wahr" zurück
				}
			}
		}
                //Sollten die Parameter nicht mit den Daten aus der DB übereingestimmt haben
		connection.Close(); //So schließe die Verbindung
		return false; //Und geben "false" zurück
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		connection.Close();
		return null;
	}
}

Hashing

Nun ist es natürlich äußerst fahrlässig Passwörter klar in der Datenbank zu hinterlegen. Im besten Fall soll es unmöglich sein, das Passwort wieder im Klartext aus der Datenbank zu beziehen und genau hier wird Hashing genutzt. Im Gegensatz zur Verschlüsselung ist es dabei nicht mehr möglich das gehashte Passwort in das ursprüngliche zurückzuwandeln.
Es gibt ziemlich viele Hashfunktionen, zwei der Bekanntesten sind MD5 und SHA.

MD5
SHA-2
Vorteile
Nachteile
Vorteile
Nachteile
Schnell
Nicht kollisionsresistent
Bisher nicht geknackt
Langsamer als MD5
Viele Regenbogentabellen
Gilt als kollisionsresistent

Die Schnelligkeit beider Hashfunktionen ist aber auch eine der größten Schwächen in Bezug auf die Sicherheit des Passworts. So ist heutzutage möglich ein 8-stelliges Passwort, bestehend aus Zahlen und Buchstaben, gehasht mit SHA-2 GPU-gestützt innerhalb eines Tages zu knacken.

MD5

Nun aber zur Implementierung einer MD5-Hashfunktion mit C# und dem .NET-Framework. Dazu verwenden wir die MD5CryptoSerivceProvider-Klasse

public string CreateMD5Hash(string unhashedString)
{
	if (String.IsNullOrEmpty(unhashedString))
		return string.Empty;

	MD5 md5 = new MD5CryptoServiceProvider(); //Alternativ kann man auch MD5 md5 = MD5.Create(); verwenden
        //Create() initalisiert MD5CryptoServiceProvider, da MD5CryptoServiceProvider MD5 implementiert. MD5 ist eine abstrakte Klasse
	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = md5.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

SHA-2

Beinahe den selben Code kann man für SHA-2 verwenden. Statt dem MD5CryptoServiceProvider verwenden wir die SHA256CryptoServiceProvider-Klasse

public string CreateSHA2Hash(string unhashedString)
{
	if (String.IsNullOrEmpty(unhashedString))
		return string.Empty;

	SHA256 sha2 = new SHA256CryptoServiceProvider();
	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = sha2.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

Und jetzt haben wir eine Todsünde begangen. Das Hashes sind nicht gesalzen und können somit einfach per Rainbow Tables geknackt werden.

Salted MD5

Genau für diesen Zweck wurde HMAC entwickelt. Ein Implementierung in das .NET-Framework erfolgte mit der HMACMD5-Klasse

public string CreateSaltedMD5Hash(string unhashedString, byte[] key)
{
	HMACMD5 saltedMD5 = new HMACMD5();

	saltedMD5.Key = key;

	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = saltedMD5.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

Salted SHA-2

Für SHA2 gibt es die HMACSHA256-Klasse

public string CreateSaltedSHA2Hash(string unhashedString, byte[] key)
{
	HMACSHA256 saltedSHA2 = new HMACSHA256();

	saltedSHA2.Key = key;
	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = saltedSHA2.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

Wie ich oben bereits erwähnt habe, ist die Geschwindigkeit die größte Schwäche dieser Algorithmen. Darum sollte man MD5 und SHA auch mit Salt nicht zum Hashen von Passwörtern verwenden (MD5 und SHA dienen zur Berechnung einer eindeutigen Prüfsumme, nicht zum Hashen von Passwörtern). Für diesen Zweck wurden langsamere Algorithmen entwickelt, welche generell für das Verschlüsseln von Passwörtern verwendet werden sollten.  Die Bekanntesten sind PBKDF2 und bcrypt.

Zu guter Letzt das Demo-Projekt:

20130811_Verweis-Manager - OleDBTest_000034

MySQL_Hash.zip

[C#/.NET] AES-Ver- und Entschlüsselung mit Rijndael

Bei AES (Advanced Encryption Standard) bzw Rijndael (ausgesprochen: Rain-Dahl [rɛindaːl]) handelt es sich um einen Verschlüsselungsalgorithmus, welcher im Oktober 2000 als Standard festgelegt wurde und den Vorgänger DES (Data Encryption Standard) ablöst.
AES setzt auf eine Blockverschlüsselung, d.h. der Schlüssel wird in mehrer Rundenschlüssel eingeteilt, mit welchem pro Runde in jedem Block die Bytes ersetzt (SubBytes), verschoben (ShiftRows) und vermischt (MixColumns) werden. Es gibt je nach Schlüssellänge 10 (128 bit), 12 (192 bit) oder 14 (256 bit) Runden (R) und eine zusätzliche Endrunde, so werden R+1 Rundenschlüssel benötigt.

Für weitere Informationen empfehle ich:

Nun zur Verschlüsselung eines Strings mit AES in C# zum einen nach AES-Spezifikationen (FIPS-197), zum anderen nach Rijndael (welcher ja zum AES Algorithmus gewählt wurde).
Zwischen AES und Rijndael gibt es, trotz der Wahl von Rijndael zum Algorithmus, Unterschiede, welche eine Kompatibilität untereinander unmöglich machen.

  1. Rijndael erlaubt variable Block- und Schlüsselgrößen (128, 160, 192, 224,  256), wohingegen bei AES eine Blockgröße von 128 bit und eine Schlüsselgröße von 128, 192 oder 256 bit vorgeschrieben sind.
  2. Rijndael erlaubt zusätzlich zu Cipher Block Chaining Mode (CBC) die Verwendung von Cipher Feedback Mode (CFB) als Betriebsmodus, wohingegen AES nur Ersteres unterstützt (bzw. CFB nur mit 128 bit Feedback)

Nun aber zum Code:

AES (FIPS-197)Rijndael
Ich habe folgenden statische Klasse erstellt, mit der wir sämtliche Vorgänge (also Ver- und Entschlüsseln) durchführen können.
Statische AES Klasse

Und so nutzt man die oben erstellte Klasse, in vollem Umfang:

Aufruf/Nutzung der AES-Klasse
Folgende Klasse (Form) wird zur Erstellung des Schlüssels verwendet und über die Methode AES_CreateOwnSaltedKey aufgerufen.
Form zum Erstellen des Schlüssels

Folgende Klasse (Form) bietet die Möglichkeit der Eingabe des Schlüssels, welcher zu Entschlüsselung benötigt wird. Ein Objekt der Klasse wird in der Methode AES_DecryptWithOwnKey erstellt und angezeigt.

Klasse zur Schlüssel-Eingabe zur Entschlüsselung
Inhalt folgt noch. Der Code gleicht jenem der Aes-Klasse jedoch zu 90%

Zu guter Letzt das Demo-Projekt:

20130811_Verweis-Manager - OleDBTest_000034

AES_Rijndael.zip

[C#] Sortieren einer DataTable-Spalte nach einer Spalte eines anderen DataTable

Ich hatte die Aufgabe eine Spalte einer DataTable bzw. die DataTable(1) an sich nach der Spalte einer anderen DataTable(2) zu sortieren ohne die Reihenfolge der zweiten DataTable(2) zu verändern.
Um zu verdeutlichen, wie die Aufgabenstellung genau aussah, habe ich hier Screenshots der DataTables visualisiert in einem DataGridView im Ist/Soll-Zustand:

Die Sort-Methode des DataGridView, ebenso wie die Sort-Eigenschaft eines DataViews,

DataTable.DefaultView.Sort = "Spaltenname ASC"

bieten nur die Möglichkeit eine Spalte auf(Ascending)- bzw. absteigend(Descending) zu sortieren.

So musste ich mir dann einen eigenen Weg finden, die Aufgabe zu lösen. Die Lösung möchte ich euch natürlich nicht vorenthalten.

 private void sortList(string pExcelDataColumnName, string pSortByListColumnName)
        {
            excelDataTable.Columns.Add("TempOrderColumn", typeof(Int32)); //Dient später zum Sortieren der Zeilen

            foreach (DataRow row in excelDataTable.Rows)
            {
                foreach (DataColumn column in excelDataTable.Columns)
                {
                    if (column.ColumnName == pExcelDataColumnName)
                    {
                        object tempRowIndex = GetRowIndexOfCellValue(row
, pSortByListColumnName); row["TempOrderColumn"] = tempRowIndex; } } } DataView dv = excelDataTable.DefaultView; dv.Sort = "TempOrderColumn ASC"; excelDataTable = dv.ToTable(); excelData_dataGridView.Refresh(); }

In dieser Methode erstellen wir erstmal eine weitere Spalte, welche uns später zum Sortieren dient, danach durchlaufen wir alle Zellen der unsortierten Liste(zweimal foreach) und fragen ab, ob sich einer der Zellen in der zuvor definierten Spalte befindet(Parameter der Methode).
Sollte dies der Fall sein, so rufen wir folgende Methode auf und übergeben den Inhalt der aktuellen Zelle, sowie den Namen der zu vergleichenden Spalte:

private object GetRowIndexOfCellValue(object pCellValue, string pSortByListColumnName)
        {
            foreach (DataRow row in sortByDataTable.Rows)
            {
                foreach (DataColumn column in sortByDataTable.Columns)
                {
                    if (column.ColumnName == pSortByListColumnName && pCellValue.Equals(row
)) { return sortByDataTable.Rows.IndexOf(row); } } } return -1; }

Diese Methode durchläuft sämtliche Zellen der Liste, nach welcher wir sortieren wollen und prüft ob sich die Zelle in der als Parameter übergeben Spalte befindet(indem sie den übergeben Spaltennamen mit jenem der aktuellen Spalte(Column) vergleicht) und ob der Inhalt der Zelle mit jenem Inhalt übereinstimmt, welcher als Parameter übergeben wurde.

Sollte beide Abfragen ‚wahr‘ sein, so geben wir den Index der aktuellen Zeile zurück. Dafür verwenden wir die folgende Methode

return sortByDataTable.Rows.IndexOf(row);

Der Rückgabewert wird dann mittels

row["TempOrderColumn"] = tempRowIndex;

in die temporäre Spalte geschrieben und zu guter Letzt mit folgendem Code sortiert:

            DataView dv = excelDataTable.DefaultView;
            dv.Sort = "TempOrderColumn ASC";
            excelDataTable = dv.ToTable();
            excelData_dataGridView.Refresh();

Dies ist nötig, da man einer DataTable nicht direkt sortieren kann und somit den Umweg über das Erstellen eines DataViews gehen muss, diesen dann mittels

dv.Sort = "Spaltenname ASC" //ASC steht für 'ascending' aufsteigend
//DESC steht für 'descending' absteigend

sortiert und das DataView dann wieder in die DataTable wandelt.

Das Ergebnis sieht wie folgt aus:
2013-08-23 15_51_20-Excel nach Liste sortieren

Nun muss ich nur noch eine Excel-Tabelle aus der DataTable bzw dem DataGridView erstellen. Wie man dies bewerkstelligt, zeige ich in meinem nächsten Blogeintrag

[C#] Auslesen einer Excel Datei #2 – OleDB

Teil 1: [C#] Auslesen einer Excel Datei #1 – Interop-Assembly

OleDB steht für Object Linking and Embedding, Database und bietet einem im Gegensatz zu Interop-Assembly die Möglichkeit auf verschiedenste Datenquellen (bspw. auch auf Datenbanken zuzugreifen). Zugleich muss im Gegensatz zu Interop-Assembly kein Microsoft Excel installiert sein und die meisten Controls, wie z.B. DataGridView, akzeptieren out-of-the-box die Datenquelle (Data Source) welche OleDB bietet.

Hier ein Zitat aus dem MSDN:

OLE DB is a set of COM-based interfaces that expose data from a variety of sources. OLE DB interfaces provide applications with uniform access to data stored in diverse information sources, or data stores. These interfaces support the amount of DBMS functionality appropriate to the data store, enabling the data store to share its data.

OleDB hat einige Vorteile gegenüber Interop-Assembly:

  • Microsoft Excel muss nicht installiert sein
  • Es ist um einiger schneller als Interop

Natürlich aber auch Nachteile

  • OleDB bietet im Vergleich zu Interop in Bezug auf Excel weniger Möglichkeiten, vor allem bezüglich Zelleninformationen, wie Farbe, Rahmen oder Formatierung

Nun aber zur Nutzung von OleDB

Normalerweise bindet Visual Studio automatisch System.Data-Assembly ein und wir müssen nur noch den Namespace mittels

using System.Data.OleDb;

einbinden

Sollte es dennoch zu folgender Fehlermeldung

Fehler 1

Der Typ- oder Namespacename „OleDb“ ist im Namespace „System.Data“ nicht vorhanden. (Fehlt ein Assemblyverweis?)

müssen wir einen Assemblyverweis erstellen.

Dazu einfach mit der rechten Maustaste auf „Verweise“ klicken:

20130811_OleDBTest - Microsoft Visual Studio_000033

„Verweis hinzufügen“ wählen und in den Framework-Assemblys nach System-Data suchen und diese Assembly dann wählen.20130811_Verweis-Manager - OleDBTest_000034

Zu guter Letzt noch mit „OK“ bestätigen.

Ich persönlich bevorzuge es Objekten mittels using eine definierte Gültigkeit zuzuweisen, dies bedeutet, dass das in der Using-Anweisung definierte Objekt nach dem Verlassen der Anweisung nicht mehr benötigt wird und zerstört werden kann.

Dadurch ist es aber nötig den ConnectionString, vor dem Erstellen der OleDbConnection zu definieren und per Paramter zu übergeben.
Um einen ConnectionString zu erstellen, verwende ich den OleDbConnectionStringBuilder:

Dieser bietet eine einfache Möglichkeit, den Inhalt von Verbindungszeichenfolgen zu erstellen und zu verwalten, die von der OleDbConnection-Klasse verwendet werden.


OleDbConnectionStringBuilder csbuilder = new OleDbConnectionStringBuilder();
 csbuilder.Provider = "Microsoft.ACE.OLEDB.12.0";
 csbuilder.DataSource = "Pfad zu Excel-Datei";
 csbuilder.Add("Extended Properties", "Excel 12.0 Xml;HDR=YES");

Der Provider ist eine Softwarebibliothek, welche die nötigen Werkzeuge bietet, um auf eine Datenquelle zuzugreifen. Im ConnectionString werden die Informationen, welche den Provider in die Lage versetzen auf die Quelle zuzugreifen, hinterlegt.
Für ConnectionStrings empfehle ich: http://www.connectionstrings.com/

Im Property DataSource wird der Pfad zu Excel-Datei angegeben.

Mittels der Methode Add werden die Informationen hinterlegt, welche den Typ der Datei und Informationen zum Inhalt definieren.
In diesem Beispiel wird hinterlegt, dass es sich um eine Excel-Datei handelt, welche mit Excel 2010 erstellt wurde(Excel 12.0) und deren erste Zeile Überschriften(Header/HDR) sind.

Da nun die wichtigsten Eigenschaften definiert wurden, können wir das erste Tabellenblatt der Excel-Datei auslesen.
Dazu verwenden wir die SQL-Abfrage

SELECT * FROM [“ + Name_des_Tabellenblattes + „]

(Die eckigen Klammern werden zwingend benötigt)

             using (OleDbConnection connection = new OleDbConnection(csbuilder.ConnectionString))
            {
                DataTable sheet1 = new DataTable();
                connection.Open();
                string sqlQuery = @"SELECT * FROM [" + Name_des_Tabellenblattes + "]";
                using (OleDbDataAdapter adapter = new OleDbDataAdapter(sqlQuery, connection))
                {
                    adapter.Fill(sheet1);
                    excelData_dataGridView.DataSource = sheet1;
                }
                connection.Close();
            }

Und erstellen ein Objekt vom Typ OleDbDataAdapter mit den Parametern „SQL-Query“ und dem ConnectionString.
Danach füllen wir den DataTable mittels adapter.Fill(Name_des_DataTables) und füllen ein DataGridView mittels

DataGridView.DataSource = Name_des_DataTables;

Namen der Tabellenblätter auslesen

Nun ist natürlich ungeschickt den Namen des Tabellenblattes hartcodiert im Code zu hinterlegen.
Um die Tabellenblattnamen auszulesen und in ein StringArray zu laden verwenden wir folgenden Code:

OleDbConnectionStringBuilder csbuilder = new OleDbConnectionStringBuilder();
csbuilder.Provider = "Microsoft.ACE.OLEDB.12.0";
csbuilder.DataSource = pExcelPath;
csbuilder.Add("Extended Properties", "Excel 12.0 Xml;HDR=YES");

System.Data.DataTable dt = null;

using (OleDbConnection connection = new OleDbConnection(csbuilder.ConnectionString))
{
    connection.Open();
    dt = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    connection.Close();
}

String[] excelSheets = new String[dt.Rows.Count];

int i = 0;
foreach (DataRow row in dt.Rows)
{
    excelSheets[i] = row["TABLE_NAME"].ToString();
    i++;
}

Mit dem StringArray kann man nun eine ganze Menge anfangen.
Ich habe beispielsweise das StringArray als Parameter an ein Objekt einer Form übergeben, welches jeden String im Array(mittels foreach-Schleife) in eine ListView kopiert und man so das gewünschte Tabellenblatt auswählen kann.

DemoProjekt

Hier könnt ihr das Demoprojekt herunterladen:

20130811_Verweis-Manager - OleDBTest_000034

OleDBTest

[C#] Auslesen einer Excel Datei #1 – Interop-Assembly

Um Daten aus einer Excel-Datei in das Programm zu laden, gibt es mehrere Wege:

  • Zugriff auf die Excel-Datei über die Interop-Schnittstelle
  • Zugriff auf die Excel-Datei über einen Datenbank-Provider (hier: OLEDB)

Beginnen wir mit dem ersten genannten Weg, dem Zugriff über die Interop-Assembly.
Um einen Zugriff zu erhalten, müssen wir zuerst die Assembly einbinden, dies tun wir, indem wir einen Verweis auf die DLL hinzufügen.
Dazu klicken wir mit der rechten Maustaste auf das Item „Verweise“ im gewünschten Projekt und betätigen das Feld „Verweis hinzufügen…“.
Verweis_hinzufügen

Anschließend öffnet sich Dialog, indem wir einige DLLs finden.
Wir benötigen die Microsoft.Office.Interop.Excel-DLL, welche im Reiter .NET zu finden ist.

Interop_Excel_Verweis
Anschließend binden wir den Namespace der DLL in das Projekt (die gewünschte Klasse) ein.

 using Microsoft.Office.Interop.Excel; 

Nun zum eigentlichen Auslesen.
Zuerst müssen wir die verschiedenen Variablen anlegen, die Excel benötigt:

  • Die Excel-Applikation
  • Das Workbook
  • Das Worksheet
ApplicationClass app = new ApplicationClass(); //Die Excel-Applikation
Workbook book = null; //Das Workbook
Worksheet sheet = null; //Das Worksheet

Nun weisen wir die Werte zu:

 book = app.Workbooks.Open(path, Missing.Value, Missing.Value,
                                         Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                                         Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                                         Missing.Value, Missing.Value, Missing.Value, Missing.Value);

Dies lässt Excel das Workbook öffnen.
Falls dies im Hintergrund geschehen soll (Excel soll nicht sichtbar laufen), müssen folgende Optionen gesetzt sein:

 app.Visible = false;
app.ScreenUpdating = false;
app.DisplayAlerts = false; 

Nun da wir das komplette Excel-File geöffnet haben, wollen wir die Dateien der verschiedenen Worksheets (zu Deutsch: Tabellenblätter) auslesen.
Dazu weisen wir dem Objekt vom Typ Worksheet das Worksheet aus dem Excel-File zu:

 sheet = (Worksheet)book.Worksheets[1]; 

Es ist zu beachten, dass hier das erste Worksheet auch den Index 1 hat. (Kein zero-based-indexing!)
Jetzt lesen wir das Worksheet aus. Dazu muss man angeben, ab welcher Zelle und bis zu welcher Zelle man auslesen will, man muss also einen Bereich (eine Range) angeben.
Für dies existiert die Klasse Range.
Nun weisen wir der Range den gesamten Bereich zu, in dem Werte stehen.
Dazu erstellen wir ein char-Array bzw. einen string (ein String ist ein char-Array):

 string alphabet= "ABCDEFGHIJKLMNOPQRSTUVQXYZ"; 

Nun lesen wir aus, wie viele Spalten es gibt:

 int colCount = sheet.UsedRange.Columns.Count; 

Den Buchstaben der letzten Spalte bekommen wir nun ganz einfach durch Nutzen des Index (colCount):

 char lastColChar = alphabet[iColumnCount]; 

Um die letzte genutzte Spalte zu finden nutzen wir:

int rowCount = sheet.UsedRange.Rows.Count; 

Die Range definieren wir dann wie folgt:

 Range range = sheet.get_Range("A1", lastColChar.toString() + rowCount.toString()); 

Der erste Parameter definiert die obere, rechte Zelle, der zweite die untere, linke.
Nun haben wir den Bereich definiert.
Das Auslesen ist nun sehr simpel:
Da wir alle benötigten Daten haben, können wir ein Array erstellen und das Array schnell füllen:

 object[,] myExcelFileValues = (object[,])range.Value2; 

Nun haben wir alle Zellen im Speicher und können Excel beenden (!!!), dies ist wichtig, da die Datei sonst nicht freigegeben wird.
Dies geschieht nach dem First-In-Last-Out-Prinzip.
Zuerst löschen wir also das Range-Objekt:

 range = null; 

Nun löschen wir das Worksheet und rufen den GarbageCollector auf:

Marshal.FinalReleaseComObject(sheet);
app.DisplayAlerts = false;
sheet = null; 
GC.Collect();
GC.WaitForPendingFinalizers(); 

Anschließend lassen wir das Workbook schließen und löschen das Workbook-Objekt:

  book.Close(false, Missing.Value, Missing.Value); 
 Marshal.FinalReleaseComObject(book);
 book = null;

Daraufhin schließen wir Excel/die Applikation und löschen diese aus dem Speicher:

 app.Quit();
Marshal.FinalReleaseComObject(app);
app = null; 

Nun ist das Programm beendet.

© 2018 Abou Chleih. Alle Rechte vorbehalten.

Thema von Anders Norén.