Membuat Web Service Dengan Windows Communication Foundation (WCF)


Sebuah layer dalam software engineering adalah pemisahan secara logika dalam mengelompokkan kode program. Biasanya pengelompokkan ini dilakukan berdasarkan peran dan fungsi kode program. Berbeda dengan layer, tier adalah kode program yang berjalan di sebuah fisik yang berbeda, misalnya di komputer server. Tier dibedakan berdasarkan fisik (seperti komputer server) ini. Biasanya, sebuah tier diwakili oleh unit eksekusi (seperti file exe) yang berbeda. Sebagai contoh, bila terdapat A.EXE, B.EXE, dan C.EXE sebagai bagian dari sebuah aplikasi di komputer yang sama dimana mereka saling berkomunikasi melalui mekanisme Interprocess Communication (IPC) seperti shared memory, maka mereka boleh dianggap sebagai tier yang berbeda.

Windows Communication Foundation (WCF) adalah fasilitas dari .NET Framework untuk mempermudah komunikasi antar-tier. Sebagai latihan, saya akan mengubah kode program yang saya buat di artikel sebelumnya, Melakukan Binding Collection Di WPF, sehingga kode program tersebut murni hanya merupakan presentation tier dengan WPF. Nantinya, ia akan mengakses business service tier yang berada di aplikasi berbeda. Domain model yang sebelumnya dipakai langsung di aplikasi WPF tersebut akan saya pindahkan ke business service tier karena logika bisnis akan dikerjakan disini (pada latihan sederhana saya, logika bisnis-nya adalah method Total() yang menghitung total setelah diskon).

Saya akan mulai dengan membuat sebuah proyek console application Visual C# yang saya beri nama LatihanBusinessLogicTier. Untuk memakai WCF, saya perlu men-klik kanan nama proyek, kemudian memilih Add Reference…. Pada tab .NET, saya memilih System.ServiceModel dan men-klik tombol OK. Selain itu, saya perlu melakukan langkah yang sama untuk menambahkan referensi ke System.Runtime.Serialization agar nantinya saya bisa memakai atribut seperti [DataContract] dan [DataMember].

Saya kemudian memindahkan ItemPenjualan.cs dari proyek sebelumnya ke proyek baru ini. Setelah itu, saya mengubah namespace dari LatihanMVVM menjadi LatihanBusinessLogicTier. Mengapa domain model diletakkan disini? Bila seandainya logika bisnis (pada contoh ini menghitung Total()) diletakkan di presentation tier yang memakai WPF lalu suatu saat nanti ada presentation tier baru yang berbasis web, maka kode program Total() harus diduplikasi. Ini bukanlah sesuatu yang baik. Oleh sebab itu, saya membuat presentation tier menjadi sebuah aplikasi‘dummy’ yang tidak tahu apa-apa selain bertugas menampilkan.

Walaupun domain model dikerjakan oleh business service tier, object-nya tidak akan dikembalikan begitu saja ke presentation tier. Dengan kata lain, sebaiknya business service tier tidak mengembalikan instance dari ItemPenjualan secara langsung ke presentation tier. Mengapa demikian? Lazy loading tidak akan bekerja dengan baik karena presentation tier tidak lagi memiliki akses ke database. Karena lazy loading tidak bekerja, maka tidak semua method di domain model dapat dikerjakan. Lagi pula, presentation tier tidak perlu memanggil method yang mewakili business logic tersebut. Sebaiknya ia hanya memperoleh nilai dan menampilkannya (atau sebaliknya, mengirim nilai).

Oleh sebab itu, saya perlu membuat class yang akan dipakai oleh data transfer object (DTO). Sebuah class DTO hanya memiliki property tanpa method. Walaupun property-nya hampir sama seperti milik domain model, mereka biasanya terbatas untuk keperluan service tertentu. Sebagai contoh, navigation property (misalnya mengakses Penjualan dari ItemPenjualan) tidak disertakan bila tidak dibutuhkan oleh client. Beberapa programmer merasa bahwa penggunaan DTO terlalu berlebihan untuk kasus yang sederhana. Yup! Sebenarnya, saya dapat langsung men-share domain model ke client untuk contoh sederhana ini. Tapi pada aplikasi yang lebih besar dan lebih rumit (dimana domain model memiliki navigation property dan asosiasi seperti one-to-many), penggunaan DTO merupakan sesuatu yang disarankan.

Setelah menemukan rancangan arsitektur yang akan dipakai, saya siap untuk membuat kode program. Saya akan mulai dengan membuat sebuah interface baru yang mewakili service contract dengan nama ItemPenjualanService yang isinya seperti berikut ini:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace LatihanBusinessLogicTier
{
    [ServiceContract]
    public class ItemPenjualanService
    {
        [OperationContract]
        public void Simpan(ItemPenjualanSaveDTO itemPenjualanDTO)
        {
            // Kode Program Disini!
        }

        [OperationContract]
        public DaftarItemPenjualanDTO LihatSemua()
        {
            // Kode Program Disini!
        }
    }

    [DataContract]
    public class ItemPenjualanDTO
    {
        [DataMember]
        public long Id;

        [DataMember]
        public string NamaBarang;

        [DataMember]
        public int Jumlah;

        [DataMember]
        public decimal Harga;

        [DataMember]
        public decimal DiskonPersen;

        [DataMember]
        public decimal Total;
    }

    [CollectionDataContract]
    public class DaftarItemPenjualanDTO : List<ItemPenjualanDTO> { }

    [DataContract]
    public class ItemPenjualanSaveDTO
    {
        [DataMember]
        public string NamaBarang;

        [DataMember]
        public int Jumlah;

        [DataMember]
        public decimal Harga;

        [DataMember]
        public decimal DiskonPersen;
    }
}

Pada kode program di atas, DTO untuk operasi Simpan() tidak memiliki property Id dan Total karena memang tidak dibutuhkan untuk operasi tersebut. Dengan demikian, sebuah DTO tidak selalu memiliki property yang sama dengan domain class yang diwakilinya.

Kode program di atas belum selesai. Saya perlu mengimplementasikan isi service untuk kode program di atas, tapi sebelumnya saya akan terlebih dahulu membuat persistence layer.

Langkah selanjutnya adalah menambahkan Entity Framework sebagai persistence layer yang akan mengakses database. Untuk itu, saya men-install Entity Framework terbaru di proyek dengan memilih menu Project, Manage NuGet Packages…. Setelah Entity Framework berhasil ditambahkan pada proyek, saya perlu mengubah App.config sama seperti pada proyek di artikel sebelumnya. Saya juga perlu men-klik kanan nama proyek dan memilih Add Reference…, .NET, MySql.Data.Entity for EF6 agar dapat memakai database MySQL dengan Entity Framework 6. Berikutnya, saya men-copy file LatihanContext.cs dan MyHistoryContext.cs dari proyek di artikel sebelumnya ke proyek baru ini. Saya perlu mengubah namespace yang dipakai oleh kedua file tersebut dari LatihanMVVM menjadi LatihanBusinessLogicTier. Setelah ini persistence layer sudah selesai dibuat dan siap dipakai.

Saya akan membuat implementasi untuk service Simpan() di ItemPenjualanService.cs yang isinya adalah seperti berikut ini:

...
[ServiceContract]
public class ItemPenjualanService
{

  [OperationContract]
  public void Simpan(ItemPenjualanSaveDTO itemPenjualanDTO)
  {
    using (var db = new LatihanContext())
    {
      ItemPenjualan p = new ItemPenjualan()
      {
        NamaBarang = itemPenjualanDTO.NamaBarang,
        Jumlah = itemPenjualanDTO.Jumlah,
        Harga = itemPenjualanDTO.Harga,
        DiskonPersen = itemPenjualanDTO.DiskonPersen
      };
      db.DaftarItemPenjualan.Add(p);
      db.SaveChanges();
    }
  }

  ...
}
...

Saya juga mengimplementasikan service LihatSemua() yang isinya seperti berikut ini:

...
[ServiceContract]
public class ItemPenjualanService
{

  ...
  [OperationContract]
  public DaftarItemPenjualanDTO LihatSemua()
  {
    DaftarItemPenjualanDTO hasil = new DaftarItemPenjualanDTO();
    using (var db = new LatihanContext())
    {
      foreach (var p in db.DaftarItemPenjualan)
      {
        hasil.Add(new ItemPenjualanDTO()
        {
          Id = p.Id.Value,
          NamaBarang = p.NamaBarang,
          Harga = p.Harga,
          DiskonPersen = p.DiskonPersen,
          Total = p.Total()
        });
     }
   }
   return hasil;
  }
  ...
}
...

Saya juga membuat sebuah service HitungTotal() yang akan mengembalikan sebuah decimal seperti berikut ini:

...
[ServiceContract]
public class ItemPenjualanService
{

  ...
  [OperationContract]
  public decimal HitungTotal(ItemPenjualanSaveDTO itemPenjualanDTO)
  {
    ItemPenjualan p = new ItemPenjualan()
    {
       NamaBarang = itemPenjualanDTO.NamaBarang,
       Jumlah = itemPenjualanDTO.Jumlah,
       Harga = itemPenjualanDTO.Harga,
       DiskonPersen = itemPenjualanDTO.DiskonPersen
    };
    return p.Total();
  }
  ...
}
...

Pada langkah berikutnya, saya perlu men-host web services yang sudah saya buat. Agar sederhana, saya akan memakai fasilitas self-hosting. Pada prakteknya, web services sebaiknya di-host ke web server seperti Internet Information Services (IIS). Untuk membuat aplikasi console yang dapat melayani request (self-hosting), saya akan membuat kode program berikut ini pada file Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace LatihanBusinessLogicTier
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri baseAddress = new Uri("http://localhost:80/LatihanBusinessLogicTier/Service");
            ServiceHost selfHost = new ServiceHost(typeof(ItemPenjualanService), baseAddress);
            try
            {
                selfHost.AddServiceEndpoint(typeof(ItemPenjualanService), new WSHttpBinding(), "ItemPenjualanService");
                ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
                smb.HttpGetEnabled = true;                
                selfHost.Description.Behaviors.Add(smb);
                selfHost.Open();
                Console.WriteLine("Business Logic Tier sudah aktif dan siap melayani request.");
                while (true)
                {
                    System.Threading.Thread.Sleep(50);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Telah terjadi kesalahan: {0}", ex.Message);
                selfHost.Abort();
            }
            Console.ReadLine();
        }
    }
}

Salah satu kelebihan WCF adalah ia bisa mendukung satu atau lebih binding. Pada kosa kata WCF, binding adalah sesuatu yang menentukan bagaimana proses komunikasi terjadi (protokol yang dipakai, format data, dan sebagainya). Contoh binding yang disediakan oleh .NET saat ini adalah WSHttpBinding untuk web services berbasis SOAP, WebHttpBinding yang dapat dipakai untuk membuat web services berbasis REST, NetTcpBinding untuk komunikasi jaringan melalui socket, dan sebagainya. Programmer juga dapat mengimplementasikan binding baru bila perlu. Menariknya, sebuah aplikasi boleh memiliki lebih dari satu binding. Salah contoh penerapannya, sebuah aplikasi yang sama dapat men-ekspos services yang dimilikinya dalam bentuk REST maupun SOAP. Pada contoh ini, saya hanya memakai WsHttpBinding sehingga services akan dipublikasi dalam bentuk layanan SOAP yang mendukung spesifikasi WS-* .

Sekarang saatnya menjalankan program yang berisi web services ini. Tapi, sebelumnya, saya perlu menutup Visual Studio dan menjalankan dengan memilih Run As Administrator. Hal ini karena bila program tersebut dijalankan oleh user yang tidak memiliki hak akses, akan muncul pesan kesalahan HTTP could not register URL ... Your process does not have access rights to this namespace.

Bila saya menjalankan program, saya akan memperoleh pesan seperti berikut ini:

Business Logic Tier sudah aktif dan siap melayani request.

Untuk menguji apakah services dapat diakses dengan baik, saya akan membuka Visual Studio Command Prompt (2010) dan memberikan perintah WcfTestClient. Pada aplikasi WcfTestClient yang muncul, saya memilih menu File, Add Service…. Lalu pada dialog pengisian yang muncul, saya mengisi dengan nilai http://localhost/LatihanBusinessLogicTier/Service dan men-klik tombol OK. Saya akan memperoleh hasil seperti pada gambar berikut ini:

Menguji web services yang telah dibuat

Menguji web services yang telah dibuat

Saya akan mencoba mengerjakan service LihatSemua() dengan men-double click nama service tersebut. Kemudian pada bagian kanan, saya men-klik tombol Invoke. Saya akan memperoleh hasil seperti pada gambar berikut ini:

Memanggil service `LihatSemua()`

Memanggil service `LihatSemua()`

Saya juga dapat mencoba mengerjakan service Simpan() seperti yang terlihat pada gambar berikut ini:

Memanggil service `Simpan()`

Memanggil service `Simpan()`

Service Simpan() tidak mengembalikan respon berupa sebuah nilai. Untuk memastikan apakah ia berhasil dikerjakan dengan baik, saya perlu memeriksa database secara manual untuk melihat apakah data pada tabel sudah bertambah.

Setelah memastikan bahwa business service tier sudah beres, saya akan memperbaharui presentation tier agar memanggil business service tier. Saya perlu menghapus file yang berkaitan dengan Entity Framework karena kini presentation tier tidak lagi mengakses database secara langsung. Saya juga perlu membuang domain model yang sudah saya buat sebelumnya (file ItemPenjualan.cs) karena presentation tier tidak lagi menampung logika bisnis. Yang tersisa hanya dua komponen untuk MVVM, yaitu view (file MainWindow.xaml) dan view model (file ItemPenjualanViewModel.cs). Lalu, mana model-nya? MVVM tidak akan pernah lengkap tanpa model, bukan?

Yang kini menjadi model dalam MVVM disini adalah DTO yang dikembalikan oleh business service tier. Muncul sebuah pertanyaan disini: Class DTO didefinisikan di business service tier, apakah saya perlu mereferensi class tersebut dengan menyertakan proyek business service tier sebagai dependency? Tidak harus! Lalu, class apa yang harus saya pakai? Karena class DTO hanya berisi data tanpa logika program, maka saya bisa menghasilkan class tersebut secara otomatis selama business service tier mempublikasikan metadata untuk services yang dimilikinya. Dengan demikian, saya tidak perlu mereferensikan proyek business service tier di proyek presentation tier sehingga dapat mengurangi ketergantungan.

Untuk menghasilkan DTO dan kode program pembantu lainnya secara otomatis, saya men-klik kanan padaService References (bukan References!) di Solution Explorer, lalu memilih Add Service Reference…. Saya memastikan terlebih dahulu bahwa program untuk business service tier sudah dijalankan, kemudian mengisi dialog yang muncul seperti pada gambar berikut ini:

Menghasilkan kode program dan DTO di client berdasarkan metadata

Menghasilkan kode program dan DTO di client berdasarkan metadata

Bila saya melihat di Object Browser, saya akan menemukan beberapa class baru yang dibuat oleh Visual Studio 2010 secara otomatis untuk saya, seperti yang terlihat di gambar berikut ini:

Class yang dihasilkan

Class yang dihasilkan

Class DTO yang dihasilkan hampir mirip seperti asli-nya di proyek business service tier, hanya saja sudah dilengkapi dengan implementasi INotifyPropertyChanged. Dengan demikian, sebenarnya DTO dapat langsung dilewatkan oleh view model ke view (binding langsung dari view ke model DTO). Selain itu, saya juga akan menemukan interface ItemPenjualanService dan ItemPenjualanServiceChannel serta sebuah class ItemPenjualanServiceClient. Saya dapat mengakses web services yang disediakan business service tier melalui class tersebut.

Langkah terakhir adalah mengubah view model (file ItemPenjualanViewModel.cs) agar memakai DTO yang dihasilkan sehingga kode programnya menjadi seperti berikut ini:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows;
using System.Collections.ObjectModel;
using LatihanMVVM.BusinessLogicService;


namespace LatihanMVVM
{
    public class ItemPenjualanViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ItemPenjualanSaveDTO model;

        private ICommand simpanCommand;        

        private ItemPenjualanServiceClient itemPenjualanService;

        private ObservableCollection<ItemPenjualanDTO> listItemPenjualan;

        public ItemPenjualanViewModel(ItemPenjualanSaveDTO itemPenjualan = null)
        {
            this.model = itemPenjualan ?? new ItemPenjualanSaveDTO();            

            this.itemPenjualanService = new ItemPenjualanServiceClient();
            this.ListItemPenjualan = new ObservableCollection<ItemPenjualanDTO>(itemPenjualanService.LihatSemua());
        }


        public ObservableCollection<ItemPenjualanDTO> ListItemPenjualan 
        {
            get { return this.listItemPenjualan; }
            private set { this.listItemPenjualan = value; }
        }

        public string NamaBarang
        {
            get { return model.NamaBarang; }
            set
            {
                if (value != model.NamaBarang)
                {
                    model.NamaBarang= value;
                    PropertyChanged(this, new PropertyChangedEventArgs("NamaBarang"));
                }
            }
        }

        public int Jumlah
        {
            get { return model.Jumlah; }
            set
            {
                if (value != model.Jumlah)
                {
                    model.Jumlah= value;
                    PropertyChanged(this, new PropertyChangedEventArgs("Jumlah"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Total"));
                }
            }
        }

        public decimal Harga
        {
            get { return model.Harga; }
            set
            {
                if (value != model.Harga)
                {
                    model.Harga = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("Harga"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Total"));                     
                }
            }
        }

        public decimal DiskonPersen
        {
            get { return model.DiskonPersen; }
            set
            {
                if (value != model.DiskonPersen)
                {
                    model.DiskonPersen = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("DiskonPersen"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Total"));
                }
            }
        }

        public string Total
        {
            get 
            {
                decimal? total = itemPenjualanService.HitungTotal(model);
                if (!total.HasValue)
                {
                    return "-";
                }
                else
                {
                    return total.Value.ToString("C");
                }
            }
        }

        public ItemPenjualanSaveDTO Model
        {
            get { return this.model; }
        }

        public ItemPenjualanService ItemPenjualanService
        {
            get { return this.itemPenjualanService; }
        }

        public ICommand SimpanCommand
        {
            get
            {
                if (this.simpanCommand == null)
                {
                    this.simpanCommand = new SimpanCommand(this);
                }
                return this.simpanCommand; 
            }
        }

    }


    public class SimpanCommand : ICommand
    {

        private ItemPenjualanViewModel viewModel;

        public SimpanCommand(ItemPenjualanViewModel viewModel)
        {
            this.viewModel = viewModel;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return !viewModel.Total.Equals("Rp0");
        }

        public void Execute(object parameter)
        {            
            viewModel.ItemPenjualanService.Simpan(viewModel.Model);            
            MessageBox.Show("Data berhasil disimpan ke database");
        }

    }

}

Sebenarnya view model dapat meng-ekpos DTO secara langsung untuk dipakai oleh view. Akan tetapi, saya berusaha menjaga agar tidak perlu ada perubahan pada view sehingga saya hanya perlu mengubah view model saja. Bila aplikasi dijalankan, ia akan bekerja sama seperti sebelumnya, tapi kali ini business logic dan database bisa saja berada di komputer terpisah, seperti yang terlihat pada gambar berikut ini:

Arsitektur Program

Arsitektur Program

Perihal Solid Snake
I'm nothing...

6 Responses to Membuat Web Service Dengan Windows Communication Foundation (WCF)

  1. kevin soundgraphia mengatakan:

    blog terbaik yang pernah kubaca. thank you pak keep posting

  2. mufti (@muff_29) mengatakan:

    kk mau tanya, jika kita ingin membuat web service , apakah website yang jadi server client itu harus di hosting? terimakasih

    • Solid Snake mengatakan:

      Bila hanya ingin menguji pada komputer lokal, bisa dijalankan pada komputer yang sama dan tidak perlu di-host.

      Bila dalam bentuk aplikasi seperti pada artikel ini dan ingin bisa diakses dari komputer berbeda, aplikasi tersebut perlu dijalankan pada komputer yang memiliki IP publik dan dapat diakses dari luar.

      • mufti (@muff_29) mengatakan:

        saya ingin membuat Restful WCF service, dengan server ASP.NET dan client Android, apakah asp nya harus di host untuk mengaksesnya? atau bisa menggunakan wifi ya? terimakasih

  3. Solid Snake mengatakan:

    Selama client bisa berhubung dengan server, apapun itu (wifi lokal, LAN, WAN/internet, VPN), web services seperti REST dapat dikerjakan karena web services adalah application layer yang berada di atas emulasi physical layer (kabel atau radio), transport layer (TCP/IP), dan seterusnya.

  4. MARIANA SARAGI mengatakan:

    Bagus nih, masih bingung sih mau cobain tutorial hosting wcf di IIS cuma ga ada projeknya. bingung he tutorial basa Inggris semua nemu satu http://windowshosting.blogdetik.com/2014/07/15/hosting-wcf-service-pada-iis/ cuma ga ada contoh WCFnya apa yang mau di hosting coba:/

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: