Suche: ASP.NET

Hintergrundaufgaben unter ASP.NET ausführen 👍 👎

Im Vergleich zu manch anderen Laufzeitumgebungen für Web-Anwendungen ist es bei ASP.NET auch hier recht einfach, Aufgaben im Hintergrund (beispielsweise über separate Threads) auszuführen. Obwohl das grundsätzlich jederzeit sehr einfach – abgesehen von den inhärenten Schwierigkeiten, die Nebenläufigkeit mit sich bringen kann – möglich ist, ist das einfache Vorgehen, wie man es aus dem Desktop-Bereich kennt, fehleranfällig.

Im einfachsten Fall würde man asynchron per Task.Run(…) (als mit häufig genutzten Standardwerten vorbelegte Abkürzung für Task.Factory.StartNew(…)) zur Verwendung des Standard-Thread-Pools oder manuell per Thread-Klasse arbeiten. Dies ist jedoch insofern problematisch, als dass die ASP.NET-Anwendungsdomäne davon nicht benachrichtigt wird und daher beispielsweise einerseits auf die Beendigung des Vorgangs nicht gewartet und andererseits auf das Herunterfahren des Arbeitsprozesses nicht reagiert werden kann.

Bereits seit längerer Zeit werden daher u. a. die beiden Methoden RegisterObject und UnregisterObject der HostingEnvironment-Klasse zur Verfügung gestellt, deren Verwendung jedoch etwas umständlich ist und auf Grund neuerer Möglichkeiten, die gleich im Anschluss vorgestellt werden, an dieser Stelle nicht weiter ausgeführt werden soll. Wir verwenden stattdessen die seit .NET 4.5.2 verfügbare QueueBackgroundWorkItem-Methode:
Hintergrundaufgabe hinzufügen
010203
HostingEnvironment.QueueBackgroundWorkItem(cT => {    /* Implementierung der Aufgabe */});
Etwas ungeschickt ist es jedoch, wenn dies beispielsweise in einem separaten Projekt ausgelagert werden soll, um jeweils von einer Desktop- und Web-Anwendung darauf zuzugreifen – außerhalb der ASP.NET-Laufzeitumgebung ist die Verwendung dieser Methode nämlich nicht möglich. Daher stellen wir für diesen Fall eine Alternative zur Verfügung, um zumindest ähnliches Verhalten für alle Anwendungsfälle zur Verfügung zu stellen:
Hilfsmethode (inkl. Fallback) implementieren
010203040506070809101112
public static class TaskUtility {    public static void DoBackgroundWork(        Action<CancellationToken> action,        CancellationToken cancellationToken = default(CancellationToken)    ) {        if(HostingEnvironment.IsHosted) {            HostingEnvironment.QueueBackgroundWorkItem(action);        } else {            Task.Run(() => action(cancellationToken), cancellationToken);        }    }}
Hilfsmethode verwenden
010203
TaskUtility.DoBackgroundWork(cT => {    /* Implementierung der Aufgabe */});
Alternativ wäre es natürlich auch möglich, nur die eigentliche Aufgabe auszulagern und dann anwendungsspezifisch zu starten. Weiterführende Informationen zur Verwendung des CancellationToken stellt das MSDN insbesondere über die Struktur CancellationToken und die Klasse CancellationTokenSource zur Verfügung.

Bezeichner eines Members mit C# ermitteln 👍 👎

Bei C# kommt man beispielsweise bei der Fehlerbehandlung (z. B. per ArgumentException und abgeleiteten Klassen), Logging (im Sinne von "Methode MethodName betreten/verlassen") oder auch beim Routing im Rahmen des ASP.NET MVC-Frameworks in die Notwendigkeit, Programmbezeichner als Zeichenketten anzugeben.

Etwas ärgerlich ist hier jedoch immer, was man bei grundsätzlich statisch typisierten Sprachen wie C# für gewöhnlich vermeiden möchte: Derartige Zeichenketten sind literal hinterlegt und dementsprechend einer Refaktorierung nicht ohne Weiteres zugänglich. Dieses Problem löst nun das relativ neue Konstrukt nameof, indem es die (einfachen) Bezeichner von Variablen, Typen und Membern als Zeichenkette liefert:
Beispiel-Klasse definieren und Member-Bezeichner ermitteln
01020304050607080910111213141516171819202122
public class Person {    public string FirstName {        get;        set;    }
public string LastName { get; set; }

public void Marry(Person person) { /* … */ }}

Debug.WriteLine(nameof(Person)); // "Person"Debug.WriteLine(nameof(Person.FirstName)); // "FirstName"Debug.WriteLine(nameof(Person.LastName)); // "LastName"Debug.WriteLine(nameof(Person.Marry)); // "Marry"
Praktisch ist nun, dass bei der Umbenennung einer Eigenschaft oder Methode, sich dieser Vorgang auch auf die entsprechende Verwendung innerhalb von nameof auswirkt und somit für Konsistenz sorgt.

Ein weiterer theoretischer Anwendungsfall ergibt sich im Rahmen einer INotifyPropertyChanged-Implementierung, jedoch lässt sich dieses Problem auch ganz ohne manuelle Angabe per CallerMemberName-Attribut lösen, wie ich in einem früheren Beitrag bereits beschrieben habe.

Aufzählungswert per Zeichenkette ermitteln 👍 👎

Vor sehr langer Zeit habe ich bereits einen Artikel zu Enumerationen in C# geschrieben, an welchen ich indirekt anknüpfen möchte. Oftmals steht man im Bereich der Softwareentwicklung vor dem Problem der Interoperabilität, da mehrere – bisweilen äußerst unterschiedliche – Systeme zusammenarbeiten müssen.

Ein klassischer Fall im Bereich der Webentwicklung ist die Zusammenarbeit aus Anwendung und Datenbank, beispielsweise hier im Blog zwischen der Programmiersprache C# mit ASP.NET MVC-Framework und MySQL-Datenbank. Etwas konkreter soll es darum gehen, C#-Aufzählungstypen zusammen mit MySQL-ENUMs zu verwenden, welche grundsätzlich erst einmal als bloße Zeichenketten zu behandeln sind. Für das folgende Beispiel nehmen wir an, dass eine Tabelle user existiert, die eine Spalte state vom Typ ENUM('ACTIVE','INACTIVE','LOCKED') enthält.

Unter Verwendung des ADO.NET Entity Framework würde beim Database First-Ansatz eine entsprechende Klasse user mit der Eigenschaft state vom Typ string erzeugt. Da es sich um eine partielle Klasse handelt, erweitern wir diese um eine neue Eigenschaft State. Diese soll anschließend die automatische Konvertierung für state übernehmen und ermöglicht uns im Programmcode einen typsicheren Zugriff auf den Status des Benutzers:
Aufzählungstyp UserState anlegen
0102030405
public enum UserState {    Active,    Inactive,    Locked}
Erweiterung der Klasse user
010203040506070809
public partial class user {    public UserState State {        get {            return (UserState) Enum.Parse(typeof(UserState), this.state, true);        }        set {            this.state = value.ToString().ToUpper();        }    }
Die Verwendung gestaltet sich nun – mit beiden Eigenschaften – wie gewohnt komfortabel:
Verwendung
0102030405060708091011121314151617
user dbUser = new user() {    State = UserState.Active};  // üblicherweise aus Datenbank ermittelt

string state = dbUser.state; // "ACTIVE"UserState userState = dbUser.State; // Active
dbUser.State = UserState.Inactive;

state = dbUser.state; // "INACTIVE"userState = dbUser.State; // Inactive
dbUser.state = "LOCKED";

state = dbUser.state; // "LOCKED"userState = dbUser.State; // Locked
Weitere Informationen zu den Enum.Parse-Methoden finden sich wie üblich im MSDN. Es lohnt sich außerdem ein Blick auf die Enum.TryParse(…)-Methoden mit generischem Typ-Parameter.

Datei per ASP.NET MVC hochladen 👍 👎

Vor einiger Zeit hatte ich bereits beschrieben, wie sich eine Datei per ASP.NET MVC ausliefern lässt. Ergänzend dazu möchte ich mit diesem Beitrag erklären, wie sich Dateien per ASP.NET MVC einfach hochladen lassen.

Zuerst erstellen wir eine minimale Ansicht (im Beispiel für eine Galerie) mit einem entsprechenden Formular:
View: ~/Views/Gallery/Upload.cshtml
010203040506
@using(Html.BeginForm("Upload", "Gallery", FormMethod.Post, new {    enctype = "multipart/form-data"})) {    <input type="file" name="file" />    <input type="submit" />}
Zur Verarbeitung verwenden wir die folgende einfache Methode im Controller:
Controller: ~/Controllers/GalleryController.cs
010203040506070809
public class GalleryController : Controller {    public ActionResult Upload(HttpPostedFileBase file = null) {        if(file != null) {            file.SaveAs(file.FileName);        }
return View(); }}
Über HttpPostedFileBase stehen uns – wie im Beispiel ersichtlich – insbesondere die Eigenschaft FileName mit dem ursprünglichen Dateinamen, sowie die praktische Methode SaveAs(…) zur Verfügung.

Auch mehrere Dateien hochzuladen ist kein Problem, dazu müssen wir der Methode Upload lediglich ein HttpPostedFileBase-Array übergeben:
Controller: ~/Controllers/GalleryController.cs (multiples Hochladen)
0102030405060708091011
public ActionResult Upload(HttpPostedFileBase[] files = null) {    if(files != null) {        foreach(HttpPostedFileBase file in files) {            if(file != null) {                file.SaveAs(file.FileName);            }        }    }
return View();}
Nun können wir in der Ansicht mehrere Felder zur Auswahl einer Datei anbieten:
View: ~/Views/Gallery/Upload.cshtml (multiples Hochladen)
0102030405060708
@using(Html.BeginForm("Upload", "Gallery", FormMethod.Post, new {    enctype = "multipart/form-data"})) {    <input type="file" name="files" />    <input type="file" name="files" />
<input type="submit" />}

Benutzerdefinierte Validierung unter ASP.NET MVC 👍 👎

Sehr praktisch unter ASP.NET MVC sind Attribute zur Validierung in Models. Standardmäßig bietet das .NET-Framework im Namensraum System.ComponentModel.DataAnnotations beispielsweise die Attribute Range, Required und StringLength, deren Verwendung wohl selbsterklärend sein dürfte.

Manchmal benötigt man jedoch etwas speziellere Validierungen und möchte naheliegenderweise dennoch auf eine einheitliche Vorgehensweise setzen. Hierfür steht das (abstrakte) Attribut Validation zur Verfügung, von welchem wir eine konkrete Implementierung für unser Vorhaben ableiten können.

Für unser Beispiel möchten wir ein Attribut zur Validierung implementieren, welches angibt, dass ein Feld (üblicherweise indirekt aus einem Formular vom Benutzer befüllt) mindestens eine Ziffer, zwei Großbuchstaben und drei Kleinbuchstaben enthalten muss. Wir halten es bewusst einfach, um uns auf den Kontext zu konzentrieren:
Benutzerdefiniertes Attribut zur Validierung implementieren
010203040506070809101112131415161718
[AttributeUsage(AttributeTargets.Property)]public class PasswordAttribute : ValidationAttribute {    public override bool IsValid(object value) {        string val = value as string;
if(!String.IsNullOrWhiteSpace(val)) { return ( val.Count(c => Char.IsDigit(c)) >= 1 && val.Count(c => Char.IsUpper(c)) >= 2 && val.Count(c => Char.IsLower(c)) >= 3 ); } else { return false; } }}
Dies ist eine sehr grundlegende Implementierung, die sich durchaus noch erweitern (und optimieren) lässt. Grundsätzlich genügt dieses Vorgehen jedoch bereits, um Verwendung zu finden. Das Attribut wird dazu wie üblich über der entsprechenden Eigenschaft der Model-Klasse deklariert:
Benutzerdefiniertes Attribut zur Validierung verwenden
01020304050607
public class User {    [Password]    public string Password {        get;        set;    }}
Eine allgemeine Einführung zum MVC-Framework möchte ich gerne in einem späteren Beitrag liefern.

123

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche