Intro:The ServiceLayer wraps the boundaries for your entire API. All call should enter through your ServiceLayer and your service layers main responsibilities are to:
You can find more information about the ServiceLayer's Pattern found here:ServiceLayerhttp://patternshare.org/default.aspx/Home.MF.ServiceLayerGetting Started:You can use the Service to manage all of your Create, Read, Update, Delete, DeepLoad/Saveoperations without having to get into creating a pipeline for those requests. We felt we did not want to burden the developer in jumping through many hoops to work with his data repository.Here's a simple sample that get's all of my accounts from a the Accounts Table.AccountService accountsService = new AccountsService();TList<Accounts> accountList = accountsService.GetAll();Stepping through the logic, the AccountsService will Authorize the request by the current securitycontext if authorization is enabled. It will check to see the current ConnectionScope, to figure out if a Transaction is opened and which dataProvider to use. There is a simple way to configure the provider you would like to use, be it the sqlClientProvider vs webServiceProvider, multiple configured databases for the same provider type, or dynamic connection strings More on this later. It will then get the current settings from the ConnectionScope to use to make the correct call for the DataRepository.The call will be sent into the DataRepository and returned back to the ServiceLayer. If there is an exception, HandleException is called and checks to see if you've implemented a custom exception handling scheme, i.e. log & rethrow, stop, have a custom handler. All of which is done via Configuration in the app/web.config from the ExceptionHandling Capabilities from Enterprise Library. A full sample entlib.config is generated for you when generating the UnitTests for your application. You can get a feel for some of the types of options youhave.Here is an example of the ExceptionHadling configuration. <exceptionHandling> <exceptionPolicies> <add name="NoneExceptionPolicy"> <exceptionTypes> <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="None" name="Exception"> <exceptionHandlers> <add logCategory="Exceptions" eventId="100" severity="Error" title="netTiers.Petshop Exception Handling" formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.XmlExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" priority="0" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" name="Logging Handler" /> </exceptionHandlers> </add> </exceptionTypes> </add> </exceptionPolicies> </exceptionHandling>It's easiest to just point the Enterprise Library Configurator tool to your app/web.config and configure your own custom settings. That way you don't have to mess with the xml yourself.If there are no exceptions, or you want to continue processing after an exception, the data, your entity or TList<> is returned.Your API Consists of:AccountService accountsService = new AccountsService();//GetAll() TList<Accounts> accountList = accountsService.GetAll(); //GetPagedl() TList<Accounts> accountList = accountsService.GetPaged("IsActive = 1 AND AccountName LIKE 'smi%'"); //GetByFk() TList<Accounts> accountList = accountsService.GetByCustomerId(25); //GetIX() TList<Accounts> accountList = accountsService.GetByAccountCreatedDate(new DateTime("1/1/2006"));//Get() entity.Entitykey; TList<Accounts> accountList = accountsService.Get(entity.EntityKey); //Insert() Account accountEntity = new Account();accountEntity.AccountName = "MyAccountName";accountEntity.CreatedDate = DateTime.Now;TList<Accounts> accountList = accountsService.Insert(accountEntity);//Delete() TList<Accounts> accountList = accountsService.Delete(accountEntity); //Delete() TList<Accounts> accountList = accountsService.Delete(23); //Update() accountEntity.AccountName = "MyAccountName 2"; TList<Accounts> accountList = accountsService.Update(accountEntity); //GetByManyToManyl() TList<Customers> accountList = accountsService.GetCustomers_From_AccountsReceivable(); //GetCustomProcedureName() TList<Accounts> accountList = accountsService.GetByAccountMaturationDate(); //DeepLoadByIdl() using PK Account account = accountsService.DeepLoadByAccountId(id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));//DeepLoadByIdl() using FK TList<Account> account = accountsService.DeepLoadByCustomerId(id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));//already instatiated objects //DeepLoad TList<Account> account = accountsService.DeepLoad(myAccountEntity, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));//DeepSave TList<Account> account = accountsService.DeepSave(myAccountEntity, false, DeepSaveType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));Workflow Pipeline:A Service instance serves as a workspace for implementing your types behavior. If you were creating a complex Order system, you would have a CreateOrder behavior that would have to do many things. In this example, let's say you had to *check the inventory, *validate employee, *check employee pricing *discover and calculate vendor shipping from 3rd party for a given set of weight of all the products.Processors:A Processor is considered a logical unit of work and you are responsible for creating the processors or behavior for your application.Now, it would be overkill if you had to create processors for CRUD as well, so those methods are exposed as part of the API for each service.More information on workflow management:http://patternshare.org/default.aspx/Home.HW.ProcessManagerInformation about the Processor Command:http://www.dofactory.com/Patterns/PatternCommand.aspxMost Flexible, when using a Strategy for each processor.http://www.dofactory.com/Patterns/PatternStrategy.aspxThe logical flow is like this: Every Service, is an instance based workspace for managing your application.ex: OrdersService ordersService = new OrdersService();In that workspace there is a Pipeline framework for you to add multiple processors to conduct your Units of Work.Every processor is responsible for a single logical business action item(Unit Of Work).One processor might be to make several checks to ensure the order is valid.EX: ValidateInventoryProcessor(Order o)1. Find if the products in the order are in inventory.2. Find out which warehouses these items belong to.3. Calculate a route for supply chain to get these items.4. Fire off notifications to warehousing, etc.You would create other processors to ValidateEmployee, Check Employee Processing, Get Vendor Shipping Information, Billing, Notifications.ex ordersService.ProcessorList.Add(new InventoryProcessor(o));ordersService.ProcessorList.Add(new VerifyEmployeeProcessor(o.EmployeeIdSource));ordersService.ProcessorList.Add(new EmployeeOrderProcessor(o));ordersService.ProcessorList.Add(new BillingProcessor(o));ordersService.ProcessorList.Add(new ShippingProcessor(o));ordersService.ProcessorList.Add(new OrderNotificationProcessor(o));Every processor will return a class that implements IProcessorResult, which there is a GenericProcessorResult created one for you. Basically, this class is responsible for tracking processor state and aggregates all the BrokenRules or that the process has accumulated. By tracking state here, you know where exactly the pipeline failed. If you do not track state inside the process, the Service Pipeline will attempt to do so for you. //Execute Processor List ServiceResult result = ordersService.Execute();Events will fire before and after any processor execution. Every processor result will be aggregated into the ServiceResult class. This will let youall errors that occured, as well as any Exceptions that fired. If an unhandled exception has occured you can stop execution if you set it to AbortOnFailure.if (result.HasErrors){ ShowErrors(result.Error); ShowExceptions(result.Exceptions);}If there are errors, you can tap into the ServiceResult's Error property which is a newline delimeted list of all the errors from entity validation. I've attached the class diagram in hopes that this would make more sense. I'm hoping to finish up a sample app for the community very soon.For basic CRUD, you could still access it normally, but in most of the complex logic, this would lie inside of a processor.ordersService.GetAll();ordersService.Save(o);
AccountService accountsService = new AccountsService();TList<Accounts> accountList = accountsService.GetAll();
<exceptionHandling> <exceptionPolicies> <add name="NoneExceptionPolicy"> <exceptionTypes> <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="None" name="Exception"> <exceptionHandlers> <add logCategory="Exceptions" eventId="100" severity="Error" title="netTiers.Petshop Exception Handling" formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.XmlExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" priority="0" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" name="Logging Handler" /> </exceptionHandlers> </add> </exceptionTypes> </add> </exceptionPolicies> </exceptionHandling>
AccountService accountsService = new AccountsService();//GetAll() TList<Accounts> accountList = accountsService.GetAll(); //GetPagedl() TList<Accounts> accountList = accountsService.GetPaged("IsActive = 1 AND AccountName LIKE 'smi%'"); //GetByFk() TList<Accounts> accountList = accountsService.GetByCustomerId(25); //GetIX() TList<Accounts> accountList = accountsService.GetByAccountCreatedDate(new DateTime("1/1/2006"));//Get() entity.Entitykey; TList<Accounts> accountList = accountsService.Get(entity.EntityKey); //Insert() Account accountEntity = new Account();accountEntity.AccountName = "MyAccountName";accountEntity.CreatedDate = DateTime.Now;TList<Accounts> accountList = accountsService.Insert(accountEntity);//Delete() TList<Accounts> accountList = accountsService.Delete(accountEntity); //Delete() TList<Accounts> accountList = accountsService.Delete(23); //Update() accountEntity.AccountName = "MyAccountName 2"; TList<Accounts> accountList = accountsService.Update(accountEntity); //GetByManyToManyl() TList<Customers> accountList = accountsService.GetCustomers_From_AccountsReceivable(); //GetCustomProcedureName() TList<Accounts> accountList = accountsService.GetByAccountMaturationDate(); //DeepLoadByIdl() using PK Account account = accountsService.DeepLoadByAccountId(id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));//DeepLoadByIdl() using FK TList<Account> account = accountsService.DeepLoadByCustomerId(id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));//already instatiated objects //DeepLoad TList<Account> account = accountsService.DeepLoad(myAccountEntity, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));//DeepSave TList<Account> account = accountsService.DeepSave(myAccountEntity, false, DeepSaveType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));
public class InventoryProcessor : ProcessorBase { private Entities.Orders order; private GenericProcessorResult genericProcessorResult; /// <summary> /// Inventory Processor /// </summary> /// <param name="order"></param> public InventoryProcessor(Entities.Orders order) { this.order = order; } /// <summary> /// Process the IProcessResult /// </summary> /// <returns></returns> public override IProcessorResult Process() { try { order.AddInventoryRules(); //check stock order.Validate(); if (!order.IsValid) ProcessResult.AddBrokenRulesList(typeof (Entities.Orders), order.BrokenRulesList); } catch(Exception exc) { if (DomainUtil.HandleException(exc, "NoneExceptionPolicy")) throw; } return ProcessResult; } /// <summary> /// ProcessResult /// </summary> public override IProcessorResult ProcessResult { get { if (genericProcessorResult == null) { genericProcessorResult = new GenericProcessorResult(); } return genericProcessorResult; } }
/// <summary> /// Add Extra Custom Validation Rules /// </summary> public void AddInventoryRules() { ValidationRules.AddRule(InventoryRuleCheck, new ValidationRuleArgs("UnitsInStock")); } /// <summary> /// Check Inventory /// </summary> /// <param name="target"></param> /// <param name="e"></param> /// <returns></returns> public bool InventoryRuleCheck(object target, ValidationRuleArgs e) { foreach(OrderDetails detail in OrderDetailsCollection) { if (detail.ProductIDSource == null) continue; if (detail.ProductIDSource.UnitsInStock < detail.Quantity) { e.Description = string.Format("{0} - is out of stock, {1} units are on order.", detail.ProductIDSource.ProductName, detail.ProductIDSource.UnitsOnOrder); return false; } } return true; }
using System;using System.Collections.Generic;using System.Text;using Northwind.Entities;using Northwind.Entities.Validation;using Northwind.Services;using Northwind.Services.Processors.Orders;namespace NorthwindWebConsole{ class Program { static void Main(string[] args) { //create a fake order OrdersService service = new OrdersService(); Orders o = new Orders(); ProductsService products = new ProductsService(); TList<Products> plist = products.GetAll(); for(int j=0;j<10;j++) { OrderDetails detail = new OrderDetails(); detail.ProductID = plist[j].ProductID; detail.ProductIDSource = plist[j]; detail.Quantity = Convert.ToInt16(j * j); o.OrderDetailsCollection.Add(detail); } service.ProcessorList.Add(new InventoryProcessor(o)); ServiceResult result = service.Execute(); if (result.HasErrors) Console.WriteLine(result.Error); Console.ReadLine(); } }}
Robert Hinojosa ------------------------------------- Member of the Codesmith Tools, .netTiers, teams http://www.nettiers.com-------------------------------------
service.ProcessorList.Add(new InventoryProcessor(o)); ServiceResult result = service.Execute(); if (result.HasErrors) Console.WriteLine(result.Error);
Yes, very helpful article. Was there an answer ever created to the question above in regard to where the processors are added to the processor list? It seems you can add them in the web form; however they are business logic and should be stored in the service layer so that any call by a web form to a service method would use that logic. Thanks in advance for your answer.