ASP.NET Time Tracker Starter Kit: Design und Implementierung
In diesem Artikel werden die Design- und Architekturentscheidungen für die Time Tracker-Beispielanwendung beschrieben. Außerdem wird der Code ausführlich dargestellt und erläutert.
Auf dieser Seite
- Übersicht
- Ziele
- Anwendungsarchitektur
- Datenebene
- Geschäftslogikebene
- Erstellen von Berichten
- Erstellen von Diagrammen mit GDI+
- Globalisierung
- Sicherheit
- Bereitstellung
Übersicht
Was ist das ASP.NET Time Tracker Starter Kit?
Die ASP.NET Time Tracker Starter Kit-Anwendung (im Folgenden als "Time Tracker" bezeichnet) ist eine Geschäftslösung, mit der Benutzer die mit einem Projekt verbrachte Zeit erfassen können. Außerdem haben Projekt-Manager die Möglichkeit, den Status der von ihnen verwalteten Projekte besser zu verfolgen, indem sie die Zeiteinträge der Benutzer sowie detaillierte Berichte zu diesen Einträgen anzeigen können.
Die Time Tracker-Anwendung präsentiert verschiedene Schlüsselfunktionen von ASP.NET und .NET Framework. Wie bei den ASP.NET Starter Kit Portal- und Commerce-Anwendungen besteht der Hauptzweck der Time Tracker-Anwendung darin, Architektur- und Entwicklungsempfehlungen für ASP.NET-Entwickler bereitzustellen.
Hauptfeatures:
- Die ASP.NET Time Tracker Starter Kit-Anwendung ist globalisiert (d. h., so konzipiert und entwickelt, dass sie in verschiedenen Kulturen eingesetzt werden kann) und erlaubt eine zukünftige Lokalisierung.
- Logische, drei Ebenen umfassende Architektur
- Sicherheit durch Verwendung der Windows-Authentifizierung
- Abrufen von Benutzerkontoinformationen aus Active Directory oder NT SAM
- DataGrid mit benutzerdefinierter Inlinebearbeitung
- Dynamische Diagrammbilderstellung mit GDI+
- Microsoft-Datenzugriffsanwendungsblöcke für .NET
- Mobile Web Forms, die Microsoft Mobile Internet Toolkit (MMIT) verwenden
- Dynamische Berichterstellung
- Schlanke Datenklassen, die für die Schnittstelle zwischen der Geschäftslogikebene und der Benutzeroberflächenebene verwendet werden
- Rollenbasierte Sicherheit, bei der ein benutzerdefiniertes Principal für die Rollenautorisierung verwendet wird
Dieses Whitepaper enthält ausführliche Informationen über die Time Tracker-Anwendung und ermöglicht einen Einblick aus Sicht der Entwickler. Darüber hinaus wird in diesem Artikel dargestellt, wie Sie Time Tracker als Vorlage für die Erstellung anderer Geschäftsanwendungen verwenden können. Hierbei werden die wichtigsten Features und Implementierungstechnologien der Anwendung erläutert.
Ziele
Da Time Tracker ein Beispiel für eine Geschäftsanwendung ist, die intern in einem Unternehmen verwendet werden würde, stellen sich die zu lösenden Probleme für Entwickler anders dar:
- Die Wartung ist wichtiger als die Gesamtleistung: Die Anwendung soll einfach zu verwalten sein. Benutzer sollen nahtlos zur Anwendung hinzugefügt werden können. Die Time Tracker-Anwendung verwendet die Windows-Authentifizierung und fügt die Benutzer bei ihrem ersten Besuch der Site zur Time Tracker-Datenbank hinzu. Außerdem kann jedes Mitglied der Administratorrolle über die Benutzerdetailseite ebenfalls Benutzer hinzufügen. Sobald Benutzer zur Domäne des Unternehmens gehören, sind sie dazu berechtigt, die Time Tracker-Anwendung zu verwenden. Anonyme Benutzer haben keinen Zugriff auf die Site.
- Nutzung vorhandener Informationen: Der Vor- und Nachname eines Benutzers kann aus dem Active Directory oder aus der NT Security Account Manager-Datenbank (NT SAM oder NT-Sicherheitskontenverwaltung) des Unternehmens abgerufen werden. Falls Vor- und Nachname nicht erfolgreich abgerufen werden können, zeigt die Anwendung statt dessen die Domäne und den Benutzernamen an. Für Active Directory und für NT SAM wird jeweils eine Implementierung bereitgestellt, falls der Zugriff auf eine Active Directory-Installation nicht möglich ist.
- Saubere Trennung zwischen logischen Ebenen: In einer Intranetumgebung nutzen Firmenanwendungen vermutlich bestimmte Stufen oder Ebenen der Anwendung gemeinsam bzw. verwenden diese erneut. Die Time Tracker-Anwendung ermöglicht dies, indem sie "schlanke" Objekte als Schnittstelle zwischen der Benutzeroberfläche und der Geschäftslogikebene verwendet.
Das ASP.NET Time Tracker Kit wurde als Inline- und als Codebehind-Modell entwickelt. Die SDK-Version wurde mit Hilfe des Inline-Codemodells geschrieben und für das ASP.NET Web Matrix Project sowie das .NET Framework SDK optimiert. Die zweite Version wurde in Microsoft ® Visual Studio.NETT unter Verwendung des Codebehind-Modells geschrieben. Eine C#- und eine VB .NET-Implementierung wurde sowohl für die die SDK-Version als auch für die Visual Studio.NETT-Version erstellt.
Anwendungsarchitektur
Bei der ASP.NET Time Tracker Starter Kit-Anwendung handelt es sich um eine ASP.NET-Webanwendung, die eine logische, drei Ebenen umfassende Architektur verwendet.
Datenzugriff, Geschäftslogik und Benutzeroberflächenlogik sind in verschiedene Klassen unterteilt. N-stufige Architekturen haben zahlreiche Vorteile. Dazu gehören beispielsweise:
- Die Benutzeroberflächen-, Geschäftslogik- und Datenzugriffsebenen sind sauber getrennt. Diese Isolierung fördert die Codewiederverwendung und erleichtert die Codeverwaltung und -erweiterung. Zudem wird die Entwicklung durch die klare Zerlegung der Funktionalität vereinfacht, denn die Entwickler bzw. Entwicklerteams können sich bei der Implementierung auf verschiedene Bestandteile der Anwendung konzentrieren.
- Die Geschäftsregeln sind in einer Komponente zusammengefasst, die einfach wieder verwendet werden kann. Eine höherwertige Programmiersprache (wie C# oder Visual Basic .NET) wird zur Entwicklung der Geschäftsregeln bereitgestellt.
- Der Datenzugriffscode wird an einer zentralen Stelle gespeichert, wodurch die Entwicklung und Wartung vereinfacht werden.
Datenebene
Das ASP.NET Time Tracker Starter Kit verwendet eine Microsoft SQL Server 2000-Datenbank (MSDE 2000 wird ebenfalls unterstützt). Das physische Datenbankschema wurde nach einer sorgfältigen Analyse der Time Tracker-Anforderungen und -Anwendungsfälle erstellt.
Beispiele für diese Anforderungen sind:
- Ein Benutzer gibt die Zeit für ein Projekt und eine Kategorie ein.
- Benutzer haben eine Rolle, die festlegt, welche Funktionen sie in der Anwendung verwenden dürfen.
- Nur Benutzer, die Mitglied eines Projekts sind, können die Zeit für das entsprechende Projekt protokollieren.
- Ein Projekt hat eine oder mehrere Kategorien.
- Jedem Projekt kann ein Benutzer als Projekt-Manager zugewiesen werden.
Datenbankschema
Gespeicherte Prozeduren
Die Time Tracker-Anwendung verwendet gespeicherte Prozeduren, um alle Datenbankabfragen zu kapseln. Dies ermöglicht eine saubere Trennung zwischen der Datenbank und der Datenzugriffsebene. Die Verwendung gespeicherter Prozeduren bietet Leistungsvorteile, da die Prozeduren bei der ersten Ausführung optimiert werden und anschließend für nachfolgende Aufrufe im Speicher verbleiben. Parameter mit strikter Typbindung in gespeicherten Prozeduren sowie die Möglichkeit, Berechtigungen für jede gespeicherte Prozedur festzulegen, verbessern die Sicherheit. Ferner genügt für Benutzer, die auf gespeicherte Prozeduren zugreifen, eine Zugriffsberechtigung für die gespeicherten Prozeduren. Eine Zugriffsberechtigung für die zugrunde liegenden Tabellen ist dabei nicht erforderlich.
Je nachdem, welche Änderungen an den Tabellen in der Datenbank vorgenommen werden, können die gespeicherten Prozeduren geändert werden, ohne dass Änderungen an der Datenebene erforderlich sind. Änderungen an Signaturen von gespeicherten Prozeduren reichen allerdings fast immer bis zur Daten-, Geschäftslogik- und Präsentationsebene.
Die folgenden gespeicherten Prozeduren bilden eine Teilmenge dessen, was zum Implementieren
der Time Tracker-Anwendung erforderlich ist.
Zeiteinträge
| Name | Parameter | Beschreibung | Ergebnis |
| GetTimeEntry | @EntryLogID | Ruft Zeiteintragdetails aus der Tabelle EntryLog ab. | SQL-Ergebnissatz, der eine Zeile und alle Spalten aus der Tabelle EntryLog enthält. |
| AddTimeEntry |
@UserName @ProjectID @CategoryID @EntryDate @Description @Duration |
Fügt einen Zeiteintrag zur Tabelle EntryLog hinzu. | Neue EntryLogID (falls erfolgreich). |
| UpdateTimeEntry |
@EntryLogID @UserName @ProjectID @CategoryID @EntryDate @Description @Duration |
Aktualisiert die Zeiteintragsdaten für den angegebenen Zeiteintragsdatensatz. | |
| ListTimeEntries |
@UserQueryID @UserID @StartDate @EndDate |
Ruft Zeiteinträge in der Tabelle EntryLog für den angegebenen Benutzer und Datumsbereich ab. | SQL-Zeiteintrags-Ergebnissatz, der alle Spalten in der Tabelle EntryLog enthält. |
| DeleteTimeEntry | @EntryLogID | Löscht den angegebenen Datensatz aus der Tabelle EntryLog. |
Ein gutes Beispiel für eine der komplexeren gespeicherten Prozeduren in der Time Tracker-Anwendung ist die gespeicherte Prozedur ListTimeEntries. Diese Prozedur ruft rollenbasiert für einen angegebenen Benutzer Zeiteinträge aus der Tabelle EntryLog ab. Benutzer können beispielsweise immer ihre eigenen Zeiteinträge anzeigen. Administratoren können alle Zeiteinträge für sämtliche Benutzer anzeigen. Projekt-Manager können die Zeiteinträge für die Mitglieder der von ihnen verwalteten Projekte anzeigen. Benutzer mit der Beraterrolle können allerdings nur ihre eigenen Zeiteinträge anzeigen.
ListTimeEntries
CREATE PROCEDURE ListTimeEntries
(
@QueryUserID int,
@UserID int,
@StartDate datetime,
@EndDate datetime
)
AS
DECLARE @QueryUserRoleID int
SELECT @QueryUserRoleID = Users.RoleID
FROM Users
WHERE Users.UserID = @QueryUserID
IF @QueryUserRoleID = 2
BEGIN
SELECT
EntryLogID, EntryLog.Description, Duration, EntryDate,
EntryLog.ProjectID AS ProjectID,
EntryLog.CategoryID AS CategoryID,
Categories.Abbreviation AS CategoryName,
Projects.Name AS ProjectName,
ManagerUserID, Categories.Abbreviation AS CatShortName
FROM
EntryLog
INNER JOIN
Categories
ON
EntryLog.CategoryID = Categories.CategoryID
INNER JOIN
Projects
ON
EntryLog.ProjectID = Projects.ProjectID
WHERE
UserID = @UserID
AND
EntryDate >= @StartDate
AND
EntryDate <= @EndDate
AND
ManagerUserID = @QueryUserID
END
ELSE IF @QueryUserRoleID = 1 or @QueryUserID = @UserID
BEGIN
SELECT
EntryLogID, EntryLog.Description, Duration, EntryDate,
EntryLog.ProjectID AS ProjectID,
EntryLog.CategoryID AS CategoryID,
Categories.Abbreviation AS CategoryName,
Projects.Name AS ProjectName,
ManagerUserID, Categories.Abbreviation AS CatShortName
FROM
EntryLog
INNER JOIN
Categories
ON
EntryLog.CategoryID = Categories.CategoryID
INNER JOIN
Projects
ON
EntryLog.ProjectID = Projects.ProjectID
WHERE
UserID = @UserID
AND
EntryDate >= @StartDate
AND
EntryDate <= @EndDate
END
GO
Datenzugriffsebene
Datenzugriffsebenen werden verwendet, um datenbankspezifischen Code zu kapseln, wodurch die Datenbankdetails von der Ebene der Geschäftslogik getrennt werden. Diese Art der Trennung ermöglicht die Aggregation mehrerer unterschiedlicher Datenbanken, ohne dass Änderungen an der Ebene der Geschäftslogik oder der Präsentationsebene erforderlich werden. Die Datenzugriffsebene wird mit Hilfe von Microsoft Data Access Application Blocks (DAAB) implementiert.
Bei DAAB handelt es sich um eine NET.-Komponente, die optimierten Datenzugriffscode für die Ausgabe von Befehlen für eine SQL Server-Datenbank enthält. Durch die Verwendung von DAAB wird die zum Erstellen, Testen und Warten des Datenbankzugriffs erforderliche Menge benutzerspezifischen Codes reduziert. Aufrufe für Datenbankzugriffe können so auf eine einzige Codezeile anstelle der sonst üblichen sechs oder mehr erforderlichen Zeilen reduziert werden.
DataSet ds = SqlHelper.ExecuteDataset( ConfigurationSettings.AppSettings[Web.Global.CfgKeyConnString], CommandType.StoredProcedure, "ListAllProjects");
Im obigen Codebeispiel wird die gespeicherte Prozedur ListAllProjects aufgerufen, und der Ergebnissatz wird einem DataSet zugewiesen. Dieser Aufruf erfordert nur eine Codezeile. Falls DAAB nicht und ADO.NET (System.Data) direkt verwendet wird, sind hingegen sechs Codezeilen erforderlich.
SqlConnection myConnection = new SqlConnection(
ConfigurationSettings.AppSettings[Web.Global.CfgKeyConnString]);
SqlCommand myCommand = new SqlCommand("ListAllProjects", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
SqlDataAdapter myDataAdapter = new SqlDataAdapter(myCommand);
DataSet myDataSet = new DataSet();
myDataAdapter.Fill(myDataSet);
Vergleich zwischen ExecuteDataSet und ExecuteReader
Die Time Tracker-Anwendung verwendet im gesamten Code die Methode ExecuteDataSet anstelle von ExecuteReader.
Der DataReader ist ein verbundener Reader, der eine Vorwärtsanzeige von Daten ermöglicht. Da DataReaders vorwärtsgerichtet sind, können Sie die Daten sehr schnell lesen. Von Nachteil ist jedoch, dass in der DAAB die ExecuteDataReader-Methode eng an den SQL Server gebunden ist, wohingegen ExecuteDataSet nicht datenbankspezifisch ist. Außerdem muss der DataReader nach der Verwendung explizit geschlossen werden.
Geschäftslogikebene
Die Geschäftslogikebene trennt den anwendungsspezifischen Code bzw. die Geschäftsbereiche des Unternehmens von der Benutzeroberfläche und dem datenbankspezifischen Code. Andere von einer Firma erstellten Unternehmensanwendungen können bei Bedarf die Geschäftslogikebene verwenden, so dass ein Maximum an Codeverwertung erreicht wird. Die Trennung ermöglicht auch die Erstellung von Webdiensten, die die Funktionen der Geschäftslogikebene verwenden.
Die Sicherheit, die festlegt, welche Daten ein Benutzer anzeigen darf, wird von der Geschäftslogikebene und den gespeicherten Prozeduren durchgesetzt. Beispielsweise kann ein Projekt-Manager nur Zeiteinträge für Berater anzeigen, die Mitglieder in seinen Projekten sind. Daher übernimmt die TimeEntry.List-Methode die UserID des anfordernden Benutzers als Parameter. Der Benutzername wird über die Datenzugriffsebene an die zugrunde liegende gespeicherte Prozedur übergeben. Es werden nur Daten, die der Benutzer anzeigen darf, an die Geschäftslogikebene zurückgegeben.
Die Time Tracker-Anwendung verwendet schlanke Klassen, um Informationen einzuschließen, die von der Datenbankzugriffsebene zurückgegeben werden. Die TimeEntry-Klasse ist ein gutes Beispiel für eine der in Time Tracker verwendeten Klassen.
TimeEntry
Die TimeEntry-Klasse kapselt alle Zeiteintragsdetails.
Öffentliche Eigenschaften
- Name
- Typ
- CategoryID
- Ganzzahl
- CategoryName
- Zeichenfolge
- CategoryShortName
- Zeichenfolge
- Day
- Zeichenfolge
- Description
- Zeichenfolge
- Duration
- Dezimal
- EntryDate
- Datum/Zeit
- EntryLogID
- Ganzzahl
- ProjectID
- Ganzzahl
- ProjectName
- Zeichenfolge
Konstruktoren
- TimeEntry ()
- TimeEntry (entryLogID)
- TimeEntry (entryLogID, userName, projectID, categoryID, taskDate, description, duration)
Statische Methoden
- GetEntries (userName, startDate, endDate): Gibt eine Liste aller Zeiteinträge in einem bestimmten Zeitraum für den durch UserName angegebenen Benutzer zurück.
- Remove (): Entfernt den Zeiteintrag aus der Datenbank.
Methoden
- Load (): Ruft mit Hilfe der EntryLogID-Eigenschaft Zeiteintragsinformationen für den angegebenen Eintrag aus der Datenbank ab.
- Save (alle Zeiteintragseigenschaften): Fügt Zeiteintragsinformationen hinzu oder aktualisiert sie. Wenn die EntryLogId-Eigenschaft den Wert 0 hat, werden die Informationen als neuer Zeiteintrag hinzugefügt. Andernfalls werden die Informationen aktualisiert. Die Eigenschaften UserName und ProjectID müssen vor dem Aufrufen von Save festgelegt werden.
Benutzerdefinierte Auflistungen
Da die Time Tracker-Präsentationsebene sehr listenzentriert ist, verwendet die Anwendung eigene benutzerdefinierte Auflistungen, um Informationen zwischen der Geschäftslogikebene und der Präsentationsebene zu übermitteln. Als mögliche Alternativen können auch die Objekte DataSet, DataTable, DataView oder beliebige andere Objekte verwendet werden, die die Schnittstellen ICollection und IEnumerable implementieren. Die Verwendung von benutzerdefinierten Auflistungen anstelle dieser Alternativen bietet eine Reihe von Vorteilen:
- Eine benutzerdefinierte Auflistung enthält weniger Code als z. B. ein DataSet.
- Benutzerdefinierte Auflistungen ermöglichen eine klarere Trennung zwischen der Datenebene und der Präsentationsebene.
- Jede Auflistung ist ein klassenspezifisches Objekt.
Die in der Time Tracker-Anwendung verwendeten benutzerdefinierten Auflistungen werden von der ArrayList-Basis abgeleitet. Die ArrayList wird so angepasst, dass sie ihre internen Objekte und die entsprechenden Felder sortieren kann. Nachfolgend sehen Sie ein Beispiel für eine in der Time Tracker-Anwendung verwendete benutzerdefinierte Auflistung.
public class TimeEntriesCollection : ArrayList
{
public enum TimeEntryFields
{
InitValue,
Day,
Category,
Project,
Hours,
Description
}
public void Sort(TimeEntryFields sortField, bool isAscending)
{
switch (sortField)
{
case TimeEntryFields.Day:
base.Sort(new DayComparer());
break;
case TimeEntryFields.Category:
base.Sort(new CategoryComparer());
break;
case TimeEntryFields.Description:
base.Sort(new DescriptionComparer());
break;
case TimeEntryFields.Hours:
base.Sort(new HoursComparer());
break;
case TimeEntryFields.Project:
base.Sort(new ProjectComparer());
break;
}
if (!isAscending) base.Reverse();
}
#region ICompare Implementations
private sealed class DayComparer : IComparer
{
public int Compare(object x, object y)
{
Components.TimeEntry first = (Components.TimeEntry) x;
Components.TimeEntry second = (Components.TimeEntry) y;
return first.EntryDate.CompareTo(second.EntryDate);
}
}
private sealed class CategoryComparer : IComparer
{
public int Compare(object x, object y)
{
Components.TimeEntry first = (Components.TimeEntry) x;
Components.TimeEntry second = (Components.TimeEntry) y;
return first.CategoryName.CompareTo(second.CategoryName);
}
}
private sealed class ProjectComparer : IComparer
{
public int Compare(object x, object y)
{
Components.TimeEntry first = (Components.TimeEntry) x;
Components.TimeEntry second = (Components.TimeEntry) y;
return first.ProjectName.CompareTo(second.ProjectName);
}
}
private sealed class DescriptionComparer : IComparer
{
public int Compare(object x, object y)
{
Components.TimeEntry first = (Components.TimeEntry) x;
Components.TimeEntry second = (Components.TimeEntry) y;
return first.Description.CompareTo(second.Description);
}
}
private sealed class HoursComparer : IComparer
{
public int Compare(object x, object y)
{
Components.TimeEntry first = (Components.TimeEntry) x;
Components.TimeEntry second = (Components.TimeEntry) y;
return first.Duration.CompareTo(second.Duration);
}
}
#endregion
}
Wenn in der Time Tracker-Anwendung eine Datenauflistung erforderlich ist, wird eine benutzerdefinierte Auflistung verwendet.
Statische Methoden
Darüber hinaus werden die Aufrufmethoden für die Rückgabe von benutzerdefinierten Auflistungen als statisch deklariert. Statische Methoden gehören nicht zu einem spezifischen Objekt, sondern zu dem Typ selbst. Der Vorteil der Verwendung statischer Methoden liegt daher darin, dass ein Objekt nicht instantiiert werden muss, um die Methode aufzurufen. GetProjects ist beispielsweise eine statische Methode in der Project-Klasse. Die Methode zum Aufrufen einer Liste aller Projekte besteht aus nur einer Codezeile.
Projects.DataSource = Project.GetProjects();
Abrufen von Benutzerinformationen
Die Integration der Time Tracker-Anwendung in das firmeneigene Netzwerk erfolgt dadurch, dass Benutzerkontoinformationen entweder aus einer NT SAM oder einem Active Directory abgerufen werden. Wenn ein Benutzer zum ersten Mal die Time Tracker-Website aufruft, werden seine Rolleninformationen aus der Time Tracker-Datenbank abgerufen. Mit der DirectoryHelper-Klasse wird der Vor- und Nachname des Benutzers aus der angegebenen Benutzerkontoquelle abgerufen, die in der Datei web.config konfiguriert ist. Falls der Benutzer nicht gefunden werden kann, wird die Identität des Benutzers anstelle des Namens verwendet.
Die DirectoryHelper-Klasse kann einfach erweitert werden, um weitere Benutzerinformationen aus der NT SAM oder dem Active Directory abzurufen.
//*********************************************************************
//
// This does the core directory searching
//
//*********************************************************************
public static bool FindUser(string identification, ref string FirstName, ref string LastName)
{
bool result = false;
// Determine which method to use to retrieve user information
// WindowsSAM
if (ConfigurationSettings.AppSettings[Web.Global.CfgKeyUserAcctSource] == "WindowsSAM")
{
// Extract the machine or domain name and the user name from the
// identification string
string [] samPath = identification.Split(new char[] {'\\'});
_path = _windowsSAMPath + samPath[0];
try
{
// Find the user
DirectoryEntry entryRoot = new DirectoryEntry(_path);
DirectoryEntry userEntry = entryRoot.Children.Find(samPath[1], "user");
LastName = userEntry.Properties["FullName"].Value.ToString();
}
catch
{
result = false;
}
result = true;
}
// Active Directory
else
{
_path = _activeDirectoryPath;
// Setup the filter
identification = identification.Substring(identification.LastIndexOf(@"\") + 1,
identification.Length - identification.LastIndexOf(@"\")-1);
string userNameFilter = string.Format(_filter, identification);
// Get a Directory Searcher to the LDAPPath
DirectorySearcher searcher = new DirectorySearcher(_path);
if (searcher == null)
{
return false;
}
// Add the propierties that need to be retrieved
searcher.PropertiesToLoad.Add("givenName");
searcher.PropertiesToLoad.Add("sn");
// Set the filter for the search
searcher.Filter = userNameFilter;
try
{
// Execute the search
SearchResult search = searcher.FindOne();
if (search != null)
{
FirstName = SearchResultProperty(search, "givenName");
LastName = SearchResultProperty(search, "sn");
result = true;
}
else
result = false;
}
catch
{
result = false;
}
}
return resu<
}
Präsentationsebene
Die Präsentationsebene ist verantwortlich für die Benutzeroberfläche und kommuniziert unmittelbar mit der Geschäftslogikebene. Wenn die Präsentationsebene vom Rest der Anwendung getrennt wird, können unterschiedliche Benutzeroberflächen erstellt werden (beispielsweise für Web Forms, Windows Forms und mobile Geräte), die alle die gleiche Geschäftslogik und den gleichen Datenbankzugriffscode verwenden.
Benutzersteuerelemente
Die Hauptregisterkarten, die sich ganz oben auf dem jeweiligen Bildschirm befinden, und die untergeordneten Registerkarten unter Admin werden als Benutzersteuerelemente implementiert, so dass die Vorteile der Codewiederverwendung ausgenutzt werden können. Die Autorisierung wird durch das Benutzersteuerelement durchgesetzt, das entsprechend der Rolle des Benutzers entscheidet, ob die Registerkarten Berichte und Admin angezeigt werden oder nicht. Die Rolle des Benutzers kann aus einem verschlüsselten Cookie abgerufen werden, in dem Benutzerinformationen gespeichert sind (siehe Abschnitt "Sicherheit" weiter unten).
Die Benutzersteuerelemente sind gelb markiert.
Die Zeiterfassungsseite
Auf der Zeiterfassungsseite können Zeiteinträge hinzugefügt, geändert oder entfernt werden.
DataGrid mit benutzerdefinierter Inlinebearbeitung
Eine der vielfältigen Funktionen des DataGrid-Steuerelements ist die Inlinebearbeitung. Hierfür wird eine Spalte als EditTemplate definiert und mit einem anderen ASP.NET-Serversteuerelement, z. B. TextBox oder DropDownList, gefüllt. Das nachstehend abgebildete Arbeitszeitkarten-Datenblatt ist ein Beispiel für diese Funktion.
Die Time Tracker-Anwendung basiert auf den integrierten Inlinebearbeitungsfunktionen von DataGrid, so dass sich eine Änderung in der Projekt-DropDownList auch auf die Liste der Kategorien in der entsprechenden Kategorie-DropDownList auswirkt.
Erstellen von Berichten
Projektbericht
Wenn Projekt-Manager den Fortschritt ihrer Teams bei verschiedenen Projekten verfolgen möchten, können sie über die Berichtsseite den Projektbericht aufrufen. Dieser Bericht wird auf der Grundlage der im Startbildschirm ausgewählten Projekte erstellt. Die Daten für diesen Bericht werden nach Projektzusammenfassung, Kategoriezusammenfassung und Ressourcenzusammenfassung gruppiert. Jeder Beratername in der Ressourcenzusammenfassung ist ein Hyperlink, der zum Ressourcenbericht des jeweiligen Beraters führt.
Mit dem DataList-Websteuerelement kann die Zusammenfassung der Projekte angezeigt werden. In diesem Bericht werden drei DataList-Steuerelemente verwendet: ProjectList (äußeres Feld), CategoryList (mittleres Feld) und EntryList (inneres Feld). Die DataList-Steuerelemente werden ineinander geschachtelt, indem eine DataList in die Elementvorlage der übergeordneten DataList eingefügt wird.
<asp:datalist id="ProjectList" RepeatColumns="1" runat="server"> <headerstyle cssclass="header-gray" /> <headertemplate> Project Report </headertemplate> <itemtemplate> <asp:datalist id="CategoryList" RepeatColumns="1" runat="server"> <itemtemplate> ...
Jedes Projekt muss mit allen zugehörigen Kategorien in Beziehung stehen, und jede Kategorie muss mit allen zugehörigen Ressourcen in Beziehung stehen. Um diese Beziehungen zu implementieren, müssen die Datenquellen für CategoryList und EntryList dynamisch während der Laufzeit zugewiesen werden.
<asp:datalist id="CategoryList" DataSource='<%# ListCategory((int) DataBinder.Eval(Container.DataItem, "ProjectID")) %>' runat="server">
ListCategory ist eine Hilfsfunktion, mit der alle Kategoriezusammenfassungen für ein bestimmtes Projekt abgerufen werden können. Beachten Sie, dass die ProjectId aus der ProjectList an die CategoryList übergeben wird.
Die EntryList-Datenquelle wird ebenfalls mit derselben Technik implementiert.
<asp:datalist id="EntryList" DataSource='<%# ListTimeEntries((int) DataBinder.Eval(Container.DataItem, "CategoryID")) %>' runat="server">
Ressourcenbericht
Der Ressourcenbericht kompiliert Zeitzusammenfassungen für einen oder mehrere Berater und listet Zeiteintragsdetails für einen angegebenen Datumsbereich auf. Mit Hilfe dieses Berichts kann ein Projekt-Manager sich einen schnellen Überblick über die Zeiteinträge eines Beraters bzw. aller Berater verschaffen, die er verwaltet.
Hier wird dasselbe Verfahren wie beim Erstellen des Projektberichts angewendet, wobei allerdings die Struktur leicht verändert ist. Der Ressourcenbericht enthält eine DataList (UserList, äußeres Feld) mit einem geschachtelten DataGrid (TimeEntryGrid, inneres Feld). In dieser Implementierung wird das DataGrid verwendet, weil das Sortieren der Eintragsdetails ein Bestandteil der Berichtsanforderungen ist. Der Code für die Sortierung ist dem für die Zeiterfassungsseite implementierten Code sehr ähnlich.
Die Syntax zum Verknüpfen von UserList mit TimeEntryGrid ist mit der im Projektbericht verwendeten Syntax vergleichbar.
<asp:datagrid id="TimeEntryGrid" DataSource='<%# ListTimeEntry((int)DataBinder.Eval(Container.DataItem, "UserID"), _startDate, _endDate) %>' runat="server" AutoGenerateColumns="False" AllowSorting="True" OnSortCommand="TimeEntryGrid_Sort">
ListTimeEntry ist eine Hilfsfunktion, mit der alle Zeiteinträge für einen bestimmten Benutzer in einem angegebenen Datumsbereich abgerufen werden. Der UserID-Parameter wird aus der übergeordneten DataList (UserList) übergeben. Der Datumsbereich stammt aus dem Berichtsstartprogramm.
Erstellen von Diagrammen mit GDI+
Auf der Zeiterfassungsseite wird basierend auf den Zeiteintragsdaten des aktuellen
Benutzers ein Balkendiagramm dynamisch generiert. Das Diagramm stellt die Stunden
eines Benutzers in einer bestimmten Woche visuell dar.
![]()
Diagrammseite
Um während der Laufzeit dynamisch ein Diagramm zu erstellen, ist die separate Seite TimeEntryBarChart.aspx erforderlich, um das Bild zurückzugeben. Das Bild wird im Antwortdatenfluss in Form von Binärdaten zurückgegeben, die einem Bildsteuerelement zugewiesen sind. Die separate Seite ist erforderlich, weil Text-/HTML-Inhalte im Antwortdatenstrom nicht mit binärem Inhalt vermischt werden können. Die Zeiterfassungsseite verweist dann auf diese Diagrammseite als Bildquelle.
Die Abfragezeichenfolge wird verwendet, um die Informationsdaten für die X-Achse und die Y-Achse zu übermitteln. Das Datenformat der einzelnen Achsen besteht aus einer Wertezeichenfolge, deren einzelne Werte durch einen senkrechten Strich (|) voneinander getrennt sind. Beachten Sie, dass Werte aus der Abfragezeichenfolge als Zeichenfolgentyp übermittelt werden. Die Diagrammklasse ist dafür zuständig, die Werte in den korrekten Datentyp zu konvertieren. In diesem besonderen Fall enthält die X-Achse Tagbezeichnungen (z. B. Mo, Di ...), und die Y-Achse enthält die Gesamtstunden für die jeweiligen Tage. Nachstehend ein Beispiel für die Abfragezeichenfolge und den Code zum Abrufen der Daten.
http://localhost/TTWeb/TimeEntryBarChart.aspx?xValues=Mon|Tue|Wed|Thu|Fri|Sat|Sun&yValues=2|4.5|2|3|0|0|0
string xValues, yValues;
xValues = Request.QueryString["xValues"];
yValues = Request.QueryString["yValues"];
bar.CollectDataPoints(xValues.Split("|".ToCharArray()),
yValues.Split("|".ToCharArray()));
Um eine bessere Bildqualität zu erzielen, wird das Bild im PNG-Format ausgegeben. (Beachten Sie, dass das PNG-Format von Netscape 4.x-Browsern nicht unterstützt wird.) Da das Bild im PNG-Format vorliegt, kann das Bitmapobjekt nicht direkt in den Response.OutputStream schreiben, weil der OutputStream die für PNG erforderliche Funktion für den wahlfreien Zugriff nicht unterstützt. Dieses Problem wird umgangen, indem MemoryStream verwendet wird, um das Bitmapobjekt in den Response.OutputStream zu schreiben.
Bitmap bmp; MemoryStream memStream = new MemoryStream(); BarGraph bar = new BarGraph(); bmp.Save(memStream, ImageFormat.Png); memStream.WriteTo(Response.OutputStream);
Diagrammklassen
Es gibt vier Klassen, die zum Implementieren der Diagrammerstellungsfunktion verwendet werden: ChartItem, ChartItemsCollection, Chart, PieChart und BarGraph. Die ersten drei sind die grundlegenden Klassen zur Erstellung verschiedener Diagrammtypen. Entwickler können diese Implementierung erneut verwenden, um eigene, benutzerdefinierte Diagrammklassen zu erstellen. Die Klasse BarGraph wird mit Hilfe derselben Technik abgeleitet.
ChartItemDiese Klasse stellt einen einzelnen Datenpunkt dar und enthält ausreichend Informationen, um das Element im Diagramm erstellen zu können. Zur Implementierung der Zeiterfassung wird jedes Diagrammelement den einzelnen Balken im Diagramm direkt zugeordnet.
ChartItemsCollection.NET verfügt über die abstrakte Klasse CollectionBase, mit deren Hilfe Entwickler auf einfache Weise Auflistungen für ihre benutzerdefinierten Klassen erstellen können. ChartItemCollection implementiert diese Funktion, um eine Auflistung von Datenpunkten zum Erstellen des Diagramms darzustellen.
ChartDiese Klasse wird aus folgenden Gründen als abstrakte Klasse implementiert:
- Zur Definition eines Standards für Funktionen und Schnittstellen für beliebige daraus abgeleitete Klassen. In diesem Fall müssen alle abgeleiteten Klassen die Draw()-Methode überschreiben, einen Satz vordefinierter Farben besitzen und eine Auflistung von Datenpunkten kapseln.
- Zur Begrenzung der Anzahl der Datenpunkte, die für das abgeleitete Diagramm angegeben werden können. Die Begrenzung der Anzahl der Werte für das Diagramm ist deshalb von Bedeutung, weil die Anzeige zu vieler Daten im Diagramm den Nutzen beeinträchtigen könnte.
Als Ableitung der Chart-Klasse besitzt BarGraph eine komplexe Implementierung, um vorhandene Datensätze während der Laufzeit dynamisch in informationell sinnvolle Diagramme umzuwandeln. Das Hauptziel besteht jedoch darin, Entwicklern das Erstellen notwendiger Balkendiagramme im Entwicklungsprozess zu erleichtern.
Um beispielsweise das Diagramm auf der Zeiterfassungsseite anzuzeigen, müssen nur zwei BarGraph-Eigenschaften festgelegt werden, um das Aussehen anzupassen. Diese Eigenschaften besitzen Standardwerte, die ihnen bei der Erstellung der Klasse zugewiesen werden. In vielen Fällen müssen Entwickler also keine dieser Eigenschaften festlegen. Sie können stattdessen direkt die CollectDataPoints()-Methode aufrufen, um die Datenpunkte zu übermitteln und anschließend die Draw()-Methode aufrufen, um die Bitmap zu erstellen.
Auf die komplexen Methoden und mathematischen Berechnungen zum Erstellen eines Balkendiagramms mit Hilfe von GDI+ kann aus Platzgründen in diesem Dokument nicht eingegangen werden. Folgendes sollte beim Implementieren von Balkendiagrammen jedoch beachtet werden:
- Nehmen Sie sich die Zeit, das Diagramm während der Entwurfsphase vor der Implementierung auf Papier zu zeichnen.
- Berechnen Sie im Voraus alle Abmessungen und Maße für jeden Teil des Diagramms.
- Vergewissern Sie sich, dass das Diagramm bei einer beliebigen Anzahl Datenpunkte korrekt skaliert wird.
Mobile Geräte
Mobile Geräte wie Pocket PCs und Mobiltelefone werden von der Time Tracker-Anwendung eingeschränkt unterstützt. Benutzer können mit ihren mobilen Geräten Zeiteinträge anzeigen, hinzufügen und aktualisieren. Das Microsoft Mobile Information Toolkit wird verwendet, um die Unterstützung für mobile Geräte zu implementieren.
Gewöhnlich können ASP.NET Web Forms-Seiten nur ein serverseitiges Formular (z. B <form runat="Server">) pro Seite enthalten. Da jedoch mobile Geräte normalerweise kleinere Bildschirme haben, lässt eine mobile Web Forms-Seite die Definition verschiedener Formulare für eine einzelne Seite zu. Dies hat die folgenden Vorteile:
- Sie können die Komplexität Ihrer Website verringern, indem Sie die Verwendung vieler kleiner Seiten vermeiden.
- Sie können Ihre mobile Webanwendung in derselben Weise wie Ihre Desktopanwendung verwalten. Eine mobile Seite kann mehrere Formulare haben, ähnlich wie ein Desktopformular verschiedene andere zugehörige Formulare haben kann.
- Sie können die Vorteile der Seitenebenenfunktionen nutzen, die mobile Web Forms bieten, z. B. die automatische Seitenstatusverwaltung (auch als Viewstate bezeichnet). Vorteilhaft ist auch die Anpassung an Geräte, die Mehrfachbildschirme in einer einzigen Antwort empfangen können.
Globalisierung
Globalisierung bezeichnet den Prozess des Entwerfens und Entwickelns einer Anwendung, die lokalisierte Benutzeroberflächen und regionale Daten für Benutzer in verschiedenen Kulturen unterstützt. In .NET Framework werden Informationen über bestimmte Kulturen durch die CultureInfo-Klasse dargestellt. Diese Informationen beziehen sich auf verwendete Schriftsysteme, Kalender, Konventionen zum Datums- und Zeitformat, numerische Konventionen, Währungskonventionen sowie Sortierregeln.
Lokalisierung bezeichnet den Prozess, bei dem die Ressourcen einer Anwendung in lokalisierte Versionen für die von der Anwendung unterstützten Kulturen übersetzt werden.
Für die Time Tracker-Anwendung wird lediglich die Globalisierung durchgeführt. Die Ressourcen der Anwendung werden nicht lokalisiert.
Die aktuelle Kultur wird bei jeder an die Anwendung gestellten Seitenanfrage auf der Grundlage der Browsereinstellungen des Benutzers geändert.
// For each request initialize the culture values with
// the user language as specified by the browser.
protected void Application_BeginRequest(Object sender, EventArgs e)
{
if (Request.UserLanguages != null)
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(Request.UserLanguages[0]);
else
// Default to English if there are no user languages
Thread.CurrentThread.CurrentCulture =
new CultureInfo("en-us");
Thread.CurrentThread.CurrentUICulture =
Thread.CurrentThread.CurrentCulture;
}
Sicherheit
Das Sicherheitsdesign verwendet sowohl Authentifizierung als auch Autorisierung. Die Authentifizierung ist der Prozess, bei dem die Anwendung die Identität und die Anmeldeinformationen eines Benutzers überprüft. Bei der Autorisierung werden die Berechtigungen eines authentifizierten Benutzers für den Zugriff auf eine angeforderte Ressource überprüft.
Die TimeTracker-Anwendung unterstützt die Windows-basierte Authentifizierung. Der Authentifizierungsmodus wird in der Datei web.config definiert, und die Eigenschaft User.Identity.Name verwaltet den Benutzernamen. Die Windows-Authentifizierung verwendet eine Domänen/Active Directory-Struktur mit dem NTLM-Challenge/Response-Protokoll.
Die Autorisierung für die Time Tracker-Anwendung wird über rollenbasierte Sicherheit verarbeitet, um festzustellen, ob ein Benutzer Zugriff auf eine bestimmte Ressource hat oder nicht. Benutzer werden in unterschiedliche Rollen eingeteilt (Administratoren, Manager und Berater), und die Rollenzuordnungen werden in der Datenbank gespeichert. Die Navigationsregisterkarten prüfen die Rolle des aktuellen Benutzers, um zu ermitteln, welche Registerkarten anzuzeigen sind. Bei dem Versuch, Seiten zu laden, wird für jede Seite eine Prüfung ausgeführt, um sicherzustellen, dass der Benutzer die erforderliche Rolle für den Zugriff auf die Seite hat. Diese Prüfungen verhindern, dass unautorisierte Benutzer auf Funktionen zugreifen können, für die sie keine Berechtigung haben.
Überdies legt die Benutzerrolle in der Time Tracker-Anwendung fest, welche Aufgaben der Benutzer ausführen darf. Beispielsweise können Berater nur die Zeiteinträge der Projekte ändern, die ihnen zugewiesen sind. Projekt-Manager können nur die Projektdetails der Projekte ändern, die sie verwalten (besitzen). Die nachstehende Tabelle zeigt die Funktionen der einzelnen Rollen. Die leeren Zellen in der Tabelle geben an, dass die Rolle nicht die Berechtigung zum Ausführen der verknüpften Aufgabe hat.
| Aufgabe | Berater | Projekt-Manager | Administrator |
| Zeiteinträge erstellen | Zugewiesene Projekte | Zugewiesene und eigene Projekte | Zulassen |
| Zeiteintragsdetails ändern | Zugewiesene Projekte | Zugewiesene und eigene Projekte | Zulassen |
| Zeiteinträge entfernen | Zugewiesene Projekte | Zugewiesene und eigene Projekte | Zulassen |
| Zeiteinträge anzeigen | Zugewiesene Projekte | Zugewiesene und eigene Projekte | Zulassen |
| Projekte erstellen | Zulassen | ||
| Projektdetails ändern | Eigene Projekte | Zulassen | |
| Projekte entfernen | Zulassen | ||
| Projekte anzeigen | Eigene Projekte | Zulassen | |
| Projektberichte anzeigen | Eigene Projekte | Zulassen | |
| Ressourcenberichte anzeigen | Eigene Projekte | Zulassen | |
| Benutzer erstellen | Zulassen | ||
| Benutzerdetails ändern | Zulassen | ||
| Benutzer anzeigen | Zulassen |
Zusätzlich wird die Benutzereingabe bereinigt, um Skriptangriffe oder andere böswillige Angriffe auf das System zu verhindern.
TTSecurity.GetUserRole()
Bereitstellung
Während die Time Tracker-Anwendung die Windows-Authentifzierung verwendet, werden Benutzerinformationen wie die Benutzerrolle in einem verschlüsselten Cookie gespeichert (mit Hilfe der Funktionalität, die von der formularbasierten Authentifizierungsklasse bereitgestellt wird), wenn der Benutzer zum ersten Mal authentifiziert wird. Wenn die Time Tracker-Anwendung in einer Webfarm bereitgestellt wird, muss auf allen Webservern in der Webfarm derselbe Verschlüsselungsschlüssel verwendet werden. Andernfalls muss die Lastenausgleichsaffinität aktiviert sein.
In einem Webfarmszenario können sich Benutzer anmelden, indem sie einen Server verwenden (dies bedeutet, dass ein Cookie für diesen Server erstellt wird). Anschließend fordern die Benutzer jedoch eine Seite an, für die eine Authentifizierung von einem anderen Server der Farm (der kein Cookie hat) erforderlich ist. Dies könnte dazu führen, dass Sie sich jedes Mal anmelden müssen, wenn Sie auf einen anderen Webserver stoßen. Standardmäßig ist der Entschlüsselungschlüssel des Authentifizierungscookies auf autogenerate gesetzt. Dies bedeutet, dass für die Entschlüsselung ein zufälliger, vom jeweiligen Server abhängiger Schlüssel festgelegt ist. Daher können die Server nicht die Cookies der anderen Server verwenden. Damit die Farm korrekt konfiguriert ist, muss der Computerschlüssel in der web.config-Datei der einzelnen Webserver denselben Wert haben.
Darüber hinaus wird der Sitzungsstatus für bestimmte Seiten in der Time Tracker-Anwendung verwendet (d. h. die Projektdetailseite). Dies bedeutet, dass die Sitzungsinformationen in einem Statusserver oder SQL Server gespeichert werden müssen, wenn die Lastenausgleichsaffinität nicht aktiviert ist. Dies kann ebenfalls in der Datei web.config festgelegt werden.
Die mobile Webseiten werden als alternative Website bereitgestellt, um andere Authentifizierungseinstellungen für die mobilen Seiten und die Desktopanwendungsseiten zuzulassen.
Weitere Informationen
- Die gesamte Dokumentation und den vollständigen Quellcode finden Sie unter http://www.asp.net.
- Eine funktionsfähige Version der Site finden Sie unter http://www.asp.net.