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 :)

poniedziałek, 4 stycznia 2010

SQL-owe LIKE w LINQ

Pisząc aplikację chciałem wypróbować możliwości dające mapowanie tabel z bazy danych na obiekty. Skorzystałem z technologi LINQ która została przedstawiona w .NET 3.5. Problemem na jaki się natknąłem było użycie operatora LIKE znanego z SQLa. Zapytania:

SELECT imie, nazwisko
FROM Osoba
WHERE imie LIKE ‘%an’

SELECT imie, nazwisko
FROM Osoba
WHERE imie LIKE ‘an%’

SELECT imie, nazwisko
FROM Osoba
WHERE imie LIKE ‘%an%’



są często stosowane tam, gdzie użytkownik szuka jakiejś konkretnej informacji ale nie zna jej pełnej nazwy. LINQ nie zawiera w swojej składni operatora like, można natomiast wykorzystać funkcje działające na łańcuchach.


1. StartsWith("string"):

            var q = from a in db.Osobas
                    where a.Imie.StartsWith("pa")
                    select a.Imie;
 
Wyrażenie to wygeneruje następujące zapytanie do bazy danych:
SELECT [t0].[Imie]
FROM [dbo].[Osoba] AS [t0]
WHERE [t0].[Imie] LIKE 'pa%'
 
2. EndsWith("string")
 
 
            var q = from a in db.Osobas
                    where a.Imie.EndsWith("pa")
                    select a.Imie;
 
Wyrażenie to wygeneruje następujące zapytanie do bazy danych:
SELECT [t0].[Imie]
FROM [dbo].[Osoba] AS [t0]
WHERE [t0].[Imie] LIKE '%pa'
 
3. Contains("string")
 
            var q = from a in db.Osobas
                    where a.Imie.Contains("pa")
                    select a.Imie;
  
Wyrażenie to wygeneruje następujące zapytanie do bazy danych:
SELECT [t0].[Imie]
FROM [dbo].[Osoba] AS [t0]
WHERE [t0].[Imie] LIKE '%pa%'

Jak więc widać mamy całą gamę funkcji dzięki którym możemy w łatwy sposób generować zapytania do bazy danych z wykorzystaniem operatora LIKE.


Istnieje jeszcze jeden sposób wykorzystania tego operatora. Dla niektórych może się on wydać bardziej "naturalny" od wykorzystywania funkcji.

Dodając do naszego projektu przestrzeń:
using System.Data.Linq.SqlClient;
Uzyskujemy dostęp do kilku przydatnych funkcji w klasie 
SqlMethods
 Przykład:
 
            var q = from a in db.Osobas
                    where SqlMethods.Like(a.Imie, "p_o%")
                    select a.Imie;
 

  
Tak skonstruowane zapytanie pozwoli nam wyszukać wszystkie wyrazy których pierwsza litera to p, druga dowolna, trzecia o a kolejne dowolne. Czyż to nie proste?

sobota, 26 grudnia 2009

Wysyłanie meili w .NET

Wysyłanie maila w .NET to jedna z prostszych czynności jakie możemy wymagać od naszego programu.

1. Przestrzeń System.Net.Mail

Przestrzeń ta zawiera wszystko co jest nam potrzebne aby w łatwy i szybki sposób wysłać maila. Najczęściej używanymi klasami z pewnością są:
Attachment – reprezentuje załącznik wiadomości
MailAddress – klasa reprezentująca mail nadawcy lub odbiorcy
MailMessage – reprezentuje wiadomość do wysłania
SmtpClient – pozwala na wysłanie utworzonego maila protokołem SMTP
SmtpException – wyjątek w przypadku błędu podczas wysyłania wiadomości


2. Tworzenie wiadomości email

 
Korzystając z wymienionych wcześniej klas możemy utworzyć naszą wiadomość. Rozpoczniemy od utworzenia obiektu klasy MailMessage:
    public MailMessage();
    public MailMessage(MailAddress from, MailAddress to);
    public MailMessage(string from, string to);
    public MailMessage(string from, string to, string subject, string body);

Jak widać mamy do dyspozycji 4 konstruktory. Parametry które przyjmują to:
From – mail osoby wysyłającej
To – mail osoby do której kierujemy wiadomość
Subject – temat wiadomości
Body – treść wiadomości
Wszystkie te parametry można ustawić po utworzeniu obiektu klasy MailMessage:
            MailMessage message = new MailMessage();
            //właściwość To jest typu MailAdressCollection tak więc możemy
            //dodać wiele osób do których kierujemy naszą wiadomość:
            //message.To.Add(new MailAddress("do_kogo1@o2.pl");
            //message.To.Add(new MailAddress("do_kogo2@o2.pl");
            //message.To.Add(new MailAddress("do_kogo3@o2.pl");
            message.To.Add(new MailAddress("do_kogo@o2.pl"));
            message.From = new MailAddress("od_kogo@o2.pl");
            message.Subject = "Temat naszej wiadomości";
            message.Body = "Treść naszej wiadomości";

Oprócz tych podstawowych właściwości możemy także ustawić takie rzeczy jak:
            //Wysłanie ukrytej kopii
            message.Bcc.Add(new MailAddress("do_kogo@o2.pl"));
            //Wysłanie kopi
            message.CC.Add(new MailAddress("do_kogo@o2.pl"));
            //Powiadomienie o stanie wysłania; opóźnienie, dostarczenie, niepowodzenie
            message.DeliveryNotificationOptions = DeliveryNotificationOptions.OnSuccess;
            //Priorytet wiadomości
            message.Priority = MailPriority.High;


3. Dodawanie załączników do wiadomości
 
Wiele wysyłanych przez nas wiadomości zawiera dodatkowe dane w postaci zdjęć, plików PDF czy też innego rodzaju dane audio/wideo. Aby do wiadomości dodać załączniki tworzymy obiekty klasy Attachment a w jego konstruktorze podajemy ścieżkę do pliku na dysku:
            message.Attachments.Add(new Attachment(@"C:\Users\Patryk\Documents\CV.docx"));
            message.Attachments.Add(new Attachment(@"C:\Users\Patryk\Documents\Lista_Zakupow.docx"));



4. Wsparcie dla znaczników HTML
 
Aby włączyć w wiadomości wsparcie dla znaczników HTML należy najpierw ustawić właściwość IsBodyHtml na true:
message.IsBodyHtml = true;
Po tej operacji możemy używać znaczników HTML w naszej wiadomości:
message.Body = @"<html><body><h1>Hello</h1><br><br>Co u ciebie słychać?</body></html>";
Aby dodać jakąś grafikę bezpośrednio w wiadomości (nie jako załącznik) należy skorzystać z klas AlternateView oraz LinkedResource:
            //treść naszej wiadomości ze znacznikami HTML
            string body = "<html><body><h1>Hello</h1><br><br>Co u ciebie słychać?<br>Tutaj moje zdjęcie z zeszłych wakacji: <br> <img src=\"cid:Zd1\"></body></html>";
            //tworzymy alternatywny widok
            AlternateView htmlView = AlternateView.CreateAlternateViewFromString(body);
            //wybieramy zdjęcie które zostanie dodane do naszej wiadomości HTML
            LinkedResource image = new LinkedResource(@"C:\Users\Patryk\Pictures\przyklad_graphic.png", "image/png");
            //nadajemy Id naszemu zdjęciu (później wstawiamy je jako cid:"Id"
            image.ContentId = "Zd1";
            htmlView.LinkedResources.Add(image);
            //dodajemy widok do wiadomości
            message.AlternateViews.Add(htmlView);



5. Wysyłanie wiadomości
 
Czas teraz na najważniejszą część czyli wysłanie wiadomości. Wysyłamy za pomocą protokołu SMTP więc tworzymy najpierw obiekt tej klasy:
SmtpClient smtp = new SmtpClient("poczta.o2.pl", 587);
W konstruktorze jak widać podałem port SMTP wykorzystywany przez o2.pl oraz nazwę serwera.
Jeśli serwer wymaga uwierzytelnienia, należy stworzyć obiekt klasy NetworkCredential i w konstruktorze podać wymagane parametry:
smtp.UseDefaultCredentials = false;
smtp.Credentials = new NetworkCredential("uzytkownik", "haslo");
Na końcu oczywiście wysyłamy nasze dzieło:
smtp.Send(message);

Jeśli zrobiliśmy wszystko zgodnie z instrukcją, nasza wiadomość powinna zostać wysłana bez żadnych błędów.
Wracając jeszcze do portów SMTP to należy sprawdzić jaki jest używany przez danego dostawcę poczty. Najczęściej to 587 od kiedy TP SA zablokowała port 25.

Tyle odnośnie wysyłania wiadomości. Miłego spamowania J

sobota, 12 grudnia 2009

Factory Method

aplikacja
Wzorcem obiektowym którym dzisiaj się zajmiemy jest Factory Method Pattern (Metoda Wytwórcza). Jest jeden z kreacyjnych wzorców projektowych, a jak sama nazwa sugeruje służy do tworzenia obiektów. Ściśle ujmując dostarcza on interfejsu do tworzenia obiektów, ale o typie obiektów decydują jego podklasy.
Zobaczmy na schemat klas:



Jak widać mamy tu do czynienia z dwoma klasami abstrakcyjnymi: Product czyli obiekt który chcemy utworzyć oraz Creator definiujący sposób jego utworzenia.

Zobaczmy na przykład. Rozważmy produkcję laptopów. Mamy dwie fabryki wytwarzające gotowe laptopy. Jedna z fabryk zlokalizowana jest w Polsce druga w Japonii. Fabryki produkują dwa rodzaje laptopów: modelA i modelB. Diagram klas:


Typ wyliczeniowy LaptopModel pozwala nam w prosty sposób tworzyć obiekty potrzebnego nam typu. Można także robić to w inny sposób jak np. przekazać string czy daną typu int. Enum wydał mi się tu jednym z najbardziej uniwersalnych i prostych rozwiązań.

Kod aplikacji można pobrać stąd: aplikacja