CinemaServer (Backend Sample)

Veröffentlicht: 31. Jan 2006

AdventureWorks Cinema 1.0

Das Backend stellt die Server-Funktionalität für das AdventureWorks Cinema Beispiel zur Verfügung.

Ziel dieser Backend-Implementierung ist zu zeigen, wie eine mehrschichtige Anwendung mit einer Web Service-Schnittstelle komfortabel kommunizieren und rollenbasierte Sicherheit mit Hilfe der in .NET vorhandenen Konzepte dennoch umgesetzt werden kann. Dabei werden mehrere Schichten verwendet, die auch unabhängig voneinander eingesetzt werden können.

Das Backend wurde vollständig in C# programmiert.

Auf dieser Seite
Aufbau des SamplesAufbau des Samples
Technologien/KonzepteTechnologien/Konzepte
Datenhaltung und -zugriffDatenhaltung und -zugriff
GeschäftslogikGeschäftslogik
Web ServiceWeb Service
DownloadDownload

Aufbau des Samples

Auch der Applikationsserver ist in mehrere Schichten eingeteilt. Die Web Service Schicht stellt die Serverfunktionalität rechnerübergreifend auch anderen Maschinen und Betriebsystemen zur Verfügung, enthält aber selbst keine eigentliche Logik.

Die eigentliche Geschäftslogik wird von der Server Geschäftslogik implementiert.

Die Aufgabe der Datenzugriffsschicht ist es, von der konkreten Datenbank zu abstrahieren, um die Geschäftslogik nicht an eine bestimmte Datenbank zu binden. Um die Datenbank auszutauschen reicht es also, die Datenzugriffsschicht gegen eine andere Version auszutauschen.

Als Datenbank für das Backend wird eine SQL Server 2005 Express Datenbank verwendet. Diese ist kostenlos, auch für den kommerziellen Einsatz, und trotz der Beschränkungen der Express Edition für die AdventureWorks Cinema Anwendung mehr als ausreichend leistungsfähig.

Backend Datenbank

Technologien/Konzepte

Web Service

"Contract-First"-Entwicklung von ASP.NET Web Services

Authentifizierung mit einem benutzerdefinierten SOAP-Header

Geschäftslogik

Rollenbasierte Sicherheit mit IPrincipal und IIdentity

Datenzugriffsschicht

Datenbankzugriff mit ADO .NET

Exception-Management mit dem "Exception Builder" Entwurfsmuster

SQL Server

Übergabe von Arrays an Stored Procedures mit Hilfe von XML

Datenhaltung und -zugriff

Die Datenzugriffsschicht (Data Access Layer) wird von der Geschäftslogik verwendet, um komfortabel auf die Datenbank zuzugreifen. Dabei ist es Aufgabe der Datenzugriffsschicht, das Laden der Daten vorzunehmen und so genannte Data Transfer Objects (DTO) daraus zu erstellen. Außerdem ist die Datenzugriffsschicht dafür verantwortlich, DTOs in die Datenbank zu schreiben.

Übersicht über die Architektur der Datenzugriffsschicht

Die Einordnung der Datenzugriffsschicht in das Gesamtbild zeigt folgende Abbildung:

Einordnung der Datenzugriffsschicht in das Gesamtbild

Die Datenbank

Datenbankschema

Die Beispielanwendung ist darauf ausgelegt, die Erstellung und Modifikation von Daten ohne Verbindung zum Server zu erlauben (Offline Modus). Aus diesem Grund muss jeder Client in der Lage sein, eine eindeutige ID zu erstellen, um Dubletten zu vermeiden, die Synchronisationsprobleme hervorrufen könnten. Anders gesagt: die ID der Entitäten (der Primärschlüssel) muss weltweit eindeutig sein. Um dies zu erreichen wurde eine GUID (in der Datenbank uniqueidentifier genannt) als Datentyp für alle Primärschlüssel verwendet, da sie genau diese Funktionalität bietet.

Alle Tabellen in der Cinema Datenbank und ihre Beziehungen:

Alle Tabellen in der Cinema Datenbank und ihre Beziehungen
Klicken Sie für eine größere Darstellung auf das Bild.

Die Verwendung von gespeicherten Prozeduren

Gespeicherte Prozeduren werden nur dazu verwendet, Daten in die Datenbank zu schreiben, also beim Erstellen, Ändern und Löschen von Datensätzen.

Interessant ist dabei die Befüllung der Intersektionstabellen (z.B. Users_Roles), die im selben Schritt wie das Erstellen/Aktualisieren der Benutzerdaten erfolgen soll. Dazu müssen den gespeicherten Prozeduren CreateUser/UpdateUser alle Rollen übergeben werden, denen der Benutzer angehört. Das Problem an dieser Stelle ist jedoch, dass es nicht möglich ist, Arrays oder andere Sammlungen an die Datenbank zu übergeben. Dank den neuen XML Möglichkeiten des neuen SQL Server 2005 ist dies jedoch kein Problem mehr. Im C# Teil der Datenzugriffsschicht wird einfach ein XML Dokument erstellt, das mit dem text Datentyp an die Datenbank übergeben und von dieser entsprechend behandelt wird.

Es wurden keine gespeicherten Prozeduren für das Abrufen der Daten erstellt, da der C# Teil der Datenzugriffsschicht die zurückgegebenen Daten auch zu Objekten konvertieren muss und der Quellcode leichter zu verstehen und warten ist, wenn die Abfragen und der Konvertierungs-Code an einer Stelle gehalten werden.

Der Quellcode der Datenzugriffsschicht

Öffentliche Schnittstelle

Die Datenzugriffsschicht ist in mehrere statische Klassen eingeteilt, eine Klasse pro Entität. Jede Klasse hat das Suffix "Dal".

Diese Klassen werden verwendet, um die Funktionalität nach Entität zu gruppieren, z.B. wird die CountryDal-Klasse verwendet, um Länder zu erstellen, aktualisieren, löschen und alle Länder abzurufen.

Implementierungsdetails

Eine eigene Klasse mit dem Namen ExceptionBuilder wird verwendet, um alle Ausnahmen zu erzeugen, die geworfen werden. Dies ermöglicht die Protokollierung aller Ausnahmen auf einfache Art und Weise. Die ExceptionBuilder-Klasse enthält dabei eine eigene Methode für jeden auftretenden Ausnahmezustand (z.B. ein Argument ist NULL, ein Argument ist ungültig oder die Kommunikation mit der Datenbank ist fehlgeschlagen).

Der Nachteil dieses Ansatzes ist, dass FxCop die Parameternamen nicht mehr überprüfen kann, die den Konstruktoren von ArgumentNullException oder ArgumentException übergeben werden.

Alle Klassen der Datenzugriffsschicht verwenden die statische Klasse SqlHelper, die die Verwaltung von Datenbankverbindungen und Transaktionen kapselt. Diese verlangt, dass Parameter, die an eine gespeicherte Prozedur oder eine Abfrage übergeben werden sollen, als IDictionary<string, object> übergeben werden. Der Grund dafür ist, dass einige Werte konvertiert werden müssen, bevor sie der Datenbank übergeben werden können. Z.B. muss der Wert NULL als System.DBNull.Value übergeben werden.

Der Nachteil hierbei ist, dass nur Eingabeparameter an die Datenbank übergeben werden können, aber keine Ausgabe-, Ein-/Ausgabe- oder Rückgabeparameter. Da diese Parameterrichtungen jedoch nicht benötigt werden ist dies eine akzeptable Einschränkung.

Geschäftslogik

Die Geschäftslogik-Schicht des Servers enthält die Geschäftslogik des AdventureWorks Cinema Samples. Einige Methoden der Geschäftslogik bestehen aus einfachen Aufrufen der Datenzugriffsschicht, andere enthalten zusätzliche Geschäftsregeln und führen mehrere Operationen auf der Datenzugriffsschicht aus. Dabei wird mithilfe von rollenbasierter Sicherheit geprüft, ob der aktuell angemeldete Benutzer überhaupt berechtigt ist, bestimmte Aktionen mit der Geschäftslogik auszuführen.

Wichtig für die rollenbasierte Sicherheit sind hier insbesondere die Klassen CinemaPrincipal und CinemaIdentity. Anhand dieser Klassen wird gezeigt, wie man eigene Implementierungen der IPrincipal und IIdentity Schnittstellen schreiben und für rollenbasierte Sicherheit verwenden kann.

Darf eine Methode nur von bestimmten Benutzern aufgerufen werden, so kann dies durch Verwendung des PrincipalPermission-Attributs sichergestellt werden:

[PrincipalPermission(SecurityAction.Demand,
Role = CinemaPrincipal.EmployeeRoleName)]
public static void CreateNewsItem(NewsItem newsItem)
{
    NewsDal.CreateNewsItem(newsItem);
}

Im Web Service wird ein neuer CinemaPrincipal erzeugt und an die Thread.CurrentPrincipal-Eigenschaft zugewiesen. Bei der Erzeugung des CinemaPrincipal wird diesem ein User-Objekt des aktuell angemeldeten Benutzers übergeben. Beim Aufruf einer Methode die mit einer PrincipalPermission gekennzeichnet ist, prüft das .NET Framework über die IsInRole()-Methode aus dem IPrincipal-Interface, ob der Principal zu einer Gruppe ("Role") gehört, die in der PrincipalPermission gefordert wird. Da der CinemaPrincipal ein User-Objekt enthält, kann er über dieses in der IsInRole()-Methode prüfen, ob der Benutzer Mitglied der angeforderten Gruppe ist. Nur wenn diese Prüfung erfolgreich ist, wird der Methodenaufruf ausgeführt, ansonsten wird eine SecurityException geworfen.

Web Service

Die Clients kommunizieren mit dem Backend über einen ASP .NET Web Service. Für den Zugriff auf diesen Web Service verwenden alle Clients eine gemeinsame Komponente, den Service Access Layer.

Der AdventureWorks Cinema Web Service soll zeige, wie man "Tickets" zur Authentifizierung der Clients beim Web Service verwenden kann. Außerdem zeigt das Sample, wie man einen Web Service "contract first", d.h. ausgehend von einer Schnittstellenbeschreibung des Web Services (im Gegensatz zu "code first", wo man den Code zuerst schreibt) entwickeln kann.

Authentifizierung

Der Web Service authentifiziert Clients über "Tickets". Nach der Anmeldung am Web Service über die LogOn-Methode wird ein benutzerdefinierter SOAP-Header erzeugt, der in der weiteren Kommunikation automatisch zwischen dem Client und dem Server übertragen wird. Dieser Header enthält eine Id (ein "Ticket"), über das alle weiteren Aufrufe einer bestimmten Sitzung und somit der Benutzerkennung, welche in der LogOn-Methode angegeben wurde, zugeordnet werden kann.

Durch das SoapHeader-Attribut mit "Direction=SoapHeaderDirection.Out" der LogOn-Methode wird festgelegt, dass diese Methode einen SoapHeader vom Typ SessionHeader an den Client überträgt. In der LogOn-Methode wird überprüft, ob Benutzerkennung und Kennwort korrekt sind, und anschließend eine neue Sitzung erzeugt. Die Sitzung enthält einen Verweis auf den Benutzer und die IP-Adresse des Clients. Die Id dieser Sitzung wird im SessionHeader gespeichert:

[WebMethod]
[SoapHeader("SessionHeader", Direction = SoapHeaderDirection.Out)]
public LogOnResponse LogOn(LogOnRequest LogOnRequestMessage)
{
    LogOnResponse response = new LogOnResponse();

    try
    {
        response.User = UserManager.LogOn(
            LogOnRequestMessage.UserName, LogOnRequestMessage.Password);

        Session session = new Session();
        session.User = response.User;
        session.Id = Guid.NewGuid();
        session.IPAddress = Context.Request.UserHostAddress;
        session.StartDate = DateTime.Now;

        SessionManager.CreateSession(session);

        SessionHeader = new SessionHeader();
        SessionHeader.SessionId = session.Id;

    }
    catch (Exception ex)
    {
        LogException(ex);
    }

    return response;
}

Methoden, die nur von bestimmten Benutzern ausgeführt werden sollen, können den Methodenaufruf mit der Id aus dem SessionHeader einer Sitzung und somit einem Benutzer zuordnen. Durch das SoapHeader-Attribut mit "Direction = SoapHeaderDirection.In" wird der SessionHeader, den der Server in der LogOn Methode an den Client geschickt hat, vom Client an den Server übertragen. Anschließend wird in der EnsureSessionIsValid()-Methode geprüft, ob es zu der SessionId aus dem SessionHeader eine gültige Session in der Datenbank gibt, und ob die IP-Adresse des Clients mit der IP-Adresse welche die Session erzeugt hat übereinstimmt.

[WebMethod]
[SoapHeader("SessionHeader", Direction = SoapHeaderDirection.In)]
public void DeleteUser(DeleteUserRequest DeleteUserRequestMessage)
{
    EnsureSessionIsValid();

    // weiterer Code.
}

Falls die Id aus dem SessionHeader in der EnsureSessionIsValid()-Methode einem Benutzer zugeordnet werden konnte, wird ein neues CinemaPrincipal-Objekt angelegt und dem aktuellen Thread zugewiesen. Dies ermöglicht die Verwendung von rollenbasierter Sicherheit in der Geschäftslogik-Schicht des Backends.

Thread.CurrentPrincipal = new CinemaPrincipal(session);

"contract first" Web Service Entwicklung

Beim "contract first" Ansatz wird zuerst ein Dienstvertrag (contract) für den Web Service definiert. Aus diesem wird anschließend der Code generiert.

Alle Nachrichten, die der Web Service verschicken und empfangen kann wurden zuerst in XML Schema Dateien definiert. Aus diesen werden die Data Transfer Objects (DTOs) und Nachrichtenklassen generiert, die als Datentypen für die Web Service Operationen verwendet werden. Die Generierung des Codes aus den XSD Dateien erfolgt mit dem Tool Xsd.exe aus dem .NET Framework SDK.

Download

Voraussetzungen

Installieren Sie Visual Studio 2005 inklusive der Programmiersprache C# und SQL Server 2005 Express.

Installation

1.

Entpacken Sie die Datei "CinemaServer.zip".

2.

Öffnen Sie die Solution "CinemaServer.sln" in Visual Studio 2005.

3.

Stellen Sie sicher, dass der CinemaWebService als Startprojekt ausgewählt ist (das Projekt wird in fetter Schriftart dargestellt). Ist dies nicht der Fall, klicken Sie mit der rechten Maustaste auf das Projekt und wählen Sie "Als Startprojekt auswählen."

4.

Wählen Sie Debuggen -> Start (F5) oder Debuggen -> Ohne Debugger starten (Ctrl+F5) aus.

5.

Nun können Sie jeden Client im Online-Modus verwenden. Alternativ können Sie einen eigenen Client entwickeln. Um dies zu vereinfachen, kann Ihr Client den ServiceAccessLayer referenzieren, der den Aufruf des Webdienstes vereinfacht.

Die in die Solution integrierte Datenbank enthält bereits einige Testdaten, die zusätzlich auch in der mit dem Client ausgelieferten SampleData.xml zu finden sind:

BenutzerkennungPasswortNachnameVornameRolle

Administrator

admin

Tester

Hans

Employee, Customer

peter1

geheim

Brehm

Peter

Customer

heiko

strenggeheim

Elmsheuser

Heiko

Customer

franzK

sehrgeheim

Kohl

Franz

Customer

tPlate

totalgeheim

Plate

Tanja

Customer

scholl

sowasvongeheim

Scholl

Thorsten

Customer

nina81

geheimergehtsfastnicht

Vietzen

Nina

Employee

wackerroland

nocheinweniggeheimer

Wacker

Roland

Customer

melspeck

geheimstesderwelt

Speckmann

Melanie

Employee, Customer

Hier erreichen Sie unser Downloadcenter, um das Sample herunterzuladen.


AdventureWorks Cinema 1.0

Home

Downloads

Die Anwendungen

CinemaServer

Windows Client

Mobile Client

Office Client

Web Client

RSS Service