Bagikan melalui


Memvalidasi dengan Lapisan Layanan (C#)

oleh Stephen Walther

Pelajari cara memindahkan logika validasi Anda dari tindakan pengontrol dan ke lapisan layanan terpisah. Dalam tutorial ini, Stephen Walther menjelaskan bagaimana Anda dapat mempertahankan pemisahan kekhawatiran yang tajam dengan mengisolasi lapisan layanan Anda dari lapisan pengontrol Anda.

Tujuan dari tutorial ini adalah untuk menjelaskan salah satu metode melakukan validasi dalam aplikasi MVC ASP.NET. Dalam tutorial ini, Anda mempelajari cara memindahkan logika validasi anda dari pengontrol dan ke lapisan layanan terpisah.

Memisahkan Kekhawatiran

Saat Anda membuat aplikasi MVC ASP.NET, Anda tidak boleh menempatkan logika database Anda di dalam tindakan pengontrol Anda. Mencampur database dan logika pengontrol Anda membuat aplikasi Anda lebih sulit dipertahankan dari waktu ke waktu. Rekomendasinya adalah Anda menempatkan semua logika database Anda di lapisan repositori terpisah.

Misalnya, Listing 1 berisi repositori sederhana bernama ProductRepository. Repositori produk berisi semua kode akses data untuk aplikasi. Daftar ini juga mencakup antarmuka IProductRepository yang diterapkan repositori produk.

Daftar 1 -- Model\ProductRepository.cs

using System.Collections.Generic;
using System.Linq;

namespace MvcApplication1.Models
{
    public class ProductRepository : MvcApplication1.Models.IProductRepository
    {
        private ProductDBEntities _entities = new ProductDBEntities();

        public IEnumerable<Product> ListProducts()
        {
            return _entities.ProductSet.ToList();
        }

        public bool CreateProduct(Product productToCreate)
        {
            try
            {
                _entities.AddToProductSet(productToCreate);
                _entities.SaveChanges();
                return true;
            }
            catch
            {
                return false;
            }
        }

    }

    public interface IProductRepository
    {
        bool CreateProduct(Product productToCreate);
        IEnumerable<Product> ListProducts();
    }

}

Pengontrol di Listing 2 menggunakan lapisan repositori dalam tindakan Index() dan Create(). Perhatikan bahwa pengontrol ini tidak berisi logika database apa pun. Membuat lapisan repositori memungkinkan Anda mempertahankan pemisahan kekhawatiran yang bersih. Pengontrol bertanggung jawab atas logika kontrol alur aplikasi dan repositori bertanggung jawab atas logika akses data.

Daftar 2 - Controllers\ProductController.cs

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        private IProductRepository _repository;

        public ProductController():
            this(new ProductRepository()) {}

        public ProductController(IProductRepository repository)
        {
            _repository = repository;
        }

        public ActionResult Index()
        {
            return View(_repository.ListProducts());
        }

        //
        // GET: /Product/Create

        public ActionResult Create()
        {
            return View();
        } 

        //
        // POST: /Product/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude="Id")] Product productToCreate)
        {
            _repository.CreateProduct(productToCreate);
            return RedirectToAction("Index");
        }

    }
}

Membuat Lapisan Layanan

Jadi, logika kontrol alur aplikasi termasuk dalam pengontrol dan logika akses data berada di repositori. Dalam hal ini, di mana Anda menempatkan logika validasi Anda? Salah satu opsinya adalah menempatkan logika validasi Anda di lapisan layanan.

Lapisan layanan adalah lapisan tambahan dalam aplikasi MVC ASP.NET yang menengahi komunikasi antara lapisan pengontrol dan repositori. Lapisan layanan berisi logika bisnis. Secara khusus, berisi logika validasi.

Misalnya, lapisan layanan produk di Listing 3 memiliki metode CreateProduct(). Metode CreateProduct() memanggil metode ValidateProduct() untuk memvalidasi produk baru sebelum meneruskan produk ke repositori produk.

Daftar 3 - Model\ProductService.cs

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication1.Models
{
    public class ProductService : IProductService
    {

        private ModelStateDictionary _modelState;
        private IProductRepository _repository;

        public ProductService(ModelStateDictionary modelState, IProductRepository repository)
        {
            _modelState = modelState;
            _repository = repository;
        }

        protected bool ValidateProduct(Product productToValidate)
        {
            if (productToValidate.Name.Trim().Length == 0)
                _modelState.AddModelError("Name", "Name is required.");
            if (productToValidate.Description.Trim().Length == 0)
                _modelState.AddModelError("Description", "Description is required.");
            if (productToValidate.UnitsInStock < 0)
                _modelState.AddModelError("UnitsInStock", "Units in stock cannot be less than zero.");
            return _modelState.IsValid;
        }

        public IEnumerable<Product> ListProducts()
        {
            return _repository.ListProducts();
        }

        public bool CreateProduct(Product productToCreate)
        {
            // Validation logic
            if (!ValidateProduct(productToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateProduct(productToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }

    }

    public interface IProductService
    {
        bool CreateProduct(Product productToCreate);
        IEnumerable<Product> ListProducts();
    }
}

Pengontrol Produk telah diperbarui di Daftar 4 untuk menggunakan lapisan layanan alih-alih lapisan repositori. Lapisan pengontrol berbicara dengan lapisan layanan. Lapisan layanan berbicara dengan lapisan repositori. Setiap lapisan memiliki tanggung jawab terpisah.

Daftar 4 - Controllers\ProductController.cs

Listing 4 – Controllers\ProductController.cs
using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        private IProductService _service;

        public ProductController() 
        {
            _service = new ProductService(this.ModelState, new ProductRepository());
        }

        public ProductController(IProductService service)
        {
            _service = service;
        }

        public ActionResult Index()
        {
            return View(_service.ListProducts());
        }

        //
        // GET: /Product/Create

        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Product/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
        {
            if (!_service.CreateProduct(productToCreate))
                return View();
            return RedirectToAction("Index");
        }

    }
}

Perhatikan bahwa layanan produk dibuat di konstruktor pengontrol produk. Ketika layanan produk dibuat, kamus status model diteruskan ke layanan. Layanan produk menggunakan status model untuk meneruskan pesan kesalahan validasi kembali ke pengontrol.

Memisahkan Lapisan Layanan

Kami telah gagal mengisolasi pengontrol dan lapisan layanan dalam satu hal. Pengontrol dan lapisan layanan berkomunikasi melalui status model. Dengan kata lain, lapisan layanan memiliki dependensi pada fitur tertentu dari kerangka kerja MVC ASP.NET.

Kami ingin mengisolasi lapisan layanan dari lapisan pengontrol kami sebanyak mungkin. Secara teori, kita harus dapat menggunakan lapisan layanan dengan semua jenis aplikasi dan tidak hanya aplikasi MVC ASP.NET. Misalnya, di masa depan, kita mungkin ingin membangun front-end WPF untuk aplikasi kita. Kita harus menemukan cara untuk menghapus dependensi pada status model MVC ASP.NET dari lapisan layanan kita.

Di Daftar 5, lapisan layanan telah diperbarui sehingga tidak lagi menggunakan status model. Sebaliknya, ia menggunakan kelas apa pun yang mengimplementasikan antarmuka IValidationDictionary.

Daftar 5 - Models\ProductService.cs (didecoupled)

using System.Collections.Generic;

namespace MvcApplication1.Models
{
    public class ProductService : IProductService
    {

        private IValidationDictionary _validatonDictionary;
        private IProductRepository _repository;

        public ProductService(IValidationDictionary validationDictionary, IProductRepository repository)
        {
            _validatonDictionary = validationDictionary;
            _repository = repository;
        }

        protected bool ValidateProduct(Product productToValidate)
        {
            if (productToValidate.Name.Trim().Length == 0)
                _validatonDictionary.AddError("Name", "Name is required.");
            if (productToValidate.Description.Trim().Length == 0)
                _validatonDictionary.AddError("Description", "Description is required.");
            if (productToValidate.UnitsInStock < 0)
                _validatonDictionary.AddError("UnitsInStock", "Units in stock cannot be less than zero.");
            return _validatonDictionary.IsValid;
        }

        public IEnumerable<Product> ListProducts()
        {
            return _repository.ListProducts();
        }

        public bool CreateProduct(Product productToCreate)
        {
            // Validation logic
            if (!ValidateProduct(productToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateProduct(productToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }

    }

    public interface IProductService
    {
        bool CreateProduct(Product productToCreate);
        IEnumerable<Product> ListProducts();
    }
}

Antarmuka IValidationDictionary ditentukan dalam Daftar 6. Antarmuka sederhana ini memiliki satu metode dan satu properti.

Daftar 6 - Model\IValidationDictionary.cs

namespace MvcApplication1.Models
{
    public interface IValidationDictionary
    {
        void AddError(string key, string errorMessage);
        bool IsValid { get; }
    }
}

Kelas di Listing 7, bernama kelas ModelStateWrapper, mengimplementasikan antarmuka IValidationDictionary. Anda dapat membuat instans kelas ModelStateWrapper dengan meneruskan kamus status model ke konstruktor.

Daftar 7 - Models\ModelStateWrapper.cs

using System.Web.Mvc;

namespace MvcApplication1.Models
{
    public class ModelStateWrapper : IValidationDictionary
    {

        private ModelStateDictionary _modelState;

        public ModelStateWrapper(ModelStateDictionary modelState)
        {
            _modelState = modelState;
        }

        #region IValidationDictionary Members

        public void AddError(string key, string errorMessage)
        {
            _modelState.AddModelError(key, errorMessage);
        }

        public bool IsValid
        {
            get { return _modelState.IsValid; }
        }

        #endregion
    }
}

Terakhir, pengontrol yang diperbarui di Listing 8 menggunakan ModelStateWrapper saat membuat lapisan layanan di konstruktornya.

Daftar 8 - Controllers\ProductController.cs

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        private IProductService _service;

        public ProductController() 
        {
            _service = new ProductService(new ModelStateWrapper(this.ModelState), new ProductRepository());
        }

        public ProductController(IProductService service)
        {
            _service = service;
        }

        public ActionResult Index()
        {
            return View(_service.ListProducts());
        }

        //
        // GET: /Product/Create

        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Product/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
        {
            if (!_service.CreateProduct(productToCreate))
                return View();
            return RedirectToAction("Index");
        }

    }
}

Menggunakan antarmuka IValidationDictionary dan kelas ModelStateWrapper memungkinkan kami untuk sepenuhnya mengisolasi lapisan layanan kami dari lapisan pengontrol kami. Lapisan layanan tidak lagi bergantung pada status model. Anda dapat meneruskan kelas apa pun yang mengimplementasikan antarmuka IValidationDictionary ke lapisan layanan. Misalnya, aplikasi WPF mungkin mengimplementasikan antarmuka IValidationDictionary dengan kelas koleksi sederhana.

Ringkasan

Tujuan dari tutorial ini adalah untuk membahas salah satu pendekatan untuk melakukan validasi dalam aplikasi MVC ASP.NET. Dalam tutorial ini, Anda belajar cara memindahkan semua logika validasi Anda dari pengontrol Anda dan ke lapisan layanan terpisah. Anda juga mempelajari cara mengisolasi lapisan layanan dari lapisan pengontrol dengan membuat kelas ModelStateWrapper.