Blog Home  Home Feed your aggregator (RSS 2.0)  
HP's Blog - SprachenC
Hans-Peter Schelian's Weblog
 
# Wednesday, December 30, 2009

Vor kurzem wurde im Zuge eines Projektes  (Unterstützung eines Entwickler Teams für eine Winform Anwendung im Logistikumfeld) unter anderem die Anforderung an mich heran getragen, dass der Kunde die Ansprechpartner für eine bestimmte Logistische Aktivität aus einer Combobox auswählen möchte.

Hierbei sollte in der Combobox, nicht nur ein Feld aus der Datenbank angezeigt werden, sondern die Anzeige sollte aus insgesamt 2 Feldern zusammengebaut werden.

Das Format sollte so aussehen: Nachname, Vorname

Hier ein Beispiel:

Mustermann, Hans

Dabei sollte die Lösung vollkommen im Client implementiert sein und nicht durch eine geänderte Abfrage der Datenbank realisiert werden.

Nun ist es aber so, dass man dem DisplayMember nur ein Feld der Datengebundenen Tabelle (Objekt) zuweisen kann und nicht mehrere oder sogar wie hier gewünscht diese Felder auch noch in einem bestimmten Format.

Sicherlich könnte man sich ein eigenen Objekt (List Objekt) erzeugen welches man dann als Datenquelle für die Bindung verwendet, aber es geht auch (viel) einfacher.

Hierzu verwenden wir dem Format Event der Combobox.

Dieser Event bekommt zwei Parameter mit übergeben:

object sender

ListControlConvertEventArgs e

Wobei wir für die hier beschriebene Lösung lediglich den ListControlConvertEventArgs Parameter benötigen und auch verwenden.

in e.ListItem wird das Datengebundene Objekt übergeben und in e.Value kann man den gewünschten Wert zurückgeben.

Hier ein zusammenhängendes Beispiel:

private void cmbSpediteurKontakt_Format(object sender, ListControlConvertEventArgs e)
{
	// Hier caste ich mir den übergeben Wert in das original Objekt
	var spediteurKontakt = ((SpediteureKontakte) e.ListItem);  
	// Und nun bastele ich mir die gewünschet Anzeige zusammen
	e.Value = String.Format("{0}, {1}", spediteurKontakt.Nachname, spediteurKontakt.Vorname);
}

Und das Ergebnis sieht dann so aus:

image

Tips und Tricks | C#
Wednesday, December 30, 2009 3:07:00 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, September 29, 2009

In einer Windows Anwendung wird in einer Form unter anderem ein (eigentlich sind es zwei, aber dazu später mehr) DateTimePicker verwendet um Termine einzugeben.

Da der DateTimePicker nicht gerade komfortabel ist um sowohl Datum als auch Uhrzeiten einzugeben, wird ein DateTimePicker in der Option DropDownCalender (das ist deaktivierte Eigenschaft ShowUpDown) eingesetzt um das Datum elegant aus dem DropDownCalender auszuwählen und ein zweites DateTimePicker Control mit der gesetzen Eigenschaft ShowUpDown und dem entsprechenden Custom Format HH:mm um nur die Uhrzeit mit den Pfeilen hoch und runter einstellen zu können.

image

Nun ist es aber so, dass man im DateTimePicker keinen Inkrement Wert eingeben kann, um den die Minuten erhöht bzw vermindert werden wenn man einmal auf den Pfeil hoch oder runter klickt. Somit wird jeweils um eine Minute hoch oder runter gezählt.

In meinem Fall nun wollte der Kunde aber das Termine nicht Minutengenau sondern immer auf 15 Minuten (Viertelstunde) Basis, also 00, 15, 30 und 45 erfasst werden können.

Da ich nicht gleich ein eigenen Control entwickeln wollte, habe ich einfach eine kleine Routine in den Change Event des DateTimePicker eingebaut.

Nachfolgend der Code Ich denke die Routine ist selbsterklärend

private void startzeitTimePicker_ValueChanged(object sender, EventArgs e)
{
    DateTimePicker dtp = (DateTimePicker)sender;
    // Wenn nicht Minute nicht 0,15,45 oder 60 dann müssen wir was tun
    if ((((dtp.Value.Minute != 0) && (dtp.Value.Minute != 15)) && (dtp.Value.Minute != 30)) &&
        (dtp.Value.Minute != 45))
    {
        // Auch noch einfach, nur 14 Minuten drauf und gut ist
        if ((dtp.Value.Minute == 1) || (dtp.Value.Minute == 16) || (dtp.Value.Minute == 31) || (dtp.Value.Minute == 46))
        {
            dtp.Value = dtp.Value.AddMinutes(14);
        }
        else
        {
            // Zeit wurde heruntergezählt.
            if ((dtp.Value.Minute == 14) || (dtp.Value.Minute == 29) || (dtp.Value.Minute == 44) || (dtp.Value.Minute == 59))
            {
                int x = 14;     // Deshalb auf jeden Fall mal 14 Minuten abziehen,
                if (dtp.Value.Minute == 59)  // und wenn die Minuten auf 59 stehen, dann muss noch eine Stunde (60 minuten) mehr abgezogen werden
                {
                    x += 60;
                }
                dtp.Value = dtp.Value.AddMinutes(-x);
            }
            else // Wert muss manuell eingegeben worden sein, dann runden wir auf die nächst höhere Viertelstunde
            {
                for (int i = 0; i < 15; i++)
                {
                    int x = dtp.Value.Minute + i;
                    if ((x == 15) || (x == 30) || (x == 45) || (x == 60))
                    {
                        dtp.Value = dtp.Value.AddMinutes(i);
                        break;
                    }
                }
            }
        }
    }
}

Hoffe das hilft dem einen oder anderen (Oder mir selbst beim nächsten mal wenn ich vor dem gleichen Problem stehen Wink)

Code | Tips und Tricks | C#
Tuesday, September 29, 2009 12:39:00 PM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Friday, August 07, 2009

In einer Anwendung verwende ich eine Klasse um die Appointmens eines Kalenders abzubilden. Diese Klasse ist relativ Komplex und um diesen Beitrag nicht ebenfalls zu komplex werden zu lassen, verwende ich hier eine (sehr) abgespeckte Version dieser Klasse um das Thema Sortierung einer ArrayListe von Komplexen Objekten zu veranschaulichen.
Die Verwendung einer ArrayList war hier zwingend vorgeschrieben (typisierte Listen List<Type> kamen nicht in Frage)

Zuerst einmal die Aufgabenstellung:

In einer ArrayListe (nennen wir Sie AppointmentList) befinden sich Termine in Form von Objekten (nennen wir sie Appointment). Diese Objekte haben unter anderem eine Eigenschaft (Property) Startdatum (vom Type DateTime).

Da die Appointment in der AppointmentList unsortiert vorliegen und eine Sortierung (eigentlich zwei Sortierungen Auf und Absteigend) nach dem Startdatum des Termins benötigt wird, bestand die Aufgabe die AppointmentList zu sortieren.

Die ArrayLIst verfügt über eine Methode Sort. Da es sich bei den zu sortierenden Objekten aber um ein Komplexe Klasse handelt, muss man der Sort Methode einen Comparer an die Hand geben, damit die Methode Sort Ihre Aufgabe durchführen kann.

Hier nun die Umsetzung:

Zuerst einmal die (komplexe) Klasse Appointment (eine abgespeckte Variante)

public class Appointment
{

    public Appointment()
    {
        appointmentGUID = Guid.NewGuid();
    }


    private readonly Guid appointmentGUID = new Guid("00000000000000000000000000000000");

    public Guid AppointmentGUID
    {
        get { return appointmentGUID; }
    }

    private DateTime startDate;

    public DateTime StartDate
    {
        get { return startDate; }
        set
        {
            startDate = value;
            OnStartDateChanged();
        }
    }

    protected virtual void OnStartDateChanged()
    {
    }


    private DateTime endDate;

    public DateTime EndDate
    {
        get { return endDate; }
        set
        {
            endDate = value;
            OnEndDateChanged();
        }
    }

    protected virtual void OnEndDateChanged()
    {
    }



    private string title = "";

    [DefaultValue("")]
    public string Title
    {
        get { return title; }
        set
        {
            title = value;
            OnTitleChanged();
        }
    }

    protected virtual void OnTitleChanged()
    {
    }


    public override string ToString()
    {
        return Title;
    }

}

Der Comparer für diese Aufgabe sieht dann so aus (Methode zum Sortieren mit Implementierung der IComparer Schnittstelle:

public class AppointmentComparer : IComparer
{
    private bool descendSorting;
    public AppointmentComparer(bool descend)
    {
        descendSorting = descend;
    }

    int IComparer.Compare(object a, object b)
    {
        var b1 = (Appointment)a;
        var b2 = (Appointment)b;

        if (descendSorting)
        {
            return DateTime.Compare(b2.StartDate, b1.StartDate);
        }
        else
        {
            return DateTime.Compare(b1.StartDate, b2.StartDate);
        }
    }

}

Und verwenden kann man diesen Comparer wie folgt:

            al.Sort(new AppointmentComparer(false));
            al.Sort(new AppointmentComparer(true));

al wurde dabei wie folgt deklariert:

ArrayList al = new ArrayList();
Code | C#
Friday, August 07, 2009 1:14:00 PM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, January 06, 2009

Hintergrund

In einer Windows Form Applikation wird in einer Form ein DataGridView verwendet das je nach Bedarf Schreibgeschützt (Alle Spalten) oder zur Eingabe von Daten (nicht Schreibgeschützt) verwendet werden soll. Soweit würde die Anforderung auch kein Problem darstellen, wenn - aber dazu lest einfach den Rest des Beitrags.

Nun ist es aber so, dass auch in der Variante indem das DataGridView als Eingabe (also nicht Schreibgeschützt) verwendet wird, die eine oder andere Spalte des DataGridView sehr wohl (zur Zeit der Entwicklung, also nicht zur Laufzeit) auf Schreibgeschützt gesetzt wurden.

Eigentlich sollte das ganze kein Problem darstellen, also "Einfach sein", wie es im Lied der FANTA4 beschrieben ist, aber wie geht es im Lied weiter; "is es aber nicht", und genau so ist es auch in dem hier beschriebenen Fall.

Das Problem liegt daran, dass durch das setzen der ReadOnly Eigenschaft des DataGridView auf True das DataGridView "vergisst" welche Spalten des DataGridView während der Entwicklungszeit auf ReadOnly gesetzt wurden.

Nachdem man während der Laufzeit die ReadOnly Eigenschaft des DataGridView einmal auf True und dann später wieder zurück auf False gesetzt hat, sind alle Spalten, auch diese die vorher ReadOnly waren plötzlich nicht mehr Schreibgeschützt.

Die Idee

Die Idee der nachfolgenden Lösung war schnell entstanden und wird im folgenden beschrieben.

Beim Start der Windows Form (am besten direkt im Konstruktor) muss man die Information der Schreibgeschützten Spalten sichern, und diese nachdem die ReadOnly Eigenschaft des DataGridView zurück auf False geändert wurde, einfach wieder auf die ursprünglichen Werte herstellen.

Die Lösung

Um die Information, welche Spalten beim Start der Windows Form Schreibgeschützt sind zu speichern, verwende ich ein typisiertes Array von int Werten um den Spalten Index der Schreibgeschützten Spalten zu sichern.

private List<int> saveReadOnlyColumn;

Die Logik zum Speichern der Informationen habe ich in eine eigene Methode (siehe nachfolgenden Code der Methode saveReadOnlyColumnInformation) ausgelagert, welche ich dann einfach am Ende des Konstruktor aufrufe.

private void saveReadOnlyColumnInformation()
{
    saveReadOnlyColumn = new List<int>();

    foreach (DataGridViewColumn o in dgVerladung.Columns)
    {
        if (o.ReadOnly)
        {
            saveReadOnlyColumn.Add(o.Index);
        }
    }
}

So nun haben wir die Informationen der Schreibgeschützten Spalten im typisierten Array für den späteren Gebrauch gespeichert.

Dann kümmern wir uns nun noch darum, diese gespeicherte Information immer dann wenn die ReadOnly Eigenschaft auf False gesetzt wurde, wieder auf die Startwerte zurücksetzen.

Hierzu können wir einfach den ReadOnlyChanged Event des DataGridView verwenden, welcher immer dann ausgelöst wird, wenn man im Code die ReadOnly Eigenschaft ändert.

Nachfolgend ist der Code dargestellt um die vorher gespeicherten Schreibgeschützten Spalten wieder herzustellen

private void dgVerladung_ReadOnlyChanged(object sender, EventArgs e)
{
    if (dgVerladung.ReadOnly == false)
    {
        foreach (int i in saveReadOnlyColumn)
        {
            dgVerladung.Columns[i].ReadOnly = true;
        }
    }
}

 

Der Vollständigkeit halber hier noch der Code des Konstruktor's (Hier erklärt die Zeile 4) dargestellt.

public formPlanungen()
{
    InitializeComponent();
    saveReadOnlyColumnInformation();
    Application.Idle += new EventHandler(Application_Idle);
}
Programmierung | Code | Tips und Tricks | C#
Tuesday, January 06, 2009 11:26:34 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, November 04, 2008

Problemstellung

Die Daten eines DataGridView sollen über die Zwischenablage in verschiedene andere Anwendungen (unter anderem auch in Excel) kopiert und eingefügt werden können.

Hierbei soll es wahlweise Möglich sein, beim kopieren in die Zwischenablage, die Spaltenköpfe des DataGridView mit in die Zwischenablage zu kopieren. Außerdem soll es neben der Tastenkombination Strg + C auch über Programmcode (Button und oder Contextmenü) möglich sein, die Daten in die Zwischenablage zu kopieren.

Umsetzung - Teil 1 Spaltenüberschriften einschließen

Ob die Spaltenköpfe beim kopieren der DataGridView Daten mit einbezogen werden kann über die Eigenschaft ClipboardCopyMode des DataGridView gesteuert werden.

Die Eigenschaft kann auf folgende Werte gesetzt werden:

image

Der Standardwert lautet EnableWithoutHeaderText (Kopieren der markierten Daten ohne Spaltenköpfe).

Um sicherzustellen, dass auch die Spaltenköpfe mit in die Zwischenablage kopiert werden, muss der Wert EnableAlwaysIncludeHeaderText gesetzt werden.

DataGridView1.ClipboardCopyMode = EnableAlwaysIncludeHeaderText ;

Umsetzung - Teil 2 Programmatisches kopieren der Zwischenablage

Um per Programmcode die Daten (mit oder ohne Spaltenköpfe) in die Zwischenablage zu kann der folgende Code verwendet werden:

Clipboard.SetDataObject(DataGridView1.GetClipboardContent(), true);
Programmierung | Code | C#
Tuesday, November 04, 2008 2:50:43 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Wednesday, October 29, 2008

Die Anforderung:

In einem gebundenen DataGridView, soll eine Suchfunktion implementiert werden, die es ermöglicht, dass man je nach angewählter Spalte innerhalb der Spalte nach einem in ein Suchfeld einzugebenden Text gesucht werden kann. Allerdings soll nicht nur auf genaue Übereinstimmung, sondern auch nur auf Teil -Übereinstimmung der dem Suchbegriff entsprechende erste Eintrag gefunden werden.

Die Problematik:

Grundsätzlich wird für die Umsetzung der Anforderung von der BindingSource eine Methode Find zur Verfügung gestellt.

Beispiel:

bool found = false;

int i = customerBindingSource.Find(dgCustomer.SortedColumn.DataPropertyName, txtSearch.Text);
if (i > -1)
{
    customerBindingSource.Position = i;
}
else
{
}

Soweit funktioniert das Prima, wenn der Suchtext genau mit dem Inhalt der Zelle entspricht.

Was aber wenn zum Beispiel in einer Spalte die Werte für den Name der erste Eintrag der mir M beginnt gefunden werden soll?

Dann funktioniert die Find Methode nicht, und leider bietet weder die BindingSource noch das DataGridView hierzu eine entsprechende Möglichkeit dies einfach per Methodenaufruf zu realisieren.

Die Eine Lösung:

Schauen wir uns doch mal an, was wir mit dem oben noch leeren Else Zweig anfangen können.

Das wir über die BindingSource nicht weiter kommen haben wir schon festgestellt, also müssen wir uns selbst etwas basteln.

Beispiel:

string searchField = dgCustomer.SortedColumn == null ? 
"Hier einfach den DataPropertyName des gewünschten Standardsuchfeldes wenn keine Spalte markiert ist" : dgCustomer.SortedColumn.DataPropertyName;
string searchCellName = dgCustomer.SortedColumn == null ? 
"Hier einfach den Namen des gewünschten Standardsuchfeldes wenn keine Spalte markiert ist" : dgCustomer.SortedColumn.Name;
bool found = false;

int i = customerBindingSource.Find(searchField, txtSearch.Text);
if (i > -1)
{
	customerBindingSource.Position = i;
}
else            
{
	foreach (DataGridViewRow row in dgCustomer.Rows)
	{
		if (row.Cells[searchCellName].Value == null)
		{
			continue;
		}

		if (row.Cells[searchCellName].Value.ToString().ToLower().StartsWith(txtSearch.Text.ToLower()))
		{
			i = customerBindingSource.Find(searchField, row.Cells[searchCellName].Value.ToString());
			if (i > -1)
			{
				customerBindingSource.Position = i;
				found = true;
				break;
			}
		}
	}
	if (!found )
	{
		string msg = string.Format(CultureInfo.CurrentUICulture, "{0} {1} konnte nicht gefunden werden", searchField, txtSearch.Text);
		MessageBox.Show(msg);                                                
	}
}

Ich Denke der Code ist selbsterklärend und bedarf keiner weiteren Erklärung.

Wenn ich mich damit täuschen sollte, dann einfach per Kommentar die Fragen stellen, oder noch besser gleich die Antworten geben smile_regular.

Code | C#
Wednesday, October 29, 2008 9:19:30 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, July 22, 2008

Das Open Source Projekt/Programm 7-ZIP erfreut sich großer Beliebtheit, und (fast) jeder wird das Programm (Die Windows Oberfläche) kennen.

Was aber wenn man gerne die Funktionalitäten von 7-ZIP in eigenen .NET Projekten (Managed Code) verwenden möchte.

Das ist nicht so einfach, da 7-ZIP in C++ (Unmanaged Code) geschrieben ist und eine direkte Verwendung der Methoden aus .NET Sprachen nicht möglich ist.

Heute bin ich aber auf einen interessanten Artikel gestoßen, der sich mit der Implementierung einer .NET Schnittstelle für die Nutzung der 7-ZIP DLL Bibliotheken beschäftigt.

In dem hier angesprochenen Artikel wird eine Schnittstellenimplementierung in C# vorgestellt.

http://www.codeproject.com/KB/DLL/cs_interface_7zip.aspx

Und wer 7-ZIP wirklich noch nicht kennen sollte, kann hier mehr darüber erfahren.

Link Tips | Open Source | C#
Tuesday, July 22, 2008 8:43:43 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, April 08, 2008

Wer sich mit Drag und Drop innerhalb des DataGridView beschäftigt wird unter Umständen ermitteln wollen, ob die Maus auf einem Spalten oder Zeilenkopf gedrückt und festgehalten wurde. Zu diesem Zweck verwende ich die nachfolgende Methode "IsCellOrRowHeader".

Hier die C# Methode IsCellOrRowHeader:

private bool IsCellOrRowHeader(int x, int y)
{
    DataGridViewHitTestType dgt = dgTodo.HitTest(x, y).Type;
    return (dgt == DataGridViewHitTestType.Cell ||
                    dgt == DataGridViewHitTestType.RowHeader);
}

Ich glaube ich hatte die Methode als Grundgerüst mal in Internet gefunden, heute wo ich diesen Beitrag verfasse, habe ich die Suchmaschine angeworfen um einen Link auf den ursprünglichen Autor bzw, den Beitrag zu finden. Ich konnte aber die Seite nicht mehr finden, wenn also jemand dieses Beitrag liest und weiß wo der Ursprung dieser Methode herkommt, dann hinterlasst doch bitte einen Kommentar.

Eine VB Variante dieser Funktion habe ich gerade doch noch gefunden.

Hier der Link: IsCellOrRowHeader in VB

Programmierung | Code | C#
Tuesday, April 08, 2008 12:43:22 PM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Friday, April 04, 2008

Auf meinem Beitrag "Erweiterungsmethoden am Beispiel einer isNumeric Methode für die String Klasse - C#" habe ich einige Interessante Reaktionen gehabt. Auch wenn die Reaktionen mal wieder über E-Mail und im Gespräch gemacht wurden und nicht über Kommentare direkt am Beitrag gemacht wurden. Ich glaube, soweit sind wir im Deutschsprachigen Raum noch immer nicht. Es gibt immer noch ganz viele die sich einfach nicht trauen einen öffentlichen Kommentar zu hinterlassen. In USA und England ist das ganz anders. Aber gut das ist ein anderes Thema.

Unter anderem wurde darauf hingewiesen, dass die Erweiterung der String Klasse um die Methode isNumeric ganz nett sei, aber nicht das gleiche wie eine Statische Methode wie ich sie im Beitrag "isNumeric - c# oder csharp" beschrieben habe. Die isNumeric Methode aus dem letztgenannten Beitrag lässt sich auf jedes Objekt und nicht nur auf Strings anwenden.

Und natürlich habe diese Kollegen recht, mit dieser Aussage.

Dann wollen wir doch einfach unsere Implementierung aus dem erst genannten Beitrag so ändern, dass Sie auch auf jedes Objekt angewendet werden kann.

Schauen wir uns dazu doch einmal die Herkunft der Datentypen an. Alle Objekt die wir erzeugen wurde irgendwann man vom Typ object abgeleitet, also warum erweitern wir nicht einfach die Basis aller Klassen und Strukturen.

Hier nun die Erweiterungsmethode isNumeric für den Basis Objekt Type object:

    public static class ObjectExtensions
    {
        public static bool isNumeric(this object o)
        {
            double retNum;
            bool isNum = Double.TryParse(Convert.ToString(o), System.Globalization.NumberStyles.Any,
                                         System.Globalization.NumberFormatInfo.InvariantInfo, out retNum);
            return isNum;
        }
    }

Und wie man nachfolgend sehen kann, ist es nun möglich die Methode isNumeric auf nahezu jedes Objekt anzuwenden:

        static void Main()
        {
            int i = 10;
            long l = 313131311;
            double d = 12.12;
            string s = "100";

            test(i);
            test(l);
            test(d);
            test(s);

        }

        private static void test(object o)
        {
            if (o.isNumeric())
            {
                Console.WriteLine("Ja das geht auch");
            }
        }
Programmierung | Tips und Tricks | C#
Friday, April 04, 2008 6:08:17 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Wednesday, April 02, 2008

Erweiterungsmethoden sind statische Methoden die in statischen Klassen definiert werden und als ersten Parameter das Schlüsselwort this mit nachfolgendem Type der zu erweiternden Klasse an die Methode übergibt. Diese Erweiterungsmethoden werden dann als Instanz Methode (wie z.B. die ToString() Methode) des jeweiligen Objekts verwendet.

Vor einiger Zeit hatte ich einen Beitrag geschrieben der sich mit der C# Implementierung der unter VB verfügbaren Funktion isNumeric beschäftigte.

In diesem Beitrag hatte ich eine public static Methode in einer Helper Klasse geschrieben, ich dachte es wäre anschaulich wenn wir nun diese Implementierung als Erweiterungsmethode der String Klasse vornehmen.

Hier zuerst noch einmal die Klassische Implementierung vor C# 3.0

public static class MathHelper
{
    public static bool IsNumeric(object Expression)
    {
        bool isNum;
        double retNum;
        isNum = Double.TryParse(Convert.ToString(Expression), System.Globalization.NumberStyles.Any,
                                System.Globalization.NumberFormatInfo.InvariantInfo, out retNum);
        return isNum;
    }
    
}

Diese konnte man wie folgt verwenden:

string o = "100";
if (MathHelper.IsNumeric(o))
    Console.WriteLine("Ja die Eingabe ist Numerisch");
else
    Console.WriteLine("Keine numerischen Eingabe");

So und nun zur Implementierung der isNumeric Funktionalität als Erweiterungsmethode der String Klasse:

Nun kann man mit dem C# 3.0 Feature der Erweiterungsmethoden (Extension Methods) diese aber auf viel elegantere weise lösen. Ich möchte dies hier an einem Beispiel der String Klasse demonstrieren. Wir erweitern also unsere String Klasse durch eine Erweiterungsmethode isNumeric.

Hierzu müssen wir zuerst eine Statische Klasse erstellen, in welcher wir dann die Erweiterungsmethode(n) deklarieren können.

public static class StringExtensions
{
    public static bool isNumeric(this string s)
    {
        double retNum;
        bool isNum = Double.TryParse(s, System.Globalization.NumberStyles.Any,
                                     System.Globalization.NumberFormatInfo.InvariantInfo, out retNum);
        return isNum;

    }
}

Verwendet wird das dann so:

string o = "100";
if (o.isNumeric())
    Console.WriteLine("Ja die Eingabe ist Numerisch");
else
    Console.WriteLine("Keine numerischen Eingabe");
Programmierung | Code | C#
Wednesday, April 02, 2008 11:01:51 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, March 31, 2008

In einem Projekt musste ich mit Daten arbeiten welche durch eine PHP Anwendung erstellt wurden (werden).

Unter anderem wird dabei die IP Adresse des Besuchers in einer Datenbank gespeichert.

Bei der Speicherung der IP wird diese aber nicht als String sondern als long Wert gespeichert.

PHP bietet hierzu folgende Funktionen an:

  • ip2long
  • long2ip

Obwohl, oder gerade weil ich kein PHP Guru bin, stellt sich mir nun die Aufgabe diese Long Werte wieder in IP Adressen zurück zu übersetzen.

Und eben nicht mit PHP sondern mit C#.

Hierzu habe ich mir 2 statische Methoden in einer IP-Helper Klasse erstellt die genau diese Funktionalität der PHP Funktionen in C# nachempfindet.

Nachfolgend meine beiden C# Methoden:

public static long ip2long(string ipAddress)
{
    System.Net.IPAddress ip;
    
    if (System.Net.IPAddress.TryParse(ipAddress,out ip))
    {                
            return (((long) ip.GetAddressBytes()[0] << 24) | (ip.GetAddressBytes()[1] << 16) |
                    (ip.GetAddressBytes()[2] << 8) | ip.GetAddressBytes()[3]);
    }
    return -1;
}

public static string long2ip(long ipAddress)
{
    System.Net.IPAddress ip;
    if (System.Net.IPAddress.TryParse(ipAddress.ToString(), out ip))
    {
        return ip.ToString();
    }
    return "";
}

Eventuell braucht das ja auch noch jemand anderes !

Programmierung | C#
Monday, March 31, 2008 12:51:53 PM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, March 27, 2008

Wer sich mit WinForms Entwicklung beschäftigt wird wohl auch mit dem DataGridView Control arbeiten.

Wenn man nun für die Spalte ein Checkbox Control "DataGridViewCheckBoxColumn" verwendet kommt es häufig vor, dass man auch auf das CheckedChanged Ereignis reagieren möchte.

Leider gibt es mit dem DataGridView Control nicht direkt die Möglichkeit einen solchen Event zu nutzen.

Außerdem wird bei der Verwendung von Datengebundenen Informationen an das DataGridView Control erst nach verlassen der aktiven Zelle die Dirty Eigenschaft der Zelle gesetzt, so dass dies nicht mehr zulässt direkt auf Änderung der Checked Eigenschaft zu reagieren.

Es gibt aber, wie fast immer, Abhilfe für das Problem.

Um  bereits vor dem Verlassen der Zelle, die Änderung der Checked Eigenschaft auszuwerten, kann man dem DataGridView den Event CurrentCellDirtyStateChanged hinzufügen und in diesem Event den nachfolgend dargestellten Code hinzufügen:

Als Einfaches Beispiel (Allgemeingültig):

private void dgVerladung_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (((DataGridView)sender).CurrentCell.OwningColumn is DataGridViewCheckBoxColumn)
    {
        ((DataGridView)sender).CommitEdit(DataGridViewDataErrorContexts.Commit);
    }
}

Ein etwas komplexeres Beispiel (nur als Beispiel):

    private void dgWorkTime_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (dgWorkTime.CurrentCell.OwningColumn.Name == disabledDataGridViewCheckBoxColumn.Name)
    {
        dgWorkTime.CommitEdit(DataGridViewDataErrorContexts.Commit);
        Day meinTag = (Day)dgWorkTime.CurrentCell.OwningRow.Cells[dayEnumDataGridViewTextBoxColumn.Name].Value;
        bool disabled = (bool)dgWorkTime.CurrentCell.Value;
        dgWorkTime.CurrentRow.Cells[startWorkingTimeDataGridViewTextBoxColumn.Name].ReadOnly = disabled;
        dgWorkTime.CurrentRow.Cells[endWorkingTimeDataGridViewTextBoxColumn.Name].ReadOnly = disabled;
        if (disabled)
        {
            dgWorkTime.CurrentRow.Cells[wochentagDataGridViewTextBoxColumn.Name].Style.BackColor =
                Properties.Settings.Default.WorkingTimeDisabledColor;
            dgWorkTime.CurrentRow.Cells[disabledDataGridViewCheckBoxColumn.Name].Style.BackColor =
                Properties.Settings.Default.WorkingTimeDisabledColor;
            dgWorkTime.CurrentRow.Cells[startWorkingTimeDataGridViewTextBoxColumn.Name].Style.BackColor =
                Properties.Settings.Default.WorkingTimeDisabledColor;
            dgWorkTime.CurrentRow.Cells[endWorkingTimeDataGridViewTextBoxColumn.Name].Style.BackColor =
                Properties.Settings.Default.WorkingTimeDisabledColor;
        }
        else
        {
            dgWorkTime.CurrentRow.Cells[wochentagDataGridViewTextBoxColumn.Name].Style.BackColor =
                dgWorkTime.CurrentRow.Cells[dayEnumDataGridViewTextBoxColumn.Name].Style.BackColor;
            dgWorkTime.CurrentRow.Cells[disabledDataGridViewCheckBoxColumn.Name].Style.BackColor =
                dgWorkTime.CurrentRow.Cells[dayEnumDataGridViewTextBoxColumn.Name].Style.BackColor;
            dgWorkTime.CurrentRow.Cells[startWorkingTimeDataGridViewTextBoxColumn.Name].Style.BackColor =
                dgWorkTime.CurrentRow.Cells[dayEnumDataGridViewTextBoxColumn.Name].Style.BackColor;
            dgWorkTime.CurrentRow.Cells[endWorkingTimeDataGridViewTextBoxColumn.Name].Style.BackColor =
                dgWorkTime.CurrentRow.Cells[dayEnumDataGridViewTextBoxColumn.Name].Style.BackColor;

        }


    }
Programmierung | Tips und Tricks | C#
Thursday, March 27, 2008 7:43:00 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, March 25, 2008

Lambda Expressions in C# umfassen sowohl Ausdrücke (expressions) als auch Anweisung's Blöcke (statement blocks).

Egal welche Art von Lambda Expressions verwendet werden, der Lambda Operator => wird jeweils dazu verwendet um die Expression zu beschreiben.

Was ist der Unterschied zwischen einem Ausdruck und einem Anweisung's Block.

Exemplarische Lambda Expression Ausdrücke
x => x * 5

(int x) => x * 5

o => o.FirstName == "Peter"

(Order o) => o.FirstName == "Peter"

Exemplarische Lambda Expression Anweisung's Blöcke

y => {return 5 * x)

Wo überall setzt man Lambda Expressions ein?

Ein großes Einsatzgebiet von Lambda Expressions sind zum Beispiel zusammen mit den ebenfalls in C# 3.0 neuen Extension Methods.

Programmierung | C#
Tuesday, March 25, 2008 7:26:33 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, March 20, 2008

Da sich Microsoft nicht einig ist wie man die Tage einer Woche in einer Aufzählung hinterlegt, muss man damit klarkommen, dass es verschiedene Auflistungen in verschiedenen Namensräumen gibt.

In einem meiner Projekte war ich nun auch damit konfrontiert, dass ich mit den beiden Aufzählungen:

  • DayOfWeek aus dem Namensraum System

und

  • Day aus dem Namensraum System.Windows.Forms

gleichzeitig zu tun hatte.

Dabei ergab sich die Notwendigkeit dass DayOfWeek in Day umwandeln musste.

Nachfolgend nun eine kleine Methode die aus DayOfWeek ein Day zurückgibt.

public static Day DayOfWeekToDay(DayOfWeek dow)
{
    return (Day)Enum.Parse(typeof(Day), dow.ToString());
}
Programmierung | Tips und Tricks | C#
Thursday, March 20, 2008 7:11:31 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, March 17, 2008

Auch wenn es auf den ersten Blick absurd erscheint, dass man eine Funktion benötigt um den ersten Tag eines Monats zu ermitteln, aber es gibt Situationen in welchen man Datumsbereiche bestimmen möchte in denen man aus einem beliebigen Datum den ersten Tag eines Monats ermitteln muss.

Nachfolgen eine C# Methode die genau dies macht:

public static DateTime FirstDateOfTheMonth(DateTime date)
{
    return date.AddDays(-(date.Day - 1));
}       
Programmierung | Tips und Tricks | C#
Monday, March 17, 2008 6:58:52 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [2]  
Autor: Hans-Peter Schelian  |  Trackback
# Wednesday, March 12, 2008

Ein einzelnes Control auf Readonly zu setzen ist einfach, aber was ist wenn man alle Controls einer Winforms Anwendung auf Readonly setzen möchte. Und ich meine wirklich auf Readonly nicht auf Enabled indem man ein Panel als Container der Controls verwendet und dann die Panel Eigenschaft Enabled auf false setzt.

Zu diesem Zweck habe ich mir eine kleine Routine erstellt, welche Rekursiv alle Controls (die ich möchte) einer ControlCollection auf Readonly setzt.

Hier nun die Methode setReadOnly:

private void setReadOnly(Control.ControlCollection Controls)
{
    foreach (Control control in Controls)
    {
        if (control is TextBox)
        {
            ((TextBox)control).ReadOnly = true;

        }
        if (control is ComboBox)
        {
            ((ComboBox)control).Enabled = false;
        }
        if (control is DateTimePicker)
        {
            ((DateTimePicker)control).Enabled = false;
        }

        if (control.Controls.Count > 0)
        {
            setReadOnly(control.Controls);
        }
    }

}
Programmierung | Tips und Tricks | C#
Wednesday, March 12, 2008 6:49:26 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [1]  
Autor: Hans-Peter Schelian  |  Trackback
# Wednesday, February 20, 2008

Fast zeitgleich mit dem Microsoft Launch von Windows Server 2008 und Visual Studio 2008 ist die Version 3.0 Beta 1 der Open Source  SharpDevelop Entwicklungsumgebung veröffentlicht worden.

Die neue Version kann man unter nachfolgendem Link herunterladen:

https://sourceforge.net/project/showfiles.php?group_id=17610&package_id=263439

Mehr Einzelheiten zu SharpDevelop im allgemeinen und der neuen Version im speziellen gibt es auf der Projekt Homepage:

http://www.icsharpcode.net/OpenSource/SD/

Open Source | C#
Wednesday, February 20, 2008 5:10:21 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, February 14, 2008

Vor einiger Zeit hatte ich in meinem Beitrag: "enum Werte an ComboBox binden" beschrieben wie man die Werte von Aufzählungen an eine ComboBox binden kann.

Nun habe ich bereits mehrfach die Frage gestellt bekommen (zum letzten mal Gestern in einem Kommentar zu diesem Beitrag) wie man denn die Wert auch wieder auslesen kann.

Das geht zum Beispiel so:

private void cmbEnum_SelectedIndexChanged(object sender, EventArgs e)
{
    switch ((AppHeightDrawMode)cmbEnum.SelectedItem)
    {
        case AppHeightDrawMode.TrueHeightAll:
            break;
        case AppHeightDrawMode.FullHalfHourBlocksAll:
            break;
        case AppHeightDrawMode.EndHalfHourBlocksAll:
            break;
        case AppHeightDrawMode.FullHalfHourBlocksShort:
            break;
        case AppHeightDrawMode.EndHalfHourBlocksShort:
            break;
        default:
            break;
    }
}

 

Wobei der folgende Code das entscheidende ist:

(AppHeightDrawMode)cmbEnum.SelectedItem

Wir könnte damit auch so arbeiten:

AppHeightDrawMode myDrawMode = (AppHeightDrawMode)cmbEnum.SelectedItem;

Dabei steht dann in myDrawMode der Enum Wert der mit der ComboBox ausgewählt wurde.


Dieses Beispiel bezieht sich auf den Beitrag: enum Werte an ComboBox binden

Kurz und Bündig | Programmierung | Code | C#
Thursday, February 14, 2008 8:08:45 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [2]  
Autor: Hans-Peter Schelian  |  Trackback
# Friday, January 25, 2008

Der NET Framework stellt zur Kommunikation mit Windows Diensten die Komponente ServiceController aus dem Namespace System.ServiceProcess zur Verfügung.

Die Verwendung dieser Komponente ist relativ einfach (Siehe nachfolgendes Beispiel).

Hier ein einfaches Beispiel:

ServiceController myServiceController = new ServiceController("ServiceName");

if (myServiceController.Status == ServiceControllerStatus.Stopped )
{
    myServiceController.Start();
}

 

In diesem Beispiel wird für den Service "ServiceName" auf dem lokalen Rechner ein ServiceController erstellt, und anschließend wird geprüft ob der Service gestoppt ist und wenn, dann wird er gestartet.

Aber was ist wenn der Service "SeviceName" gar nicht auf dem lokalen Rechner installiert ist.

Leider besitzt die Komponente ServiceController keine Eigenschaft oder Methode mit welcher man einfach prüfen kann ob der Service auf dem Computer installiert ist.
Es kommt also zu folgender Fehlermeldung Laufzeitfehler:

image

Natürlich kann man mit einem Try Catch Block den Fehler abfangen, aber es geht auch anders, ich vermeide lieber einen vorhersehbaren Fehler als dann in einer Exception darauf zu reagieren.

Zur Demonstration wie man das anders regeln kann, habe ich eine kleine Beispielklasse Namens ServiceControllerHelper geschrieben die eine statische Methode getServiceControllerForService enthält. Dieser Methode übergibt man einfach den Names des Service und erhält, wenn es den Service gibt, ein ServiceController Objekt zurück andernfalls ein null.

Hier nun die Klasse ServiceControllerHelper:

using System;
using System.ServiceProcess;

namespace ServiceControlSample
{
    public static class ServiceControllerHelper
    {
        public static ServiceController getServiceControllerForService(string shortName)
        {
            ServiceController[] services = ServiceController.GetServices();
            foreach (ServiceController service in services)
            {
                if (service.ServiceName == shortName)
                {
                    return service;
                }
            }
            return null;
        }

    }
}

 

Und so kann man diese Klasse einsetzen:

ServiceController myServiceController;

 

myServiceController = ServiceControllerHelper.getServiceControllerForService("ServiceName");

 

if (myServiceController != null)
{
    if (myServiceController.Status == ServiceControllerStatus.Stopped )
    {
        myServiceController.Start();
    }
}

 

Dieser Beitrag soll lediglich als Einstig in dieses Thema Dienen und keine vollständige Implementierung einer ServiceControllerHelper Klasse darstellen.

Code | Tips und Tricks | C#
Friday, January 25, 2008 12:54:49 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, January 24, 2008

Bedingte Kompilierung oder Kleines Präprozessor ABC für C#

Einleitung

Der Präprozessor unter C# ist nicht zu vergleichen mit den wesentlich komplexeren Präprozessoren der C und C++ Compiler.

Es gibt z.B. weder eine Makroverarbeitung noch die Möglichkeit mit #include sogenannte Header Dateien zu inkludieren. Da dieser Beitrag "Kleines Präprozessor ABC" heißt, möchte ich darauf verzichten auf die Einzelheiten solcher Unterschiede einzugehen.

Verfügbare Direktiven des C# Präprozessor

Direktive

Typ

Verwendung

#define Bezeichner Mit #define wird ein Bezeichner gesetzt. Stellen Sie sich einen Bezeichner wie eine boolsche Variable vor. #define ist dann so wie das setzen einer Variablen auf true. Einem Bezeichner können keine Werte zugewiesen werden
#undef Bezeichner Mit #undef heben Sie die Gültigkeit eines Bezeichners auf. Sie können dies auch mit dem setzen einer Variablen auf false setzen vergleichen.
#if Ausdruck Der Code innerhalb des mit #if eingeleiteten Abschnittes wird ausgeführt wenn der Ausdruck wahr ist (Bei einfachen #if Ausdrücken ist das der Fall wenn der auf #if folgende Bezeichner gesetzt ist.
#else   Ergibt der diesem #else vorangegangene Ausdruck false, dann wird der Bereich des #else ausgeführt.
#elif Ausdruck Anstelle des #else kann man mit Hilfe des #elif Ausdrucks diesen Bereich nur dann ausführen lassen, wenn der Angegebene Ausdruck true ergibt
#endif   Zeigt das Ende eines Ausdrucks bzw. einer zusammengehörenden Reihe von Ausdrücken an.
#warning Ausdruck Ausgabe einer Warnung während des Kompilieren's
#error Ausdruck Ausgabe einer Fehlermeldung während des Kompilieren's
#line Ausdruck Ermöglicht die Manipulation und Ausgabe von Zeilennummern, nähere Informationen hierüber kann man hier nachlesen
#region Direktive Bezeichnet den Anfang einer Region im Quelltext.
Beispiel #region Events
#endregion   Ende einer mir #region eingeleiteten Region im Quelltext
#pragma Direktive Mit #pragma gibt man spezielle Anweisungen für das Verhalten des Compilers.
Beispiel: #pragma warning disable 123, 4567 zum Abschalten bestimmter Compiler Warnungen. Nähere Informationen über die #pragma Direktiven gibt es hier

 

Operatoren für die Verwendung mit Ausdrücken

Operatoren Beispiel

Funktionsweise

! #if !DEBUG Das ! negiert den Wert von var. Im Beispiel bedeutet das, ist DEBUG nicht definiert, wird der #if Bereich ausgeführt.
== #if DEBUG == true == Prüft auf Gleichheit. Im Beispiel bedeutet dies, wenn DEBUG definiert ist, dann wird der Bereich des #if Ausdrucks ausgeführt.
!== #if DEBUG !== true == Prüft auf Ungleichheit. Im Beispiel bedeutet dies, wenn DEBUG definiert ist, dann wird der Bereich des #if nicht Ausdrucks ausgeführt.
&& #if DEBUG && TRACE Logisches Und. Wenn DEBUG und TRACE definiert sind, wird der #if Ausdruck wahr, also ausgeführt.
|| #if DEBUG || TRACE Logisches Oder (kein Exklusiv Oder). Der #if Ausdruck ist wahr wenn DEBUG und/oder TRACE definiert sind.

 

Beispiele

Einfaches Beispiel mit einem Define und einer Bedingten Kompilierung

#define DEBUG

#if DEBUG
     // Diese Bereich wird nur kompiliert wenn DEBUG definiert ist
    System.Diagnostics.Debug.WriteLine("DEBUG ist definiert");
#endif

Beispiel einer Anwendung mit einem error Ausdruck und einem #elif

#define RELEASE
#define DEMO

#if RELASE && DEMO
     #error RELEASE und DEMO dürfen nicht zusammen verwendet werden
#elif DEMO
     // HIER zum Beispiel der Code, der eine DEMO Version beschränkt, oder was auch immer
#endif

Programmierung | Tips und Tricks | C#
Thursday, January 24, 2008 1:31:45 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [1]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, January 22, 2008

Vor einiger Zeit hatte ich in diesem Beitrag beschrieben wie man Transaktionen mit netTiers verwenden kann. In diesem Beitrag nun möchte ich eine weiter fortgeschrittene Technik zeigen mit welcher man die Transaktion auch in verschachtelten und rekursiven Methoden verwenden kann.

Hintergrund

Obwohl es keine "echten" verschachtelten Transaktionen sind, habe ich die Überschrift so gewählt, da das Ergebnis dem von verschachtelten Transaktionen gleicht.

Der nachfolgende Beitrag erläutert die notwendigen zusätzlichen Vorkehrungen die man treffen muss um Transaktionen in verschachtelten und rekursiven Methodenaufrufen zu verwenden.

Lösungsansatz

Um Transaktionen in verschachtelten Aufrufen verwenden zu können, ist es notwendig festzustellen, ob bereits eine Transaktion vorhanden ist und ob diese geöffnet (BeginTransaktion) ist.

Gibt es eine aktive (äussere) Transaktion, so ist innerhalb der geschachtelten Methode dafür zu sorgen, dass keine lokale Transaktion erzeugt wird, bzw. dass keine globalen Methoden Aufrufe (wie Commit und Rollback) der Transaktion aufgerufen werden (Die Transaktion gehört der aufrufenden Methode, die sich darum kümmern muss ob ein Commit oder Rollback gemacht wird).

Lösung - Erweiterte Version mit localTransaction

int retval = 0;    // Definieren einer int variablen die einen Rückgabewert der Methode aufnehmen kann.
bool localTransaction = false;    // Flag welches verwendet wird um die Verwendung eine lokalen Transaktion zu signalisieren

if (ConnectionScope.Current.TransactionManager == null)
{
    ConnectionScope.Current.TransactionManager = ConnectionScope.CreateTransaction();
    localTransaction = true;    // Es gab keine äussere Transaktion also haben wir eine lokale erstellt und auch gleich geöffnet (impliziter Aufruf von BeginTransaction)
}
TransactionManager tm = ConnectionScope.Current.TransactionManager;
if (!tm.IsOpen)  // jetzt prüfen ob auch eine geöffnete Transaktion vorhanden ist (wenn nicht, dann eine lokale Transaktion aufmachen)
{
    tm.BeginTransaction();
    localTransaction = true;
}               

 

// Hier nun der eigentlich Code
// Wenn alle Datenbankoperationen durchgeführt werden konnten, wird 0 zurückgegeben, sonst ein int Wert aus dem die Aufrufende Methode den Grund des Fehlers erkennen kann und dadurch entscheiden kann ob ein Commit oder ein Rollback durchgeführt werden soll.

if (retval == 0)
{
    if (localTransaction )   // Nur wenn eine lokale Transaktion, dann ein Commit, wenn alles OK war.
    {
        tm.Commit();   
    }
}
else
{
    if (localTransaction )   // Nur wenn eine lokale Transaktion, dann ein Rollback wenn ein Fehler auftritt
    {
        tm.Rollback();   
    }
}

return retval;

Programmierung | Tips und Tricks | C#
Tuesday, January 22, 2008 7:56:08 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, January 17, 2008

Jeder der sich mit der Entwicklung von Programmen beschäftigt kommt früher oder später um die Verwendung eines Debuggers zur Fehlersuche nicht umhin.  

Doch bevor ich weiter schreibe zuerst eine kurze Begriffserklärung der wichtigsten Begriffe:

Bug-1                                 debugger

Was bedeutet denn Debuggen und was machen wir um einen Fehler zu finden?

  • Das Programm wird in der Entwicklungsumgebung im Debug Modus gestartet.
  • Es werden Breakpoints gesetzt.
  • Inhalte von Variablen werden angesehen (evtl. verändert)
  • Der Ablauf des Programms wird verfolgt

Um Komplexere Abläufe zu beobachten bietet es sich an Werte einfach über nachfolgende Methoden

  • System.Diagnostics.Debug.WriteLine("")
  • System.Diagnostics.Trace.WriteLine("")

im Ausgabefenster von Visual Studio auszugeben.

Soweit sind die Möglichkeiten recht umfangreich um einem Fehler auf die Spur zu kommen.

Was aber wenn der Fehler, der beim Kunden auftritt, einfach nicht auf unserem Entwicklungsrechner reproduzierbar ist. Wie kann man vorgehen um mehr Informationen über das Problem zu erhalten, ohne dem Kunden eine Entwicklungsumgebung auf seinen Rechner zu bügeln.

Eventuell würde es ja schon helfen wenn wir die Originaldaten (Datenbank, Dokumente etc.) vom Kunden hätten, auch das können wir uns in vielen Fällen einfach abschminken und darüber sollten wir in den meisten Fällen auch gar nicht nachdenken.

Man kann aber bereits während der Entwicklung eine Menge dazu beitragen, später einmal eine recht gute Fehleranalyse auch vor Ort beim Kunden zu erhalten.

Wenn wir während der Entwicklung, an Stellen an von welchen wir glauben Informationen für eine Fehleranalyse bekommen zu können bereits vorsorglich über die Write... Methoden der Klassen Debug und Trace des System.Diagnostics Namespace Informationen bereitstellen, dann können wir später diese Informationen dazu verwenden einen möglichen Fehler zu analysieren.

Damit wir aber später in einer Anwendung, die dann nicht im Debugger ausgeführt wird, an diese Informationen herankommen, können wir durch Einrichtung eines TraceListener dafür sorgen, dass diese Informationen auf einem beliebigen (na ja fast, es muss ein TraceListener für die gewünschte Ausgabe vorhanden sein, oder wir müssen uns einen eigenen TraceListener schreiben) Ausgabemedium wie Standard Output, einer Textdatei, einer XML Datei eine ... usw. einfach ausgeben werden.

Schauen wir uns doch kurz nachfolgende Aufstellung an welche TraceListener uns das NET Framework (ab 2.0) bereits zur Verfügung stellt:

Die Abstrakte Basis Klasse im Namespace System.Diagnostics (Von der können wir eigene TraceListener ableiten):

  • TraceListener

Folgende Implementierungen sind im Namespace System.Diagnostics verfügbar:

  • ConsoleTraceListener
  • DefaultTraceListener
  • DelimitedListTraceListener
  • Eventing.EventProviderTraceListener
  • EventLogTraceListener
  • EventSchemaTraceListener
  • TextWriterTraceListener
  • XmlWriterTraceListener

Was mach nun ein solcher TraceListener und wie können wir Ihn verwenden.

In einer .NET Anwendung wird in der Klasse Trace des  Namespace System.Diagnostics unter anderem eine statische Eigenschaft Listeners vorgehalten, diese Auflistung enthält alle TraceListener die bei den Debug und Trace Methoden aufgerufen werden.

Wird nun in unserem Code ein Methodenaufruf wie System.Diagnostics.Debug.WriteLine(txtName.Text) durchgeführt, dann führt das dazu, dass für jede in der Auflistung enthaltenen Listener die implementierte Methode WriteLine aufgerufen wird.

Wenn wir das in einem Visual Studio Projekt im Debugger Mode machen ohne irgendwelche Änderungen an der Konfiguration oder Manipulation der Auflistung in unserem Code vorzunehmen, dann enthält die Auflistung Listeners lediglich den Default TraceListener, und das ist der Listener, welche die Ausgabe im Ausgabefenster von Visual Studio vornimmt.

Wie können wir nun einen anderen TraceListener, als Beispiel den TextWriterTraceListener (Dieser Listener schreibt die Daten in eine Textdatei) verwenden um die Ausgaben für uns verwendbar zu machen, auch ohne die Anwendung im Visual Studio auszuführen?

Es gibt 2 Arten wie wir den/die Listener angeben können:

  • Aus dem Source Code
  • Aus der App.config
Angabe eines Listener aus dem Source Code

Hier im Beispiel einer Windows Forms Anwendung:

Wir fügen einfach in den Konstruktor der Windows Form folgenden Code hinzu:

System.Diagnostics.TextWriterTraceListener tListener = new System.Diagnostics.TextWriterTraceListener("Trace.Log");
System.Diagnostics.Trace.Listeners.Add(tListener);

Dieser Code fügt einen neuen Listener, in unserem Fall den TextWriterTraceListener zur Auflistung des Listener hinzu (siehe nachfolgende Abbildung):

Listener Auflistung 

Es geht aber noch einfacher, ja wirklich "noch einfacher".

Angabe des Listener in der App.Config

Um einen oder auch mehrere Listener hinzuzufügen oder auch um einen Listener (z.B. den Default Listener) zu entfernen können wir innerhalb des Bereich "Config" einen eigenen Unter-Bereich System.Diagnostics hinzufügen.

In diesem Bereich können wir dann über die App.Config Datei auf die Listeners Auflistung zugreifen und Veränderungen durchführen.

Hier ein Beispiel, welche den TextWriterListener hinzufügt und außerdem noch den Default Listener entfernt.

<configuration>
  <system.diagnostics>
    <trace autoflush="true" indentsize="4">
      <listeners>
        <add name="FileListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="Trace.log" />
        <remove name="Default" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

Soweit zur Einrichtung von TraceListener zur besseren Fehleranalyse beim Kunden.

imageÜbrigens sind Trace und Debug Statements eigentlich vollkommen gleich, ob ein Trace oder ein Debug Aufruf ausgeführt wird, hängt davon ab, ob die vordefinierten Konstanten für bedingte Compilierung während der Erstellung des Programms gesetzt waren (In Visual Studio findet man diese Schalter übrigens in den Projekt Eigenschaften auf dem Karteireiter Build (Erstellen)).

 

Und nun viel Spaß bei der Suche nach den Käfern !

Ich finde die Geschichte um die Herkunft der Bezeichnung Bug für einen Programmfehler trotz der Behauptung das es sich um eine moderne Legende handeln soll, schön und lesenswert.

Wer möchte kann hier mehr über diese Legende lesen

PS: Ich Denke, in nicht so ferner Zukunft, werde ich dann mal über die Erstellung eines eigenen Listener berichten.

Programmierung | Tips und Tricks | C#
Thursday, January 17, 2008 3:21:39 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [1]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, January 14, 2008

Unter der Rubrik Kurz und Bündig mal wieder ein VB --> C# Vergleich

Was unter VB (Visual Basic) das Schlüsselwort Friend ist, ist unter C# der Zugriffsmodifizierer Internal.

Der Zugriff auf Objekte mit dem Zugriffsmodifizierer sind auf Objekte innerhalb des gleichen Assembly beschränkt

Mehr Informationen über C# Zugriffsmodifizierer gibt es hier

Kurz und Bündig | Programmierung | C#
Monday, January 14, 2008 8:11:50 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Friday, January 11, 2008

Manchmal ist es notwendig dass man die verschiedenen Werte einer Aufzählung zu einem eindeutigen Wert kombinieren kann.

Ein Beispiel dafür sind die Zugriffsrechte auf ein Objekt, sei es eine Datei im Dateisystem oder seien es Daten in einer Datenbank.

Nehmen wir an wir müssen zwischen den folgenden Rechten unterscheiden können:

  • Lesen
  • Schreiben
  • Löschen

Nun können wir entweder verschiedene Eigenschaften wie:

  • HasReadRights
  • HasWriteRights
  • HasDeleteRights

verwenden um jeweils das Lese, Schreib oder Löschrecht für ein Objekt zu hinterlegen, oder eben wir verwenden dazu eine Aufzählung.

public enum ObjectRights
{

Read = 0x01,
Write = 0x02,
Delete = 0x04

}

Wenn wir nun ein Objekt zur Speicherung einer solchen Aufzählung definieren können wir darin bereits die kombinierten Werte ablegen, allerdings werden die Werte dann als Integer Ergebnis der Bitweisen Oder Operation abgelegt.

Beispiel:

ObjectRights Rechte = ObjectRights.Read | ObjectRights.Delete;

Ergibt zum Beispiel in der Ausgabe mit:

System.Diagnostics.Debug.WriteLine(Rechte);

folgendes:

5

Das ist das Resultat der Bitweisen Oder Operation 0x01 | 0x04

Um nun aber anstelle der Integer Werte, die Aufzählungstypen als Bitfelder zu interpretieren müssen wir der Aufzählung ein zusätzliches Attribut [Flags] mitgeben. diese geschieht wie nachfolgend abgebildet:

[Flags]
public enum ObjectRights
{

Read = 0x01,
Write = 0x02,
Delete = 0x04

}

Wenn wir nun die Werte mit dem Bitweise Oder Operator kombinieren werden die Werte wie Bitfelder behandelt.

ObjectRights Rechte = ObjectRights.Read | ObjectRights.Write | ObjectRights.Delete

Die Ausgabe von Rechte mit System.Diagnostics.Debug.WriteLine(Rechte);

ergibt nun:

Read, Delete

Um abzufragen ob ein bestimmtes Recht gesetzt ist, können wir den Bitweise Und Operator verwenden:

if ((Rechte & ObjectRights.Read) == ObjectRights.Read)
{
    System.Diagnostics.Debug.WriteLine("Lesen erlaubt");
}

 

if ((Rechte & ObjectRights.Write) == ObjectRights.Write )
{
    System.Diagnostics.Debug.WriteLine("Schreiben erlaubt");
}

 

if ((Rechte & ObjectRights.Delete) == ObjectRights.Delete)
{
    System.Diagnostics.Debug.WriteLine("Löschen erlaubt");
} Kurz und Bündig | Code | C#

Friday, January 11, 2008 7:54:31 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, January 10, 2008

Bei using muss man zwischen den nachfolgenden Optionen unterscheiden:

  • using --> Direktive
  • using --> Anweisung

using Direktive

using als Direktive zum einbinden von Namespaces kennt sicherlich jeder der mit C# schon einmal etwas programmiert hat, oder sei es auch nur jemand der einen C# Quelltext gesehen hat. Es kommt wohl kein Programm ohne den Import eines Namespaces mit Hilfe der using Direktive aus.

Beispiel für die using Direktive:

using System;
using System.Collections.Generic;

using Anweisung

using als Anweisung hingegen ist vielen sicherlich nicht so geläufig.

Was ist nun die using Anweisung und für was setzt man diese ein.

using als Anweisung definiert einen Bereich in welchem das in der using Anweisung definierte Objekt seine Gültigkeit hat. Das in der using Anweisung deklarierte Objekt wird, nachdem der mit using definierte Gültigkeitsbereich verlasen wird, automatisch verworfen (Dispose etc.).

Man teilt dem NET Framework damit ganz unmissverständlich mit, dass das in der using Anweisung deklarierte oder auch nur einfach eingeschlossene Objekt nach dem verlassen des Gültigkeitsbereiches nicht mehr benötigt wird und dieses zerstört (Referenzen entfernt und Speicher freigegeben werden kann und soll) werden soll.

Damit ein Objekt in der using Anweisung verwendet werden kann muss dieses die IDisposable Schnittstelle implementiert haben.

Mehr Detail Informationen zur using Anweisung gibt es hier

http://msdn2.microsoft.com/de-de/library/yh598w02(VS.80).aspx

Tips und Tricks | C#
Thursday, January 10, 2008 7:51:34 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Wednesday, January 09, 2008

Vorab möchte ich festhalten, dass ich in diesem Beitrag lediglich die Information darüber geben möchte wie man unter C# das äquivalent zur Finalize Methode in VB implementiert. Dies soll keine Diskussion über die Verwendung von Garbage Collection oder von Managed und Unmanaged Code werden.

Eventuell werde ich dazu mal in einem separaten Beitrag meinen Senf dazu abgeben, nun aber zum eigentlichen Thema dieses Beitrags:

Die Finalize Methode die es unter VB gibt ist unter C# der Destruktor.

Um einen Destruktor für eine Klasse unter C# zu implementieren muss man eine Methode mit der Tilde und dem Klassennamen erstellen.

Also zu Beispiel muss der Destruktor für eine Klasse MyClass wie folgt deklariert werden:

~MyClass()
{

//Hier alles rein was im Destruktor aufgerufen werden muss um alle Ressourcen des Objektes freizugeben
// Eigentlich sollte man aber wenn man einen Destruktor deklariert mindestens die IDisposable Schnittstelle
// in seiner Klasse implementiert haben und dann auch die Dispose Methode im Destruktor aufrufen, siehe hier
Dispose(false);

}

Code | C# | VB
Wednesday, January 09, 2008 8:52:37 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, January 08, 2008

Kurze GUID Beschreibung.

GU steht für Global Unique

ID steht für identifier

Zusammen = Global unique identifier = Globaler eindeutiger Bezeichner

Eine Guid ist ein 128 Bit integer Wert der dazu verwendet werden kann einem Objekt einen eindeutigen Bezeichner hinzuzufügen.

Die am häufigsten verwendete Darstellung einer Guide erfolgt jedoch nicht in dezimaler sondern in hexadezimaler Darstellung.

Wobei zu Darstellung fünf Gruppen verwendet werden.

Erste Gruppe 8 Stellen, dann 3 Gruppen mit je 4 Stellen und eine fünfte Gruppe mit 12 stellen.

Beispiel GUID Standarddarstellung:

{f563e21d-dbc3-4b6e-a59e-0eb4d898c299}

Um nun einen solche GUID in C# zu erzeugen genügt folgender Code:

private Guid myGuid = Guid.NewGuid();

Kurz und Bündig | Code | C#
Tuesday, January 08, 2008 8:02:24 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, January 07, 2008

Nachdem ich immer wieder mal gefragt werde wie man auf einfache weise enum Werte an Controls binden kann, hier unter dem Motto kurz und bündig ein Beispiel in C#.

Um die Werte eines Aufzählung's Typs (enum) an eine ComboBox (oder auch andere Controls die eine DataSource Eigenschaft zur Verfügung stellen) zu binden genügt nachfolgender Code:

Hier ein Beispiel mit eigenem enum (funktioniert natürlich auch mit System Typen)

public enum AppHeightDrawMode
{
    TrueHeightAll = 0,
    FullHalfHourBlocksAll = 1,
    EndHalfHourBlocksAll = 2,
    FullHalfHourBlocksShort = 3,
    EndHalfHourBlocksShort = 4,
}

comboBox1.DataSource = Enum.GetValues(typeof(AppHeightDrawMode));

Das Resultat sieht dann so aus:

 image

Kurz und Bündig | Code | C#
Monday, January 07, 2008 4:16:27 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [7]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, December 10, 2007

Eine immer wieder gestellte Frage lautet: Wie bilde ich die Funktion IIf() unter C# nach, oder gibt es direkt eine vergleichbare Funktion unter C#.

Unter C# ist diese Funktion tatsächlich nicht verfügbar, aber wie wir gleich sehen werden, wird sie auch nicht wirklich vermisst, denn C# hat dafür den mächtigen Bedingten Operator ?:.

Eine Logik wie hier unter VB dargestellt (Beispiel: Gib die größere von zwei Zahlen als Rückgabewert an die linke variable zurück):

Dim max As Integer = IIf(x > y, x, y)

Die Variable max bekommt, wenn x größer ist als y den Wert von x, ansonsten (y ist gleich oder größer x) den Wert von y zugewiesen.

kann unter C# unter Zuhilfenahme des ? Operators wie folgt gelöst werden:

int max = (x > y) ? x : y;

Allgemein gesagt hört sich das so an: Der bedingte Operator (?:) gibt abhängig vom Wert eines logischen Ausdrucks einen von zwei Werten zurück.

Etwas näher beschrieben lautet das dann so:

Auf der linken Seite des Operators ist ein logischer Ausdruck, der, wenn man Ihn auswertet, true oder false ergeben kann.

Je nachdem ob er true oder false ergibt wird der Rechte oder linke Ausdruck auf der rechten Seite des Operators ? ausgewertet, also das links neben dem : oder eben rechts davon stehende.

Übrigens können die Ausdrücke sowohl auf der linken als auch auf der rechten Seite des ? Operators durchaus sehr komplex sein, ein weiteres kleines Beispiel zur Demonstration von etwas komplexeren Ausdrücken wird nachfolgend dargestellt:

string currency = "$";
double price = 12.23;
double money = (currency == "$")?(price * 1.56):(price);


Mehr zum Thema interessante Operatoren in C# gibt es unter anderem hier : Operator ??

Tips und Tricks | C#
Monday, December 10, 2007 2:30:44 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, December 04, 2007

Ich habe bereits in einem früheren Beitrag über die Automatischen Eigenschaften als Spracherweiterung (oder Compilererweiterungen) in C# 3.0 bzw. Visual Studio 2008 berichtet.

Als ich mir das Konstrukt für die sogenannten "Auto-Implemented Properties" näher betrachtet habe, war ich im ersten Moment ratlos wie die Implementierung einer Schreibgeschützten Eigenschaft funktionieren soll, bzw. wie diese Schreibgeschützten Eigenschaften jemals einen Wert zugewiesen bekommen können.

Aber wie gesagt, meine Ratlosigkeit beschränkte sich auf den ersten Moment smile_regular

Schauen wir uns das Thema doch in Beispielen an.

Beispiel Klasse:

class Customer
{
    public double TotalPurchases { get; set; }
    public string Name { get; private set; }       // Schreibgeschützt
    public int CustomerID { get; private set; }  // Schreibgeschützt
}

 

Der Compiler erzeugt bei diesem Konstrukt eine private aber anonyme variable, auf diese können wir, weil anonym ja nicht zugreifen.

Wie also kann man diese Schreibgeschützten Eigenschaften verwenden?

Da kam mit zuerst der Gedanke, da kommt natürlich eine weitere Neuerung die "Objekt Initialisierer" zum Zuge.

So zum Beispiel:

Customer c = new Customer { CustomerID = 1, Name = "Max Mustermann"}

thumbs_down Aber nein, das geht nicht !

Wenn man dann aber etwas genauer hinsieht, dann liegt die Lösung eigentlich ganz offensichtlich schon in der Art der Definition der Schreibgeschützten Eigenschaft.

Bei der Definition:

public string Name { get; private set; }

Heißt es nicht { get; read only set; }

sondern

{ get; private set; }

Das bedeutet, man erlaubt nur den Private Zugriff auf den Setter.

Das wiederum bedeutet, Zugriffe bzw. Wertzuweisungen, wie die nachfolgenden funktionieren einwandfrei.

thumbs_up Beispiel Konstruktoren:

public Customer()
{
    CustomerID = 1;
}

 

public Customer(int id)
{
    CustomerID = id;
}

thumbs_up Beispiel einer Methode der Klasse:

public void incCustomerID()
{
    CustomerID++;
}


Zusammenfassend bedeutet dies, dass wir auf die Schreibgeschützten Automatischen Eigenschaften einer Klasse, innerhalb der Klasse, also private, schreibenden Zugriff auf die Eigenschaften selbst haben.

Wir benötigen also gar kein Wissen über die vom Compiler erzeugte private anonyme Variable um schreibenden Zugriff auf die ansonsten Schreibgeschützte Eigenschaft zu haben.

Programmierung | C#
Tuesday, December 04, 2007 10:19:09 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, December 03, 2007

Eine Fehlermeldung wie diese:

"Der Zugriff auf das Steuerelement txtBarcode erfolgte von einem anderen Thread"

oder so ähnlich, deutet daraufhin, dass versucht wird aus einem anderen Thread Zugriff auf ein Control zu nehmen als der in dem das Control erstellt wurde.

Das geschieht zum Beispiel wenn, man im Timer Event oder dem DataReceived Event der SerialPort Klasse aus dem System.IO.Ports Namespace versucht auf ein Control in einem Windows Forms Programm zuzugreifen.

Eine Lösung für das Problem zeigt das nachfolgende Beispiel indem ein Barcode Scanner an die seriellen Schnittstelle angeschlossen ist und der Barcode in der Text Eigenschaft des Controls txtBarcode ausgegeben werden soll.

Hier nun der Code Ausschnitt des DataReceived Events mit Thread sicherem Zugriff auf das txtBarcode Control:

if (this.txtBarcode.InvokeRequired)
{
    this.txtBarcode.Invoke((MethodInvoker)delegate()
    {
        this.txtBarcode.Text = bCode;
    }
    );
}
else
{
    this.txtBarcode.Text = bCode;
}
Tips und Tricks | C#
Monday, December 03, 2007 5:21:18 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, November 29, 2007

Einfach, kann Einfach, Einfach sein !

Sicherlich für viele ein alter Hut, aber ich habe das so vorher noch nie versucht oder gemacht.

Die Aufgabe die sich mir (wirklich nicht zum ersten mal) gestellt hat, war wie folgt:

Erstellen einer Interface Datei zur Übergabe von Informationen aus einem EDV System A zu einem EDV System B. (In dem Fall Gestern ging es um Informationen von LKW Verladungen für DHL)

Die Übergabe Datei soll in einer Textdatei mit festen Feldängen übergeben werden. Hierzu habe ich eine Definition bekommen, welche die Felder, die Reihenfolge der Felder und die Länge der Felder definiert hat..

Da ich nicht alle Felder (es waren auch nicht alles Pflichtfelder) in meinem System zur Verfügung hatte, musste ich an manchen Stellen einfach die fehlenden Daten mit den entsprechenden Anzahl Leerzeichen auffüllen.

Das kann man so machen (basteln wir uns den String zusammen):

string buffer;
buffer = "1234567890"; // Das könnte die Kundennummer sein
buffer += "         "; // Hier soll zum Beispiel der Spitzname mit 10 Stellen des Kunden hin 
// den habe ich aber nicht also 10 Leerzeichen -
// aber sind es wirklich 10 und was machen wenn es 500 Zeichen sein sollen
//dann geht das zum Beispiel einfach so:
buffer += "".PadLeft(500);

Wie gesagt, manchmal kann Einfach, einfach, Einfach sein

Tips und Tricks | C#
Thursday, November 29, 2007 9:30:47 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, November 27, 2007

Bei einigen der unter C# 3.0 Spracherweiterungen geführten Dinge handelt es sich mehr um Compilererweiterungen des C# Compilers der mit Visual Studio 2008 ausgeliefert wird, als um echte Spracherweiterungen für C#.

So funktionieren die nachfolgenden Erweiterungen auch dann wenn man Code für das NET Framework 2.0 erzeugt:

  • Implizit typisierte lokale Variablen
  • Objekt Initialisierer
  • Anonyme Typen
  • Automatische Eigenschaften

Wenn diese Dinge "Spracherweiterungen" und nicht "Compilererweiterungen" wären, dann wäre es nicht möglich ein Programm welches diese Feature verwendet für das NET 2.0 Framework zu erstellen und auf einem Rechner mit "nur" installiertem NET 2.0 Framework  auszuführen.

Hier ein Beispiel, welches ich mit VS2008 als Konsolenanwendung für den Framework 2.0 erstellt habe und auf einem Rechner mit NET 2.0 Framework ausgeführt habe:

class Program
{

    // Automatische Eigenschaften
    static public double AutoDouble { get; set; }


    // Klasse zur Verwendung mit Objekt Initialisierung ohne Konstruktoren
    public class Address
    {
        public string Name { get; set; }

        public string Street { get; set; }

        public int ZIP { get; set; }

        public string City { get; set; }

        public override string ToString()
        {
            return this.Name + "," + this.Street + "," + this.ZIP + "," + this.City ;
        }
    }




    static void Main(string[] args)
    {
        // Implizit typisierte lokale Variable
        var i = 10;

        // Verwendung der Eigenschaft AutoDouble 
        AutoDouble = 12.22;

        // Anonymer Typ
        var figur = new { Name = "Rechteck", Breite = 1024, Hoehe = 768 };

        // Objekt Initialisierung mit Feldbezeichnungen ohne Konstruktor
        Address adr = new Address { Name = "Max Musterman", Street = "Platanenweg", ZIP = 4711, City = "Musterhausen" };


        // Ausgabe der Werte
        Console.WriteLine(i.ToString());
        Console.WriteLine(AutoDouble.ToString());
        Console.WriteLine(String.Format("Die Adresse lautet {0}",adr.ToString()));
        Console.WriteLine(String.Format("Hier der anonyme Typ figur {0}",figur.ToString()));

        Console.Read();
    }


}

Nicht von diesem Beitrag betroffen sind die echten Spracherweiterungen von C# 3.0 wie:

  • Erweiterungsmethoden
  • Lambda Expressions
  • LINQ

Diese erfordern aber das die Programme für das NET 3.5 Framework erstellt werden.

Programmierung | C#
Tuesday, November 27, 2007 3:35:41 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [1]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, August 21, 2007

Immer wieder geschieht es dass ich während der Programmierung über den zu verwendenden Zugriffsmodifizierer für Klassen und Methoden nachdenken muss (manchmal länger als ich eigentlich möchte ). Aus diesem Grund mach ich mir hier wieder mal eine kleine Gedächtnisstütze in Form dieses Blog Eintrags.

Hier nun eine kleine Tabelle in welcher man einfach den benötigten Zugriffsmodifizierer ablesen kann:

Zugriffsmodifizierer Zugriff von beliebiger Stelle Zugriff aus Assembly

Zugriff aus abgeleiteterKlasse in eigener Assembly

Zugriff aus abgeleiteter Klasse in anderer Assembly Zugriff aus eigener Klasse
public Ja Ja Ja Ja Ja
protected internal - Ja Ja Ja Ja
internal - Ja Ja - Ja
protected - - Ja Ja Ja
private - - - - Ja

Programmierung | Tips und Tricks | Sprachen | C#
Tuesday, August 21, 2007 10:18:49 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Friday, August 17, 2007

Der in C# vorhandene Operator ?? bietet einem die einfache Möglichkeit mit nicht initialisierten Objekten (null values) umzugehen.

Immer wieder muss man in der Programmierung prüfen ob ein Objekt initialisiert ist und einen gültigen Wert enthält. Hierzu hat man im allgemeinen Konstrukte wie Diese verwendet.

Zum Beispiel so:

if (myObject != null)
{
	return myObject
}
else
{
	return [defaultWert der dem Objekt entspricht]
}

Einfacher geht es jedoch mit dem in C# verfügbaren Operator ??. Dieser Operator gibt, wenn das Objekt nicht null ist den linken Wert, sonst den rechten Wert zurück.

Also so:

return myObject ?? -1;

Wenn das myObject nicht null ist, wird der Wert von MyObject zurückgegeben, ist myObject null, so wird -1 zurückgegeben.

Mehr darüber kann man auf MSDN nachlesen

C#
Friday, August 17, 2007 6:52:12 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Wednesday, August 15, 2007

Auch dieser Blog Eintrag ist wieder so etwas wie ein Post It für mich selbst, Ich denke aber das bestimmt der eine oder andere genau das Problem hat und schon mit diesem kleinen Hinweis weiter kommt.

Hintergrund:

Bei der Verwendung von CodeSmith und den netTiers 2.0 Templates ist es möglich mit Transaktionen zu arbeiten. In diesem Blog Eintrag wird kurz beschrieben wie man diese unter C# und im Domain Komponenten Modell einsetzen kann.

Lösung (c#):

if (ConnectionScope.Current.TransactionManager == null)
     ConnectionScope.Current.TransactionManager = ConnectionScope.CreateTransaction();
 TransactionManager tm = ConnectionScope.Current.TransactionManager;
 if (!tm.IsOpen)
 {
     tm.BeginTransaction();
 }
  // Hier nun alle notwendigen Datenbankoperationen

 if (allesOK)
    tm.Commit();
 else
     tm.Rollback();

Mehr ist nicht zu berücksichtigen !!

Programmierung | C# | Tips und Tricks
Wednesday, August 15, 2007 10:17:50 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback

Jetzt reicht es und ich mache mir selbst ein Geschenk indem ich nun endlich dieses Problem einmal in einem Blog Eintrag festhalte.

Hintergrund:

Immer wieder stehe ich vor der Aufgabe das ich von irgendwoher Daten aus einer Datei verarbeiten (meistens Importfunktionen) muss, die unter anderem auch Datums und oder Zeitwerte enthalten.

Die Werte werden in den verschiedensten Formaten übergeben, eine der beliebtesten Formate ist es aber dass beideInformationen (Datum und Uhrzeit) in zwei verschiedenen Felder übergeben werden. Dabei wird häufig (jedenfalls ist das bei mir so) das nachfolgende Format für die Speicherung der Werte verwendet:

Datum als String z.B. : 20070815 also im Format yyyyMMdd

Uhrzeit als String z.B. : 120310 also im Forma HHmmss

Nun besteht die Aufgabe darin diese Daten zu lesen und dann in ein DateTime Objekt zu konvertieren.

Lösung:

Nachfolgend nun die von mir favorisierte und schon dutzende male verwendet Lösung:

// Source ist in C#

 string dateStr = s[2]; // Hier steht einfach ein Datumswert wie 20070815 drin
 string timeStr = s[3]; // Hier steht eine Uhrzeit wie 120310 drin 
 string dateTimeStr = dateStr + " " + timeStr; //Da steht nun  20070815 120310 also Datum mit Uhrzeit drin
 string dateTimeStrFormat = "yyyyMMdd HHmmss"; // Genau hier das ist das Format

 // Und hier wird nun der zusammen gebastelte Datum  / Uhrzeit String in ein DateTime Objekt konvertiert
 objInfo.TransDate = DateTime.ParseExact(dateTimeStr, dateTimeStrFormat, DateTimeFormatInfo.InvariantInfo);

So und nun hoffe ich das ich beim nächsten mal nicht wieder nach diesem Stückchen Quellcode suche wenn ich mal wieder vor der Aufgabe stehe, und eventuell stolpert ja auch der eine oder andere über diese Information wenn er / sie mal so etwas machen möchte.

Programmierung | Code | Tips und Tricks | C#
Wednesday, August 15, 2007 10:16:35 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Monday, July 30, 2007

Ich weiß nicht wie oft ich schon da stand, und mir mal wieder die Frage gestellt habe, wie unter c# das äquivalent für die in vb verfügbare Funktion isnumeric lautet.

Und immer wieder benötige ich einiges an Zeit um wieder mal herauszufinden dass es kein direktes äquivalent gibt, aber es gibt halt doch eine ganz einfache Lösung.

Man implementiert sich eine solche Funktion mal schnell selbst. Es gibt dazu eine ganze Reihe von ansätzen, so nach dem Motto; viele Wege führen nach Rom.

Ich möchte nun hier meinen persönlichen Favorit dokumentieren, so dass ich Ihn nicht wieder vergesse, ganz nach dem Motto, was du mal geschrieben hast, vergisst du nicht mehr so schnell.

Und hier der Ersatz für die aus vb bekannte isnumeric Funktion:

  1. public static bool IsNumeric(object Expression)   
  2. {   
  3.     bool isNum;   
  4.     double retNum;   
  5.     isNum = Double.TryParse(Convert.ToString(Expression),       System.Globalization.NumberStyles.Any, System.Globalization.NumberFormatInfo.InvariantInfo, out retNum);   
  6.     return isNum;   
  7. }  
Programmierung | Code | C#
Monday, July 30, 2007 10:12:34 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [1]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, June 28, 2007

Gestern Nacht wurden verschiedene neue Programmversionen der SharpDevelop Tools für .NET veröffentlicht.

SharpDevelop Version 2.2

SharpDevelop Reports Version 2.2

SharpZipLib Version 0.85.2

Das sagt Wikipedia zu SharpDevelop

Open Source | Allgemein | C#
Thursday, June 28, 2007 10:01:56 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Tuesday, December 05, 2006

Hier ein kurzer Source Code Ausschnitt zur Cursor Behandlung in MDI Forms.

 // Cursor var definieren um Cursor zu sichern
 Cursor oldCursor;
            
// aktuellen Cursor Zustand speichern
oldCursor = this.MdiParent.Cursor;

// Neuen Cursor setzen  
this.MdiParent.Cursor = Cursors.WaitCursor;

// Hier Aktionen ausführen 
------

// Cursor wieder zurück setzen
this.MdiParent.Cursor = oldCursor;
Tips und Tricks | C#
Tuesday, December 05, 2006 12:43:21 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Friday, October 28, 2005

Nachdem ich in einem Projekt mit dem Problem kämpfen musste, das einer der für dieses Projekt entwickelten Windows Dienste durch eine aufwendige Initialisierung bis zu einer Minute in der OnStart Methode verweilt hat bis er endlich den Dienst als gestartet anzeigt habe ich mir überlegt wie man diese Problem anders lösen könnte.

Eigentlich war das auch kein wirklich großes Problem, aber da ich auch erst einmal gezielt darüber Nachdenken musste um auf die Lösung zu kommen, denke ich dass vielleicht auch andere erst einmal auf das Problem aufmerksam werden müssen um dann einen anderen Ansatz zum Start der Windows Dienste zu verwenden.

Im Nachfolgenden Beschreibe ich eine einfach Lösung wie man einen Windows Dienst mit Hilfe eines Timers dazu bringt, sofort zu starten, unabhängig davon, ob aufwendige Initialisierungsprozesse für den Dienst durchgeführt werden müssen oder nicht.

Schauen wir und doch einfach mal wie der normale Startvorgang eines Dienstes aussieht:

Hier ein Beispiel wie es normalerweise aussehen kann, wobei osc irgendeine Klasse ist deren Methoden nach deren Initialisierung vom Service verwendet werden:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Diagnostics; 
using System.ServiceProcess; 
using System.Text; 
using System.Timers; 

namespace Namespace.Service 
{ 
      publicpartialclassMeinService : ServiceBase 
      { 
            MeineKlasse osc; 
            public MeinService() 
            { 
                  InitializeComponent(); 
                  Osc = new Osc(); 
            } 


            protectedoverridevoid OnStart(string[] args) 
            { 
                  Osc.Init(); 
                  Osc.StartAll(); 
            } 

            protectedoverridevoid OnStop() 
            { 
                  osc.StopAll(); 
            } 

            protectedoverridevoid OnContinue() 
            { 
                  osc.StartAll(); 
            } 

            protectedoverridevoid OnPause() 
            { 
                  osc.StopAll(); 
            } 

            protectedoverridevoid OnShutdown() 
            { 
                  osc.StopAll(); 
                  osc.Dispose(); 
                  osc = null; 
                  
            } 
      } 

Wenn nun davon ausgehen dass Osc.Init() und Osc.StartAll() Methoden sind die lange dauern können, dann befindet sich dieser Dienst in der Gesamt Zeit in welcher diese beiden Methoden ausgeführt werden im Dienst- Status StartPending. Dies kann in bestimmten Situationen aber mehr als unerwünscht sein. Und für diesen Fall habe ich folgende Änderungen vorgenommen, die dazu führen, dass der Service sofort nach dem Start wirklich in den Status Running übergeht.

Nachfolgend nun die Lösung für das Problem:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Diagnostics; 
using System.ServiceProcess; 
using System.Text; 
using System.Timers ; 

namespace Namespace.Service 
{ 
      publicpartialclassMeinService : ServiceBase 
      { 
            Timer startTimer = newTimer(); 
            MeineKlasse osc; 
            public MeinService() 
            { 
                  InitializeComponent(); 
                  startTimer.Interval = 1000;        // 1 seconds 
                  startTimer.Elapsed += newElapsedEventHandler(startTimer_Elapsed); 
                  startTimer.Enabled = false;              
            } 

            void startTimer_Elapsed(object sender, ElapsedEventArgs e) 
            { 
                  startTimer.Enabled = false; 
                  osc = MeineKlasse.loadActiveSystems(); 
                  osc.StartAll(); 
            } 

            protectedoverridevoid OnStart(string[] args) 
            { 
                  startTimer.Enabled = true; 
            } 

            protectedoverridevoid OnStop() 
            { 
                  osc.StopAll(); 
            } 

            protectedoverridevoid OnContinue() 
            { 
                  osc.StartAll(); 
            } 

            protectedoverridevoid OnPause() 
            { 
                  osc.StopAll(); 
            } 

            protectedoverridevoid OnShutdown() 
            { 
                  osc.StopAll(); 
                  osc.Dispose(); 
                  osc = null; 
                  
            } 
      } 
} 

Ich habe die Änderungen in Fettschrift dargestellt damit man diese gleich auf Anhieb erkennen kann.
Und die Erklärung ist eigentlich auch selbstredend.
Ich verwende also einen Timer, der vom OnStart Event eine Sekunde nach dem der Service gestartet wurde die eigentlichen Funktionen des Service aktiviert.

Manchmal ist einfach, einfach Einfach !

Programmierung | C# | Windows Dienste
Friday, October 28, 2005 6:33:36 PM (W. Europe Daylight Time, UTC+02:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Saturday, July 02, 2005

Bei der Programmierung von DotNetNuke Modulen gibt es ja wie allseits bekannt die freie Wahl der .NET Programmiersprachen.

Die beiden beliebtesten Sprachen zur Programmierung von DotNetNuke Modulen sind wohl VB.NET und C#.

Bei der Programmierung von User Controls sind jedoch obwohl beides .NET Programmiersprachen sind die Sprachspezifischen Eigenschaften wie zum Beispiel der Unterschied zwischen Groß und Kleinschreibung bei C# im Gegensatz zu VB.NET zu berücksichtigen. Das dies aber nicht die einzigen Unterschiede sind wird spätestens dann ganz deutlich wenn man versucht ein in VB.NET erstelltes User Control als Vorlage für ein C# User Control zu verwenden.

Schauen wir uns das doch einmal am Beispiel des Moduls Event, das ja Bestandteil des Core Produktes DotNetNuke ist, einmal etwas näher an.

Hier der Auszug aus der Events.ascx (mit VB.NET Codebehind)

<TR>

<TD id=colIcon vAlign=top align=center width='<%# DataBinder.Eval(Container.DataItem,"MaxWidth") %>' rowSpan=3 runat="server">

<asp:Image id=imgIcon runat="server" Visible='<%# FormatImage(DataBinder.Eval(Container.DataItem,"IconFile")) <> "" %>' 
ImageUrl='<%# FormatImage(DataBinder.Eval(Container.DataItem,"IconFile")) %>' 
AlternateText='<%# DataBinder.Eval(Container.DataItem,"AltText") %>'> </asp:Image></TD> <TD> <asp:HyperLink id=editLink runat="server" Visible="<%# IsEditable %>"
NavigateUrl='<%# EditURL("ItemID",DataBinder.Eval(Container.DataItem,"ItemID")) %>'> <asp:Image id="editLinkImage" ImageUrl="~/images/edit.gif" Visible="<%# IsEditable %>"
AlternateText="Edit" runat="server" resourcekey="Edit"/> </asp:HyperLink> <asp:Label id=lblTitle text='<%# DataBinder.Eval(Container.DataItem,"Title") %>' Cssclass="SubHead" Runat="server"> </asp:Label></TD> </TR>

Unsere besondere Aufmerksamkeit müssen wir dabei vor allem auf die Funktionen DataBinder.Eval() und EditURL() lenken.

Schauen wir uns Diese Syntax am folgenden Beispiel etwas näher an:

Visible='<%# FormatImage(DataBinder.Eval(Container.DataItem,"IconFile")) <> "" %>'

Mit dieser Zeile wird (in VB.NET ) erreicht dass nur wenn ein IconFile vorhanden ist das Control sichtbar ist, sonst wird der Wert Visible auf false gesetzt

Wenn wir eine solche Zeile in einem User Control einsetzen, welches mit C# zusammenarbeiten soll, dann ergeben sich gleich mehrere Problem (Fehlermeldungen während der Programmausführung, nicht beim übersetzen)

Als erstes wird in C# der Ausdruck nicht als If Ausdruck ausgewertet, da er nicht in () eingeschlossen ist.

Also muss das ganze auf jeden Fall schon mal so aussehen:

Visible='<%# (FormatImage(DataBinder.Eval(Container.DataItem,"IconFile")) <> "") %>'

Zu beachten hierbei sind die Klammern am Anfang und Ende der Auswertung

(FormatImage(DataBinder.Eval(Container.DataItem,"IconFile")) <> "")

Jetzt würden aber noch immer Laufzeitfehler entstehen, da C# bei der Typ Auswertung wesentlich kleinlicher (Gott sei Dank) ist, als dies VB.NET ist.

Das nächste Problem liegt in der Behandlung eines Stringvergleiches. in VB.NET ist der Vergleich string1 <> string2 ein gültiger Vergleich. In C# nicht, also müssen wir einen gültigen Vergleich für C# nehmen, der sieht dann so aus: string1 != string2.

Danach muss unser Ausdruck wie folgt aussehen:

Visible='<%# (FormatImage(DataBinder.Eval(Container.DataItem,"IconFile")) !="") %>'

Wenn Sie nun Denken, dass es nun funktioniert, muss ich Sie leider enttäuschen.

DataBinder.Eval() gibt als Datentyp ein abstraktes Objekt zurück, nun versuchen Sie mal einen Vergleich eines Objekt Datentyps mit "" einem String, das wird C# nicht zulassen.

Also bleibt uns nichts weiter übrig, als das Objekt zu einem String umzuwandeln.

Die Umwandlung können wir wie folgt vornehmen:

DataBinder.Eval(Container.DataItem,"IconFile").ToString()

Somit sieht unser Ausdruck nun wie folgt aus:

Visible='<%# (FormatImage(DataBinder.Eval(Container.DataItem,"IconFile").ToString()) !="") %>'

Der nun hier dargestellte Ausdruck ist nun C# konform und wird keinen Laufzeitfehler mehr erzeugen.

Schauen wir uns nun noch ein zweites Problem unseres Beispiels an:

<asp:HyperLink id=editLink runat="server" Visible="<%# IsEditable %>" 
NavigateUrl='<%# EditURL("ItemID",DataBinder.Eval(Container.DataItem,"ItemID")) %>'>

Der hier dargestellte Ausdruck ist der typische Code für den Hyperlink, der aus dem Anzeige Control das Edit Control aufruft.

Der nicht C# konforme Teil dieses Ausdrucks ist nachfolgend dargestellt:

NavigateUrl='<%# EditURL("ItemID",DataBinder.Eval(Container.DataItem,"ItemID")) %>'

Wenn wir das bisherige aus diesem Artikel anwenden, dann wissen wir dass wir das aus dem DataBinder.Eval() zurückgegeben Objekt in einem String umwandeln müssen.

So das unser Ausdruck wie folgt aussieht:

NavigateUrl='<%# EditURL("ItemID",DataBinder.Eval(Container.DataItem,"ItemID").ToString()) %>'

Wie ich am Anfang schon bemerkt habe berücksichtigt C# im Gegenteil zu VB.NET die Groß und Kleinschreibung, und somit haben wir mit dem Aufruf der Funktion EditURL ein Problem, da diese in Wirklichkeit eigentlich EditUrl heißt.

Also ändern wir den Ausdruck wie folgt ab:

NavigateUrl='<%# EditUrl("ItemID",DataBinder.Eval(Container.DataItem,"ItemID").ToString()) %>'

Mit dem Wissen dieser notwendigen Änderungen wenn Sie ein User Control in C# verwenden möchten, was eigentlich für VB.NET konzipiert war, können Sie nun die notwendigen Änderungen in User Controls vornehmen so dass diese dann in einem C# DotNetNuke Module verwendet werden können.

Entwicklung | C# | VB
Saturday, July 02, 2005 8:35:04 AM (W. Europe Daylight Time, UTC+02:00)  #    Comments [1]  
Autor: Hans-Peter Schelian  |  Trackback
# Thursday, March 24, 2005

Hintergrund:

Dynamisch erstellte Web Seiten haben häufig mit Parameter gespickte URL's. Manche Suchmaschinen können diese Parameter behafteten URL's nicht oder nur schlecht auswerten.

Beispiele von URL's:

Hier eine typische URL einer DotNetNuke Webseite:

http://www.dnnportal.de/default.aspx&tabid=179&type=art&site=40&parentid=48

 

Mit dem hier besprochenen Ansatz könnte die URL wie folgt aussehen http://www.dnnportal.de/Default.aspx/type/art/site/40/tabid/179/parentid/48

 

Mir ist auch schon aufgefallen das Webseiten von exakt gleichem Inhalt mit URL's ohne Parameter häufig weiter oben in den Suchmaschinen angesiedelt sind, als die gleiche Seite mit parametrisierten URL's.

 

Leider ist es nicht ohne weiteres Möglich diese Art von SFU (Spider Friendly URL's) aus einem Programm welches nicht von vornherein dafür vorgesehen ist zu erzeugen. Und wenn, dann doch meist, nur mit vielen Kompromissen und in vielen Fällen auch nur mit Änderungen im Quellcode.

 

Die Aufgabenstellung lautet also eine Möglichkeit zu schaffen, SFU URL's ohne oder mit nur geringen Änderungen des Quellcode's anwendungsdurchgängig zu implementieren.

Die Idee

Die Idee zur Erstellung eines Programms welches Suchmaschinen freundliche URL's (Spider Friendly URL's) erstellt ist im Zusammenhang mit der Suchmaschinenoptimierung für DotNetNuke Portale entstanden.

 

Nachdem ich mir viele Ansätze für solch eine Lösung im Internet angesehen hatte und mit den Ergebnissen nicht zufrieden war (Es waren meist irgendwie keine durchgängigen Lösungen), bin ich auf einen Beitrag von Scott Van Vliet gestoßen, der mir dann als Basis für die Erstellung dieser Lösung gedient hat.

 

Der Beitrag von Scott Van Vliet basiert im groben darauf dass der vom Webserver an den Client gesendete Response gefiltert wird und bei dieser Filterung die dynamischen URL's in statische URL's ausgetauscht werden Und das bei einer Anforderung von einem Client die an den Server gesendete Statische URL wieder für die Internet Verwendung in die ursprüngliche Dynamische URL umgesetzt wird.

 

Wie Sie sich Denken können ist dies ein vorhaben, dass seine Tücken haben soll.

 

Aber ich kann Ihnen schon hier verraten, es funktioniert.

 

Eine fertige Lösung. welche die Konzepte dieses Beitrags umsetzt können Sie auf DNNPortal im Download Bereich herunterladen.

 

Für registrierte Benutzer liegt auch der komplette Source Code als Download bereit.

 

Ich habe dann auf Basis dieses Lösungsansatzes dieses Modul HPS.Utilities.SFU erstellt.

Grundsätzliche Lösung

In diesem Kapitel möchte ich den Grundsätzlichen Weg beschreiben, welcher für diese Lösung beschritten wird.

Normale Webanforderung

Schauen wir uns doch zuerst einmal an wie der normale Aufruf einer Web Seite funktioniert.

 

graphic

 

Der Client sendet eine Request an den Server. Der Request besteht aus einer angeforderten URL. Nehmen wir als Beispiel die folgende URL:

 

http://www.dnnportal.de/default.aspx&tabid=163.

 

In diesem Fall bedeutet das, der Client fordert die Homepage der Web Anwendung www.dnnportal.de an.

 

Der Server empfängt nun diese Anforderung, die Webanwendung (wir sprechen ja über ASP.NET) löst die Parameter auf und schickt als Response die gewünschte Web Seite als Stream zum Browser.

 

Soweit zur normalen Anforderung einer Webseite eines Client vom Server.

Ansatzpunkte zur Umsetzung

Betrachten wir uns das ganze mal etwas näher was wir erreichen wollen:

graphic

 

Gewünscht wäre, der Client Request lautet http://www.dnnportal.de/default.aspx/tabid/163, den Request den der Server bekommt sollte aber http://www.dnnportal.de/default.aspx?tabid=163 lauten.

 

In diesem Fall würde der Server den Request des Client verstehen und im den Response (also die gewünschte Webseite) an ihn zurücksenden.

 

Schauen wir uns doch mal an was der Server an den Client sendet.

 

Es sendet ihm den Inhalt der Webseite http://www.dnnportal.de/default.aspx?tabid=163 in diesem Inhalt (der Content) können sich Links auf andere Seiten oder auf Java Skript oder sonstiges mit meist absoluten Pfadangaben enthalten, die immer auf das Wurzelverzeichnis der aktuellen Web Anwendung (also unserer Dynamischen Web Anwendung) verweist.

 

Somit erhält der Client Links die im Format http://www.dnnportal.de/default.aspx?tabid=163 gehalten sind.

 

Eigentlich wollen wir ja aber das der Client die URL Informationen im Format http://www.dnnportal.de/default.aspx/tabid/163 erhält.

 

Wenn wir uns die obige Abbildung anschauen dann ist dort bereits der Ansatz der Lösung zu erkennen.

 

Wenn wir die beiden Rechtecke Response vom Server und Request vom Client nicht als Beschreibung sondern als die Möglichkeit sehen, dort die ein und ausgehenden Stream (Anforderungen und Antworten) so zu manipulieren, dass Sie unser gewünschte Resultat ergeben.

 

Der Oberbegriff für unsere Lösung heißt also HTTPHandler.

 

Wir erzeugen also ein Klassenmodul welches als HTTPHandler später über die Web.Config aktiviert wird.

 

Also erzeugen wir eine Klasse (Diese Klasse muss von der Klasse System.Web.IHttpModule abgeleitet sein.):

public class SfuHttpModule : System.Web.IHttpModule

     {

     }
Request (Eingehende Streams)

Um den eingehenden Stream abzufangen und zu ändern bevor wir Ihn an den Webserver weiterleiten gibt es die Möglichkeit einen Event der ausgelöst wird wenn eine Anforderung zum Server geschickt wird auf eine eigene Event Methode umzulenken.

 

Dies geschieht auf folgende Art und Weise.

 

Im Init Event unseres HTTPHandler weisen wir einen neue Event Methode zu:

public void Init(HttpApplication application)

          {

               application.BeginRequest +=new EventHandler(Application_BeginRequest);

          }

Das bedeutet immer wenn ein Request an den Server gesendet wird, wird durch die Event Methode Application_Begin_Request unseres HTTPHandler aufgerufen.

 

An dieser Stelle behandle ich die dort durchgeführten Aktionen rein generisch, nähere Erläuterungen was in der Methode genau geschieht folgt später in diesem Artikel.

 

public void Application_BeginRequest(object sender, EventArgs e)

          {

               // Hier wird jetzt eine Funktion eingefügt, welche die eingehende statische URL wieder
//
in die ursprüngliche dynamische URL umwandelt. }
Response (Ausgehende Streams)

Um den Inhalt der an den Client übertragen wird zu manipulieren, ist es notwendig den vom Server ausgehenden Stream abzufangen und zu manipulieren.

 

Um es gleich vorweg zu sagen, dies ist die wesentlich größere Herausforderung. Hierbei handelt es sich ja nicht nur um eine URL die abgefangen und manipuliert werden muss, sondern um den gesamten Inhalt der Webseite die vom Server an den Client übertragen wird.

 

Aber das es dafür Lösungsmöglichkeiten gibt sehen wir ja in diesem Artikel.

 

Wir gehen also wie folgt vor und registrieren einen weiteren Event (hier fett dargestellt) in unserer Init Methode des HTTPModules:

 

public void Init(HttpApplication application)
          {
               application.BeginRequest +=new EventHandler Application_BeginRequest);

               application.PostRequestHandlerExecute += new EventHandler(application_PostRequestHandlerExecute);
          }

Das hinzufügen des Events application_PostRequestHandlerExecute hat zur Folge dass jedesmal wenn der Server einen Response an eine Client sendet unsere Event Methode aufgerufen wird bevor der Client die Daten des Response erhält.

 

An dieser Stelle auch nur die generische Beschreibung was in dieser Methode geschieht.

private void application_PostRequestHandlerExecute(object sender, EventArgs e)

{

// Daten vom Server empfangen und manipulierte Daten dann zum Client senden

}

Dieses beschriebene Ansinnen ist etwas umfangreicher als es hier dargestellt ist und wird später näher erläutert.

Zusammenfassung

Wenn wir also die eingehenden und ausgehenden Streams abfangen und manipulieren können, sollte es möglich sein, die gewünschten Anforderungen zu erfüllen.

 

Im nächsten Abschnitt werden wir auf die einzelnen Funktionen die wir implementieren müssen näher eingehen.

HTTPHandler im Detail

Nun wird es ernst, in diesem Abschnitt werden nun die einzelnen Funktionen beschrieben die notwendig sind um die ein und ausgehenden Streams abzufangen und zu manipulieren.

 

Beginnen wir mit dem einfacheren Teil.

Beginn_Request

Schauen wir uns zuerst einmal an, wie die Event Methode nach unserer Implementierung aussieht und welche Funktionalitäten darin versteckt sind:

publicvoidApplication_BeginRequest(objectsender, EventArgs e)
{
               HttpContext context = ((HttpApplication)sender).Context;
               context.RewritePath(SfuUtil.FromSfuUrl(context.Request.Path) );
}

In einfachen Worten beschrieben, wird in der Methode die Funktion context.RewritePath() aufgerufen um die eingehende URL im statischen Format durch die ursprüngliche dynamische URL umzuschreiben, so dass der Server uns die zu dieser URL gehörigen Informationen zurückliefern kann.

 

Wir verwenden hierzu die Funktion FromSfuUrl der Klasse SfuUtil. Diese Funktion der Klasse wandelt einfach die statische in die dynamische URL um.

 

Um diesen Bericht nicht unnötig aufzublasen möchte ich keine detaillierte Erläuterung der Klasse SfuUtil vornehmen. Die Klasse ist im Source Code enthalten und bei Fragen können diese gerne per Email an mich gesendet werden.

PostRequestHandlerExecute

Nun zum Interessanteren Teil der Lösung.

private void application_PostRequestHandlerExecute(object sender, EventArgs e)
          {
               HttpApplication application = (HttpApplication)sender;
               string _querystring = application.Context.Request.QueryString.ToString();
               application.Context.Response.Filter = new RequestFilter(application.Context.Response.Filter, application.User);
          }

Wie sie sehen wird im PostRequestHandlerExecute ein weiterer Event registriert. Ein Context.Response.Filter. Dieser Event wird immer dann ausgelöst wenn der Server Daten zum Client senden möchte.

 

Das was nun folgt ist der eigentliche Höhepunkt dieser Anwendung.

RequestFilter

Diese Klasse enthält nun die Funktionalität den ausgehende Stream vom Client zum Server abzufangen und zu manipulieren. Aber sehen Sie selbst:

 

Die Klasse muss von Stream abgeleitet werden, diese wiederum erfordert dass eine Anzahl von Methoden überschrieben werden müssen, da sonst die Implementierung der Klasse nicht vorgenommen werden kann.

Erforderliche Überschreibungen:

Nachfolgende werden die Methoden aufgeführt die zwingend Überschrieben werden müssen, damit unsere Klasse von der Stream Klasse abgeleitet werden kann.

public override bool CanRead

          {

               get

               {

                    return true;

               }

          }


          public override bool CanSeek

          {

               get

               {

                    return true;

               }

          }


          public override bool CanWrite

          {

               get

               {

                    return true;

               }

          }


          public override long Length

          {

               get

               {

                    return 0;

               }

          }


          public override long Position

          {

               get

               {

                    return _position;

               }

               set

               {

                    _position = value;

               }

          }


          public override long Seek(long offset, SeekOrigin origin)

          {

               return _sink.Seek(offset,origin);

          }


          public override void SetLength(long value)

          {

               _sink.SetLength(value);

          }


          public override void Close()

          {

               _sink.Close ();

          }


          public override void Flush()

          {

               _sink.Flush();

          }


          public override int Read(byte[] buffer, int offset, int count)

          {

               return _sink.Read(buffer, offset, count);

          }
Write Methode (Hier ist das Herzstück unseres Content Filter)
public override void Write(byte[] buffer, int offset, int count)

          {

               string sBuffer = Encoding.Default.GetString(buffer, offset, count);

               sBuffer = _tempBuffer + sBuffer.Trim();

               if (buffer.Length != count)         

               {

                    int idx = sBuffer.LastIndexOf(">");

                    _tempBuffer = sBuffer.Substring(idx + 1);

                    sBuffer = sBuffer.Substring(0,idx+1);

               }

                    

               MatchCollection hrefMatches = Regex.Matches(sBuffer, RegexPattern.HrefPattern, RegexOptions.IgnoreCase);

               HttpContext Context = HttpContext.Current;

               if ((hrefMatches.Count > 0))

               {

                    try

                    {

                         foreach (Match match in hrefMatches)

                         {

                              string href = match.Groups[match.Groups.Count - 2].Value;

                              if (href.IndexOf(Context.Request.Headers["Host"]) > 0)

                                   href = href.Substring(href.IndexOf(Context.Request.Headers["Host"])+ Context.Request.Headers["Host"].Length );

                              if (Regex.IsMatch(href, RegexPattern.AspxPattern) &&

                                   !Regex.IsMatch(match.Value, RegexPattern.ImgPattern) &&

                                   !Regex.IsMatch(match.Value, RegexPattern.CssPattern) &&

                                   !Regex.IsMatch(match.Value, RegexPattern.ScriptPattern))

                              {

                                   href = href.Replace(href, SfuUtil.ToSfuUrl(href));

                              }


                              if (!Regex.IsMatch(href, RegexPattern.HttpProtocolPattern) &&

                                   !Regex.IsMatch(href, RegexPattern.MailToPattern,RegexOptions.IgnoreCase) &&

                                   !Regex.IsMatch(href, RegexPattern.AnchorPattern) &&

                                   !Regex.IsMatch(href, RegexPattern.JavascriptHtmlStatementPattern))

                              {

                                   if (!Regex.IsMatch(href, RegexPattern.AbsolutePathPattern))

                                   {

                                        href = Regex.Match(Context.Request.Path, RegexPattern.CurrentPathPattern).Groups[1].Value + href;

                                   }

                                   sBuffer = sBuffer.Replace(match.Value, match.Value.Replace(match.Groups[match.Groups.Count - 2].Value, href));

                              }

                         }

                    }

                    catch (Exception ex)

                    {

                         System.Diagnostics.Debug.WriteLine(ex.Message);

                    }

               }


               byte[] bufferNew = Encoding.Default.GetBytes(sBuffer);

               _sink.Write(bufferNew, 0, bufferNew.Length);


          }

Nachdem wir den HTTPHandler erzeugt und die Events wie in diesem Artikel beschrieben haben registriert haben, wird die Methode Write immer dann ausgeführt wenn der Server etwas an den Client senden möchte. Wir müssen in dieser Methode jetzt selbst dafür sorgen dass der Client eine Antwort von unserem Server erhält. Wenn wir an dieser Stelle nichts senden, bekommt der Client keinen Response.

 

Nachdem ich mit dieser Methode gearbeitet habe sind mir fast unbegrenzte Möglichkeiten für den Einsatz eingefallen, aber bleiben wir nun erst einmal bei unserem Thema und schauen uns die Methode näher an.

 

public override void Write(byte[] buffer, int offset, int count) 

 

Die Methode erhält als Parameter im Parameter buffer den Response der vom Server auf den Request des Client zurück gesendet werden soll.

Diesen Buffer können wir nun lesen, manipulieren und anschließend an den Client als Server Response senden (Der Client bekommt davon nichts mit)

Kommen wir aber gleich zu einem Problem, was wenn nicht gelöst zu einem echten Problem werden kann.

Der Server sendet maximal 25 KByte (diese Zahl ist nicht genau, konnte keine genaue Definition finden) auf einmal an den Client. Das bedeutet wenn größere Webseiten Inhalten an den Client gesendet werden, so wird diese Methode mehrfach aufgerufen und jeweils ein Teil des Webseite an den Client gesendet. Dies kann aber in unserem Fall (den wir später noch näher erläutern) des Stringvergleiches dazu führen, dass wir nur einen Teil des gesamten String während eines Aufrufs der Methode Write enthalten haben.

 

Um dies zu berücksichtigen habe ich folgenden Code eingebaut:

int idx = sBuffer.LastIndexOf(">"); 
_tempBuffer = sBuffer.Substring(idx + 1); 
sBuffer = sBuffer.Substring(0,idx+1); 

Da der Filter dazu verwendet wird HTML Seiten an den Client zu übertragen suche ich einfach das letzte vorkommen eines abschließenden Tags.

Kopiere alles einschließlich des letzten Tags in die zu verarbeitende Puffervariable sBuffer. Alles was hinter dem letzten abschließenden Tag ist kopiere ich in einen temporären Zwischenpuffer _tempBuffer.

Dieser wird beim nächsten Aufruf an den Anfang von sBuffer kopiert und anschließen geleert.

Hierdurch wird sichergestellt dass bei den Stringvergleichen immer ein ganzer in einem Tag eingeschlossener String zur Verfügung steht und nicht nur ein Teil eines href oder ähnlichem im sBuffer verarbeitet wird.

 

Den Teil des Stringvergleiches werde ich an dieser Stelle auch nicht ausführlich beschreiben, es sei nur soviel das gesagt, dass alle vorkommen von href gesucht werden und mit der Funktion ToSfu der Klasse SfuUtil von dynamischen URL in statischen URL umgewandelt werden. Außerdem werden Tags wie src, img, java scripte etc im sBuffer gesucht und anstelle von relativen angaben wie Image\logo.gif gegen absolute angaben wie http://www.dnnportal.de/imager/logo.gif ausgetauscht.

Zum Schluss werden die Daten des sBuffer als Stream zum Client gesendet.

Klasse in der Übersicht

        public class RequestFilter : Stream
        {
            private Stream _sink;
            protected IPrincipal _user;
            private long _position;
            bool openTag, endTag;
            string _tempBuffer;
            public RequestFilter(Stream sink, IPrincipal user)
            {
                _sink = sink;
                _user = user;
                openTag = false;
                endTag = false;
                _tempBuffer = String.Empty;
            }
            public override bool CanRead
            {
                get
                {
                    return true;
                }
            }
            public override bool CanSeek
            {
                get
                {
                    return true;
                }
            }
            public override bool CanWrite
            {
                get
                {
                    return true;
                }
            }
            public override long Length
            {
                get
                {
                    return 0;
                }
            }
            public override long Position
            {
                get
                {
                    return _position;
                }
                set
                {
                    _position = value;
                }
            }
            public override long Seek(long offset, SeekOrigin origin)
            {
                return _sink.Seek(offset, origin);
            }
            public override void SetLength(long value)
            {
                _sink.SetLength(value);
            }
            public override void Close()
            {
                _sink.Close();
            }
            public override void Flush()
            {
                _sink.Flush();
            }
            public override int Read(byte[] buffer, int offset, int count)
            {
                return _sink.Read(buffer, offset, count);
            }
            public override void Write(byte[] buffer, int offset, int count)
            {
                string sBuffer = Encoding.Default.GetString(buffer, offset, count);
                sBuffer = _tempBuffer + sBuffer.Trim();
                if (buffer.Length != count)
                {
                    int idx = sBuffer.LastIndexOf(">");
                    _tempBuffer = sBuffer.Substring(idx + 1);
                    sBuffer = sBuffer.Substring(0, idx + 1);
                }
                MatchCollection hrefMatches = Regex.Matches(sBuffer, RegexPattern.HrefPattern, RegexOptions.IgnoreCase);
                HttpContext Context = HttpContext.Current;
                if ((hrefMatches.Count > 0))
                {
                    try
                    {
                        foreach (Match match in hrefMatches)
                        {
                            string href = match.Groups[match.Groups.Count - 2].Value;
                            if (href.IndexOf(Context.Request.Headers["Host"]) > 0)
                                href = href.Substring(href.IndexOf(Context.Request.Headers["Host"]) + Context.Request.Headers["Host"].Length);
                            if (Regex.IsMatch(href, RegexPattern.AspxPattern) &&
                            !Regex.IsMatch(match.Value, RegexPattern.ImgPattern) &&
                            !Regex.IsMatch(match.Value, RegexPattern.CssPattern) &&
                            !Regex.IsMatch(match.Value, RegexPattern.ScriptPattern))
                            {
                                href = href.Replace(href, SfuUtil.ToSfuUrl(href));
                            }
                            if (!Regex.IsMatch(href, RegexPattern.HttpProtocolPattern) &&
                            !Regex.IsMatch(href, RegexPattern.MailToPattern, RegexOptions.IgnoreCase) &&
                            !Regex.IsMatch(href, RegexPattern.AnchorPattern) &&
                            !Regex.IsMatch(href, RegexPattern.JavascriptHtmlStatementPattern))
                            {
                                if (!Regex.IsMatch(href, RegexPattern.AbsolutePathPattern))
                                {
                                    href = Regex.Match(Context.Request.Path, RegexPattern.CurrentPathPattern).Groups[1].Value + href;
                                }
                                sBuffer = sBuffer.Replace(match.Value, match.Value.Replace(match.Groups[match.Groups.Count - 2].Value, href));
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.Message);
                    }
                }
                byte[] bufferNew = Encoding.Default.GetBytes(sBuffer);
                _sink.Write(bufferNew, 0, bufferNew.Length);
            }
        } 
Schlussbemerkungen

Die in diesem Artikel beschriebenen Klassen sind nicht vollständig und somit nicht lauffähig.

Außerdem gibt es bei dieser Verarbeitung noch einige andere Aspekte die gesondert betrachtet werden müssen.

Einen kompletten lauffähigen HTTPHandler der genau auf diesem Artikel basiert kann hier herunterladen.

 

Für registriert Mitglieder steht hier auch der Quellcode zum Download zur Verfügung.

 

Für Anregungen, Kritik oder Verbesserungsvorschläge bitte einfach Kommentare hinterlassen.

Der Autor: Hans-Peter Schelian

ASP.NET | C#
Thursday, March 24, 2005 1:57:33 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
# Saturday, January 15, 2005

Um in einer App.config Datei Enum Werte wieder in eine Variable des Augzählungstypes einzulesen kann folgende Funktion verwendet werden.

Enum.Parse()

Beispiel:

In diesem Beispiel lesen wir den NotifyFilter eines FileSystemWatcher ein:

Der Key in der App.config sieht wie folgt aus:

<add key="fdwNotiFyFilter" value="FileName, DirectoryName, LastWrite" />

Und hier die Vewendung:

fdw.NotifyFilter = (NotifyFilters)Enum.Parse(typeof(NotifyFilters), ConfigurationSettings.AppSettings["NotifyFilter"]);
Programmierung | Tips und Tricks | C#
Saturday, January 15, 2005 6:17:52 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]  
Autor: Hans-Peter Schelian  |  Trackback
Copyright © 2010 Hans-Peter Schelian - Schelian IT Beratung. All rights reserved.