Menangani Error: The provided anti-forgery token was meant for a different claims-based user than the current user.

Hari ini saya mendapatkan ticket dari Tester mengenai error authentikasi user. Test case nya seperti ini.

  1. Navigasi ke halaman Login di tab pertama, login sebagai user ‘A’.
  2. Kemudian buka tab baru pada browser, buka home page (selain halaman login).
  3. Logout user di tab pertama.
  4. Buka halaman lain pada tab 2, page akan redirect ke halaman login dengan membawa serta query string return url.
  5. Login di tab pertama.
  6. ketika login di tab kedua akan muncul error “The provided anti-forgery token was meant for a different claims-based user than the current user”.

Pertama saya mencoba dengan menonaktifkan cache pada action login  dengan menambahkan attribut [OutputCache(NoStore = true, Location = OutputCacheLocation.None)] tapi ternyata hasil nya tetap sama.

Cara kedua, yaitu dengan cara menghilangkan [ValidateAntiForgeryToken] pada action login, tapi tetap menambahkan  @Html.AntiForgeryToken() pada form. Tapi cara ini malah mengurangi tingkat keamanan pada website.

Akhirnya, setelah googiling. Saya menemukan solusi yang lebih tepat dengan cara membuat attribute untuk menangani error tersebut. Sehingga jika terjadi error tersebut bisa dialihkan ke halaman lain.

public class AntiForgeryHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is HttpAntiForgeryException)
{
var url = string.Empty;
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
var requestContext = new RequestContext(context.HttpContext, context.RouteData);
url = RouteTable.Routes.GetVirtualPath(requestContext, new RouteValueDictionary(new { Controller = "User", action = "Login" })).VirtualPath;
}
else
{
context.HttpContext.Response.StatusCode = 200;
context.ExceptionHandled = true;
url = GetRedirectUrl(context);
}
context.HttpContext.Response.Redirect(url, true);
}
else
{
base.OnException(context);
}
}

private string GetRedirectUrl(ExceptionContext context)
{
try
{
var requestContext = new RequestContext(context.HttpContext, context.RouteData);
var url = RouteTable.Routes.GetVirtualPath(requestContext, new RouteValueDictionary(new { Controller = "Home", action = "Index" })).VirtualPath;

return url;
}
catch (Exception)
{
throw new NullReferenceException();
}
}

Tambahkan attribute tersebut pada login action

[HttpPost]
       [AllowAnonymous]
       [ValidateAntiForgeryToken]
       [AntiForgeryHandleError]
       public async Task Login(LoginViewModel model, string returnUrl)
       {

Behavior Driven Development (BDD) Using SpecFlow

Behavior Driven Development (or BDD) is an Agile software development technique that encourages collaboration between developers, QA, and non-technical or business participants in a software project. 

Dalam BDD kita menggunakan aspek Given/When/Then ketika membuat Feature. Dari Feature ini,  Tester dapat membuat scenario test. Sedangkan  bagi Developer bisa digunakan untuk membuat Unit Testing. Hindari menggunakan terlalu banyak istilah teknis di dalam feature yang kita buat. Salah satu tujuan menggunakan BDD adalah agar menghindari salah pengertian mengenai sebuah feature antara tester, analis dan developer. Kita akan menggunakan SpecFlow sebagai framework BDD kita.

Sebagai contoh kita mempunyai story seperti ini. Story ini dibuat menggunakan SpecFlow feature file.

Feature: Add Vehicle
    As an Administrator
    I want to be to add new vehicle
    In order to a Customer can reserve it

@mytag
Scenario: Vehicle Name is not blank
    Given I have entered '' into the field name
    When I press Submit
    Then I see message saying name is mandatory

Kemudian klik kanan file feature yang kita buat untuk membuat step.

right_click_generate_steps

steps_bdd.png

SpecFlow akan menghasilkan file steps seperti ini.

using System;
using TechTalk.SpecFlow;

namespace BDDTest
{
[Binding]
public class AddVehicleSteps
{
[Given(@"I have entered '(.*)' into the field name")]
public void GivenIHaveEnteredIntoTheFieldName(string p0)
{
ScenarioContext.Current.Pending();
}

[When(@"I press Submit")]
public void WhenIPressSubmit()
{
ScenarioContext.Current.Pending();
}

[Then(@"I see message saying name is mandatory")]
public void ThenISeeMessageSayingNameIsMandatory()
{
ScenarioContext.Current.Pending();
}
}
}

Kita running unit step tersebut, terlihat bahwa unit tersebut diskip karena belum ada implementasi di dalamnya.

skipped_test.png

Kita akan membuat class untuk menghandle bisnis logic untuk feature tersebut dan memodifikasi class steps di atas.

using BDDProject;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using TechTalk.SpecFlow;

namespace BDDTest
{
[Binding]
public class AddVehicleSteps
{
private IVehicleBLL _vechileBll = new VehicleBLL();
private Vehicle _vehicle = new Vehicle();
private string _message;
[Given(@"I have entered '(.*)' into the field name")]
public void GivenIHaveEnteredIntoTheFieldName(string EmptyName)
{
_vehicle.Name = EmptyName;
}

[When(@"I press Submit")]
public void WhenIPressSubmit()
{
try
{
_vechileBll.Add(_vehicle);
}
catch (Exception ex)
{
_message = ex.Message;
}
}

[Then(@"I see message saying name is mandatory")]
public void ThenISeeMessageSayingNameIsMandatory()
{
Assert.AreEqual(_message.ToLower(), "name is mandatory");
}
}
}

Kita jalankan lagi unit test kita, dan hasilnya seperti ini.

bdd_output.png

Refactoring Using CodeMaid

CodeMaid adalah ekstensi open source untuk visual studio yang bisa digunakan untuk melihat tingkat kompleksitas code program kita selain melakukan formating dan mengorganisir kode. Semakin tinggi nilai kompleksitas sebuah fungsi, semakin butuh fungsi itu untuk direfactor. Karena salah satu prinsip clean code adalah satu method harusnya hanya memiliki 1 high level abstraksi.

contoh code dengan kompleksitas yang tinggi seperti di bawah ini.

private Expression<Func<Vehicle, bool>> CreateVehicleFilter(VehicleFilterInput input)
{
Expression<Func<Vehicle, bool>> queryFilter = PredicateHelper.True();

if (!string.IsNullOrEmpty(input.Name))
queryFilter = queryFilter.And(ve => ve.VehicleTranslations.Any(t => t.Language == input.Language) ?
ve.VehicleTranslations.Any(t => t.Language == input.Language && t.TranslatedText.ToLower().Contains(input.Name.ToLower()))
: ve.Name.ToLower().Contains(input.Name.ToLower()));

if (!string.IsNullOrEmpty(input.InternalNumber))
queryFilter = queryFilter.And(ve => ve.InternalNumber.ToLower().Contains(input.InternalNumber.ToLower()));

if (!string.IsNullOrEmpty(input.ChassisNumber))
queryFilter = queryFilter.And(ve => ve.Chassis.ToLower().Contains(input.ChassisNumber.ToLower()));

if (input.VehicleBrandId != null && input.VehicleBrandId != 0)
queryFilter = queryFilter.And(ve => ve.VehicleModel.VehicleBrand.Id == input.VehicleBrandId);

if (input.VehicleModelId != null && input.VehicleModelId != 0)
queryFilter = queryFilter.And(ve => ve.VehicleModel.Id == input.VehicleModelId);

if (input.DateOfEntry != null && input.DateOfEntry != DateTime.MinValue &&
input.DateOfEntryTo != null && input.DateOfEntryTo != DateTime.MinValue)
queryFilter = queryFilter.And(ve => ve.DateEntry >= input.DateOfEntry && ve.DateEntry <= input.DateOfEntryTo); else { if (input.DateOfEntry != null && input.DateOfEntry != DateTime.MinValue) queryFilter = queryFilter.And(ve => ve.DateEntry >= input.DateOfEntry);
if (input.DateOfEntryTo != null && input.DateOfEntryTo != DateTime.MinValue)
queryFilter = queryFilter.And(ve => ve.DateEntry <= input.DateOfEntryTo); } if (input.ReleaseDate != null && input.ReleaseDate != DateTime.MinValue && input.ReleaseDateTo != null && input.ReleaseDateTo != DateTime.MinValue) { queryFilter = queryFilter.And(ve => (ve.DateActualRelease >= input.ReleaseDate && ve.DateActualRelease <= input.ReleaseDateTo) || (ve.DateSupposedRelease >= input.ReleaseDate && ve.DateSupposedRelease <= input.ReleaseDateTo)); } else { if (input.ReleaseDate != null && input.ReleaseDate != DateTime.MinValue) { queryFilter = queryFilter.And(ve => (ve.DateActualRelease >= input.ReleaseDate) || (ve.DateSupposedRelease >= input.ReleaseDate));
}
if (input.ReleaseDateTo != null && input.ReleaseDateTo != DateTime.MinValue)
{
queryFilter = queryFilter.And(ve => (ve.DateActualRelease <= input.ReleaseDateTo) || (ve.DateSupposedRelease <= input.ReleaseDateTo)); } } if (!string.IsNullOrEmpty(input.Status)) queryFilter = queryFilter.And(ve => ve.Status.ToLower().Contains(input.Status.ToLower()));

return queryFilter;
}

Terlihat pada CodeMaid Spade di Visual Studio fungsi tersebut kompleksitasnya sangat tinggi, di atas nilai 10.

code_main_high_complexity

Kita refactor, dengan membaginya ke dalam beberapa fungsi yang lebih kecil seperti ini.

private Expression<Func<Vehicle, bool>> CreateVehicleFilter(VehicleFilterInput input)
{
Expression<Func<Vehicle, bool>> queryFilter = PredicateHelper.True();

FilterVehicleByName(input, ref queryFilter);
FilterByDateOfEntry(input, ref queryFilter);
FilterByChassisNumber(input, ref queryFilter);
FilterByInternalNumber(input, ref queryFilter);
FilterByVehicleBrand(input, ref queryFilter);
FilterByVehicleModel(input, ref queryFilter);
FilterByRangeReleaseDate(input, ref queryFilter);
FilterByStatus(input, ref queryFilter);
return queryFilter;

}

CodeMaid akan menunjukan bahwa tingkat kompleksitasnya sekarang jauh lebih sedikit.

code_main_low_complexity

Error dialog when Open .cshtml File

Saya mendapat error pagi ini. Ketika melakukan double click pada View (.cshtml) file di Asp.Net MVC project pada Visual Studio, muncul popup dialog dengan pesan error. Setelah googling, ternyata untuk mensolve issue ini caranya sangat mudah.

  1. Tutup Visual Studio
  2. Hapus semua file cache pada folder %LocalAppData%\Microsoft\VisualStudio\14.0\ComponentModelCache
  3. Buka kembali Visual Studio. Harusnya tidak ada lagi error seperti sebelumnya.

Mencari Hakikat Penciptaan

Sebagai manusia dan tentunya sebagai seorang theist. Saya selalu selalu mencari jawaban tentang tujuan penciptaan, juga tentang hubungan antara Sang Pencipta dan Ciptaan-Nya. Apakah Tuhan hanya hanya dipandang sebagai dzat yang transendent-yang terpisah dari makhluknya atau Tuhan selalu menampakan bayangan-Nya dibalik ciptaan-Nya.

Dalam sebuah hadist Qudsi yang kira-kira redaksinya seperti ini: Tuhan berkata bahwa Dia adalah perbendaharaan yang tersembunyi, yang rindu untuk di kenal maka Dia menciptakan makhluk. Begitupun firmannya dalam kitab suci: Aku lebih dekat daripada urat nadi kalian sendiri.

Bukankah Tuhan tidak butuh makhluknya? Terus buat apa Dia mencipta? Apalah artinya kedermawanan tanpa si miskin kata Rumi sang sufi penyair itu. Mungkin begitupun dengan Tuhan, dia dengan ‘kesadaran’ memahmi bahwa sifat-sifat agung-Nya hanya akan bisa termanifestasi jika ada object penerima. Maka, menciptalah Dia.

Jika membahas keberadaan dzat-Nya, esensi diri-Nya. Tentulah nalar kemanusiaan kita tak sanggup sampai kesana. Dzatnya adalah mutlak, gaib di atas gaib, misteri di atas misteri. Dengan Kasih Sayang-Nya, kemudian Dia sendirilah yang akhirnya memperkenalkan diri lewat sifat-sifatNya (kita menyebutnya sebagai Ilahi atau Allah) atau dengan perbuatanNya (kita menyebut-Nya Tuhan atau Rabb).

Beberapa orang menemukan Dia lewat fenomena ciptaan-Nya, lewat hukum-hukum alam yang dibuat-Nya begitu kompleks, rumit tapi berpola.

Beberapa lagi ketika dalam keadaan fana, menemukan bahwa hanya Dia lah sebenarnya yang tampak dan yang lain hanyalah memantulkan bayangan-Nya. Sehingga ketika kembali dari fana, bagi mereka, realitas yang tampak hanyalah semu belaka. Ada juga golongan yang berpendapat bahwa memang benar bahwa ciptaan-Nya memantulkan bayangan diri-Nya tapi mengingkari keberadaan object pemantul itu sendiri adalah kesalahan. Bagi golongan ini Dia dan ciptaan-Nya saling memantulkan.

Kalau ibarat cahaya, Dia memancarkan sinarnya keseluruh semesta dan seluruh ciptaanNya meneriman cahaya tersebut sesuai dengan jarak kepada-Nya. Semakin dekat semakin terang, semakin jauh semakin gelap. Tak ada gelap, yang ada hanya tak sempurnanya kehadiran-Nya.

Tugas kita sebagai manusia, makhluk yang paling sempurna memantulkan dan menyerap seluruh sifat-sifatNya adalah berakhlak dengan akhlak-Nya. Kata Ibnu Arabi bukan posisi manusia untuk menanamkan sifat-sifat Tuhan itu kedalam dirinya, karena sebenarnya dalam dirinya sudah ada potensi sifat-sifat Tuhan ketika Tuhan meniupkan ruh kepadanya.

Diterbitkan di Life

Yang sering diabaikan, Non-functional Requirement

Ketika terlibat dalam sebuah team project pengembangan perangkat lunak, kita sebagai developer seringkali terlalu focus pada low level detail  sehingga lupa melihat big-picture berupa non-functional requirement seperti performance, scalability, availability dan security.

Performance

Performance bicara tentang seberapa cepat aplikasi tersebut, biasanya kita menggunakan term response time atau latency.Response time adalah waktu yang dibutuhkan antara sebuah request dikirim dan response diterima secara penuh. Seperti ketika kita menekan tombol hyperlink pada sebuah halaman web dan menunggu halaman page tersebut di load secara keseluruhan. Sedangkan latency adalah waktu yang diperlukan sebuah pesan untuk berpindah dalam sebuah system, dari suatu titik ke titik yang lain. Latency mengukur berapa besar waktu ketika response baru saja diterima. Sehingga Response Time akan selalu lebih besar daripada Latency.

Scalability

Scalability pada dasarnya kemampuan software untuk menangani lebih banyak pengguna, request, data dan sebagainya dalam periode waktu yang sama, misalanya berapa request per second.

Availability

Availability adalah tentang bagaimana tingkat ketersediaan (available) software untuk menangani request.  Biasanya diukur menggunakan terminasi “nines”. Seperti 99,99% (four nine) atau 99,999% (five nine).Angka-angka tersebut adalah angka uptime yang menunjukan berapa besar jumlah downtime yang bisa ditoleransi. Contoh, 99.9% (three nines) artinya downtime berkisar hanya 1 menit per harinya yang digunakan untuk jadwal maintenance berkala, upgrade atau memang terjadi kesalahan yang tidak diharapkan.

Security

Security berkaitan dengan segala sesuatu dari authentikasi dan authorisasi sampai pada bagaimana data tersebut disimpan. Authentikasi adalam proses memverifikasi user lewat credential yang dikirim. Sedangkan authorisasi terjadi setelah authentikasi, untuk membatasi resource mana yang boleh diakses oleh user  (file, url dan sebagainya).

Audit

Seringkali kita butuh untuk menyimpan log sebuah event yang bisa membantu kita untuk melakukan pelacakan perubahan sebuah data dari software system, terutama yang menyangkut transaksi yang melibatkan “uang”.  Bisanya sih disebut Audit Trail, dimana kita menyimpan data siapa yang melakukan perubahan, kapan perubahan itu terjadi dan apa saja yang berubah record yang berubah.

 

Flexibility

Flexibility adalah mengenai bagaimana software yang kita bangun memudahkan non-techincal user untuk memodifikasi business rules yang ada pada software. Sebagai contoh, kita bisa memasukkan value-value yang gampang dikonfigurasi di database ataupun diset lewat file configurasi seperti connection string, base url address, smpt server dan sebagainya.

Dan masih ada beberapa hal yang lain lagi seperti Maintability, Legal dan Localization..

 

 

 

 

Mocking Database Context In Repository Unit Test Using Effort

Seringkali kita ingin membuat unit test di level repository untuk menguji query yang kita buat pada sebuah repository. Karena pada unit testing, prinsipnya adalah melakukan isolasi dari external class ataupun external resource dan eksekusi unit testing harus bisa secepat mungkin, maka kita hindari menggunakan database secara langsung.

Jika menggunakan database secara langsung, sangat berpengaruh pada kecepatan eksekusi unit testing dan masalah data yang harus diinsert dan dihapus ketika selesai digunakan oleh unit testing. Salah satu solusinya adalah menggunakan in-memory-database. In-memory database ini akan dihancurkan oleh Garbage collector ketika sudah tidak digunakan.

Untuk mempermudah kita bisa menggunakan Effort. Instalasi cukup mudah, cukup di install lewat nuget package manager.

Pada initialisasi unit test kita mockup context kita.

Pertama, buat connection untuk Effort.

var connection = DbConnectionFactory.CreateTransient();

Selanjutnya pada class context, kita tambahkan satu constuctor untuk melewatkan connection dari Effort tersebut.

public BookStoreEntities(DbConnection connection) : base(connection, true)
{
}

pada unit test initialization method lewatkan connection Effort tadi ke context dan tambahkan code untuk membuat database jika tidak ada. Ini untuk menghindari munculnya error migrations data nantinya.

_context = new BookStoreEntities(connection);
_context.Database.CreateIfNotExists();

selanjutnya, insert data untuk table table yang akan kita gunakan nantinya.

_context.Categories.Add(new Category { Name = "Programming" });
_context.SaveChanges();
var category = _context.Categories.First();
_context.Books.Add(new Book { Title="Effort In Action", Category = category });
_context.Books.Add(new Book { Title = "C# In Action", Category = category });
_context.SaveChanges();

Kode program selengkapnya seperti ini.

[TestClass]
public class BookRepositoryTest
{
private BookStoreEntities _context;
private IUnitOfWork _unitOfWork;
private IGenericRepository _bookRepository;
[TestInitialize]
public void Setup()
{

var connection = DbConnectionFactory.CreateTransient();
_context = new BookStoreEntities(connection);
_context.Database.CreateIfNotExists();

_context.Categories.Add(new Category { Name = "Programming" });
_context.SaveChanges();
var category = _context.Categories.First();
_context.Books.Add(new Book { Title="Effort In Action", Category = category });
_context.SaveChanges();
_unitOfWork = new EFUnitOfWork(_context);
_bookRepository = _unitOfWork.GetGenericRepository();
}
[TestCleanup]
public void Cleanup()
{
_context = null;
_unitOfWork = null;
_bookRepository = null;
}
[TestMethod]
public void GetAll()
{
var books = _bookRepository.Get().ToList();
Assert.AreNotEqual(0, books.Count);
}
}

[TestMethod]
public void FindByName()
{
var books = _bookRepository.Get(x=>x.Title.Contains("Effor")).ToList();
Assert.AreNotEqual(0, books.Count);
}
[TestMethod]
public void GetById()
{
var book = _bookRepository.GetById(1);
Assert.IsNotNull(book);
}

Jika unit Test kita jalankan hasilnya seperti ini.

unit-test-effort

 

Selamat mencoba!

SOLID Principles-O-Open Closed Principle

“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

Untuk mengimplement OCP, sebelumnya kode kita harus SRP – lihat pada tulisan SOLID Prinsiples – S-Single Responsibility, sehingga kita dapat mengindentifikasi dan memisahkan behavior yang berubah kedalam berbagai class. Output dari OCP adalah sekumpulan class yang memiliki behavior yang sama tapi dengan implementasi yang berbeda-beda. Perhatikan potongan kode program di bawah ini.

public class DiscountService
{
private const int GOLD = 1;
private const int SILVER = 2;
private double _totalSales;
public DiscountService(double TotalSales)
{
_totalSales = TotalSales;
}

private int _custType;
public int CustType
{
get { return _custType; }
set { _custType = value; }
}

public double GetDiscount(int CustTpe)
{
if (_custType == GOLD)
{
return _totalSales - 100;
}
else if (_custType == SILVER)
{
return _totalSales - 50;
}
return _totalSales;
}
}

Masalah timbul ketika kita akan menambahkan beberapa tipe customer lain, maka akan sangat banyak kondisi “IF” di dalam kode program. Menambahkan kondisi “IF” baru artinya kita mengubah class CustomerService dan itu melanggar prinsip Close for modification.

Lantas bagaimana caranya mengimplementasikan prinsip Open for extension? Yaitu dengan menggunakan inheritance (lewat parent classnya) atau abstraksi (bisa menggunakan abstract class ataupun interface).

public interface ICustomer
{
string Name { get; }
double GetDiscount(double TotalSales);
}

Kemudian kita buat beberapa class konkrit untuk implementasi interface ICustomer di atas.

public class GoldCustomer : ICustomer
{
public string Name
{
get
{
return &quot;Gold&quot;;
}
}

public double GetDiscount(double TotalSales)
{
return TotalSales - 100;
}
}
public class SilverCustomer : ICustomer
{
public string Name
{
get
{
return &quot;Silver&quot;;
}
}

public double GetDiscount(double TotalSales)
{
return TotalSales - 50;
}
}

Kemudian mari kita refactor kode program class DiscountService di atas sehingga jika ingin menambah tipe customer lagi, tidak perlu mengubah implementasi dari class DiscountService.

public class DiscountService
{
private readonly ICustomer _discount;
private double _totalSales;
public DiscountService(double TotalSales, ICustomer discount)
{
_totalSales = TotalSales;
_discount = discount;
}
public virtual double GetDiscount()
{
return _discount.GetDiscount(_totalSales);
}
}

class Program
{
static void Main(string[] args)
{
double totalSales = 1000;
DiscountService discountService;
var listCustomer = new List();
listCustomer.Add(new GoldCustomer());
listCustomer.Add(new SilverCustomer());
foreach (var customer in listCustomer)
{
discountService = new DiscountService(totalSales, customer);
Console.WriteLine(&quot;Customer Type: &quot; + customer.Name);
Console.WriteLine(&quot;Total after discount : &quot; + discountService.GetDiscount());
Console.WriteLine(&quot;----------------------&quot;);
}

Console.ReadKey();

}
}

ocp

Sekarang kita bisa menambahkan 1 jenis customer lagi, tanpa harus mengubah code pada class Discount Service.

listCustomer.Add(new PlatinumCustomer());

ocp-2.png

SOLID Prinsiples – S-Single Responsibility

S.O.L.I.D prinsip adalah kumpulan dari best-practices untuk Object Oriented design. Semua design pattern pada Gang Of Four juga dibangun berdasarkan prinsip ini.

SOLID merupakan  prinsip dasar dalam membangun arsitektur software yang baik. SOLID merupakan singkatan dari 5 prinsip:

  1. S dari SRP (Single Responsibility Principle)
  2. O dari OCP (Open Closed Principle)
  3. L dari LSV (Liskov Substitution Principle)
  4. I dari ISP (Interface segregation principle)
  5. D dari DIP ( Dependency Inversion Principle)

S-Single Responsibility Principle

“A class should have only one reason to change.” – Robert C Martin

Sebuah class seharusnya diubah itu satu alasan aja. Terlalu banyak tanggung jawab yang dibebankan pada sebuah class sehingga membuat satu perubahan kode program bisa jadi menimbulkan banyak efek kepada yang lain. Fokus pada satu tanggung jawab memudahkan maintenance code.

public class Customer
{
public void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@&quot;c:\Error.txt&quot;, ex.ToString());
}
}
public void Delete()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@&quot;c:\Error.txt&quot;, ex.ToString());
}
}
}

Pada class Customer method Add dan Deleta harusnya hanya menghandle satu high level abstraksi. Di dalam class tersebut juga terdapat duplikasi kode program penulisan log.
Penulisan log harusnya ditangani oleh class lain dalam hal ini class FileLogger. Sehingga jika ada perubahan pada proses penulisan log seperti path file yang diubah atau bahkan penulisan log dipindahkan ke database, kita bisa fokus hanya pada class FileLogger saja. Ingat! SRP bukan berarti satu class hanya boleh memiliki satu method saja, tapi hanya butuh satu alasan untuk memodifikasi class tersebut.

public class Customer
    {

        FileLogger _logger = new FileLogger();
        public void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                _logger.WriteLog(ex.Message);
            }
        }
        public void Delete()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                _logger.WriteLog(ex.Message);
            }
        }
    }

    public class FileLogger
    {
        public void WriteLog(string message)
        {
            System.IO.File.WriteAllText(@&quot;c:\Error.txt&quot;, message);

        }

Apa manfaat dari SRP?
1. Reuse, lebih mudah untuk mendaya-guna kode program
2. Clarity, kode yang kita menjadi sangat jelas karena ditulis hanya focus pada satu hal
3. Naming, tentu dengan hanya berfokus pada satu hal, penamaan class jadi lebih gampang.
4. Readability, karena berkaitan dengan Clarity dan Naming, maka menjadi sangat membantu dalam pembacaan kode program
5. Reduced Coupling, menurunkan tingkat kebergantungan antar class. Semakin tinggi kebergantungan, semakin susah untuk dimodifikasi