ASP.NET Time Tracker Starter Kit: Design und Implementierung

Veröffentlicht: 22. Feb 2004 | Aktualisiert: 29. Jun 2004

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

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:

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:

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.

TimeTrackerWhitepaper_01.gif

Datenzugriff, Geschäftslogik und Benutzeroberflächenlogik sind in verschiedene Klassen unterteilt. N-stufige Architekturen haben zahlreiche Vorteile. Dazu gehören beispielsweise:

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:

Datenbankschema

TimeTrackerWhitepaper_02.gif

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

Statische Methoden

Methoden

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:

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).

TimeTrackerWhitepaper_03.gif

Die Benutzersteuerelemente sind gelb markiert.

Die Zeiterfassungsseite

TimeTrackerWhitepaper_04.gif

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.

TimeTrackerWhitepaper_05.gif

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.

TimeTrackerWhitepaper_06.gif

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.

TimeTrackerWhitepaper_07.gif

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.
TimeTrackerWhitepaper_08.gif

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.

ChartItem

Diese 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.

Chart

Diese Klasse wird aus folgenden Gründen als abstrakte Klasse implementiert:

BarGraph

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:

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.

TimeTrackerWhitepaper_09.gif

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:

TimeTrackerWhitepaper_10.gif

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