Hizmet Katmanı ile Doğrulama (C#)

tarafından Stephen Walther

Doğrulama mantığınızı denetleyici eylemlerinizin dışına ve ayrı bir hizmet katmanına nasıl taşıyacağınızı öğrenin. Bu öğreticide Stephen Walther, hizmet katmanınızı denetleyici katmanınızdan yalıtarak endişeleri net bir şekilde ayırmayı nasıl sürdürebileceğinizi açıklamaktadır.

Bu öğreticinin amacı, bir ASP.NET MVC uygulamasında doğrulama gerçekleştirmenin bir yöntemini açıklamaktır. Bu öğreticide, doğrulama mantığınızı denetleyicilerinizden dışarı ve ayrı bir hizmet katmanına taşımayı öğreneceksiniz.

Endişeleri Ayırma

bir ASP.NET MVC uygulaması oluşturduğunuzda, veritabanı mantığınızı denetleyici eylemlerinizin içine yerleştirmemelisiniz. Veritabanınızı ve denetleyici mantığınızı karıştırmak, uygulamanızın zaman içinde bakımının daha zor olmasını sağlar. Öneri, tüm veritabanı mantığınızı ayrı bir depo katmanına yerleştirmenizdir.

Örneğin, Liste 1, ProductRepository adlı basit bir depo içerir. Ürün deposu, uygulamanın tüm veri erişim kodunu içerir. Liste ayrıca ürün deposunun uyguladığı IProductRepository arabirimini de içerir.

Listeleme 1 -- Models\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();
    }

}

Listeleme 2'deki denetleyici hem Index() hem de Create() eylemlerinde depo katmanını kullanır. Bu denetleyicinin herhangi bir veritabanı mantığı içermediğini fark edin. Depo katmanı oluşturmak, endişeleri temiz bir şekilde ayırmanızı sağlar. Denetleyiciler uygulama akışı denetim mantığından, depo ise veri erişim mantığından sorumludur.

Liste 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");
        }

    }
}

Hizmet Katmanı Oluşturma

Bu nedenle, uygulama akışı denetim mantığı bir denetleyiciye, veri erişim mantığı da bir depoya aittir. Bu durumda doğrulama mantığınızı nereye koyarsınız? Bir seçenek, doğrulama mantığınızı bir hizmet katmanına yerleştirmektir.

Hizmet katmanı, ASP.NET MVC uygulamasında denetleyici ile depo katmanı arasındaki iletişime aracılık eden ek bir katmandır. Hizmet katmanı iş mantığını içerir. Özellikle doğrulama mantığı içerir.

Örneğin, Listeleme 3'teki ürün hizmeti katmanının CreateProduct() yöntemi vardır. CreateProduct() yöntemi, ürünü ürün deposuna geçirmeden önce yeni bir ürünü doğrulamak için ValidateProduct() yöntemini çağırır.

Listeleme 3 - Models\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();
    }
}

Ürün denetleyicisi Liste 4'te depo katmanı yerine hizmet katmanını kullanacak şekilde güncelleştirildi. Denetleyici katmanı, hizmet katmanıyla konuşur. Hizmet katmanı, depo katmanıyla konuşur. Her katmanın ayrı bir sorumluluğu vardır.

Listeleme 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");
        }

    }
}

Ürün hizmetinin ürün denetleyicisi oluşturucusunda oluşturulduğuna dikkat edin. Ürün hizmeti oluşturulduğunda, model durumu sözlüğü hizmete geçirilir. Ürün hizmeti, doğrulama hata iletilerini denetleyiciye geri geçirmek için model durumunu kullanır.

Hizmet Katmanını Ayırma

Denetleyici ve hizmet katmanlarını bir açıdan yalıtamadık. Denetleyici ve hizmet katmanları model durumu üzerinden iletişim kurar. Başka bir deyişle hizmet katmanı, ASP.NET MVC çerçevesinin belirli bir özelliğine bağımlıdır.

Hizmet katmanını denetleyici katmanımızdan mümkün olduğunca yalıtmak istiyoruz. Teoride, hizmet katmanını yalnızca ASP.NET bir MVC uygulamasıyla değil, her tür uygulamayla kullanabilmeliyiz. Örneğin, gelecekte uygulamamız için bir WPF ön ucu oluşturmak isteyebiliriz. hizmet katmanımızdan ASP.NET MVC model durumu bağımlılığını kaldırmanın bir yolunu bulmalıyız.

Liste 5'te, hizmet katmanı artık model durumunu kullanamayacak şekilde güncelleştirilmiştir. Bunun yerine, IValidationDictionary arabirimini uygulayan herhangi bir sınıfı kullanır.

Listeleme 5 - Models\ProductService.cs (ayrılmış)

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();
    }
}

IValidationDictionary arabirimi Listeleme 6'da tanımlanır. Bu basit arabirimin tek bir yöntemi ve tek bir özelliği vardır.

Listeleme 6 - Models\IValidationDictionary.cs

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

ModelStateWrapper sınıfı olarak adlandırılan Listing 7'deki sınıf, IValidationDictionary arabirimini uygular. Oluşturucuya bir model durumu sözlüğü geçirerek ModelStateWrapper sınıfının örneğini oluşturabilirsiniz.

Listeleme 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
    }
}

Son olarak, Liste 8'deki güncelleştirilmiş denetleyici, oluşturucusunda hizmet katmanını oluştururken ModelStateWrapper'ı kullanır.

Listeleme 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");
        }

    }
}

IValidationDictionary arabirimini ve ModelStateWrapper sınıfını kullanmak hizmet katmanımızı denetleyici katmanımızdan tamamen yalıtmamızı sağlar. Hizmet katmanı artık model durumuna bağımlı değildir. IValidationDictionary arabirimini uygulayan herhangi bir sınıfı hizmet katmanına geçirebilirsiniz. Örneğin, bir WPF uygulaması basit bir koleksiyon sınıfıyla IValidationDictionary arabirimini uygulayabilir.

Özet

Bu öğreticinin amacı, bir ASP.NET MVC uygulamasında doğrulama gerçekleştirmeye yönelik bir yaklaşımı tartışmaktı. Bu öğreticide, tüm doğrulama mantığınızı denetleyicilerinizin dışına ve ayrı bir hizmet katmanına nasıl taşıyabileceğinizi öğrendiniz. ModelStateWrapper sınıfı oluşturarak hizmet katmanınızı denetleyici katmanınızdan nasıl yalıtabileceğinizi de öğrendiniz.