V diskusích se často objevuje dotaz, jak při použití Altairis Web Providers provázat záznamy v tabulce s nějakým konkrétním uživatelem. Vzhledem k tomu, že na rozdíl od vestavěných providerů v ASP.NET providery Altairovy používají velmi jednoduchou a elegantní databázovou strukturu - uživatelé jsou v tabulce Users, která má kromě mnoha dalších sloupců důležité dva - UserName a UserId.
První z nich je uživatelské jméno, které známe na straně ASP.NET, protože Membership provideři obecně pracují na úrovni uživatelských jmen. Druhý jmenovaný sloupec je čistě věc databáze, je to primární klíč a unikátní číselný identifikátor řádku. Dělat klíče přes varchar je prasárna, i když se to na mnohých školách učí (bohužel i na MFF UK, která si hraje na nejlepší fakultu div ne ve střední Evropě).
Pokud chceme například udělat tabulku Orders s objednávkami, kde je nutné vědět, který zaměstnanec objednávku zadal, jednoduše tam dáme nějaký sloupec UserId a provážeme jej s tabulkou Users pomocí cizího klíče. To je všechno hezké, ale jak na straně ASP.NET zjistím UserId toho uživatele, abych jej mohl vložit jako parametr do Insertu? To už je trochu problém. Řešení je povícero a liší se svou elegancí. Popíšu zde řešení, které mi přijde nejpřímočařejší a nejlepší.
Je nutné si uvědomit, že databázový server netuší, který uživatel je v aplikaci přihlášen (někteří tazatelé na fóru by rádi nějakou funkci GetUserId, která vrátí přímo IDčko přihlášeného uživatele, tak ideální ten náš svět zase není). Standardní provider model ASP.NET s ID uživatelů nepracuje, ale zná uživatelské jméno přihlášeného uživatele, tudíž můžeme databázi poslat to. Databáze si už pak dohledá ID příslušného uživatele.
Jak z uživatelského jména dostat v databázi ID?
Celkem jednoduše, stačí si na to napsat v SQL funkci a pak ji jen použít. Tohle spusťte na databázi jednou, aby se funkce vytvořila:
CREATE FUNCTION [GetUserId] (
@username VARCHAR(100)
) RETURNS INT
AS BEGIN
DECLARE @userId INT
SELECT @userId = [UserId] FROM [Users] WHERE [UserName] = @username
RETURN @userId
END
Příkazu INSERT pak předáte parametr @username s uživatelským jménem a uděláte to třeba takto:
INSERT INTO Orders (UserId, Title, CreationDate) VALUES (dbo.GetUserId(@username), @title, GETDATE())
Jak na straně ASP.NET poslat uživatelské jméno databázi jako parametr?
Pokud používáte deklarativní data-binding, tzn. komponenty SqlDataSource a třeba FormView, pak insertování jistě děláte standardní metodou - tzn. FormView obsahuje nějaký formulář s vazbami <%#Bind%>, které po odeslání formuláře naplní parametry SqlDataSource a ten pak v databázi provede INSERT.
Protože uživatelské jméno v žádné komponentě nezadáváme, musíme jej získat odjinud. Jedna možnost je udělat to kódem, například v události ItemInserting. To ale není příliš elegantní a je s tím moc sena, pokud by se to mělo opakovat v padesáti stránkách, tak už to není prostě ono.
Ideální je napsat si vlastní parametr, podobně, jako máme třeba asp:QueryStringParameter nebo asp:Parameter. Stačí podědit třídu od třídy Parameter a přepsat metodu Evaluate, která vrátí hodnotu parametru.
Imports Microsoft.VisualBasic
Namespace MyNamespace
Public Class UserNameParameter
Inherits Parameter
Protected Overrides Function Evaluate(ByVal context As System.Web.HttpContext, ByVal control As System.Web.UI.Control) As Object
'vrátit jméno aktuálně přihlášeného uživatele
Return context.User.Identity.Name
End Function
End Class
End Namespace
Abychom jej mohli použít v komponentě SqlDataSource v sekci InsertParameters, musíme jej zaregistrovat, buď ve stránce pomocí direktivy <%@Register, anebo globálně pro celou aplikaci (v tomto případě doporučuji) v souboru web.config. Stačí přidat do elementu configuration/system.web/pages/controls tohle:
<add tagPrefix="my" namespace="MyNamespace"/>
Tím zaregistrujete všechny třídy v namespace MyNamespace pod prefix my a pak v SqlDataSource, který provádí daný Insert přidejte do InsertParameters tohle:
<my:UserNameParameter Name="username" />
Tím do hodnoty parametru username v SQL dotazu natlačíme jméno aktuálně přihlášeného uživatele a při Insertu se dotyčné UserId dohledá v databázi pomocí funkce GetUserId.
Pár poznámek na závěr
Obecně strašně nerad v ASP.NET píšu obsluhu událostí (třeba to FormView.ItemInserting, které by se muselo dělat, kdybychom si nenapsali parametr). Jakmile to je jen trochu možné, tak se tomu vyhnu a napíšu si vlastní komponentu, případně pomocí dědičnosti upravím nějakou již existující.
Psaní vlastních komponent nebo tříd není v ASP.NET věc profesionálů, ba právě naopak, měl by se to naučit dělat každý. Šetříte tím čas a opakující se kód, kterému je téměř vždy dobré se vyhnout, kdyby se někdy něco měnilo, změníme tu věc na jednom místě a ne na každé stránce, kterých je sice spočetně mnoho, ale mnohdy to bývá množství větší než malé.