niedziela, 28 lutego 2010

Tworzenie webserwisu WCF cz. 1


Dzisiaj coś łatwego – stworzenie prostego web serwisu WCF i jego konsumpcja za pomocą aplikacji ASP.NET. 
Całość została podzielona na 3 części: 
1. (ta którą obecnie drogi czytelniku oglądasz) obejmuje tematyką tworzenie aplikacji WCF
2. Opisuje testowanie aplikacji WCF
3. Pokazuje w jaki sposób za pomocą ASP.NET "skonsumować" aplikację WCF
Nasz web serwis będzie umożliwiał wyświetlenie wszystkich naszych klientów, wybranego klienta oraz pozwoli dodawać nowych klientów.
Dla przykładu posłużę się bazą danych AdventureWorksLT2008 działającą pod kontrolą SQL Server 2008. Kroki, które należy wykonać aby utworzyć WebService:
1. Uruchamiamy Visual Studio 2008 i tworzymy nowy projekt: WCF Service Library
Warto tutaj zauważyć, że w szablonach VS mamy do wyboru WCF Service Application. Nie zalecam jego używania, gdyż ma ograniczone możliwości. W przypadku biblioteki otrzymujemy komponent który możemy następnie jako web serwis, usługę systemu Windows itp. Z kolei WCF Service Application oferuje nam podobne możliwości co web serwisy na platformie ASP.NET.
2. Usuwamy pliki Iservice1.cs i Service1.svc (stworzymy całość od 0)
3. Dodajemy nową klasę do naszego projektu:


    [DataContract]
    public class Customer
    {
        private int _id;
        [DataMember]
        public int ID
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
            }
        }
 
        private string _firstName;
        [DataMember]
        public string FirstName
        {
            get
            {
                return _firstName;
            }
            set
            {
                _firstName = value;
            }
        }
        [DataMember]
        private string _lastName;
        public string LastName
        {
            set
            {
                _lastName = value;
            }
            get
            {
                return _lastName;
            }
 
        }
    }
}
Atrybut DataContract pozwala na utworzenie kontraktu dzięki któremu nastąpi serializacja do XMLa. Poprzez atrybuty DataMember określamy które pola mają zostać zserializowane.
4. Dodajemy do naszego projektu interfejs:




    [ServiceContract]
    public interface ICustomerService
    {
        [OperationContract]
        void AddNewCustomer(Customer customer);
        [OperationContract]
        Customer GetCustomerById(int id);
        [OperationContract]
        DataTable GetAllCustomers();
    }


ICustomerService definiuje jakie operacje będą widoczne na zewnątrz (czyli z jakich metod będzie mógł korzystać użytkownik naszej aplikacji). Znacznikiem [ServiceContract] oznaczamy klasę/strukturę lub interfejs który zamierzamy udostępnić a znacznikami OperationContract oznaczamy które metody będą eksponowane.
5. Przechodzimy do części odpowiedzialnej za obsługę connectionStringów i dodajemy odpowiedni dla naszej bazy danych:

<add name="AdventureWorks" providerName="System.Data.SqlClient"  
connectionString="Data Source=.\sqlexpress;Initial Catalog=
AdventureWorksLT2008;Integrated Security=True"/>

6. Dodajemy kolejną klasę, która będzie implementowała nasz interfejs:







    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
    class CustomerService : ICustomerService
    {
        private string providerName = ConfigurationManager.
ConnectionStrings["AdventureWorks"].ProviderName;
        private string connectionString = ConfigurationManager.
ConnectionStrings["AdventureWorks"].ConnectionString;
        #region ICustomerService Members
 
        public void AddNewCustomer(Customer customer)
        {
            DbProviderFactory dbFactory = DbProviderFactories.GetFactory
(providerName);
            DbConnection conn = dbFactory.CreateConnection();
            conn.ConnectionString = connectionString;
            DbCommand cmd = conn.CreateCommand();
            try
            {
                cmd.CommandText = "INSERT INTO SalesLT.Customer(FirstName, 
LastName) VALUES(@firstName, @lastName)";
                conn.Open();
                DbParameter parameter = dbFactory.CreateParameter();
                parameter.DbType = DbType.AnsiString;
                parameter.Value = customer.FirstName;
                parameter.ParameterName = "firstName";
                cmd.Parameters.Add(parameter);
                parameter = dbFactory.CreateParameter();
                parameter.DbType = DbType.AnsiString;
                parameter.Value = customer.LastName;
                parameter.ParameterName = "lastName";
                cmd.Parameters.Add(parameter);
                cmd.ExecuteNonQuery();
                conn.Close();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
 
        public Customer GetCustomerById(int id)
        {
            DbProviderFactory dbFactory = DbProviderFactories.GetFactory
(providerName);
            DbConnection conn = dbFactory.CreateConnection();
            conn.ConnectionString = connectionString;
            conn.ConnectionString = connectionString;
            DbCommand cmd = conn.CreateCommand();
            try
            {
                cmd.CommandText = "select FirstName, LastName from SalesLT.
Customer where CustomerID = @custId";
                DbParameter parameter = dbFactory.CreateParameter();
                parameter.DbType = DbType.Int32;
                parameter.Value = id;
                parameter.ParameterName = "custId";
                cmd.Parameters.Add(parameter);
 
                DbDataAdapter da = dbFactory.CreateDataAdapter();
                da.SelectCommand = cmd;
                DataSet ds = new DataSet();
                da.Fill(ds);
                Customer c = new Customer();
                c.ID = id;
                c.FirstName = ds.Tables[0].Rows[0]["FirstName"].ToString();
                c.LastName = ds.Tables[0].Rows[0]["LastName"].ToString();
                return c;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
 
        public System.Data.DataTable GetAllCustomers()
        {
            DbProviderFactory dbFactory = DbProviderFactories.GetFactory
(providerName);
            DbConnection conn = dbFactory.CreateConnection();
            conn.ConnectionString = connectionString;
            DbCommand cmd = conn.CreateCommand();
            try
            {
                cmd.CommandText = "select FirstName, LastName from SalesLT.
Customer";
                conn.Open();
                DbDataAdapter da = dbFactory.CreateDataAdapter();
                da.SelectCommand = cmd;
                DataSet ds = new DataSet();
                da.Fill(ds);
                conn.Close();
                return ds.Tables[0];
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
 
        #endregion
    }
Metody działają tak jak są opisane. AddNewCustomer dodaje nowego klienta, 
GetCustomerById powoduje zwrócenie imienia i nazwiska klienta o określonym Id. 
GetCustomers zwraca wszystkich klientów. 

7. Przechodzimy do modyfikacji ustawień naszego serwisu klikamy prawym klawiszem 
myszy na plik App.config i wybieramy Edit WCF Configuration. Po wybraniu tej 
opcji powinniśmy ujrzeć następujący ekran:
 
 
Pierwsze co rzuca się w oczy to niewłaściwa nazwa naszego serwisu. Aby zmienić ją na 
poprawną (AdventureWorks) wchodzimy w kolejno Services -> AdventureWorks.Service1 -> 
właściwość name. Po kliknięciu na klawisz wyboru  przechodzimy kolejno do: bin -> 
Debug -> AdventureWorks.dll -> AdventureWorks.CustomerService po kliknięciu na 
open nazwa serwisu się zmieni na prawidłową.
 
Następnie modyfikujemy punkty końcowe naszego serwisu:
 
Przechodzimy tak jak poprzednio przez wszystkie foldery i wybieramy: 
AdentureWorks.ICustomerService

Drugi punkt końcowy służy do pobierania metadanych podczas działania naszego 
serwisu. Tak więc zostawiamy wartości drugiego punktu końcowego nietknięte.  

sobota, 20 lutego 2010

WPF w praktyce


Kilka praktycznych porad dal osób dopiero zaczynających przygodę z WPF.

1. Kontrolka RichTextBox
a) pobieranie zawartości kontrolki
Jeśli ktoś korzystał wcześniej z WindowsForms i pobranie zawartości tekstu znajdującego się wewnątrz kontrolki RichTextBox nie sprawiało mu problemów (właściwość Text) to w przypadku WPF może nie być już to takie jasne.
WPF wykorzystuje nowy model "zawartości" i aby pobrać text z naszej kontrolki możemy skorzystać z poniższego kodu:

TextRange textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
string s = textRange.Text;
 
b) programowe dodawanie tekstu
Także dodawanie tekstu "z kodu" nie jest takie oczywiste jak w WindowsForms:
 
            FlowDocument flowDocument = new FlowDocument();
            Paragraph para = new Paragraph();
            para.Inlines.Add("Text to add");
            flowDocument.Blocks.Add(para);
            rtb.Document = flowDocument;
Jak więc widać, aby dodać tekst należy najpierw stworzyć obiekt klasy FlowDocument oraz Paragraph. Następnie do kolekcji Inlines Paragraphu dodajemy tekst który chcemy ujrzeć w kontrolce. Na końcu do kolekcji Blocks FlowDocumentu dodajemy nasz paragraf i ostatecznie przypisujemy właściwości Document RichTextBoxa obiekt FlowDocument.

c) wczytywanie danych z pliku
Jak się domyślacie także i ta sprawa nie jest od początku taka oczywista :). W WinForms kontrolka posiadała wygodną metodę LoadFile() która pozwalała określić np. ścieżkę z której można załadować plik. W WPF należy skorzystać bezpośrednio ze strumienia:
 
            System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();
            ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            ofd.Filter = "Plik textowy (*.txt)|*.txt";
            ofd.RestoreDirectory = true;
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string fileName = ofd.FileName;
                TextRange textRange = new TextRange(rtbToChange.Document.ContentStart, rtbToChange.Document.ContentEnd);
                System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open);
                textRange.Load(fs, System.Windows.DataFormats.Text);
                fs.Close();
            }
Skorzystałem tutaj z wygodnego (jak mi się wydaje) OpenFileDialoga znajdującego się w Windows.Forms (można także tutaj skorzystać z OpenFileDialog znajdującego się w System.Win32). Następnie postępujemy jak przy obsłudze większości plików:
1. Otwieramy plik
2. Tworzymy obiekt TextRange któremu przekazujemy granice dokumentu
3. Korzystamy z metody Load(IO.Stream, dataFormat) która wczytuje tekst do RichTextBoxa ze strumienia
4. Po zakończeniu pracy z plikiem zamykamy go.

2. Button
Problem także nie ominął mnie także w kwestii zwykłego przycisku. Znana właściwość z WinForms FlatStyle: Flat, nie występuje w WPFowskiej kontrolce Button. Aby nadać płaski styl przyciskowi można użyć poniżeszego kodu:

Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}

Powyższą linijkę wstawiamy bezpośrednio do kodu XAML podczas deklaracji Buttona.

Podsumowując:
WPF przez swoją innowacyjność wymaga poznania nowych mechanizmów, które niekiedy ułatwiają a niekiedy utrudniają pracę programiście :)

niedziela, 14 lutego 2010

O ConnectionString słów kilka…


Każdy z pewnością miał do czynienia z tworzeniem oprogramowania operującego na bazie danych(DB). Każdy też ma swój sposób na interakcję i łączenie się z DB. Należałoby się jednak zastanowić, czy stosowane techniki pozwalają w łatwy sposób na zmianę rodzaju bazy danych? Przeważnie podczas tworzenia nowego połączenia z bazą danych na stałe używamy jednego providera:
            string connectionString = @"Data Source=.\sqlexpress;Initial Catalog=AdventureWorksLT2008;Integrated Security=True";
            DbConnection conn = new SqlConnection(connectionString);
            DbCommand cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT firstname FROM customers";
            conn.Open();
            //instrukcje
            conn.Close();

Sposób powyższy ma przynajmniej dwie wady:
1. "hard code" (spolszczyłbym to na: "stałe") zakodowanie providera;
2. Brak możliwości zmiany bazy danych (np. Oracle używa innego providera) bez zbędnych modyfikacji całego kodu;

Jak widać co jest oczywiste nie zawsze jest idealne. .NET Framework dostarcza nam (zarówno dla aplikacji ASP.NET jak i WinForms czy WPF) plik pozwalający na zapis naszych ConnectionString. 
Aby skorzystać z tego dobrodziejstwa dodajemy do naszego projektu plik Appliaction Configuration File:
Po dodaniu go do projektu powinien się od razu otworzyć z zawartością:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>
 
Pomiędzy znaczniki <configuration></configuration> możemy wprowadzić m.in. nasze ConnectionString:
 
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add/>
  </connectionStrings>
</configuration>
 
Za pomocą komendy <add/> dodajemy nowy ConnectionString. Znacznik ten składa się z 3 ważnych atrybutów: 
 
name  - przyjazna nazwa
connectionString - właściwy ConnectionString
providerName - nazwa providera
 
Przykład:
  

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="AdventureWorks" providerName="System.Data.SqlClient" connectionString="Data Source=.\sqlexpress;Initial Catalog=AdventureWorksLT2008;Integrated Security=True;"/>
  </connectionStrings>
</configuration>
 
Aby odwołać się w kodzie do podanego ConnectionString-a o nazwie AdventureWorks, wystarczy najpierw dodać referencję do biblioteki System.Configuration:
 
 Teraz w miejscu gdzie chcemy utworzyć połączenie:
 
            string connectionString = ConfigurationManager.ConnectionStrings["AdventureWorks"].ConnectionString;
            DbProviderFactory factory = DbProviderFactories.GetFactory(ConfigurationManager.ConnectionStrings["AdventureWorks"].ProviderName);
            DbConnection conn = factory.CreateConnection();
            conn.ConnectionString = connectionString;
            DbCommand cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT firstname FROM customers";
            conn.Open();
            //instrukcje
            conn.Close();

Korzystamy tutaj z klasy DbProviderFactories, która tworzy odpowiedni provider na podstawie jego nazwy. Dzięki temu w jednym pliku możemy złożyć wszystkie ConnectionStrings. W razie potrzeby zmiana będzie wymagana jedynie w tym jednym pliku a nie w całym programie.

Jeżeli chodzi o aplikacje ASP.NET, sprawa jest jeszcze łatwiejsza. W pliku Web.config dodajemy wpis w miejscu przeznaczonym na ConnectionStrings i już możemy z niego korzystać tak samo jak w aplikacjach desktopowych :)