Nedávno na fóru zazněl poměrně zajímavý dotaz. Jeho autor potřeboval při kliknutí na buňku v tabulce vyvolat serverovou událost, ale tak, aby v té buňce nemuselo být tlačítko. V tomto článku si ukážeme, jak se to dělá – napíšeme si jednoduché zamlouvátko na sedadla v opeře (teď nemyslím prohlížeč, ale takovou tu budovu kde hraje orchestr a pobíhají kvílející zpěváci). Kromě toho si ukážeme základy práce s AJAXem v ASP.NET WebForms, což může ušetřit přenášená data a tím zrychlit načítání stránek, ale hlavně při postbacku přestanou stránky problikávat. A dále se seznámíme s technologií LINQ to SQL a ukážeme si její základy.
Jak to bude vypadat
Na naší stránce si uděláme tabulku o velikosti 8x3 políčka. Po kliknutí na políčko se do databáze zapíše údaj o tom, kdo na daném místě sedí, přičemž se stav obsazení sedadel zaktualizuje. Možností, jak tento problém řešit, je pochopitelně víc.
První možností, na kterou jsou odkázaní všichni, kdo nepoužívají ASP.NET WebForm (ale která i ve webformech jde poměrně snadno použít), je při kliknutí na sedadlo javascriptem zavolat nějakou webovou službu nebo handler, který zapíše informaci o tom, kdo si sedadlo rezervoval, do databáze, a vrátí aktuální stav (případně po odpovědi handleru lze udělat refresh stránky, ale to by bylo neobratné a stránka by problikla – to nechceme). Poněkud nepříjemné zde bude parsování odpovědi serveru a aktualizace tabulky ve stránce, i když s jQuery to dnes takový problém není. Handler nebo webová služba mohou odpověď vrátit v XML, JSONu nebo použít vlastní kódování (například hodnoty oddělené čárkou), případně může vrátit HTML celé tabulky sedadel, které se do stránky umístí místo aktuální tabulky. To je asi nejjednodušší, protože odpadá parsování odpovědi, i když je zde bezpečnostní riziko – někdo by mohl podvrhnout kus HTML s javascriptem, který by provedl něco ošklivého. Pokud je ale handler pod naší kontrolou, něco takového je velmi nepravděpodobné.
Druhou možností, kterou máme ve WebFormech, je vykašlat se na javascript i na webovou službu – kliknutí na sedadlo prostě vyvolá postback, na serveru si v našem C# kódu odchytíme událost Click na dotyčné buňce tabulky, a nějak na ni zareagujeme. Pak provedeme nový databinding a tabulku zaktualizujeme. A když použijeme AJAX (konkrétně komponentu UpdatePanel kolem tabulky), tak se zpátky nebude posílat celá nová stránka, ale jen ta tabulka. Tím se dostaneme s trochu větším pohodlím na prakticky stejné řešení, jako máme při použití prvního způsobu – javascriptem zavoláme handler (stránku) a zpátky dostaneme nový HTML obsah tabulky.
Jediný rozdíl je v tom, že handler voláme metodou POST a odesíláme ViewState, ale pokud se nad tím zamyslíme, zjistíme, že jej pro naši tabulku nepotřebujeme – stejně při každém postbacku tabulku znovu naplníme z databáze. Nepotřebujeme si pamatovat, co jsme ní tabulky vyplnili.
Začínáme
Pomocí SQL Server Management Studia vytvořte novou databázi a spusťte proti ní tento skript pro vytvoření jednoduché tabulky:
-- uživatelé
CREATE TABLE [Users] (
[UserId] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[UserName] NVARCHAR(100) NOT NULL UNIQUE,
[PasswordHash] BINARY(64) NOT NULL,
[PasswordSalt] BINARY(128) NOT NULL,
[Email] NVARCHAR(100) NOT NULL,
[Comment] NVARCHAR(MAX) NULL,
[IsApproved] BIT NOT NULL,
[DateCreated] DATETIME NOT NULL,
[DateLastLogin] DATETIME NULL,
[DateLastActivity] DATETIME NULL,
[DateLastPasswordChange] DATETIME NOT NULL
)
-- sály
CREATE TABLE [Halls] (
[HallId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
[SeatRows] INT NOT NULL,
[SeatColumns] INT NOT NULL
)
INSERT INTO [Halls] ([SeatRows], [SeatColumns]) VALUES (3, 8)
DECLARE @HallId INT
SELECT @HallId = SCOPE_IDENTITY()
-- sedadla v sálech
CREATE TABLE [Seats] (
[SeatId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
[HallId] INT NOT NULL REFERENCES [Halls]([HallId]) ON DELETE CASCADE,
[X] INT NOT NULL,
[Y] INT NOT NULL,
[UserId] INT NULL REFERENCES [Users]([UserId]) ON DELETE SET NULL
)
DECLARE @x INT, @y INT
SELECT @x = 0, @y = 0
WHILE (@y < 3) BEGIN
WHILE (@x < 8) BEGIN
INSERT INTO [Seats] ([HallId], [X], [Y]) VALUES (@HallId, @x, @y)
SELECT @x = @x + 1
END
SELECT @y = @y + 1, @x = 0
END
Naše databáze se skládá z pouhých tří tabulek – tabulka Users reprezentuje uživatele, tabulka Halls koncertní sály a tabulka Seats sedadla v koncertních sálech. Zároveň jsme si do databáze vygenerovali 1 koncertní sál a do něj 24 sedadel. Každému sedadlu se dá přiřadit ID uživatele a je povolena hodnota NULL, která znamená, že sedadlo je volné.
Správa uživatelů
Pro správu uživatelů použijeme knihovnu Altairis Web Security od Michala Valáška, jejíž nová verze vyšla nedávno. Stáhněte její aktuální verzi a soubory Altairis.Web.Security.dll a Altairis.Web.Security.pdb nakopírujte do adresáře Bin vaší webové aplikace.
Naše aplikace bude mít dvě stránky – Default.aspx a Login.aspx. Na stránku Login.aspx umístíme i komponentu pro registraci nově příchozích uživatelů, vypadat může nějak takto:
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Opera - přihlášení</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Login ID="Login1" runat="server" DestinationPageUrl="~/Default.aspx">
</asp:Login>
<p></p>
<asp:CreateUserWizard ID="CreateUserWizard1" runat="server"
ContinueDestinationPageUrl="~/Default.aspx" LoginCreatedUser="True">
<WizardSteps>
<asp:CreateUserWizardStep runat="server">
</asp:CreateUserWizardStep>
<asp:CompleteWizardStep runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
</div>
</form>
</body>
</html>
Vzhled nijak neřešíme, jde o funkčnost – na této stránce se lze přihlásit, nebo si založit účet. Ať už tak či onak, uživatel je po této akci přesměrován na stránku Default.aspx, kde si může zamluvit sedadlo.
Aby se pro přihlašování použila knihovna Altairis Web Security a aby byl zakázán přístup na stránku Default.aspx bez přihlášení, je nutné přidat pár věcí do web.configu, pokud používáte ASP.NET 4, měl by vypadat takto:
<?xml version="1.0"?>
<configuration>
<!-- spojení k databázi -->
<connectionStrings>
<add name="DB" providerName="System.Data.SqlClient"
connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=Opera; Integrated Security=True"/>
</connectionStrings>
<system.web>
<compilation debug="false" targetFramework="4.0" />
<!-- přilinkovat CSS soubor -->
<pages theme="Default" />
<!-- použít Altairis Web Security pro přihlašování uživatelů -->
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" connectionStringName="DB"
type="Altairis.Web.Security.TableMembershipProvider, Altairis.Web.Security" />
</providers>
</membership>
<authentication mode="Forms" />
</system.web>
<!-- zakázat nepřihlášené uživatele na stránce Default.aspx -->
<location path="Default.aspx">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
</configuration>
Přidali jsme connection string pro spojení s databází v elementu connectionStrings - upravte si ho podle svého SQL Serveru a názvu databáze.
Dále jsme v elementu system.web/pages nastavili téma pro všechny stránky.
Pak jsme v elementu system.web/membership smazali výchozího providera a přidali membership providera z knihovny Altairis Web Security. Elementem system.web/authentication jsme nastavili Forms autentizaci. Standardně se totiž do ASP.NET aplikací přihlašuje pomocí Windows autentizace, tedy vaším uživatelským účtem z Windows. Forms autentizace naproti tomu používá přihlášení pomocí vyplnění formuláře na webové stránce.
A nakonec jsme v elementu location stránce Default.aspx řekli, že na ní nechceme pustit nepřihlášené uživatele.
Element location může obsahovat jakékoliv změny konfigurace oproti tomu, co je přímo v elementu configuration. Klidně bychom dovnitř mohli dát element connectionStrings a říct tím, že na stránce Default.aspx se bude používat jiný. Nebo bychom mohli jen pro stránku Default.aspx změnit téma či membership providera. Ale nám stačí zakázat přístup nepřihlášeným uživatelům.
Aby to fungovalo, musíme ještě v aplikaci vytvořit adresář App_Themes a v něm adresář Default. Do tohoto adresáře můžete umístit CSS soubory a skiny, které se automaticky použijí na všechny stránky.
Tabulka sedadel
To by byla omáčka kolem – nyní se můžeme vrhnout na jádro pudla, totiž na samotnou tabulku sedadel a klikání dovnitř. Stránka Default.aspx může vypadat nějak takto:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Opera - zamlouvání sedadel</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>Zamlouvání sedadel</h1>
<p><asp:Label ID="ErrorLabel" runat="server" ForeColor="Red" ViewStateMode="Disabled" /></p>
<asp:Table ID="SeatsTable" runat="server" CssClass="seats">
</asp:Table>
<p class="legend">
<span class="reserved"></span> - obsazené sedadlo<br />
<span class="free"></span> - volné sedadlo<br />
<span class="yours"></span> - vaše sedadlo
</p>
</div>
</form>
</body>
</html>
Aby to vypadalo nějak hezky, do složky App_Themes/Default přidejte CSS soubor s těmito definicemi:
.legend span
{
width: 16px;
height: 16px;
display: inline-block;
}
.reserved
{
background-color: #d0d0d0;
}
.free
{
background-color: #ff0000;
}
.yours
{
background-color: #80a0ff;
}
.seats td
{
width: 40px;
height: 40px;
text-align: center;
cursor: pointer;
}
.seats td.free:hover
{
background-color: #ffa0a0;
}
To je pro tento díl vše. Příště si napíšeme jednoduchou třídu, která bude umět vracet seznam sedadel a zarezervovat sedadlo pro konkrétního uživatele.