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:

Tips und Tricks | C#
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. 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 )
Code | Tips und Tricks | C#
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#
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#
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: 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#
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 .
Code | C#
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#
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#
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#
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#
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: 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#
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#
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
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#
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#
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#
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#
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#
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: 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#
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#
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#
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: 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): 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): 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. Ü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#
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#
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: 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#
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#
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
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#
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: 
Kurz und Bündig | Code | C#
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#
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  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"} 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. Beispiel Konstruktoren: public Customer() { CustomerID = 1; } public Customer(int id) { CustomerID = id; } 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#
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#
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#
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#
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:
| 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#
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#
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
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#
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:
- 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;
- }
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;
}
Programmierung | Code | C#
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#
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
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
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.
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:
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#
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#
|
Copyright © 2010 Hans-Peter Schelian - Schelian IT Beratung. All rights reserved.
|
|