niedziela, 25 listopada 2012

Metoda jQuery.ajax()

Jedną z największych zalet jQuery, jakie do tej pory miałem okazję wykorzystać, jest asynchroniczne odwołanie się kodu napisanego w javascript do metod, napisanych w C#. W najprostszej postaci (metoda javascript -> metoda c#) służy do tego jQuery.ajax.

Jak to działa?
Na stronie c# piszemy statyczną funkcję, którą oznaczamy atrybutem "Web Method", ktora w najprostszej postaci będzie wyglądać tak:

    public partial class WebMethodCommonHelper : LayoutsPageBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        [WebMethod]
        public static string GetActualDateTime()
        {
            return DateTime.Now.ToShortTimeString();
        }
    }

Odwołanie się do tej funkcji za pomocą javascriptu będzie natomiast wyglądało w ten sposób:

function GetDateTimeFromPage() {
  $.ajax({
                        type: 'POST',
                        url: 'WebMethodCommonHelper.aspx/GetActualDateTime',
                        data: JSON.stringify({ }),
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json',
                        success: function (data) {
                            var dateTimeFromPage =  data.d.Value
                            setDateTime(dateTimeFromPage);
                        }
                    });
}

function setDateTime(dateTimeToSet)
{
//TODO: tutaj wykorzystujemy date, pobrana ze strony
}

Krótki opis, kodu, który widzimy powyżej:
a) Tworzymy stronę aspx, na której umieszamy statyczną web methodę z atrybutem [Web Method]. To ta metoda jest odpowiedzialna za "część serwerową".
b) po stronie javascriptu tworzmy 2 metody, o nazwach "GetDateTimeFromPage()" oraz "setDateTime". Jedna będzie odpowiedzialna, za pobranie danych z metody c# o nazwie "GetActualDateTime", natomiast druga będzie wykorzystywała te dane bezpośrednio na stronie ( "setDateTime").
 Metoda $.ajax w najprostszej formie, pobiera 4 parametry:
- type: 'POST' -> tryb pracy protokołu http (POST lub GET)
- url - adres naszej web methody, poprzedzony adresem do strony aspx na której się znajduje (jeżeli strona jest w odpowiednim katalogu, jak to często bywa np. w aplikacjach sharpoint, to wtedy adres może wyglądać np tak: '/_layouts/Branding/Common/WebMethodCommonHelper.aspx/GetActualDateTime'
- data - parametry, jakie przekazujemy do web methody z poziomu javascriptu (zostaną opisane później przy bardziej zaawansowanym przypadku)
-contentType: 'application/json; charset=utf-8' - kodowanie (tego zazwyczaj nie zmieniamy)
- dataType: 'json' - rodzaj danych (tego zazwyczaj nie zmieniamy)
- success: function (data) {
                           var dateTimeFromPage =  data.d;
                            setDateTime(dateTimeFromPage);
                        }
- fragment kodu, odpowiedzialny, za to, co powinno się zadziać, w przypadku, gdy metoda wykona się poprawnie. W naszym przypadku, tworzymy zmienną  'dateTimeFromPage' i przypisujemy do niej wartości, jakie zwróciła nam nasza 'Web Method', a nast. przekazujemy tą wartość do metody pomocniczej, która wykorzysta ją dalej w naszym kodzie.
Co ważne, dane które dostajemy z powrotem to: "data.d". Dla typów wbudowanych, np. 'int32', 'string' itp. dane zwracane są jako "data.d". Dla klas, należy się odwołać to zmiennych wewnątrz klasy (jeżeli klasa, którą zwracamy ma publiczną własciwość o nazie "wartosc" to odwołamy się do niej poprzez var wartosc = data.d.wartosc").

Ok, skoro najprostszy przykład mamy już za sobą, to teraz spróbujmy jakiś bardziej zaawansowany przykład, np. z wykorzystaniem sharepointa:
a) kod javasctipt

function getCurrency()
{
                    var urlLink = window.location.protocol + "//" + window.location.host + _spPageContextInfo.siteServerRelativeUrl; //_spPageContextInfo.webServerRelativeUrl;
                    $.ajax({
                        type: 'POST',
                        url: '/_layouts/Branding/Common/WebMethodCommonHelper.aspx/GetCurrency',
                        data: JSON.stringify({ url: urlLink }),
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json',
                        success: function (data) {
                            var currency = parseFloat(data.d.Value);
                            setCurrency(currency);
                        }
                    });
}
function setCurrency(currency)
{
 //TODO: tutaj ustawimy przelicznik
}

b) kod c#
     public partial class WebMethodCommonHelper : LayoutsPageBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        [WebMethod]
        public static CurrencyHelper GetCurrency(string url)
        {
            using (SPSite oSiteCollection = new SPSite(url))
            {
                using (SPWeb web = oSiteCollection.OpenWeb())
                {
                    double rate = 4.56; //CurrencyHelper.GetRate(web);
                    CurrencyHelper lineHelper = new CurrencyHelper() { Value = rate, HfName = "Rate" };
                    return lineHelper;
                }
            }
        }
        [Serializable]
        public class CurrencyHelper
        {
            public string HfName { get; set; }
            public double Value { get; set; }
        }
}

Ok. Przejdźmy do analizy kodu:
Tym razem najpierw tworzymy metodę javascript. Ponieważ będziemy wykorzystywać sharpointa, bedziemy potrzebowali utworzyć obiekty SpSite oraz SpWeb. W tym celu, wykorzystamy zmienne shapointowe dostępne w języku javascript (tylko dla sharepoint 2010 lub nowszego). Mam na myśli:
_spPageContextInfo.siteServerRelativeUrl - zwraca adres SpSite
_spPageContextInfo.webServerRelativeUrl - zwraca adres SpWeb
Z pomocą tych zmiennych tworzymy pełny adres http, strony jaka nas interesuje (w zależności od tego, na której witrynie znajduje się lista do której zamierzamy się odwołać, czy na bierzącej witrynie, czy na 'root webie').
Mają odpowiedni adres http przekazujemy go do metody jQuery.ajax jako parametr url (ważne, aby nazwa parametru w jQuery.ajax oraz metodzie c# były takie same).
Jako rezultat otrzymujemy klasę napisaną przez użytkownika, więc w ceku odczytania wartości odwołujemy się do jej publicznej właściwości (Value), którą nast. przekazujemy do metody docelowej.

W przypadku kodu c#, ponownie tworzymy metodę statyczna z adrybutem "Web Method", oraz parametrem "url". Wykorzystując parametr, otwieramy SpSite oraz SpWeb'a, a nast. wykonujemy na nim interesujące nas operacje. Jako wynik, zwracamy obiekt stworzonej przez nas klasy CurrencyHelper.


p.s. Należy pamiętać, o dołączeniu odpowiednich referencji, tj. pliku z biblioteką "jQuery" po stronie javascriptu, oraz odpowiednich namespece po stronie c#.

sobota, 27 października 2012

Podniesienie uprawnień sharepoint

Bardzo często podczas pisania programów w technologii sharepoint (np. Visual Web Part) zdarza się konieczność czasowego podniesienia uprawnień użytkownika dla wykonania konkretnej czynności. Przykładem, mogą być uprawnienia do listy. Normalny użytkownik posiada uprawnienia tylko do odczytu elementów listy (elementy listy sharpoint są podobne do rekordów w bazie danych), a co za tym idzie nie posiada uprawnień do tworzenia/modyfikowania elementów listy. Aby taki użytkownik mógł utworzyć/zmodyfikować element na takiej liście za pomocą napisanej przez nas aplikacji (np. mógł utworzyć nową 'fakturę' za pomocą aplikacji, do zarządzania fakturami, opartej na listach sharepoint), trzeba czasowo podnieść jego uprawnienia. Podniesienie uprawnień w kodzie C# w praktyce oznacza pobranie SPSite z podwyższonymi uprawnieniami (tzw. SuperSite).
Przykład:



SPSecurity.RunWithElevatedPrivileges(delegate()
{
    using (SPSite
superSite = new SPSite(SPContext.Current.Site.ID))
    {
        using (SPWeb
superWeb = superSite.OpenWeb(SPContext.Current.Web.ID))
        {
            // Your code here
        }
    }
});
 
Podczas pobierania SuperSite lepiej jest jednak wykorzystać wrapper, w postaci klasy SPSecurityHelper, zademonstrowanej przez Dana Larsona na jego tech-blogu
Klasa SPSecurityHelper:

using Microsoft.SharePoint;


    /// <summary>A class for working with elevated privilege</summary>
    public static class SPSecurityHelper
    {
        /// <summary>Returns an elevated site</summary>
        /// <param name="theSite">
        /// The site that you want an elevated instance of.
        /// You must dispose of this object unless it is part of SPContext.Current.
        /// </param>
        /// <returns>An elevated site context.</returns>
        /// <remarks>Be sure to dispose of objects created from this method.</remarks>
        public static SPSite GetElevatedSite(SPSite theSite)
        {
            var sysToken = GetSystemToken(theSite);
            return new SPSite(theSite.ID, sysToken);
        }

        /// <summary>Gets a UserToken for the system account.</summary>
        /// <param name="site"></param>
        /// <returns>A usertoken for the system account user./returns>
        /// <remarks>Use this token to impersonate the system account</remarks>
        public static SPUserToken GetSystemToken(SPSite site)
        {
            site.
CatchAccessDeniedException = false;
            try
            {
                return site.SystemAccount.UserToken;
            }
            catch (UnauthorizedAccessException)
            {
                SPUserToken sysToken = null;

                // Only use runwithelevated to grab the system user token.
                SPSecurity.RunWithElevatedPrivileges(
                    delegate()
                    {
                        using (SPSite lolcatKiller = new SPSite(site.ID))
                        {
                            sysToken = lolcatKiller.SystemAccount.UserToken;
                        }
                    }
                );
                return sysToken;
            }
        }
    }
 Wykorzystanie klasy SPSecurityHelper:

 using (SPSite superSite = SPSecurityHelper.GetElevatedSite(SPContext.Current.Site))
{
    using (SPWeb superWeb = superSite.OpenWeb(SPContext.Current.Web.ID))
    {
               // Your code here
    }
}

 Sposób wykorzystania jest bardzo prosty, tzn. do klasy SPSecurityHelper przekazujemy obiekt zwykłego SpSite, natomiast z powrotem dostajemy obiekt tego samego SpSite z podwyższonymi uprawnieniami. Dzięki wykorzystaniu klasy SPSecurityHelper kod jest krótszy oraz lepszy jakościowo.

Adres komputera, korzystającego z naszej strony

    Uzyskanie informacji nt. komputera, z którego wykonano jakąś czynność jest bardzo ważne, z punktu widzenia bezpieczeństwa. Informacja ta, jest ważna zarówno w przypadku systemów wewnętrznych (intranet), jak i systemów zewnętrznych (internet).
    W przypadku intranetu, jednym ze szczególnych przypadków jest "usuwanie rekordów z bazy danych" (np. usuwanie faktur w systemie księgowym), gdzie oprócz loginu osoby wykonującej akcję (dost. z systemu), chcemy mieć dla pewności informacje nt. komputera, z którego wykonano akcję.
    W przypadku internetu, chcemy mieć zazwyczaj informacje, nt. komputera, który próbuje się włamać do naszego systemu, lub wykonuje inną nieporządaną akcję (np. atak DDoS).

W jaki sposób uzyskać informację nt. komputera, korzystającego z naszego systemu? Wykorzystamy do tego celu klasy z przestrzeni nazw "System.Web" oraz "System.Net".

using System.Web;
using System.Net;
IPHostEntry hostEntry = Dns.GetHostEntry(HttpContext.Current.Request.ServerVariables["REMOTE_HOST"].ToString());

Praktyczny sposób wykorzystania zmiennej hostEntry prezentuje przykład, wykonany na zwykłej stronie asp.net (web forms):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web;
using System.Net;
using System.Text;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string ComputerName = "";
        IPAddress[] IpAdressList = null;
        StringBuilder sb = new StringBuilder();
        try
        {
            IPHostEntry hostEntry = Dns.GetHostEntry(HttpContext.Current.Request.ServerVariables["REMOTE_HOST"].ToString());
            ComputerName = hostEntry.HostName.ToString();
            IpAdressList = hostEntry.AddressList;
        }
        catch { }
        sb.AppendLine(string.Format("Nazwa komputera {0}", ComputerName));
        if (IpAdressList != null)
        {
            for(int i=0; i < IpAdressList.Length; i++)
            {
                sb.AppendLine(string.Format("Typ adresu IP {0}, adres IP {1}", IpAdressList[i].AddressFamily, IpAdressList[i].ToString()));
            }
        }
        string computerInfo = sb.ToString();
    }
}

   Posiadając informacje nt. komputera, możemy te informacje zapisać do bazy danych, lub do logów. W przypadku aplikacji asp.net preferuję wykorzystania gotowego rozwiązania, jakim jest log4net, natomiast w przypadku Sharepoint 2010 logowanie do natywnych logów sharepointa (jak sie to robi opisze w jednym z przyszłych wpisów).

Edit: Niestety te metody w praktyce nie okazały się na tyle dobre, jak być powinne. Nowe metody:

        public static string GetUser_IP()
        {
            string VisitorsIPAddr = string.Empty;
            if (HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != null)
            {
                VisitorsIPAddr = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
            }
            else if (HttpContext.Current.Request.UserHostAddress.Length != 0)
            {
                VisitorsIPAddr = HttpContext.Current.Request.UserHostAddress;
            }
            return VisitorsIPAddr;
        }
        public static string GetUserIP()
        {
            string ipList = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
            if (!string.IsNullOrEmpty(ipList))
            {
                return ipList.Split(',')[0];
            }
\            return HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
        }
Używanie
        string visitorIp = GetUser_IP();
        int intAddress = BitConverter.ToInt32(IPAddress.Parse(visitorIp).GetAddressBytes(), 0);

środa, 10 października 2012

Pobieranie aktualnego kursu walut z NBP

Bardzo często, podczas pisania aplikacji finansowo-księgowych zdarza nam się potrzeba przeliczania walut po ich aktualnym kursie. O ile w przypadku naprawdę dużych systemów klient najprawdopodobniej narzuci nam własne kursy (które będziemy importować np. z SAP), o tyle w przypadku pisania małych i średnich systemów przeliczanie walut po aktualnym kursie będziemy musieli wykonać samemu. Z pomocą przychodzi nam tutaj Narodowy Bank Polski, który codziennie pomiędzy godź. 10.30-12.30 wystawia plik XML z aktualnymi kursami na dany dzień. Co ważne, robi to w sposób podmiany starego pliku, tzn. że do godź. ~10.30 istnieje plik XML z wczorajszym kursem, po czym pomiędzy godzinami 10.30-12.30 zastępowany jest on nowym plikiem, o takiej samej nazwie, pod tym samym linkiem, ale z aktualnym kursem na dany dzień. Wspomniany plik XML można znaleźć pod tym linkiem.

Dzięki temu linkowi, będziemy znali aktualne kursy NBP, po których będziemy mogli dokonywać przeliczeń pomiędzy walutami po aktualnym kursie wg. NBP. Aby nieco uprościć całą sprawę, napisałem metodę, która pobiera zawartość tego pliku i udostępnia kursy w bardziej przystępnej, obiektowej formie. Mając tą metodę, możemy później napisać "timer job" (sharepoint timer job, lub aplikację konsolową uruchamianą za pomocą Windows Task Scheduler), który będzie umieszczał zawartość tego pliku w bazie danych, liście sharepointa, czy gdzie tylko chcemy.

Metoda, która pobiera aktualny kurs wygląda tak:
        private static List<Currency> ImportData(string url)
        {
            // Creates an HttpWebRequest with the specified URL.
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            // Sends the HttpWebRequest and waits for the response.           
            HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
            // Gets the stream associated with the response.
            Stream receiveStream = myHttpWebResponse.GetResponseStream();
            Encoding encode = System.Text.Encoding.GetEncoding("iso-8859-2");
            // Pipes the stream to a higher level stream reader with the required encoding format.
            StreamReader readStream = new StreamReader(receiveStream, encode);
            string readedData = readStream.ReadToEnd();
            myHttpWebResponse.Close();
            // Releases the resources of the Stream.
            readStream.Close();
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(readedData);
            XmlNodeList currencyListXml = xmlDoc.GetElementsByTagName(Constraints.POSITION);
            var date = xmlDoc.GetElementsByTagName(Constraints.PUBLICATION_DATE);
            DateTime currencyDate = Convert.ToDateTime(date[0].FirstChild.Value);

            List<Currency> currencyList = new List<Currency>();
            foreach (XmlNode currencyXml in currencyListXml)
            {
                //XmlNodeList nodes = currencyXml.ChildNodes;
                Currency currency = new Currency();
                currency.CurrencyName = currencyXml.ChildNodes[0].FirstChild.Value;
                currency.Converter = currencyXml.ChildNodes[1].FirstChild.Value;
                currency.CurrencyCode = currencyXml.ChildNodes[2].FirstChild.Value;
                currency.CurrencyRate = Convert.ToDouble(currencyXml.ChildNodes[3].FirstChild.Value);
                currency.CurrencyDate = currencyDate;
                currencyList.Add(currency);
            }
            return currencyList;
        }
 Jako parametr podajemy adres url do pliku XML z listą walut. Wykorzystano parametr, ponieważ NBP wystawia kilka, różnych plików XML z aktualnymi kursami walut (LastA.xml, LastB.xml, LastC.xml) w zależności od tego, ile i które waluty nam są potrzebne korzystamy z odpowiedniego linku (LastA.xml podany w aplikacji pobiera 37 najpopularniejszych walut).

 Sama metoda, nie jest zbyt skomplikowana. Podajemy na wejście adres url, nast. za pomocą obiektów HttpWebRequest oraz HttpWebResponse pobieramy zawartość w pliku XML jako stream. Następnie tworzymy z niego XmlDocument, z którego po kolei wydobywamy interesujące nas dane i przekazujemy je do obiektów stworzonej przez nas klasy 'Currency'. Na zakończenie metoda zwraca listę obiektów 'Currency', na której to później dużo łatwiej jest nam operować.

Przykładowy program, który pobiera aktualne kursy walut, a nast. wyświetla je na konsoli wygląda następująco:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;

namespace GetAcctualNBPCurrency
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = @"http://www.nbp.pl/kursy/xml/LastA.xml";
            List<Currency> currencyList = ImportData(url);

            foreach (Currency currency in currencyList)
            {
                Console.WriteLine("Kurs dla waluty {0} ({1}) na dzień {2} wynosi: {3}", currency.CurrencyCode, currency.CurrencyName, currency.CurrencyDate.ToShortDateString(), currency.CurrencyRate);
            }
            Console.ReadKey();
        }
        private static List<Currency> ImportData(string url)
        {
            // Creates an HttpWebRequest with the specified URL.
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            // Sends the HttpWebRequest and waits for the response.           
            HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
            // Gets the stream associated with the response.
            Stream receiveStream = myHttpWebResponse.GetResponseStream();
            Encoding encode = System.Text.Encoding.GetEncoding("iso-8859-2");
            // Pipes the stream to a higher level stream reader with the required encoding format.
            StreamReader readStream = new StreamReader(receiveStream, encode);
            string readedData = readStream.ReadToEnd();
            myHttpWebResponse.Close();
            // Releases the resources of the Stream.
            readStream.Close();
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(readedData);
            XmlNodeList currencyListXml = xmlDoc.GetElementsByTagName(Constraints.POSITION);
            var date = xmlDoc.GetElementsByTagName(Constraints.PUBLICATION_DATE);
            DateTime currencyDate = Convert.ToDateTime(date[0].FirstChild.Value);

            List<Currency> currencyList = new List<Currency>();
            foreach (XmlNode currencyXml in currencyListXml)
            {
                //XmlNodeList nodes = currencyXml.ChildNodes;
                Currency currency = new Currency();
                currency.CurrencyName = currencyXml.ChildNodes[0].FirstChild.Value;
                currency.Converter = currencyXml.ChildNodes[1].FirstChild.Value;
                currency.CurrencyCode = currencyXml.ChildNodes[2].FirstChild.Value;
                currency.CurrencyRate = Convert.ToDouble(currencyXml.ChildNodes[3].FirstChild.Value);
                currency.CurrencyDate = currencyDate;
                currencyList.Add(currency);
            }
            return currencyList;
        }
    }
    class Currency
    {
        public string CurrencyName { get; set; }
        public string Converter { get; set; }
        public string CurrencyCode { get; set; }
        public double CurrencyRate { get; set; }
        public DateTime CurrencyDate { get; set; }

        public Currency() { }
    }
    class Constraints
    {
        public static readonly string PUBLICATION_DATE = "data_publikacji";
        public static readonly string POSITION = "pozycja";
        public static readonly string CURRENCY_NAME = "nazwa_waluty";
        public static readonly string CONVERETER = "przelicznik";
        public static readonly string CURRENCY_CODE = "kod_waluty";
        public static readonly string CURRENCY_RATE = "kurs_sredni";
    }
}
P.S. Zasady korzystania z danych publikowanych na stronie NBP można znaleźć w statusie serwisu pod tym adresem.

sobota, 22 września 2012

Podlgądanie zawartości pliku *.wsp

W aplikacji sharepoint, skompilowane rozwiązania (solucje) kompilowane są do plików z rozszerzeniem *.wsp. Pliki, te są paczkami, zawierającymi pliki, jakie nast. zostaną wgrane na serwer podczas instalacji pliku .wsp. Jeżeli chcemy podejrzeć zawartość pliku wsp, aby np. mieć pewność, czy pliki z obrazkami (*.png) poprawnie załączyły się podczas kompilacji, możemy to zrobić, zmieniając rozszerzenie pliku z '*.wsp", na "*.cab", a nast. przejrzeć plik "*.cab" za pomocą eksploratora plików systemu windows. Przykład:

MojaSolucja.wsp -> MojaSolucja.cab

Obrona przed XTML Injection, XSS (Cross-Site Scripting) poprzez Strip

Jednym z najpoważniejszych zagrożeń związanych z bezpieczeństwem aplikacji internetowych jest HTML Injection, czyli jedna z odmian Code Injection. Problem bezpieczeństwa związany HTML Injection polega głównie na Cross-Site Script, czyli wstrzyknięciu na stronę www kodu HTML zawierającego wykonujące się skrypty (np. w języku 'Javascript'), które mogą służyć m.in. do zmiany ustawień konta, wyciągania wrażliwych danych od użytkownika i innych niepożądanych czynności.
Kod HTML zawierający szkodliwe skrypty można wstrzyknąć np. do textBoxów pobierających od użytkownika dane.

W jaki sposób się przed tym bronić? Jednym ze skutecznych sposobów ochrony przed tego typu nieczystymi zagraniami jest walidacja tekstu wpisywanego przez użytkownika za pomocą wyrażeń regularnych.Metodą, która wykonuje za nas odpowiednią walidację, jest przedstawiona poniżej metoda Strip:

using System.Text.RegularExpressions;

public static string Strip(string text)
{
    return Regex.Replace(text, @"<(.|\n)*?>", string.Empty);
}
 Stosowanie przez nas tej metody, na każdym "stringu" wpisywanym przez użytkownika, przed zapisaniem go do bazy danych, nie będzie miało zauważalnego wpływu na wydajność, za to zdecydowanie podniesie bezpieczeństwo naszej aplikacji.





środa, 12 września 2012

Wyzerowanie (czyszczenie) witryny sharepoint

Czyli... coś czego... niemal nigdy nie powinno się robić, jednak moja wyobraźnia jest na tyle pojęta, że... jednak jestem w stanie sobie wyobrazić syt. w której trzeba programistycznie wyczyścić całą witrynę sharepoint. Można to zrobić za pomocą przedstawionej poniżej metody, którą można podpiąć pod kod deaktywujący feature.

Uwaga: ta metoda jest niczym "broń atomowa" dla witryny sharepoint, więc jej ewentualne użycie powinno być wcześniej 10-krotnie przemyślane.

     private void ClearAll(SPFeatureReceiverProperties properties)
        {

            SPWeb baseWeb = (SPWeb)properties.Feature.Parent;
            Guid siteId = baseWeb.Site.ID;
            Guid webId = baseWeb.ID;
            SPSecurity.RunWithElevatedPrivileges(
                delegate()
                {
                    using (SPSite adminSite = new SPSite(siteId))
                    {
                        try
                        {
                            SPWeb web = adminSite.AllWebs[webId];
                            if (web.Exists)
                            {
                                int listIndex = 0;
                                while (web.Lists.Count > listIndex)
                                {
                                    try
                                    {
                                        web.Lists[listIndex].Delete();
                                    }
                                    catch (Exception ex) { listIndex++; };
                                }

                                while (web.ContentTypes.Count > 0)
                                {
                                    int errorCount = 0;

                                    while (web.ContentTypes[0].FieldLinks.Count > errorCount)
                                    {
                                        try
                                        {
                                            web.ContentTypes[0].FieldLinks
                                                .Delete(web.ContentTypes[0].FieldLinks[errorCount].Id);
                                        }
                                        catch (Exception ex)
                                        {
                                            errorCount++;
                                        }
                                    }
                                    web.ContentTypes[0].Delete();
                                }
                                while (web.Fields.Count > 0)
                                {
                                    int fieldIndex = 0;
                                    while (fieldIndex < web.Fields.Count)
                                    {
                                        try
                                        {
                                            web.Fields.Delete(web.Fields[fieldIndex].InternalName);
                                        }
                                        catch (Exception ex) {
                                            fieldIndex++;
                                        };
                                    }
                                }
                                web.Update();
                                web.ResetRoleInheritance();
                                if (Helpers.checkGroup(SolutionResourcesAccessor.RMUAHRGroupName, web.SiteGroups))
                                    web.SiteGroups.Remove(SolutionResourcesAccessor.RMUAHRGroupName);
                                if (Helpers.checkGroup(SolutionResourcesAccessor.RMUAUsersGroupName, web.SiteGroups))
                                    web.SiteGroups.Remove(SolutionResourcesAccessor.RMUAUsersGroupName);
                                if (Helpers.checkGroup(SolutionResourcesAccessor.RMUAAdminGroupName, web.SiteGroups))
                                    web.SiteGroups.Remove(SolutionResourcesAccessor.RMUAAdminGroupName);
                                while (web.Navigation.QuickLaunch.Count > 0)
                                {
                                    web.Navigation.QuickLaunch.Delete(web.Navigation.QuickLaunch[0]);
                                    web.Update();
                                }
                                while (web.Navigation.TopNavigationBar.Count > 0)
                                {
                                    web.Navigation.TopNavigationBar.Delete(web.Navigation.TopNavigationBar[0]);
                                    web.Update();
                                }
                            }

                            SPLimitedWebPartManager mainLWPM =
                                web.GetLimitedWebPartManager(SPUrlUtility.CombineUrl(web.Url, "default.aspx")
                                   , System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
                            while (mainLWPM.WebParts.Count != 0)
                                mainLWPM.DeleteWebPart(mainLWPM.WebParts[0]);
                            web.ResetRoleInheritance();
                        }
                        catch (Exception ex)
                        {

                        }
                    }
                }
            );

        }

Wyświetlenie wszystkich webpartów aktualnie umieszczonych na stronie

Podczas tworzenia rozwiązań na platformę 'Sharepoint' czasami zdarza się, że chcemy obejrzeć wszystkie web party, jakie aktualnie są podpięte na oglądanej przez nas stronie. Do tego celu służy nam specjalny parametr dopisywany w querystringu. Parametr nosi nazwę 'contents' i przyjmuję wartość równą 1:

?contents=1

To rozwiązanie jest szczególnie użyteczne w sytuacjach, w których stworzony przez nas 'web part' wywala stronę, na której się znajduję. Wtedy w sposób szybki i bezproblemowy, dopisujemy odpowiedni fragment do querystringa i możemy taki 'web part' w sposób szybki, miły i przyjemny usunąć z naszej strony.

środa, 22 sierpnia 2012

jQuery growfield plugin

Kolejnym bardzo interesującym pluginem jQuery, który chciałbym zaprezentować, jest "growfield", czyli funkcjonalność dynamicznego rozszerzania się textBoxa znana z przeglądarki Mozilla Firefox. Na czym ta funkcjonalność polega? Na tym, że mając textbox i wpisując/usuwając z niego tekst, textbox automatycznie się rozszerza/minimalizuje.
Użytkownicy przeglądarki Mozilla Firefox mają tą funkcję w standardzie, aby jednak uzyskać podobną funkcjonalność w innych przeglądarkach, należy skorzystać z darmowego pluginu jQuery o nazwie "growfield".

Growfield, stanowi część projektu jQuery, i podobnie jak on, udostępniony jest na licencji MIT/X11.
Samo włączenie funkcji autorozszerzania jest banalnie proste i polega na
  • podpięciu odpowiednich bibliotek do projektu (zarówno standardowego jQuery.js w wersji minium 1.8, oraz growfield.js w wersji minimum 1.3),
  • dodaniu referencji do tych plików na stronie asp.net
  • wywołania prostego skryptu jQuery która aktywuje feature na odpowiednim obiekcie
Przykład skryptu aktywującego:

<script type="text/javascript">
    $(function () {
        $('.cssAreaNote').growfield();
    });
</script>

Samo podpięcie 'growfield' nie jest niczym innym, niż wywołanie dodatkowej funkcji na selektorze.W tym konkretnym przykładzie, jako element wyszukujący selektora  użyłem klasy CSS.

Uwaga: w trakcie pracy i testowania tej funkcjonalności spotkałem się z błędem w bibliotece, objawiającym się tym, że czasami do textboxa dopisywane były dodatkowe wartości (dokładnie '111'). Problem był już poruszony na forum StackOveflow. Tak jak ktoś dobrze zauważył, problem polegał na tym, że drugi "textBox" (rozwiązanie opiera się na pomyśle 2x textBoxów, z czego w jednym czasie tylko jeden z nich jest widoczny) miał taką samą nazwę jak pierwszy :/
Rozwiązanie tego problemu jest bardzo proste:
  • bierzemy plik z biblioteką 'growfield.js'
  • odnajdujemy funkcję o nazwie 'createDummy'
  • zmieniamy fragment odpowiadający za tworzenie "kopii zapasowej'' (czyli textBoxa nr.2) na:   
var dummy = o.clone().addClass('growfieldDummy').attr('name', '').attr('id', o.attr('id') + '-dummy')
                               .css({position: 'absolute', left: -9999, top: 0, height: '20px', resize: 'none'})
                               .insertBefore(o).show();

Możliwe, że w momencie, w którym Ty, mój drogi czytelniku pobierzesz najnowszą wersję tej biblioteki, problem będzie już rozwiązany. Możliwe jednak, że... pojawi się również u Ciebie, a wtedy będziesz wiedział co zrobić, aby temu zaradzić ;)

wtorek, 21 sierpnia 2012

jQuery tooltip plugin

Często podczas pracy, zdarza się potrzeba skorzystania z funkcjonalności, której nie posiadają standardowe biblioteki dostarczone przez platformę .NET. Rozwiązania tego problemu są dwa:
  • napisać rozwiązanie samodzielnie
  • skorzystać z gotowego rozwiązania
 Ponieważ jestem przeciwnikiem ponownego odkrywania koła, a po doświadczeniach związanych z pracą (i efektami tej pracy) na kontrolkach firmy DevExpress jestem wielkim fanem korzystania z gotowych rozwiązań wielokrotnie zanim samemu zacznę cokolwiek zacznę kodować, sprawdzam, czy taki problem nie został już przez kogoś innego rozwiązany.
Z uwagi na fakt, iż w mojej obecnej firmie, aktualnie nie posiadamy zakupionych komercyjnych kontrolek firm trzecich, to wykorzystujemy wszelkiego rodzaju rozwiązania dostępne za darmo. Przykładem takich kontrolek, są darmowe pluginy do jQuery. Jednym z takich pluginów jest 'jQuery tooltip plugin' udostępniony za darmo na stronie http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/.
Z pluginem tym, spotkałem się w momencie, gdy w jednej z aplikacji, pojawiła się potrzeba dłuższego czasu wyświetlania tooltipa. Opisy pól w tooltipie były na tyle duże, że normalny człowiek w kilka sek. nie był w stanie ich przeczytać.
Rozwiązaniem okazała się darmowa kontrolka, jQuery tooltip plugin, którą tutaj przedstawiam. Główne zalety kontrolki:
- nielimitowany czas wyświetlania tooltipa
- możliwość schowania tooltipa w momencie wystąpienia zdarzenia jQuery (podpięcie 'schowania' pod event)
- tooltip oraz jego zawartość określana za pomocą edytowalnej klasy CSS
- możliwość umieszczania obrazków, 'image map' oraz linków w tooltipie.
- bardzo łatwa obsługa podpinania komponentu do istniejących rozwiązań (plugin przechwytuje istniejącego tooltipa i się pod niego podszywa).
- licencja MIT pozwalająca na darmowe, komercyjne zastosowanie kontrolek


Samo podpięcie nowego, lepszego tooltipa, do istniejącego rozwiązania z tooltipami jest banalnie proste. Potrzebujemy:
  • dodać do naszego projektu pliki *.js z biblioteką jQuery oraz "jQuery tooltip plugin"
  • umieścić referencje do tych plików, na stronach, na których będziemy się do nich odwoływać (zazwyczaj takie rzeczy umieszcza się w master page)
  • utworzyć nową "pustą" klasę CSS, która będzie nam służyła jako "wskaźnik", dla każdego typu tooltipa (osobny css dla "obrazków", osobny dla "zwykłego tekstu" itp.)
  • dodać referencje do pliku *.css w którym znajduje się nasz znacznik, na stronie, na której będziemy korzystać z tego pluginu (zazwyczaj takie rzeczy umieszcza się w master page)
  •  do każdego obiektu, w którym ma być użyty extra tooltip, dołożyć klasę css odpowiedniego typu
  • upewnić się, że pliki posiadają wypełnioną właściwość "tooltip" (kontrolki asp.net) lub 'title' (kontrolki html).
  • przy uruchamianiu strony, wywołać skrypt aktywujący jQuery plugin tooltip
Na pierwszy rzut oka wydaje się tego sporo, ale w praktyce 90% z tych rzeczy powinno być już "zrobione" wcześniej (tooltipy w kontrolkach, pliki js osobno, pliki css osobno, do tego jakiś master page, z referencjami do tych plików).

Przykładowa kontrolka, z tooltipem wygląda tak:
<asp:TextBox ID="txtHelper" runat="server" ToolTip="tooltipHelper" CssClass="cssHelper"></asp:TextBox>
 Natomiast skrypt, aktywujący tooltipy dla każdego obiektu, który posiada klasę CSS o nazwie "cssHelper" wygląda tak:
    $("document").ready(function () {
        var objs = $(".cssHelper");

        jQuery.each(objs, function () {
            $("#" + this.id).tooltip();
        });
    });

P.S. Zachęcam do zapoznania się z projektem. Więcej przykładów znajduje się na stronie projektu w zakładce demo.

Edit:
Aby zrobić "multiline", czy też "page break" to należy:
                $('#selector').tooltip({
                    content: function () {
                        return $(this).attr('title');
                    }
                });
I dzięki temu, w tooltipie można skorzystać z  <br>

sobota, 14 lipca 2012

O jQuery słów kilka

jQuery jest to biblioteka napisana w języku 'javascript' przeznaczona jako darmowe rozszerzenie/uproszenie tego języka udostępniona na mocy licencji X11. W praktyce jQuery, oferuje to samo co standardowy 'javascript' jednak w o wiele bardziej przyjemnej, przyjaznej i uproszczonej formie. Kodu jest zdecydowanie mniej, jest on bardziej przejrzysty i o wiele bardziej niezawodny niż w przypadku własnoręcznie pisanego javascriptu.
Podstawą jQuery są tzw. selektory, czyli funkcje/klasy (w języku 'javascript'  funkcja jest klasą), które przeszukują drzewo DOM dokumentu w poszukiwaniu obiektów spełniających podane kryteria.
Standardowy selektor:
var txtHelper = $("#txtHelper");
Jest  równoznaczny wyrażeniu:
var txtHelper = document.getElementById("txtHelper");
Oba wyrażenia zwrócą bowiem obiekt (kod TextBoxa widziany z poziomu Visual Studio):
 <asp:TextBox ID="txtHelper" runat="server" ToolTip="tooltipHelper" CssClass="cssHelper"></asp:TextBox>
 Jeżeli ktoś jeszcze nigdy nie korzystał z jQuery i zastanawia się, czym jest znaczek $, to wyjaśniam, iż jest on nazwą funkcji/klasy reprezentowanej przez bibliotekę jQuery (w języku 'javascript' funkcja jest klasą, a klasa jest funkcją, więc odwołując się do funkcji odwołujemy się równocześnie do instancji jej klasy). W praktyce, pisząc $('alaMaKota') wywołujemy funkcję(klasę) o nazwie $ z parametrem 'alaMaKota'.
Gdyby ktoś potrzebował więcej wyjaśnień w sprawie, skąd się wziął dolar, to odsyłam do odpowiedniego wpisu: Greg Sidelnikov wyjaśnia "JavaScript DollarSign ($)"

Wracając do samego selektora, w celu wyszukania odpowiednich elementów (kontrolek) w dokumencie preferuję posługiwanie się klasami CSS, zamiast "ID" elementu.
Powodów jest kilka:
- inne kontrolki (np. ListView) czasami mają wpływ na nazwę naszego elementu widzianego w kontekście DOM, więc wykorzystywanie takiego 'id' jaki mamy w VS czasami nie zwraca oczekiwanego przez nas elementu
- czasami potrzebujemy wyszukać elementy nie posiadające swojego serwerowego 'id'
- czasami potrzebujemy wyszukać grupę identycznych elementów, a dzięki wykorzystaniu jQuery i CSS dostajemy grupę elementów spełniających nasze wymagania, po których możemy później iterować pętlą "each".

Nie bez znaczenia jak też tutaj fakt, że jeden obiekt może posiadać nieskończenie wiele klas CSS.
Przykład wykorzystania selektora z CSS, pętli "each" oraz dodatkowej funkcjonalności jQuery (dostarczonej w osobnych bibliotekach).
    $("document").ready(function () {
        var objs = $(".cssHelper");

        jQuery.each(objs, function () {
            $("#" + this.id).tooltip();
        });
    });
 W tym konkretnym przykładzie,  utworzony został skrypt, który automatycznie uruchamia się po zakończeniu ładowania się strony do przeglądarki. Skryp ten, wyszukuje wszystkie obiekty, które mają klasę CSS o nazwie "bar", a następnie, za pomocą pętli "each" aktywuje u nich funkcję "tooltip()".

Sama funkcja "tooltip()" dostępna jest jako darmowy plugin jQuery i znacznie rozszerza opcję standardowego "tooltipa" jednak o tym rozszerzeniu napiszę nieco więcej w innym wpisie.

Wracając jednak do selektora, jedną z jego wspaniałych zalet, jest np. możliwość bezproblemowego odwoływania się do obiektów, do których odwoływanie się za pomocą standardowego javascriptu stanowi poważnym problem. Przykładem jest tutaj "HiddenField", do którego odwołujemy się w sposób nieco "pokrętny", jednak przedstawiona poniżej wersja, jest obecnie najbardziej poprawna.
Sam 'HiddenField' dostaje swojego "opakowującego" div-a:
            <div class="hfFoo-wrap">
                <asp:HiddenField runat="server" ID="hfFoo" />
            </div>
 Natomiast funkcja która, która go poprawnie wyszukuje (i ustawia/sprawdza wartość) wygląda tak:

function FooBarFunction() {
    var hfFoo = $('.hfFoo-wrap input[type=hidden]');
    hfFoo.val('Bar');
    var isBar = hfFoo.val();
}
Dzięki czemu widzimy, że funkcje wyszukiwania selektora jQuery są o wiele potężniejsze niż standardowe wyszukiwanie obiektów, za pomocą "czystego" javascriptu. Ponieważ przedstawione tutaj przykłady stanowią jedynie wierzchołek góry lodowej oraz możliwości jQuery, zachęcam więc, do samodzielnego zapoznania się z tą wspaniałą, dostępną za darmo biblioteką.
 Oficjalna strona projektu.

Find Control Iteractive

Skoro pierwszy wpis powitalny mam już za sobą, najwyższa pora, opublikować jakiś wpis merytoryczny. Pierwszy wpis merytoryczny, jest poświęcony metodzie roboczo nazwanej FindControlIterative, która wykorzystana na stronach ASP.NET służy do znalezienia konkretnej kontrolki na stronie za pomocą kodu języka C# (tzw. "code behind").
        public Control FindControlIterative(Control root, string id)
        {
            Control ctl = root;
            LinkedList<Control> ctls = new LinkedList<Control>();

            while (ctl != null)
            {
                if (ctl.ID == id)
                    return ctl;
                foreach (Control child in ctl.Controls)
                {
                    if (child.ID == id)
                        return child;
                    if (child.HasControls())
                        ctls.AddLast(child);
                }
                ctl = ctls.First.Value;
                ctls.Remove(ctl);
            }
            return null;
        }
 Metoda ta, w sposób iteracyjny sprawdza wszystkie "kontrolki w kontrolce", aż do momentu znalezienia szukanej przez nas kontrolki, po czym przeszukiwanie ustaje i dostajemy szukaną przez nas kontrolkę. W przypadku nie odnalezienia danej kontrolki, dostajemy "null". Najlepszym, znanym mi sposobem, na wykorzystanie tej metodzie, to uruchomienie jej na aktualnym "Master Page" (o ile strona na której operujemy posiada jakiś master page).

                    Label lblFoo = FindControlIterative(this.
Page.Master, "lblFoo") as Label;
                    lblFoo.Text = "Bar";

Dzięki tej metodzie, mamy szybki i bezproblemowy dostęp do wszystkich kontrolek dostępnych na stronie, z poziomu "code behind" każdej kontrolki wykorzystanej na danej stronie (taki odpowiednik jQuery, tyle, że po stronie kodu C#).


poniedziałek, 9 lipca 2012

Pierwszy wpis

Witam na moim techblogu.

Celem tego blogu, jest propagowanie wiedzy oraz pomoc innym developerom podczas ich codziennej pracy nad kodem (i nie tylko). Jest to moje aktywne podziękowanie dla innych blogów tego typu oraz ich autorów z których wielokrotnie korzystałem, a zarazem moja drobna cegiełka w ulepszanie świata.

Ponieważ jako zawodowemu programiście wielokrotnie zdarzało mi się wykorzystywać znalezione materiały jako element własnego oprogramowania, jestem wielkim zwolennikiem licencji X11 (zwanej licencją MIT) i jeżeli nie jest napisane inaczej, to wszelkie zamieszczone tutaj materiały są mojego autorstwa (bądź też znalezione tak dawno, że nie pamiętam gdzie powstały) i udostępnione są tutaj na licencji X11.