Click here to Skip to main content
15,867,453 members
Articles / Web Development / HTML

Generic Multi Purpose .NET Layered Framework

Rate me:
Please Sign up or sign in to vote.
4.83/5 (27 votes)
17 Nov 2015CPOL18 min read 33.7K   903   51   14
Example of a multi purpose Layered framework for simplifying modern .NET application development

Introduction

I started .NET development in the early days of the .NET framework (going back as far as the 1.0 release in 2002). While in those days concepts like SOC (Separation Of Concerns) , loosely coupling through interfaces, programming to an interface, not an implementation and Inversion Of Control (IOC) through Dependency Injection (DI) weren't hot topics (in those days we were mostly busy with fighting the "DataSet"), I already started thinking about separating the logic into distinct layers (logical). Although these days you will find a lot of information on this concept, finding a well elaborate example isn't simple. So for this reason, I started out to craft one myself. Hope you enjoy it ...

Topology of my vision on a Layered Framework

Image 1

The proposed topology holds five primary logical layers. This are the common layers to retrieve, validate and store date. This is a common approach for most modern frameworks. Next I distinct five so-called "shared-layers" which provide functionality to one ore more primary layers. I will briefly discuss the five primary layers and their dependency on the "shared-layers" in next sections.

Image 2

Project solution structure in Visual Studio

Image 3

The Visual Studio Project Solution structure mostly reflects the proposed topology as described before. Important note is that due to the fact that all layers are coupled through interfaces, all of them can be replaced by a custom implementation if necessary. For the purpose of this tutorial, I created a code-first Entity Framework data model as our DataStorageLayer. In the solution the implementation of this layer can be found in the EntityFrameworkDbLayerCodeFirst project. Next, the DataAccessLayer is implemented in both the EntityFrameworkDataAccessLayer and the EntityFrameworkGenericRepositoryLayer. The DataServiceLayer is implemented by the DataServiceLayer project in the VS solution. This layer is responsible for reading the data from the data access layer, prepare the retrieved data for the presentation layer and post the data back to the data access layer. Next the BusinessLogicLayer is represented by the BuinessLogicLayer in the VS solution. This layer acts as mediator between the data access layer and presentation layer. Finally the PresentationLayer depends on the specific needs of the end users and may have many forms (Web application, Winforms Application, Mobile Application ...). For this article, I created a simple console application, a Windows Forms application (which will consume a special variant of our service layer by referencing a WCF webservice) and an ASP.NET MVC 5 application (represented by the Z* type pojects) which will directly interact with the Business Logic. I will go to each leayer in next sections (and i will also explain in the appropriate section the specific "helper" layers that I didn't specifically mention, e.g. FaultLayer, UtilityLayer, ModelLayer, EnumLayer, DataTransfertObjectLayer and WcfServiceLayer). I will explain the DataStorage, DataService and BusinessLogic layers in high-level and go in more depth of each layer when I explain the last layer, the presentation layer (otherwise I will have to repeat quit the same contents twice). Of course, full details can be found in attached code base.

Data Storage

The data storage is implemented by the EntityFrameworkDBLayerCodeFirst project. As mentiond below, i created a simple domain model which contains the storage of an electricity or gas device. Next i created an entity framework context (DeviceContext) from the domain model. The device context class is used as an ORM (Object Relational Mapper) between the domain classes and the depending SQL-Server database.

The Database Context

namespace EntityFrameworkDBLayerCodeFirst
{
    
    public partial class DeviceContext : DbContext
    {
        public DbSet<Device> Devices { get; set; }
        public DbSet<ElecDevice> ElecDevices { get; set; }
        public DbSet<GasDevice> GasDevices { get; set; }
        public DeviceContext() : base("DeviceContext")
        {
        }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}

The Domain Model

To propagate reusability, the model existis in it's own right and is stored in a separate project called ModelLayer. For the purpose of this article I created a very simple model holding an abstract base class, an abstract class holding the blueprint for a generic device and two concrete device classes representing an electricity or gas device.

namespace ModelLayer
{
    public abstract class BaseModel
    {
        public string EntityName { get; set; }
        public bool IsDirty { get; set; }
    }

    public abstract partial class Device : BaseModel
    {
        public int DeviceId { get; set; }
        public string DeviceName { get; set; }
        public string SerialNumber { get; set; }
        public string ImageUrl { get; set; }
    }
    
    public partial class ElecDevice : Device
    {
        public bool IsSmartElecDevice { get; set; }
    }

    public partial class GasDevice : Device
    {
        public bool IsSmartGasDevice { get; set; }
    }
}

Domain Model Class Diagram and Database Diagram

Image 4

The image above shows the class diagram of our simple domain model.

Image 5

Image 6

The image above shows the database model created from our domain model by entity framework. As you will note, EF created a single table to represent our class inheritence hierarchy in the underlying SQL-Server database. Although we created a base class, abstract device class and 2 concrete device classes (one for elec and one for gas) in our domain layer, we only have a single database table. this is called a Table per Hierarchy (TPH) and is the default of EF. (alternatively you can configure EF to create a Table Per Type (TPT) or Table Per Concrete class (TPC)). A discriminator column stores the distinction beteen the different concrete types. So in our case the Discriminator fied will hold values "ElecDevice" or "GasDevice" depending on the created type (these reflect the names of the concrete model classes).

Data Access

The data access layer is responsible for both retrieval and persistance of the database data as stored by the Data Storage layer. The data access layer is also responsible for converting the domain model objects (which are persistence aware) into so called DTO or simple Data Transfer Objects. It is a general good idea to not expose (in our case) DbSet specific tables to the higher layers, thus to avoid technology specific code to be transferred up to the business logic and presentation layer. DTO objects just contain "data" (properties with getters and setters), no persistence specific code, no validation methods nor business logic, in other words just POCO's (Plain Old Clr Objects) should be transferred. Again, ...think SOC (Separation Of Concerns). The solution classes involved here are EntityFrameworkDataAccessLayer, ModelLayer, DataTransferObjectLayer, DataserviceLayer and EntityFrameworkGenericRepositoryLayer. I will briefly touch each of them in upcoming sections.

Unit Of Work and Repository Pattern

Image 7

Example of Unit Of Work and Repository pattern in context of a ASP.NET MVC Controller approach.

Even though our domain model contains (for demo purpose) only a single table, we should nevertheless implement our data persistence in a transactional way. This means that if we persist multiple inserts, updates, deletes of one or multiple tables this should be correctly commited to the underlying database. To achive this, the presented topology uses the UnitOfWork pattern in combination with a Repository based data retrieval and persistency model. The image belows shows how we can conceptualy achieve this. For implementation details, please check the code base classes: InterfaceLayer.IUnitOfWork, InterfaceLayer.IGenericRepository, EntityFrameworkGenericRepositoryLayer.EntityFrameworkGenericRepository and EntityFrameworkDataAccessLayer.UnitOfWork.

Data Service

Image 8

The DataServiceLayer is the mediator between the DataAccessLayer and the BusinessLogicLayer. The data service layer uses the data access layer to retrieve data and convert it to DTO objects. The DTO objects are then used by the business logic layer as basis for the presentationlayer and data validation. Next after manipulation of the respective DTO data in the presentation layer and validation in the business logic layer the data service is responsible for mapping back the DTO into it's persistence aware presentation as represented by the modellayer. The proposed solution uses AutoMapper to achieve the mapping between the objects. The relations between the different classes is shown below. For implementation details, please check the code base classes: InterfaceLayer.IUnitOfWork, InterfaceLayer.IGenericRepository, EntityFrameworkGenericRepositoryLayer.EntityFrameworkGenericRepository, EntityFrameworkDataAccessLayer.UnitOfWork. and DataServiceLayer.ElecDeviceDataService

Business Logic

Image 9

The BusinessLogicLayer is the mediator between the DataServiceLayer and the PresentationLayer. The business logic layer is responsible for sending the DTO objects to the presentation layer and validate the data contained by the DTO before persistence. For implementation details, please check the code base classes: InterfaceLayer.IValidationDictionary, InterfaceLayer.IElecDeviceDataService, UtilityLayer.ModelStateWrapper, BusinessLogicLayer.BaseBusinessLogic and BusinessLogicLayer.ElecDeviceLogic.

Service Logic

Image 10

If we want to expose our business to the outside world (this means outside the .NET environment) then we should build a "wrapper" round the business logic classes. In the proposed solution, this is achieved by using a WebService (WCF) based approach. For implementation details, please check the code base classes: WcfServiceLayer.IElecDeviceWcfService, WcfServiceLayer.ElecDeviceWcfService, InterfaceLayer.IBusinessLogic, BusinessLogicLayer.ElecDeviceLogic and FaultLayer.GenericFault.

Image 11

Presentation Logic

To quickly show the use of the framework, I've added two small applications. The first is a ASP.NET MVC5 based application which makes direct use of the businesslogic layer through dependency injections (using Ninject). The latter is a Windows Forms application which indirectly references the businesslogic though a webservice reference. I'll briefly explain the two approaches in the remainder sections of this article.

Image 12

ASP.NET MVC 5 Application handling Electricity Device Administration

Image 13

I've created a default ASP.NET MVC 5 application for this purpose. Most important classed are highlighted in the project structure as mentioned above. First we have the NinjectWebCommon.cs class which we will use for inject the dependencies as required by our business logic class. Next we have our MVC controller ElecDevicesController which holds a reference to our businesslogic component. Each CRUD (Create Read Update Delete) action in our controller has it's View reprensentation as defined in the ElecDevices subfolder. Finally our MVC application holds a Web.config file which holds the connectionstring for our data storage layer.

NinjectWebCommon.cs

/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IUnitOfWork<DeviceContext>>().To<UnitOfWork>();
    kernel.Bind<IElecDeviceDataService>().To<ElecDeviceDataService>();
}

I used Ninject (check: http://www.ninject.org/) as dependency injection engine. Ninject is a well documented and lightweight DI container for resolving the interface dependencies in all of your applications. In the case of our simple Web Application, we only have to inject the ElecDataService and UnitOfWork implementation classes into our MVC Controller. This is realized by adding the appropriate bindings in the RegisterServices method of the NinjectWebCommon.cs class.

Presentation Layer: ElecDevicesController.cs

The controller holds the CRUD (Create-Read-Update-Delete) actions for our ElecDevice model. For brevity, i will only explain the construction code, read and update. Insert and Delete actions are similar and can be viewed from the codebase.

Constructor
IElecDeviceDataService _elecDeviceDataService;
ElecDeviceLogic _elecDeviceLogic;

public ElecDevicesController(IElecDeviceDataService elecDeviceDataService)
{
    _elecDeviceDataService = elecDeviceDataService;
    _elecDeviceLogic = new ElecDeviceLogic(_elecDeviceDataService, new ModelStateWrapper(this.ModelState));
}

The controller constructor gets the data service implementation class injected. Next the controller holds a reference (owns) to the business logic class. Finally the business logic class is instantiated with the injected data service class and model state for validation purposes.

Image 14

Read Data

Image 15

// GET: ElecDevices
public ActionResult Index()
{
    IList<ElecDeviceDTO> elecDeviceDTOList = _elecDeviceLogic.GetListCollection();
    if (_elecDeviceLogic.HasErrors)
    {
        return RedirectToAction("ShowError", "Error", new { error = _elecDeviceLogic.ErrorMessage });
    }
    return View(elecDeviceDTOList.ToList<ElecDeviceDTO>());

}

The Index action of our MVC controller class is responsible for retrieving the devices (in our case, all devices ...) from the data source. To realize this, we only have to execute the GetListCollection() method on our business logic class. Also note that our presentation layer controller is unaware of how the data is retrieved, which leads to a good application of the Separation Of Concepts (SOC) pattern. Finally after retrieval the data is passed to the related View.

Image 16

Update Data

Image 17

// GET: ElecDevices/Edit/id
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    ElecDeviceDTO elecDeviceDTO = _elecDeviceLogic.GetEntityById(id);
    if (elecDeviceDTO == null)
    {
        return HttpNotFound();
    }
    if (_elecDeviceLogic.HasErrors)
    {
        return RedirectToAction("ShowError", "Error", new { error = _elecDeviceLogic.ErrorMessage });
    }
    return View(elecDeviceDTO);
}
// POST: ElecDevices/Edit/id
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "DeviceId,DeviceName,SerialNumber,ImageUrl,IsSmartElecDevice")] ElecDeviceDTO elecDeviceDTO)
{
    _elecDeviceLogic.Update(elecDeviceDTO);

    if (ModelState.IsValid)
    {
       return RedirectToAction("Index");
    }
    return View(elecDeviceDTO);
}

Updating data involves two steps. The first is a "GET" Edit Action where we load the selected item (the GetListXXX method returned all data in a list, when we want next to edit an item we should retrieve the details for the specific selected item). Also note that the business logic layer returned a DTO type. The returned data is next passed to the actions View. The second is a "POST" Edit Action which will take the possible updated values from the View. Both Actions require our Business Logic instance, the first one to retrieve the current item and the second one the persist the possible modified data. Again, our controller is unaware from the underlying technology used to retrieve or persist the data.

Validate Data

Image 18

In the constructor of our controller: _elecDeviceLogic = new ElecDeviceLogic(_elecDeviceDataService, new ModelStateWrapper(this.ModelState)); we supplied the modelstate to our business logic. ModelState holds the data bound to the View. As you will see later, validation logic is added to the business logic layer. If the supplied data violates the validation rules, then appropriate error messages are returned and displayed in the presentation layer.

Business Logic Layer: ElecDeviceLogic.cs

Constructor
public partial class ElecDeviceLogic : BaseBusinessLogic, IBusinessLogic<ElecDeviceDTO>
{
   private IElecDeviceDataService _elecDeviceService;
   public ElecDeviceLogic(IElecDeviceDataService elecDeviceService)
   {
       _elecDeviceService = elecDeviceService;
       ValidationDictionary = null;
   }
   public ElecDeviceLogic(IElecDeviceDataService elecDeviceService, IValidationDictionary validationDictionary)
   {
       _elecDeviceService = elecDeviceService;
       ValidationDictionary = validationDictionary;
   }

   ...
}

This sample project exposes a very simple domain object (ElecDevice), so for the sake of simplicity we provide a business logic layer which is only mapped to this single domain object. In fact, there are no "hard rules" how to define the business logic layer. We may, like this example does, create a 1-to-1 mapping between the domain object, DTO and Business Logic class, or we may centralize multiple domain objects in a single business logic class, all depends on how we want to expose the data in our application. We may have for example a single business logic class to represent a total order tracking system, e.g. a customer domain class, order header class and orderline class, each mapped to a specific entity domain class (customer, order, orderdetail) but exposed as a single DTO, wrapped in a single business logic class ... Note that our business logic class has two constructors, each injecting the data service class, the second constructor taking a validation object as a parameter.

Read Data
public IList<ElecDeviceDTO> GetListCollection(Expression<<, bool>> filter = null, string includeProperties = "")
{
    this.Initialize();

    try
    {
        List<ElecDeviceDTO> elecDeviceDTOList = _elecDeviceService.GetElecDevices();
        InfoMessage = "List of ElecDeviceDTO retrieved !";
        return elecDeviceDTOList;
    }
    catch(Exception ex)
    {
        BuildErrorMessage(ex);
        return null;
    }
}

The GetListCollection method in our business logic class holds a reference to the injected data service class. In turn, the data service class will access the underlying storage layer to retrieve the data. Although not used in this example, the GetListCollection provides a filter expression which can be used to limit the amount of data to be returned.

Update Data
public void Update(ElecDeviceDTO entityToUpdate)
{
    this.Initialize();
    try
    {
        if (!this.Validate(entityToUpdate))
        {
            Exception ex = new Exception("Update of existing device did not pass business logic validation, please check validationerrors for more info !");
            ex.Source = string.Format("{0}.{1}", this.GetType().AssemblyQualifiedName, this.GetType().Name);
            BuildErrorMessage(ex);
        }
        else
        {
            _elecDeviceService.UpdateElecDevice(entityToUpdate);
            InfoMessage = String.Format("Update Succeeded:\r\n{0}", ObjectMetaData<ElecDeviceDTO>.GetObjectState(entityToUpdate, null));
        }
    }
    catch (Exception ex)
    {
        BuildErrorMessage(ex);
    }
}

public bool Validate(ElecDeviceDTO entityToValidate)
{
    if (String.IsNullOrEmpty(entityToValidate.DeviceName))
        ValidationDictionary.AddError("DeviceName", "The name of the device is a required field !");
    if (!String.IsNullOrEmpty(entityToValidate.ImageUrl))
    {
        if (!entityToValidate.ImageUrl.Trim().StartsWith("~") || entityToValidate.ImageUrl == null)
            ValidationDictionary.AddError("ImageUrl", "The URL of the image must start with a relative path notation (~) !");
    }
    return ValidationDictionary == null ? true: ValidationDictionary.IsValid;
}

Before handling the persistence as provided by Data Service layer, the Business Logic layer first checks any validation rules. Note that the Business Logic layer does not throw any errors, but builds a custom error message which holds all violations of validation rules and/or all other possible encountered erros. Next it's the responsibility of the presentation layer to check the HasErrors flag and act appropriate.

Data Service Layer: ElecDeviceService.cs

Constructor
public partial class ElecDeviceDataService : IElecDeviceDataService
{
    IUnitOfWork<DeviceContext> _repositoryService;
    public ElecDeviceDataService(IUnitOfWork<DeviceContext> repositoryService)
    {
        _repositoryService = repositoryService;
        Mapper.CreateMap<ElecDevice, ElecDeviceDTO>();
        Mapper.CreateMap<ElecDeviceDTO, ElecDevice>();
    }

    ...
}

The constructor of the Data Service holds an injected reference to the repository service. In our case, the repository service is a UnitOfWork implementation which holds a reference to the EntityFramework context class and is responsible for the real CRUD operations. Of course as we develop against an Interface, we can easily replace the data layer with an alternative one (or provide a "mock" class for testing purposes ...). The Data Service layer is also responsible for the mapping between the persistent aware data objects (in our case Entity Framework DbSet Entities) and the Data Transfer Objects (DTO).

Read Data
public List<ElecDeviceDTO> GetElecDevices()
{
    List<ElecDevice> elecDeviceList = _repositoryService.ElecDeviceRepository.GetListCollection().ToList<ElecDevice>();
    List<ElecDeviceDTO> elecDeviceDTOList = Mapper.Map<List<ElecDevice>, List<ElecDeviceDTO>>(elecDeviceList);
    return elecDeviceDTOList;
}

The GetElecDevices method reads the data from the repository service. As the result is returned as EF entities, these must first be transformed to DTO entities before returning them to the Business Logic layer.

Update Data
public void UpdateElecDevice(ElecDeviceDTO elecDeviceToUpdateDTO)
{
    ElecDevice elecDeviceToUpdate = Mapper.Map<ElecDeviceDTO, ElecDevice>(elecDeviceToUpdateDTO);
    _repositoryService.ElecDeviceRepository.Update(elecDeviceToUpdate,elecDeviceToUpdate.DeviceId);
    _repositoryService.Commit();
}

The UpdateElecDevice method takes a (validated) DTO object from Business Logic layer, maps it to a persistence aware (EF Entity) object and delegates the update to the respective repository service.

Data Access Layer: Unit Of Work and Repository

The repository and unit of work patterns are intended to create an abstraction layer between the data access layer and the business logic layer of an application. Implementing these patterns can help insulate your application from changes in the data store and can facilitate automated unit testing or test-driven development (TDD).

Repository: EntityFrameworkGenericRepository.cs
public partial class EntityFrameworkGenericRepository<TEntity, TDBContext> : IGenericRepository<TEntity, TDBContext>
    where TEntity : class where TDBContext : DbContext
{
    internal TDBContext context;
    internal DbSet<TEntity> dbSet;
    public EntityFrameworkGenericRepository(TDBContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    ...
}

Each entity class (represented by generic placeholder TEntity) will be part of a database context (represented by generic placeholder TDBContext). This approach will become more clear when we discuss the UnitOfWork pattern in next section, but for now, just remember that we use a generic class (determined by the IGenericRepository interface) to persist our entities. The generic class holds all common CRUD operations which are valuable for each entity class represented by TEntity. By creating the generic class, we ommit to rewrite this boilerplate code over and again for each specific entity of the context. We will next detail the GetListCollection and Update methods of our generic data persistence class. (please refer to the codebase for the remaining methods).

    public IList<TEntity> GetListCollection(Expression<Func<TEntity, bool>> filter = null, string includeProperties = "")
    {
        IList<TEntity>query = dbSet.ToList<TEntity>();


        if (filter != null)
        {
            throw new NotImplementedException();
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            throw new NotImplementedException();
        }

        return query;
    }
    ...
}

The GetListCollection retrieves the required entities (with possible filter which is out of scope here ...) from the underlying context. TEntity will be replaced by it's realworld implementation (e.g. ElecDevice in our case).

public virtual void Update(TEntity entityToUpdate, Object entityId)
{
    TEntity originalEntity = dbSet.Find(entityId);
    context.Entry(originalEntity).State = EntityState.Detached;
    dbSet.Attach(entityToUpdate);
    context.Entry(entityToUpdate).State = EntityState.Modified;
}

The update method takes two parameters, the first one is of type TEntity and holds the changes values, the second one is the PK (Primary Key) of the modified entity. The update method first extracts the original entity from the database and detaches it from the context. Next the modified version is attached to the context and the state is set to modified. When the parent data service calls the commit statement, the changes will be updated in the underlying database.

Image 19

UnitOfWork: UnitOfWork.cs
public partial class UnitOfWork : IUnitOfWork<DeviceContext>
{
    private bool _disposed = false;
    private DeviceContext _context;
    private EntityFrameworkGenericRepository<ElecDevice, DeviceContext> _elecDeviceRepository;
    public IGenericRepository<ElecDevice, DeviceContext> ElecDeviceRepository
    {
        get
        {
            if (_elecDeviceRepository == null)
            {
                _elecDeviceRepository = new EntityFrameworkGenericRepository<ElecDevice, DeviceContext>(_context);
            }
            return _elecDeviceRepository;
        }
    }
    public UnitOfWork()
    {
        _context = new DeviceContext();
    }
    public void Commit()
    {
        _context.SaveChanges();
    }

    ...
}

The UnitOfWork pattern is a wrapper around our repository pattern. In our demo we only apply CRUD actions on a single entity (ElecDevice), but in real world applications many entities of different types should be addressed and if they should be handled in a transactional way (this means all updates pass or fail as a unit), then they should share the same context. If for example, we also should take care of GasDevice types, we can add a property _gasdeviceRepository to the UnitOf Work class which shares the same context. The example code below demonstrates this:

private EntityFrameworkGenericRepository<GasDevice, DeviceContext>_gasDeviceRepository;
public IGenericRepository<GasDevice, DeviceContext> GasDeviceRepository
{
    get
    {
        if (_gasDeviceRepository == null)
        {
            _gasDeviceRepository = new EntityFrameworkGenericRepository<GasDevice, DeviceContext>(_context);
        }
        return _gasDeviceRepository;
    }
}

Windows Forms Application handling Electricity Device Administration

Windows Client Application

The ASP.NET MVC application had a direct reference to the Business Logic Layer. In the second demo, we want to expose our Business Logic as a service, so that also non NET clients can access the logic. To demonstrate this approach, I created a simple Windows Forms project where a user can retrieve, update, insert and delete electricity devices.

private void FormDevice_Load(object sender, EventArgs e)
{
    try
    {
        this.Cursor = Cursors.WaitCursor;
        _wcfProxy = new ServiceReferenceElecDevice.ElecDeviceWcfServiceClient();
    }
    catch(Exception ex)
    {
        this.ResetBindingSource();
        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    finally
    {
        this.Cursor = Cursors.Default;  
    }
}

After adding the WCF service reference we first create an instance of our WCF service proxy.

private void getDeviceButton_Click(object sender, EventArgs e)
{
    try
    {
        this.Cursor = Cursors.WaitCursor;
        Int32 searchId;
        Int32.TryParse(deviceIdTextBox.Text, out searchId);
                       
        this.elecDeviceDTOBindingSource.DataSource = _wcfProxy.GetElecDeviceById(searchId);
    }
    catch(FaultException<ServiceReferenceElecDevice.GenericFault> faultEx)
    {
        MessageBox.Show(ErrorHandler.GetError(faultEx) + "\r\n" + faultEx.Detail.ErrorDetails,"Error Occured",MessageBoxButtons.OK,MessageBoxIcon.Error);
    }
    catch (Exception ex)
    {
        this.ResetBindingSource();
        MessageBox.Show(ErrorHandler.GetError(ex),"Error Occured",MessageBoxButtons.OK,MessageBoxIcon.Error);
    }
    finally
    {
        this.Cursor = Cursors.Default;
    }
}

When the user asks for a device, the proxy method is executed and returns the device as a DTO. The returned DTO is bound to the datasource of the bindingsource of the windows form. Any exceptions (faults and others) are catched and displayed in a message box.

private void updateButton_Click(object sender, EventArgs e)
{
    try
    {
        this.Cursor = Cursors.WaitCursor;
        elecDeviceDTOBindingSource.EndEdit();
        _elecDeviceDTO = (elecDeviceDTOBindingSource.Current as ElecDeviceDTO);
        if(_elecDeviceDTO != null)
        {
            _wcfProxy.UpdateElecDevice(_elecDeviceDTO);
        }
    }
    catch (FaultException<ServiceReferenceElecDevice.GenericFault> faultEx)
    {
        MessageBox.Show(ErrorHandler.GetError(faultEx) + "\r\n" + faultEx.Detail.ErrorDetails, "Error Occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    catch (Exception ex)
    {
        this.ResetBindingSource();
        MessageBox.Show(ErrorHandler.GetError(ex), "Error Occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    finally
    {
        this.Cursor = Cursors.Default;
    }
}

The update process is similar (as is the delete and insert), in this case the UpdateElecDevice method is executed by the proxy and any errors (faults and others) are catched and displayed in a message box. Next I will explain the code hosted by the service.

Service Layer: WcfServiceLayer.cs

Service Interface
[ServiceContract]
public interface IElecDeviceWcfService
{
    [FaultContract(typeof(GenericFault))]
    [OperationContract]
    ElecDeviceDTO GetElecDeviceById(int? id);
    [FaultContract(typeof(GenericFault))]
    [OperationContract]
    IList<ElecDeviceDTO> GetElecDevices();
    [FaultContract(typeof(GenericFault))]
    [OperationContract]
    void InsertElecDevice(ElecDeviceDTO entityToInsert);
    [FaultContract(typeof(GenericFault))]
    [OperationContract]
    void UpdateElecDevice(ElecDeviceDTO entityToUpdate);
    [FaultContract(typeof(GenericFault))]
    [OperationContract]
    void DeleteElecDevice(int? id);
}

As mentioned before, our Service Layer will be a "wrapper" round our Business Logic Layer. To enable this, we first define an WCF Service Interface which exposes the methods that a consuming client (e.g. our windows application) can execute on the serviec. In our case we expose the typical CRUD methods to read, insert, update and delete electricity devices. Also not that each exposed method contains a FaultException. As already described before, fault exceptions are a special kind of exceptions, specifically used in case of web service based interactions. This because the consuming client may not always be a .NET client. A fault exception is returned as a standard SOAP-message en thus also interpretable by non .NET consumers.

Service Implementation

As with the other implementations, we will limit the explanation to the constructor, read and update operations. Please refer to the codebase for all other supplied methods of the service;

public class ElecDeviceWcfService : IElecDeviceWcfService
{
    IUnitOfWork<DeviceContext> _unitOfWork;
    IElecDeviceDataService _elecDeviceDataService;
    IBusinessLogic<ElecDeviceDTO> _elecDeviceLogic;
   
    public ElecDeviceWcfService()
    {
        _unitOfWork = new UnitOfWork();
        _elecDeviceDataService = new ElecDeviceDataService(_unitOfWork);
        _elecDeviceLogic = new ElecDeviceLogic(_elecDeviceDataService, new ModelStateWrapper(new Dictionary<string, string="">()));
    }

    public ElecDeviceWcfService(IBusinessLogic<ElecDeviceDTO> elecDeviceLogic)
    {
            _elecDeviceLogic = elecDeviceLogic;
    }
    ...
}</string,>

Note that our WCF service has two constructors. The default one which does not take any parameters, and a second one where the device logic gets "injected" (through DI-container). By default, the generated WCF proxy will only expose a default parameterless constructor, so any kind of dependency injection is not possible by default. (The proxy is a CLR class that exposes a single CLR interface representing the service contract. The proxy provides the same operations as service's contract, but also has additional methods for managing the proxy life cycle and the connection to the service. The proxy completely encapsulates every aspect of the service: its location, its implementation technology and runtime platform, and the communication transport.). Nevertheless, you can change the way how WCF proxy's are generated and as such provide them with constructors which accept parameters. But for the sake of simplicity we will stuck with the parameterless constructor. Most important thing to notice here is that our WCF Service holds a reference to our Business Logic Class, an can, as such, execute the public methods exposed by our Business Logic Class. Also note that the instantiation of our business logic component adds an instance of the default ModelStateWrapper which will add validation to our services.

public ElecDeviceDTO GetElecDeviceById(int? id)
{
    try
    {
        Int32 newId;
        bool converted = Int32.TryParse(id.ToString(), out newId);
        if (!converted)
        {
            this.ThrowFault(
                "Unable to parse the id parameter to an integer value !",
                "Please provide a valid integer number as id !");
        }
        ElecDeviceDTO returnValue = _elecDeviceLogic.GetEntityById(newId);
        if (_elecDeviceLogic.HasErrors)
        {
            this.ThrowFault(
                string.Format("An error occured while executing {0}.{1}", _elecDeviceLogic.GetType().AssemblyQualifiedName, this.GetType().Name),
                    _elecDeviceLogic.ErrorMessage);
        }
        return returnValue;
                
    }
    catch(FaultException<GenericFault> genericFault)
    {
        throw genericFault;
    }
    catch (Exception ex)
    {
        this.ThrowFault(ErrorHandler.GetErrorMessage(ex),ErrorHandler.GetErrorDetails(ex));
    }
    return null;
}

The GetElecDeviceById takes an integer Id as input (id of the device to search for), checks if a valid Id is provided, otherwise throws a fault error. Next the service executes GetEntityById on the Business Logic instance and returns the DTO if found. If not found or any errors occured, then the service returns the appropriate fault exception to the consuming client.

public void UpdateElecDevice(ElecDeviceDTO entityToUpdate)
{
    try
    {
        _elecDeviceLogic.Update(entityToUpdate);
        if (_elecDeviceLogic.HasErrors)
        {
            this.ThrowFault(
                string.Format("An error occured while executing {0}.{1}", _elecDeviceLogic.GetType().AssemblyQualifiedName, this.GetType().Name),
                    _elecDeviceLogic.ErrorMessage);
        }
    }
    catch (FaultException<GenericFault> genericFault)
    {
        throw genericFault;
    }
    catch (Exception ex)
    {
        this.ThrowFault(ErrorHandler.GetErrorMessage(ex), ErrorHandler.GetErrorDetails(ex));
    }
}

The update method delegates the entity to update to the Business Logic component. If any error occurs during update (validation or other) then the WebService throws a fault exception to the consuming client.

Final Word

It is crucial in todays modern (.NET) application development to have a good understanding on how to structure your application into a layered framework. I know from experience that many developers are to urged to dive directly into code, getting fast results but leads many times to poor architecture and to many dependencies on some kind of technology. It may sound as a cliché, but the only constant in software development is "change", and each software architect should try to strive to start with a development platform which is suitable for change. Terms like programming to an interface, not an implmentation, dependency injection and separation of concerns (SOC) through loosely coupled logical layers are core elements in good software development. Feel free to use and adapt the code as it suits your projects.

Not Using the Code

To make it possible to upload the project all dependencies (packages, bin, obj ...) have been removed, so dependencies should be resolved (automatically through Nuget or manually if necessary).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect REALDOLMEN
Belgium Belgium
Working in the IT-Branch for more then 20 years now. Starting as a programmer in WinDev, moved to Progress, shifted to .NET since 2003. At the moment i'm employed as a .NET Application Architect at RealDolmen (Belgium). In my spare time, i'm a die hard mountainbiker and together with my sons Jarne and Lars, we're climbing the hills in the "Flemish Ardens" and the wonderfull "Pays des Collines". I also enjoy "a p'tit Jack" (Jack Daniels Whiskey) or a "Duvel" (beer) for "l'après VTT !".

Comments and Discussions

 
Questionabout GenericFault Pin
ramiz sümer7-May-19 20:39
ramiz sümer7-May-19 20:39 
PraiseMy vote of 5 Pin
Alejandro Giunta21-Dec-15 3:51
Alejandro Giunta21-Dec-15 3:51 
Excelent Work!
QuestionHow to Fit MVVM in this structure Pin
Praveen Raghuvanshi26-Nov-15 21:05
professionalPraveen Raghuvanshi26-Nov-15 21:05 
AnswerRe: How to Fit MVVM in this structure Pin
Emmanuel Nuyttens29-Nov-15 7:17
Emmanuel Nuyttens29-Nov-15 7:17 
GeneralRe: How to Fit MVVM in this structure Pin
Praveen Raghuvanshi29-Nov-15 19:13
professionalPraveen Raghuvanshi29-Nov-15 19:13 
QuestionMy vote of 5 but... Pin
Marc Greiner at home18-Nov-15 3:10
Marc Greiner at home18-Nov-15 3:10 
AnswerRe: My vote of 5 but... Pin
Emmanuel Nuyttens24-Nov-15 22:31
Emmanuel Nuyttens24-Nov-15 22:31 
GeneralRe: My vote of 5 but... Pin
Marc Greiner at home25-Nov-15 1:43
Marc Greiner at home25-Nov-15 1:43 
GeneralIt is too complex Pin
engintanrikulu18-Nov-15 0:28
engintanrikulu18-Nov-15 0:28 
GeneralRe: It is too complex Pin
Emmanuel Nuyttens18-Nov-15 1:35
Emmanuel Nuyttens18-Nov-15 1:35 
GeneralRe: It is too complex Pin
Super Lloyd9-Jul-16 21:59
Super Lloyd9-Jul-16 21:59 
GeneralMy vote of 5 Pin
Anil Kumar @AnilAwadh17-Nov-15 23:17
professionalAnil Kumar @AnilAwadh17-Nov-15 23:17 
SuggestionValidation Errors in ModelState Pin
WagMelo17-Nov-15 10:15
professionalWagMelo17-Nov-15 10:15 
GeneralGood Article Pin
Santhakumar M17-Nov-15 8:09
professionalSanthakumar M17-Nov-15 8:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.