Hizmet Katmanı ile Doğrulama (C#)Validating with a Service Layer (C#)

ile Stephen Waltherby Stephen Walther

Doğrulama mantığınızı denetleyici eylemlerinizin dışında ve ayrı bir hizmet katmanında nasıl taşıyacağınızı öğrenin.Learn how to move your validation logic out of your controller actions and into a separate service layer. Bu öğreticide, Stephen Walther, hizmet katmanınızı denetleyici katmanınızdan yalıtarak sorunları nasıl keskin bir şekilde koruyabileceğinizi açıklar.In this tutorial, Stephen Walther explains how you can maintain a sharp separation of concerns by isolating your service layer from your controller layer.

Bu öğreticinin amacı, bir ASP.NET MVC uygulamasında doğrulama gerçekleştirme yöntemini açıklıyor.The goal of this tutorial is to describe one method of performing validation in an ASP.NET MVC application. Bu öğreticide, doğrulama mantığınızı denetleyicilerden ve ayrı bir hizmet katmanına taşımayı öğreneceksiniz.In this tutorial, you learn how to move your validation logic out of your controllers and into a separate service layer.

Kaygıları ayırmaSeparating Concerns

Bir ASP.NET MVC uygulaması oluşturduğunuzda, veritabanı mantığınızı denetleyici eylemlerinizin içine yerleştirmemelisiniz.When you build an ASP.NET MVC application, you should not place your database logic inside your controller actions. Veritabanınızı ve denetleyici mantığınızı karıştırmak, uygulamanızın zaman içinde bakımını daha zor hale getirir.Mixing your database and controller logic makes your application more difficult to maintain over time. Öneri, tüm veritabanı mantığınızı ayrı bir depo katmanında yerleştirmesidir.The recommendation is that you place all of your database logic in a separate repository layer.

Örneğin, Listeleme 1, ProductRepository adlı basit bir depo içerir.For example, Listing 1 contains a simple repository named the ProductRepository. Ürün deposu, uygulamanın tüm veri erişim kodunu içerir.The product repository contains all of the data access code for the application. Liste ayrıca, ürün deposunun uyguladığı ıproductrepository arabirimini de içerir.The listing also includes the IProductRepository interface that the product repository implements.

Listeleme 1--Models\productdepotory.csListing 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, dizin () ve Create () eylemleri içinde depo katmanını kullanır.The controller in Listing 2 uses the repository layer in both its Index() and Create() actions. Bu denetleyicinin herhangi bir veritabanı mantığı içermediğini unutmayın.Notice that this controller does not contain any database logic. Bir depo katmanı oluşturmak, kaygılara yönelik temiz ayrımı korumanıza olanak sağlar.Creating a repository layer enables you to maintain a clean separation of concerns. Denetleyiciler, uygulama akış denetim mantığından sorumludur ve veri erişim mantığıyla depo sorumludur.Controllers are responsible for application flow control logic and the repository is responsible for data access logic.

Listeleme 2-Controllers\ProductController.csListing 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şturmaCreating a Service Layer

Bu nedenle, uygulama akış denetim mantığı bir denetleyiciye aittir ve veri erişim mantığı bir depoya aittir.So, application flow control logic belongs in a controller and data access logic belongs in a repository. Bu durumda, doğrulama mantığınızı nereye yerleştirmeniz gerekir?In that case, where do you put your validation logic? Bir seçenek, doğrulama mantığınızı bir hizmet katmanınayerleştirmadır.One option is to place your validation logic in a service layer.

Bir hizmet katmanı, bir ASP.NET MVC uygulamasındaki bir denetleyici ve depo katmanı arasındaki iletişimi destekleyen ek bir katmandır.A service layer is an additional layer in an ASP.NET MVC application that mediates communication between a controller and repository layer. Hizmet katmanı iş mantığını içerir.The service layer contains business logic. Özellikle, doğrulama mantığını içerir.In particular, it contains validation logic.

Örneğin, kod 3 ' teki ürün hizmeti katmanının bir CreateProduct () yöntemi vardır.For example, the product service layer in Listing 3 has a CreateProduct() method. 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.The CreateProduct() method calls the ValidateProduct() method to validate a new product before passing the product to the product repository.

Listeleme 3-Models\ProductService.csListing 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, kod 4 ' te depo katmanı yerine hizmet katmanını kullanmak üzere güncelleştirilmiştir.The Product controller has been updated in Listing 4 to use the service layer instead of the repository layer. Denetleyici katmanı hizmet katmanıyla oynanır.The controller layer talks to the service layer. Hizmet katmanı, depo katmanıyla konuşur.The service layer talks to the repository layer. Her katmanın ayrı bir sorumluluğu vardır.Each layer has a separate responsibility.

Listeleme 4-Controllers\ProductController.csListing 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 hizmeti 'nin ürün denetleyicisi oluşturucusunda oluşturulmuş olduğuna dikkat edin.Notice that the product service is created in the product controller constructor. Ürün hizmeti oluşturulduğunda, model durum sözlüğü hizmete geçirilir.When the product service is created, the model state dictionary is passed to the service. Ürün hizmeti, doğrulama hata iletilerini denetleyiciye geri geçirmek için model durumunu kullanır.The product service uses model state to pass validation error messages back to the controller.

Hizmet katmanını bağlamaDecoupling the Service Layer

Denetleyiciyi ve hizmet katmanlarını tek bir şekilde yalıtamadı.We have failed to isolate the controller and service layers in one respect. Denetleyici ve hizmet katmanları, model durumu üzerinden iletişim kurar.The controller and service layers communicate through model state. Diğer bir deyişle, hizmet katmanının, ASP.NET MVC çerçevesinin belirli bir özelliğine bağımlılığı vardır.In other words, the service layer has a dependency on a particular feature of the ASP.NET MVC framework.

Hizmet katmanını denetleyici katmanımız kadar mümkün olduğunca yalıtmak istiyoruz.We want to isolate the service layer from our controller layer as much as possible. Teorik olarak, hizmet katmanını yalnızca bir ASP.NET MVC uygulaması değil, herhangi bir uygulama türüyle kullanabilmelidir.In theory, we should be able to use the service layer with any type of application and not only an ASP.NET MVC application. Örneğin, gelecekte uygulamamız için bir WPF ön ucu oluşturmak isteyebilirsiniz.For example, in the future, we might want to build a WPF front-end for our application. ASP.NET MVC modeli durumundaki bağımlılığı hizmet katmanımızdan kaldırmanın bir yolunu bulduk.We should find a way to remove the dependency on ASP.NET MVC model state from our service layer.

5. listede, hizmet katmanı artık model durumunu kullanmamasını sağlayacak şekilde güncelleştirilmiştir.In Listing 5, the service layer has been updated so that it no longer uses model state. Bunun yerine, ıvalidationdictionary arabirimini uygulayan herhangi bir sınıfı kullanır.Instead, it uses any class that implements the IValidationDictionary interface.

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

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, liste 6 ' da tanımlanmıştır.The IValidationDictionary interface is defined in Listing 6. Bu basit arabirim tek bir yönteme ve tek bir özelliğe sahiptir.This simple interface has a single method and a single property.

Listeleme 6-Models\IValidationDictionary.csListing 6 - Models\IValidationDictionary.cs

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

ModelStateWrapper sınıfı adlı Listeleme 7 ' deki sınıf ıvalidationdictionary arabirimini uygular.The class in Listing 7, named the ModelStateWrapper class, implements the IValidationDictionary interface. Bir model durum sözlüğünü oluşturucuya geçirerek ModelStateWrapper sınıfının örneğini oluşturabilirsiniz.You can instantiate the ModelStateWrapper class by passing a model state dictionary to the constructor.

Listeleme 7-Models\ModelStateWrapper.csListing 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şturucu içinde hizmet katmanını oluştururken ModelStateWrapper kullanır.Finally, the updated controller in Listing 8 uses the ModelStateWrapper when creating the service layer in its constructor.

8-Controllers\ProductController.cs listelemeListing 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.Using the IValidationDictionary interface and the ModelStateWrapper class enables us to completely isolate our service layer from our controller layer. Hizmet katmanı artık model durumuna bağlı değil.The service layer is no longer dependent on model state. Ivalidationdictionary arabirimini uygulayan herhangi bir sınıfı hizmet katmanına geçirebilirsiniz.You can pass any class that implements the IValidationDictionary interface to the service layer. Örneğin, bir WPF uygulaması, bir basit koleksiyon sınıfıyla ıvalidationdictionary arabirimini uygulayabilir.For example, a WPF application might implement the IValidationDictionary interface with a simple collection class.

ÖzetSummary

Bu öğreticinin amacı, bir ASP.NET MVC uygulamasında doğrulama gerçekleştirmeye yönelik bir yaklaşımı tartışmaktır.The goal of this tutorial was to discuss one approach to performing validation in an ASP.NET MVC application. Bu öğreticide, tüm doğrulama mantığınızı denetleyicilerden ve ayrı bir hizmet katmanına nasıl taşıyabileceğinizi öğrendiniz.In this tutorial, you learned how to move all of your validation logic out of your controllers and into a separate service layer. Ayrıca, bir Modelstatesarmalayıcı sınıfı oluşturarak hizmet katmanınızı denetleyici katmanınızdan yalıtmak için de öğrenirsiniz.You also learned how to isolate your service layer from your controller layer by creating a ModelStateWrapper class.