Name mangling, po polsku maglowanie nazw, to w kontekście stron wzorcowych (ang. master pages) proces podmieniania identyfikatorów kontrolek przy generowaniu strony wynikowej (ze strony wzorcowej i strony z właściwą zawartością), a celem tej operacji jest zapewnianie, że identyfikatory będą na pewno unikalne w obrębie strony. Technicznie operacja ta sprowadza się do połączenia identyfikatora kontrolki z identyfikatorem kontenera w jakiej kontrolka została umieszczona, a dokładniej jego UniqueID. Operacja ta jest potrzebna ale stwarza też problemy i prowadzi niestety do błędów. Zacznę od opisu scenariusza:
Żeby nie przeciągać problem rozwiązałem rezygnując z deklaratywnego definiowana parametrów dla źródła danych na poziomie strony na rzecz zdefiniowania ich w kodzie. Oczywiście to nie wszystko. Sztuczka polega na tym, żeby przy podawaniu identyfikatora kontrolki, która będzie źródłem danych dla parametru należy użyć właściwości Control.UniqueID:
Dociekliwi mogą powiedzieć, że jest jeszcze właściwość Control.ClientID. Od razu mówię, że jeśli użyjemy tej właściwości to błąd również wystąpi. Na pierwszy rzut oka może wydawać się to dziwne ponieważ ClientID zawiera już zmieniony identyfikator. Różnica polega na tym, że do oddzielenie poszczególnych składowych identyfikatora ClientID używany jest inny separator niż przy UniqueID. Poniżej przykład dwóch identyfikatorów dla tej samej kontrolki:
UniqueID: ctl00$ContentPlaceHolder1$Nazwa
ClientID: ctl00_ContentPlaceHolder1_Nazwa
Dlaczego jednak użyto innych separatorów. Zacznijmy od tego, że aby proces podmieniania nazw był odwracalny ASP.NET musi umieć wydzielić z wygenerowanego identyfikatora jego składowe czyli identyfikatory kolejnych kontenerów i na końcu kontrolki. To jest przyczyna, dla której wprowadzono separatory. Separatorem powinien być oczywiście znak, który nie może wystąpić w bazowym identyfikatorze, w tym przypadku '$' ale można to zmienić.
Tutaj dochodzimy do momentu, w którym potrzebujemy z jakiegoś powodu odwołać się do wyrenderowanej kontrolki z poziomu JavaScript'u. Oczywiście potrzebujemy jakiś identyfikator i tu pojawiaja się problem. Otóż co zrobić jeśli jako separator użytu znaku niedozwolonego w identyfikatorach przez JavaScript? Problem ten postanowiono obejść wprowadzając ClientID i używając innego dozwolonego separatora czyli podkreślnika '_'. Reasumując UniqueID (renderuje się do atrybutu name tagu HTML) identyfikuje globalnie kontrolkę na potrzeby silnika ASP.NET, a ClientID (renderuje się do atrybutu id tagu HTML) na potrzeby JavaScript'u
- Utworzyłem stronę wzorcową.
- Utworzyłem stronę z zawartością.
- Do strony z zawartością dodałem kontrolki GridView oraz SqlDataSource.
- Skonfigurowałem połączenie z bazą danych.
- Uruchomiłem stronę i GridView wyświetlił dane ze wskazanej tablicy.
- Dodałem do strony kilka pól tekstowych i przycisk.
- Dodałem metodę obsługującą naciśnięcie przycisku:
protected void Button_Click(object sender, EventArgs e) { SqlDataSource.Insert(); }
- W kodzie strony deklaratywnie zdefiniowałem parametry wejściowe dla zapytania wstawiającego dane do bazy:
<asp:SqlDataSource ID="SqlDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:Database %>" SelectCommand="SELECT [Kolumna_1], [Kolumna_2], [Kolumna_3] FROM [Tabela]" InsertCommand="INSERT INTO users ([Kolumna_1], [Kolumna_2], [Kolumna_3]) VALUES (@Param_1,@Param1_2,@Param_3)"> <InsertParameters> <asp:FormParameter Name="Param_1" FormField="PoleTekstowe_1" /> ... </InsertParameters> </asp:SqlDataSource>
W powyższym kodzie PoleTekstowe_1 to identyfikator pola tekstowego będącego źródłem danych dla parametru Param_1.
Żeby nie przeciągać problem rozwiązałem rezygnując z deklaratywnego definiowana parametrów dla źródła danych na poziomie strony na rzecz zdefiniowania ich w kodzie. Oczywiście to nie wszystko. Sztuczka polega na tym, żeby przy podawaniu identyfikatora kontrolki, która będzie źródłem danych dla parametru należy użyć właściwości Control.UniqueID:
protected void Page_Load(object sender, EventArgs e) { this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_1", this.PoleTekstowe_1.UniqueID)); this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_2", this.PoleTekstowe_2.UniqueID)); this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_3", this.PoleTekstowe_3.UniqueID)); }Control.UniqueID zawiera już przemaglowany identyfikator (globalnie unikalny w obrębie strony) i w związku z tym źródło danych nie ma problemu ze znalezieniem odpowiedniej kontrolki.
Dociekliwi mogą powiedzieć, że jest jeszcze właściwość Control.ClientID. Od razu mówię, że jeśli użyjemy tej właściwości to błąd również wystąpi. Na pierwszy rzut oka może wydawać się to dziwne ponieważ ClientID zawiera już zmieniony identyfikator. Różnica polega na tym, że do oddzielenie poszczególnych składowych identyfikatora ClientID używany jest inny separator niż przy UniqueID. Poniżej przykład dwóch identyfikatorów dla tej samej kontrolki:
UniqueID: ctl00$ContentPlaceHolder1$Nazwa
ClientID: ctl00_ContentPlaceHolder1_Nazwa
Dlaczego jednak użyto innych separatorów. Zacznijmy od tego, że aby proces podmieniania nazw był odwracalny ASP.NET musi umieć wydzielić z wygenerowanego identyfikatora jego składowe czyli identyfikatory kolejnych kontenerów i na końcu kontrolki. To jest przyczyna, dla której wprowadzono separatory. Separatorem powinien być oczywiście znak, który nie może wystąpić w bazowym identyfikatorze, w tym przypadku '$' ale można to zmienić.
Tutaj dochodzimy do momentu, w którym potrzebujemy z jakiegoś powodu odwołać się do wyrenderowanej kontrolki z poziomu JavaScript'u. Oczywiście potrzebujemy jakiś identyfikator i tu pojawiaja się problem. Otóż co zrobić jeśli jako separator użytu znaku niedozwolonego w identyfikatorach przez JavaScript? Problem ten postanowiono obejść wprowadzając ClientID i używając innego dozwolonego separatora czyli podkreślnika '_'. Reasumując UniqueID (renderuje się do atrybutu name tagu HTML) identyfikuje globalnie kontrolkę na potrzeby silnika ASP.NET, a ClientID (renderuje się do atrybutu id tagu HTML) na potrzeby JavaScript'u