.NET Tip #29: Jak získat v ASP.NET ID uživatele při použití Altairis Web Providers

Tomáš Herceg       24. 5. 2009       SQL, ASP.NET/IIS, Databáze       9665 zobrazení

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

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Diskuse: .NET Tip #29: Jak získat v ASP.NET ID uživatele při použití Altairis Web Providers

Akademická otázka: Proč jsou varchar klíče prasárna? Samozřejmě taky používám integery, ale zajímá mě to. Je důvodem nemožnost použít autoincrement? Nároky na úložný prostor? Velikost indexu? Napdá mě, že GUID je vlastně taky varchar a v mnoha databázích se pro uložení KEY používá ;-))

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Prasárna je to proto, že se nad těmi klíči dělají indexy. Dělat index nad stringem není vhodné, protože stringy se pomalu porovnávají.

GUID prosím není žádný varchar, on se jen vypisuje v textové reprezentaci, aby se to nějak dalo číst, ale jinak je to 128-bitové číslo.

Důležitým aspektem je také konstantní a malá velikost daného pole, kterou splňuje GUID i integer, ale v málokterých případech string (jedině že byste snad indexoval sloupcem CHAR(4), ale i to je nestandardní a musel by k tomu být sakra dobrý důvod).

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Varchar klíč není dobré řešení, je lepší se mu vyhnout a používat právě něco, co je na klíče přímo určené - třeba guidy.

Například v textové reprezentaci varcharu můžou být znaky, které dělají problémy - guid se reprezentuje v 16tkové soustavě, takže tam problém nehrozí a navíc je využitý celý rozsah.

Ale je pravda, že SQL Server je udělaný tak, aby šel string v rámci možností indexovat, a proto bych, pokud to vyžaduje povaha objektu index na varcharem využil. Zatím se tomu ale úspěšně vyhýbám (řetězcový klíč rozložený na datově reprezentovatelné komponenty, klíč podle guidu a na varcharu jen index atp.).

Viděl jsem pár databází, které nesprávně řetězcový klíč používaly a byla to hrůza. Pak takový zbastlený informační systém přepisovat je opravdu šílené a máte pocit, že kdyby se vám dostal původní autor do rukou, tak to s ním dopadne špatně.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Hlavně GUID je 128-bit integer, je to normální číslo, to, jak vypadá, je jenom věc výpisu pro člověka.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Ano, proto píšu, že je pro klíče ideální - v paměti je to "jen" 16bytů. Navíc právě ta "věc výpisu pro člověka" je často důležitá - trochu se rozskočí formátování nebo bude v klíči nějaký nečekaný znak (apostrof, odřádkování) a může z toho být hned problém (při generování scriptů, při přímém dotazování do db atp.).

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: .NET Tip #29: Jak získat v ASP.NET ID uživatele při použití Altairis Web Providers

Nemohl by jste napsat ukázku kódu i v C#?Nějak se mi nedaří to napsat :(

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
 

nahlásit spamnahlásit spam 0 odpovědětodpovědět

namespace MyNamespace

{

public class UserNameParameter : Parameter

{

protected override object Evaluate(System.Web.HttpContext context, System.Web.UI.Control control)

{

return context.User.Identity.Name;

}

}

}

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Pokud napíšu danou funkci,tak mi to vyhodí error

Line 5:  namespace MyNamespace
Line 6:  {
Line 7:      public class UserNameParameter : Parameter
Line 8:      {
Line 9:          protected override object Evaluate(System.Web.HttpContext context, System.Web.UI.Control control)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tento error :

Compiler Error Message: CS0246: The type or namespace name 'Parameter' could not be found (are you missing a using directive or an assembly reference?)

??

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Jako sorry, ale tohle jsou naprostý základy...

přidej na začátek souboru toto:using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Já vim,ale tak každý nějak začíná..a mě se nejlíp učí praxí..sice vypadám jak lama,ale to mě netrápí

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Ale nás to trápí, protože musíme odpovídat na dotazy, které jsou tady milionkrát zodpovězené ve fóru a každý si je může přečíst v článcích. Navíc když si ty články přečtete, ušetříte tím asi 150 dalších dotazů ve fóru a čas 150 lidí, kteří na to budou muset odpovídat.

Rádi odpovíme na věci, které v článcích nejsou, nebo jsou, ale nejsou naprosto banální. Tohle banalita je a když si přečteš články, bude to pro tebe přínosnější, než útržkovité informace z fór, protože v článcích je to vysvětleno uceleně.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Já se snažím číst všechny články,problém je v tom,že jako progr.jazyk používám C# a tady je většina článků ve VB.net,proto občas ty blbé dotazy..Budu se snažit je omezit :))

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Anebo můžete použít konvertor z VB do C# a opačně, není to 100%, ale v 90% případů to jede krásně.

Stačí do googlu napsat VB.NET to C# resp. C# to VB.NET a najde to.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: .NET Tip #29: Jak získat v ASP.NET ID uživatele při použití Altairis Web Providers

Pěkné řešení. Pokud nejsou informace o uživatelích na stejném stroji jako aplikační data, pak se může posílat místo username přímo user-id, které se dá snadno získat z libovolného membership-providera:System.Web.Security.Membership.GetUser().ProviderUserKey

Pak by funkce GetUserId pouze castovala z varcharu na int.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Aha, tak vlastnosti ProviderUserKey jsem si nějak nevšimnul, pak je tedy můj postup zbytečně složitý, stačí si napsat parametr, který vrátí tohle a funkce na SQL Serveru je vlastně zbytečná.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: .NET Tip #29: Jak získat v ASP.NET ID uživatele při použití Altairis Web Providers

skusil som pouzit toto riesenie na moj problem ale aj napriek tomu my to hadze tuto chybu :

Parser Error 
Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately. 

Parser Error Message: Object reference not set to an instance of an object.

Source Error: 

Line 265:        </UpdateParameters>
Line 266:        <InsertParameters>
Line 267:            <custom:get_logged_user_id_parameter Name="ArticleAuthorId" />
Line 268:            <asp:Parameter Name="ArticleTitle" Type="String" />
Line 269:            <asp:Parameter Name="ArticleText" Type="String" />


Source File: /Admin/Article.aspx    Line: 267 

Version Information: Microsoft .NET Framework Version:2.0.50727.3074; ASP.NET Version:2.0.50727.3074

trieda toho pozmeneneho parametru vyzera takto :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

/// <summary>
/// Summary description for get_logged_user_id_parameter
/// </summary>
namespace LoggedUserIdParameter
{
    public class get_logged_user_id_parameter: Parameter
    {
        protected override object Evaluate(System.Web.HttpContext context, System.Web.UI.Control control)
        {
            return System.Web.Security.Membership.GetUser().ProviderUserKey;
        }
    }
}

nevedel by mi niekto povedat ze v com moze byt problem ? thx

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Přidejte si referenci na vámi vytvořenou komponentu.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

pokial ste tym mysleli toto :

using LoggedUserIdParameter;

tak to som tam uz mal to nepomoha

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Špatně jsem se vyjádřil - omlouvám se.

Zaregistrujte si vlastně vytvořenou komponentu do stránky

např.

<%@ Register Namespace="MyComponents" TagPrefix="custom" %>

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

diky moc uz to fici :)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: .NET Tip #29: Jak získat v ASP.NET ID uživatele při použití Altairis Web Providers

Jak by tedy vypadala funkce kdyby se user-id získávalo pomocí System.Web.....ProviderUserKey?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

System.Web.Security.Membership.GetUser().ProviderUserKey

nahlásit spamnahlásit spam 0 odpovědětodpovědět

díky...a jak by se tedy napsala funkce aby se použil System.Web.Security.Membership.GetUser().ProviderUserKey?Tz.ta lehčí varianta,jak vyplynulo z diskuze

předem děkuji :)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

No ten parameter pak bude místo username (string) vracet přímo IDčko (pravděpodobně int). Takže by mělo stačit v parametru použít System.Web.Security.Membership.GetUser().ProviderUserKey a v SQL pak není už vůbec potřeba funkce GetUserId.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Jsem lama...ale moc tomu nerozumím..nemohl by jsi to napsat?abych to viděl na vlastní bulvy :D

nahlásit spamnahlásit spam 0 odpovědětodpovědět
Imports Microsoft.VisualBasic

Namespace MyNamespace
    Public Class UserIdParameter
        Inherits Parameter

        Protected Overrides Function Evaluate(ByVal context As System.Web.HttpContext, ByVal control As System.Web.UI.Control) As Object
            Return System.Web.Security.Membership.GetUser().ProviderUserKey
        End Function
    End Class
End Namespace

<my:UserIdParameter Name="userId" />

INSERT INTO Orders (UserId, Title, CreationDate) VALUES (@userId, @title, GETDATE())

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Moc díky za pomoc,ale bohužel mi to stále vrací error při kompilaci Namespace

Error txt:

Compiler Error Message: CS0246: The type or namespace name 'Parameter' could not be found (are you missing a using directive or an assembly reference?)

rozumím co je špatně,ale nevím jak to opravit..jsem teprve začátečník..ale rychle se učím :D

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Přidejte do toho souboru nahoru tohle:

Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

A nastudujte si alespoň základy programování ve VB.NET, ať víte, co to dělá a proč se to tam píše. Bez toho se v ASP.NET programovat pořádně nedá.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

jj díky za odpověď..tím že jsou tu ukázky "pouze" ve VB.net,tak mi to dělá problémy..píšu v C#

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback