Abou Chleih

{the magic lies between the brackets}

Menü Schließen

Schlagwort: C# (Seite 1 von 4)

ASP.NET Core App via Apache bereitstellen #2 – Konfiguration Apache

Um eine weitere Apache Instanz zu erstellen, gibt es bereits ein Skript, welches einem einige Arbeit abnimmt.
Die grundsätzliche Idee dahinter ist jedoch einfach: Man kopiert die originale Instanz (apache2) , also Konfigurationsdateien, Binaries, etc. in einen zweiten Ordner (apache2-xxx).

Je nach System findet sich das Skript in
/usr/share/doc/apache2.2-common/examples/setup-instance xxx
oder
/usr/share/doc/apache2/examples/setup-instance xxx

xxx ist hierbei der Name der Instanz, die erstellt werden soll: /usr/share/doc/apache2/examples/setup-instance netcore

Dies erstellt also die entsprechenden Verzeichnis und Dateien /etc/apache2-netcore, /etc/logrotate.d/apache2-netcore, /var/log/apache2-netcore sowie das Init-Skript für den Boot /etc/init.d/apache2-netcore.

Um die entsprechenden Links zu aktivieren, also apache2-netcore als Service verfügbar zu machen. Führen wir nun ein update-rc.d apache2-netcore defaults aus.
Dies verlinkt die executables in /etc/rcX.d/, also dem entsprechenden Runlevel für den Boot Prozess.

Nun können wir den Service mit init.d oder service starten: /etc/init.d/apache2-netcore start oder service apache2-netcore start

Nun können wir uns an die Konfiguration de Apache2 Instanz NetCore machen. Dazu navigieren wir ins Verzeichnis /etc/apache2-netcore/sites-available/ und bearbeiten das file 000-default.conf (HTTP) oder default-ssl.conf (HTTPS).

Diese Dateien haben folgenden Aufbau (in diesem Beispiel hört die ASP.NET Core Application auf localhost:5000):

<VirtualHost *:8080>
     ServerName sub.mydomain.com 
     ErrorLog ${APACHE_LOG_DIR}/errorNetCore.log 
     CustomLog ${APACHE_LOG_DIR}/accessNetCore.log combined 
     ProxyPass / http://localhost:5000/ 
     ProxyPassReverse / http://localhost:5000/ 
     ProxyPreserveHost On
</VirtualHost>

ServerName: Hört auf Aufruf von sub.mydomain.com
ErrorLog: Schreibt Error Log in entsprechendes File, (Apache bspw. PHP Fehler kommen hier rein)
CustomLog: Schreibt alle Aufrufe in File (=AccessLog Config)
ProxyPass: Forward Proxy Config, leitet alle Anfragen von http://sub.mydomain.com:8080/ an http://localhost:5000/ weiter
/ steht hier für das Top Directory, ProxyPass /sub/ http://localhost:5000/ würde bspw. http://sub.mydomain.com:8080/sub/ an http://localhost:5000/ weiterleiten
ProxyPassReverse: Reverse Proxy, leitet Response um. Schreibt alle Urls in der Rückantwort um, bspw. sollte http://localhost:5000 zu http://sub.mydomain.com:8080 umgeschrieben werden.
ProxyPreserveHost: Header bleibt erhalten

Nun speichern wir die Seite und aktivieren sie mit a2ensite sub.mydomain.com und starten die Instanz neu service apache2-netcore restart.

Anschließend starten wir die ASP.NET Core Applikation: dotnet PFADZURDLL

Observer Pattern (Beobachter-Muster) in Java und C#

Wie benachrichtige ich mehrere Clients möglichst ohne Zeitverlust und ohne das Rad neu zu erfinden?
Eine weitverbreitete Möglichkeit ist das Observer Pattern (und erweitert, das Subscriber Pattern).
Es ist push-basiert, der Beobachtete gibt also allen Beobachtern Bescheid, was eine Benachrichtigung an die Beobachter in kürzester Zeit ermöglicht.

ObserverPattern

Was wir in Java dafür brauchen sind folgende Klassen und Interfaces, welche wir im Code referenzieren. In C# gibt es keine äquivalenten Klassen, wir müssen etwas selber schreiben.

Java
import java.util.Observable;
import java.util.Observer;

Erstellen wir nun die Klasse, welche später beobachtet werden soll.
In C# nutzen wir EventHandler für eine asynchrone Benachrichtigung:

JavaC#
public class Beobachteter extends Observable
public class Observable
	{
		private bool changed;
		event EventHandler notifier;
		
		public void addObserver(Observer obs)
		{
			notifier += obs.update;
		}
	
		private void notifyObservers(dynamic obj)
		{
			if (this.changed) {
				if(notifier != null)
					 notifier(obj, EventArgs.Empty);
			}
			this.changed = false;
		}
		
		public void setChanged()
		{
			this.changed = true;
		}
		
		public void doIt(){
			setChanged();
			notifyObservers(32123);
		}
		
	}

Wir lassen unseren Beobachteten die Klasse Observable im Namespace java.util.* erben. (Siehe oben)
Dadurch bekommt die Klasse u.A. folgende Methode vererbt, welche wir im Folgenden nutzen:

JavaC#
public void AddObserver(){
       Beobachteter.addObserver(Observer o);
}
 Durch die eigene Implementierung, stehen uns die identischen Methoden zur Verfügung. 

Wie man auf dem Bild sieht, muss man nun die Beobachter (Observer) einem Beobachtbaren (Observable) hinzufügen, im Folgenden nenne ich diesen dann Beobachteten.
Wir erstellen verschiedene Beobachter und fügen diese nun einem Objekt der Klasse Beobachteter zu, welche – der Einfachheit halber – direkt in den Beobachtern erstellt wird.
Die Beobachter implementieren das Interface von java.util.Observer.

JavaC#
public class Beobachter implements Observer{
    public Beobachter(){
         Beobachteter observable = new Beobachteter();
         observable.addObserver(this); //fügt dem Beobachteten mich als Beobachter hinzu.
    }
}
 	public class Observer
	{
		string name;
		
		public Observer()
		{
		}
		
		public Observer(string name){
			this.name = name;
		}
	
		public void update(object message, EventArgs args){
			//Console.WriteLine(this + "received: "+message+obs);
		}
		
		public override string ToString()
		{
			return "{Observer,"+this.name+"}";
		} 
	}

Nun haben wir einen oder mehrere Beobachter und einen Beobachteten.
Wie lassen wir die Beobachter nun wissen, wenn sich etwas ändert?
Die Klasse Observable bietet hier einige passende Methoden. Ich fokussiere mich hier allerdings auf folgende zwei:

JavaC#
 setChanged(); 
 notifyObservers(message);
setChanged();  
notifyObservers(message);

Durch das dynamic keyword können wir dem EventHandler „notifier“ jegliche Daten als Parameter durchreichen, diese Implementierung vereinfacht das Handling der Daten extrem.

setChanged() ist von Nöten, da ein Beobachter auch nur benachrichtigt wird, wenn sich auch etwas geändert hat.
In Java ist es zudem sehr praktisch, dass man jede Art von Daten in der Methode

notifyObservers();

versenden kann. Mit dieser werden die Beobachter auch benachrichtigt.

Im Java Observer wird nun die Methode

update(Observable _ob, Object message)

, welche wir durch das Interface implementierten, ausgeführt.
In C# rufen der EventHandler die Observer-Methode

 update(dynamic message, EventArgs empty) 

aus.

Im Anhang befindet sich das C# Test Projekt, als .sln Solution:
ObserverPattern

Release des Valve Server Tools für Windows Phone 8

Gestern war es soweit, das Valve Server Tool ging im Windows Phone Store online und kann nun auf Windows Phone 8.1 Geräten installiert werden.

„Das Valve Server Tool ist eine App zur Abfrage von Server-Daten der HLDS (Half-Life-Dedicated Servers) und SRCDS (Source Dedicated Servers), bspw. Counter-Strike:Source Servern für Game-Server Admins.
Welche Map läuft gerade auf dem Server und wie viele Spieler befinden sich auf diesem? Welche Regeln sind für diesen gerade aktiv?

Features:
* Klassische Server Information über das Valve Query Protokoll (A2S_INFO)
* Informationen über die aktuellen Spieler auf dem Server (A2S_PLAYER)
* Informationen über die, auf dem Server gültigen Regeln (A2S_RULES)

App-Sprache: Englisch“

Zur Projektseite geht’s hier entlang.

Update:
– Fixed connection issues to servers

Screenshots:
Serverdata_wvga PlayerStats_wvga Mainmenu_wvga Rules_wvga

UML Beziehungen und ihre Umsetzung im Code

Wer Software plant und entwickelt wird unweigerlich mit der Unified Modeling Language (kurz UML) in Berührung kommen.
Ich werde in  diesem Blog-Eintrag vor allem auf das Klassendiagramm, genauer auf die Beziehungen der einzelnen Klassen eingehen.

Das Klassendiagramm:

UML_ClassdiagramDas Klassendiagramm zeigt  Klassen eines Namespaces an und deren jeweilige Abhängigkeiten. Es handelt sich um eine statische Darstellung, da lediglich die Attribute und Methoden, sowie die Verbindungen der Klassen untereinander dargestellt werden. Es wird nicht gezeigt, wie diese Verbindungen stattfinden.

Darstellungselemente:

Zur Realisierung eines Klassendiagramms stehen folgende Formen und Notationen zur Verfügung

Klasse

ClassEine Klasse wird mit als Rechteck dargestellt und enthält Attribute und Methoden (Operationen). Sollte es sich um eine abstrakte Klasse handeln, so wird der Klassennamen kursiv dargestellt

Zusätzlich werden die in der Klasse enthaltenen Attribute und Operationen (bspw. Methoden) mit deren Daten- oder ggf. Rückgabetypen und Sichtbarkeiten dargestellt.

Class_ExplanationClass_AttrOperation

Zur Darstellung der Sichtbarkeit von Attributen und Funktionen bietet UML folgende Zeichen:

  • + steht für public, also eine öffentliche Funktion/ein öffentliches Attribut
  • # steht für protected, also eine geschützte Funktion/ein geschütztes Attribut
  • – steht für eine private Funktion/ein privates Attribut

Wie Anfangs erwähnt, stellt das Klassendiagramm auch die Beziehungen der Klassen untereinander dar. Ich werde in diesem Beitrag auf folgende Beziehungen eingehen und zu der jeweiligen Beziehung ein Codebeispiel (C#) geben:

  • Vererbung
  • Binäre Assoziation (zwei Klassen sind beteiligt)
  • Aggregation
  • Komposition
Vererbung:

Eine Vererbung (engl.: Inheritance) ist auch als „Ist-Ein-Beziehung“ oder „Ist-Implementiert-Als-Beziehung“ bekannt.

Als Beispiel dienen hier die Klassen Employee und Manager
Da ein Manager auch ein Mitarbeiter (Employee) ist, erbt die Klasse Manager von Employee
Dadurch werden Variablen, Felder und Methoden, welche nicht private sind in die erbende Klasse übernommen.
Achtung: Konstruktoren werden nicht vererbt und müssen daher manuell angesprochen werden:

public Manager(string _name):base(_name){ ... }

Die Umsetzung in C# würde wie folgt aussehen (Bild):

Employee-Klasse
Manager-Klasse
(Binäre) assoziation:

Eine Assoziation ist eine beliebige Beziehung zwischen den Objekten von Klassen. Hier werden wir nur die binären Assoziationen (2 Klassen sind beteiligt) behandeln.

Dies ist eine gerichtete Assoziation, das bedeutet, dass Klasse 1 die Klasse 2 kennt, dagegen Klasse 2 nichts von Klasse 1 weiß. In der Beschreibung wird Beschrieben auf welche Weise die Objekte in Verbindung stehen

Links sehen wir eine bidirektionale binäre Assoziation, d.h. die Firma kennt seinen Mitarbeiter und der Mitarbeiter seine Firma.

Die Form der Assoziation sähe in der Programmierung wie folgt aus (Man beachte die Kommentierung):

Bidirektionale binäre Assoziation
Aggregation:

Bei der Aggregation handelt es sich um eine spezielle Form der Assoziation. Sie ist auch als „Ganzes-Teil-Beziehung“ oder „Hat-Ein-Beziehung“ bekannt.
Darunter versteht man ein Objekt (Aggregatobjekt), welches aus verschiedenen Einzelteilen (Objekten) besteht, wobei diese nicht existenzabhängig sind (d.h. sie leben weiter, auch wenn das Aggregatobjekt zerstört wird.

Eine Beispiel für eine Aggregation wäre das Auto (als Ganzes) und ein Rad, als Teil.

Allgmeines BeispielAuto-Rad-Beispiel
public class Aggregatklasse
{
    ExistenzUnabhängigeKlasse objKlasse;
    public void doSomething(ExistenzUnabhängigeKlasse obj)
    {
      //Das Objekt der Klasse ExistenzUnabhängigeKlasse wird als Parameter übergeben 
      objKlasse = obj; 
	  // und ist somit noch existent wenn die Aggregatklasse zerstört wird
    }
}
Wird nachgereicht…

Komposition:

CompositionBei der Komposition handelt es sich um eine strenge Form der Aggregation. Das Kompositionsobjekt besteht, wie bei der Aggregation auch, aus mehreren Einzelteilen (Komponentenobjekte), welche jedoch im Gegensatz zur Aggregation mit der Zerstörung des Ganzen (Kompositionsobjekt) zerstört werden (Existenzabhängigkeit).
Damit ist auch die Kardinalität/Multiplizität auf Seite des Kompositionsobjekts immer 1. Das bedeutet, dass eine Komponente nur Teil eines Objekts sein kann. Somit wird sichergestellt, dass die Lebensdauer aller Objekte gleich ist. Die Kardinalität auf der Seite der Komponenten kann jedoch variable sein

Beispiel:
Ein Kunde einer Bank hat mindestens eine Anschrift und keine oder mehrere hinterlegte Bankverbindungen (kann per Rechnung zahlen).

CompositionExampleStirbt der Kunden, so wohnt er logischerweise nicht mehr und ist ebenso wenig ein Kunde der Bank. Seine Anschrift(en), sowie seine Bankverbindung(en) werden also zerstört

Die Umsetzung im Code würde wie folgt aussehen:

Codebeispiel Komposition

[C#/.NET] Eigenen OpenFileDialog erstellen

Zwar hat das .NET-Framework mit den von der CommonDialog-Klassen ebernden Dialog-Klassen (dazu gehören OpenFileDialog, FolderBrowserDialog) schon Dialoge, welche die Funktion bieten Daten zu öffnen, zu speichern oder das Verzeichnis zu wählen. Diese sind aber leider nicht erweiterbar und daher nicht für sämtliche Zwecke zu gebrauchen.

In diesem Beitrag werde ich deshalb eine Möglichkeit aufzeigen, einen eigenen Dialog zu erstellen für verschiedene Zwecke zu erstellen.

Einfacher Dialog zum Öffnen von Dateien

Zum Erstellen eines einfachen OpenFileDialogs benötigen wir erstmal eine Form. In der Form platzieren wir ein TreeView, sowie zwei Buttons. Zusätzlich benötigen wir eine ImageList, welche die eigentlichen Icons der Dateien und Verzeichnisse zwischenspeichert und aus welcher wir die benötigten Icons beziehen werden.

Nachdem wir die benötigten Controls hinzugefügt haben, benötigen wir eine Methode, die uns alle Laufwerke in die TreeView lädt. Diese bilden nämlich jeweils den Stammknotenpunkt (RootTreeNode).

Die Methode sieht wir folgt aus und soll bei dem Aufruf der Form durchlaufen werden:

private void GetAllDrives()
{
	DriveInfo[] drives = DriveInfo.GetDrives();
	foreach (var drive in drives)
	{
		TreeNode rootTreeNode = new TreeNode();
		rootTreeNode.Text = drive.Name;
		rootTreeNode.Tag = drive.Name;
		rootTreeNode.ImageIndex = GetIconOfFile_Folder(drive.Name);
		rootTreeNode.SelectedImageIndex = rootTreeNode.ImageIndex;
		rootTreeNode.Nodes.Add(" "); //Placeholder to enable expanding (+)
		FolderAndFiles_treeView.Nodes.Add(rootTreeNode);
	}
}

Die Methode GetIconOfFile_Folder bezieht das Icon des Elements, wie es der Windows Explorer auch anzeigt.
Zum Beziehen wird der Namespace System.Runtime.InteropServices benötigt. Diesen binden wir per

using System.Runtime.InteropServices;

ein.
Folgender Code wird zum Beziehen der Icons benötigt:

[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
	public IntPtr hIcon;
	public IntPtr iIcon;
	public uint dwAttributes;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
	public string szDisplayName;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
	public string szTypeName;
};

class Win32
{
	public const uint SHGFI_ICON = 0x100;
	public const uint SHGFI_LARGEICON = 0x0;    // 'Large icon
	public const uint SHGFI_SMALLICON = 0x1;    // 'Small icon

	[DllImport("shell32.dll")]
	public static extern IntPtr SHGetFileInfo(string pszPath,
								uint dwFileAttributes,
								ref SHFILEINFO psfi,
								uint cbSizeFileInfo,
								uint uFlags);
}

private int GetIconOfFile_Folder(string Path)
{
	IntPtr hImgSmall;    //the handle to the system image list
	SHFILEINFO shinfo = new SHFILEINFO();

	hImgSmall = Win32.SHGetFileInfo(Path, 0, ref shinfo,
								   (uint)Marshal.SizeOf(shinfo),
									Win32.SHGFI_ICON |
									Win32.SHGFI_SMALLICON);

	System.Drawing.Icon myIcon =
		   System.Drawing.Icon.FromHandle(shinfo.hIcon);

	FolderAndFiles_imageList.Images.Add(myIcon);

	return FolderAndFiles_imageList.Images.Count - 1; //Ab 0 (zero) wird angefangen, somit ist die Gesamtzahl n+1
}

Bisher werden nur die aufklappbaren Laufwerke angezeigt. Sie besitzen jedoch noch keine weiteren Knoten. Diese füllen wir indem wir einen EventHandler für das BeforeExpand-Event mittels

FolderAndFiles_treeView.BeforeExpand += FolderAndFiles_treeView_BeforeExpand;

erstellen.

Die dazugehörige Methode FolderAndFiles_treeView_BeforeExpand sieht wie folgt aus:

private void FolderAndFiles_treeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
	e.Node.Nodes.Clear();
	GetFilesAndFolder(e.Node, (string)e.Node.Tag);
}

Das Event ruft also die Methode GetFilesAndFolder auf, welche zuerst die unter dem erweiterten Knoten liegenden Knoten löscht und danach die Verzeichnisse und Dateien bezieht. Diese Methode benötigt zum einen den erweiterten Knoten, sowie das Verzeichnis aus welchem die Ordner und Dateien bezogen werden sollen (dies wurde im Tag gespeichert) als Parameter.
Die Methode selbst sieht wie folgt aus:

private void GetFilesAndFolder(TreeNode tn, string Path)
{
	try
	{
		string[] Directories = Directory.GetDirectories(Path);
		string[] Files = Directory.GetFiles(Path);

		foreach (string dir in Directories)
		{
			TreeNode dirTreeNode = new TreeNode();
			dirTreeNode.Tag = dir;
			dirTreeNode.Text = new DirectoryInfo(dir).Name;
			dirTreeNode.ImageIndex = GetIconOfFile_Folder(dir);
			dirTreeNode.SelectedImageIndex = dirTreeNode.ImageIndex;
			dirTreeNode.Nodes.Add(" ");
			tn.Nodes.Add(dirTreeNode);
		}

		foreach (string file in Files)
		{
			TreeNode fileTreeNode = new TreeNode();
			fileTreeNode.Tag = file;
			fileTreeNode.Text = new FileInfo(file).Name;
			fileTreeNode.ImageIndex = GetIconOfFile_Folder(file);
			fileTreeNode.SelectedImageIndex = fileTreeNode.ImageIndex;
			tn.Nodes.Add(fileTreeNode);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
	}
}

Den schwierigsten Teil haben wir jetzt hinter uns, die Dateien und Verzeichnisse werden bezogen und in der TreeView angezeigt und die jeweiligen Icons bezogen.
Jetzt müssen wir lediglich noch die zwei Buttons belegen.
Ich habe dies so gelöst:
Abbrechen

private void Cancel_button_Click(object sender, EventArgs e)
{
	this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
	this.Close();
}

Annehmen

private void Accept_button_Click(object sender, EventArgs e)
{
	string filePath = (string)FolderAndFiles_treeView.SelectedNode.Tag;
	if (CheckIfPathIsFile(filePath) == true) // Sollte es sich um eine Datei handeln
	{
		this.FilePath = filePath; //Bei FilePath handelt es sich um ein public Property
		this.DialogResult = System.Windows.Forms.DialogResult.OK;
		this.Close();
	}
	else //Sollte es sich um ein Verzeichnis handeln
	{
		FolderAndFiles_treeView.SelectedNode.Expand(); //erweitere den aktuell gewählten Knoten
	}
}

In der Methode des AcceptButton-ClickEvents wird auch geprüft, ob es sich bei dem gewählten Element um eine Datei oder ein Verzeichnis handelt. Dies geschieht mit folgender Methode:

private bool CheckIfPathIsFile(string Path)
{
	FileAttributes attr = File.GetAttributes(Path);
	if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
		return false;
	else
		return true;
}

Es wird also Wahr zurückgegeben, wenn es sich um eine Datei handelt.

Zu guter Letzt müssen wir nur noch den FileDialog aufrufen, dies geschieht mit folgender Methode:

private void OpenSimpleFileDialog()
{
	SimpleOpenFileDialog simpleOpenFileDia = new SimpleOpenFileDialog();
	if(simpleOpenFileDia.ShowDialog() == System.Windows.Forms.DialogResult.OK)
		MessageBox.Show(simpleOpenFileDia.FilePath);
}

Das Ergebnis sieht wie folgt aus:

2013-12-02 14_43_34-SimpleOpenFileDialog

[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

[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

© 2018 Abou Chleih. Alle Rechte vorbehalten.

Thema von Anders Norén.