2
\$\begingroup\$

I have an Entity Framework working with a SQL database. A Web Forms application is currently communicating directly with the Entity Framework model from code behind. I have been tasked with creating a 'middle layer' Business Logic Layer to sit between the UI and DAL (EF).

I have come up with the below for the Person entity. I am looking for advice on ways to improve this structure so I can propagate it throughout the entire project.

The properties for person are the same as the Entity Framework model, all I do is call the conversion methods when I need to convert between the two.

using BLL.Members;
using DAL;
using System;
using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BLL.People
{
public class Person
{
    public int ID { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Salt { get; set; }
    public string PasswordKey { get; set; }
    public DateTime? Expiry { get; set; }
    public string Firstname { get; set; }
    public string Surname { get; set; }
    public string FullName { get; set; }
    public string EmailAddress { get; set; }
    public AccountStatus AccountStatus { get; set; }        
    public DateTime DateCreated { get; set; }
    private int CreatedBy { get; set; }
    private DateTime LastUpdated { get; set; }
    private int LastUpdateBy { get; set; }
    public Member Member { get; set; }

    private ProjectEntities _context = null;
    protected ProjectEntities context
    {
        get
        {
            if (_context == null)
                _context = new ProjectEntities();

            return _context;
        }
    }

    #region Translation

    /// <summary>
    /// Translates a DAL.Person to BLL.Person
    /// </summary>
    /// <param name="_person"></param>
    /// <returns></returns>
    private static Person TranslateEntityToBLLPerson(DAL.Person _person)
    {
        Person per = new Person();
        per.ID = _person.ID;
        per.Username = _person.Username;
        per.Password = _person.Password;
        per.Salt = _person.Salt;
        per.PasswordKey = _person.PasswordKey;
        per.Expiry = (_person.Expiry == null) ? per.Expiry = null : per.Expiry = _person.Expiry;
        per.Firstname = _person.Firstname;
        per.Surname = _person.Surname;
        per.FullName = _person.FullName;
        per.EmailAddress = _person.EmailAddress;
        per.AccountStatus = (AccountStatus)_person.AccountStatus;
        per.DateCreated = _person.DateCreated;
        per.CreatedBy = _person.CreatedBy;
        per.LastUpdated = _person.LastUpdated;
        per.LastUpdateBy = _person.LastUpdateBy;
        per.Member = Member.TranslateEntityToBLLMember(_person.People_Members);

        return per;
    }

    /// <summary>
    /// Translates a BLL.Person to DAL.Person
    /// </summary>
    /// <param name="per"></param>
    /// <returns></returns>
    private static DAL.Person TranslateBLLPersonToEntity(Person per)
    {
        DAL.Person _person = new DAL.Person();
        _person.ID = per.ID;
        _person.Username = per.Username;
        _person.Password = per.Password;
        _person.Salt = per.Salt;
        _person.PasswordKey = per.PasswordKey;
        _person.Expiry = (per.Expiry == null) ? _person.Expiry = null : _person.Expiry = _person.Expiry;
        _person.Firstname = per.Firstname;
        _person.Surname = per.Surname;
        _person.FullName = per.FullName;
        _person.EmailAddress = per.EmailAddress;
        _person.AccountStatus = Convert.ToInt32(per.AccountStatus);
        _person.DateCreated = per.DateCreated;
        _person.CreatedBy = per.CreatedBy;
        _person.LastUpdated = per.LastUpdated;
        _person.LastUpdateBy = per.LastUpdateBy;

        return _person;
    }

    #endregion

    /// <summary>
    /// Adds a new person to the database
    /// </summary>
    /// <param name="_person"></param>
    /// <returns></returns>
    public bool addNewPerson(Person _updateBy)
    {
        DAL.Person newPer = TranslateBLLPersonToEntity(this);

        newPer.DateCreated = DateTime.Now;
        newPer.CreatedBy = _updateBy.ID;
        newPer.LastUpdateBy = _updateBy.ID;
        newPer.LastUpdated = DateTime.Now;

        context.Entry(newPer).State = System.Data.Entity.EntityState.Added;

        try
        {
            context.SaveChanges();
            return true;
        }
        catch (DbEntityValidationException ex)
        {
            foreach (var validationErrors in ex.EntityValidationErrors)
            {
                foreach (var validationError in validationErrors.ValidationErrors)
                {
                    Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
                }
            }

            return false;
        }
    }

    /// <summary>
    /// Updates a person
    /// </summary>
    /// <param name="_person"></param>
    /// <returns></returns>
    public bool updatePerson(Person _updateBy)
    {
        DAL.Person updatePer = TranslateBLLPersonToEntity(this);

        updatePer.LastUpdated = DateTime.Now;
        updatePer.LastUpdateBy = _updateBy.ID;

        context.Entry(updatePer).State = System.Data.Entity.EntityState.Modified;

        try
        {
            context.SaveChanges();
        }
        catch (DbEntityValidationException ex)
        {
            foreach (var validationErrors in ex.EntityValidationErrors)
            {
                foreach (var validationError in validationErrors.ValidationErrors)
                {
                    Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
                }
            }
        }

        return true;
    }

    /// <summary>
    /// Retrieve a person by ID
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public static Person fromID(int id)
    {
        ProjectEntities context = new ProjectEntities();

        DAL.Person per = context.People.FirstOrDefault(p => p.ID == id);

        if (per != null)
            return TranslateEntityToBLLPerson(per);
        else
            return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="email"></param>
    /// <returns></returns>
    public static Person fromEmail(string email)
    {
        ProjectEntities context = new ProjectEntities();

        DAL.Person per = context.People.FirstOrDefault(p => p.EmailAddress == email);

        if (per != null)
            return TranslateEntityToBLLPerson(per);
        else
            return null;
    }

    public bool resetPassword(string _newPassword)
    {
        var crypto = new SimpleCrypto.PBKDF2();

        this.Password = crypto.Compute(_newPassword);
        this.Salt = crypto.Salt;

        try
        {
            updatePerson(this);

            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    /// <summary>
    /// Logs in the user
    /// </summary>
    /// <param name="_username"></param>
    /// <param name="_password"></param>
    /// <returns></returns>
    public static Person loginPerson(string _username, string _password)
    {
        ProjectEntities context = new ProjectEntities();

        //Initilize the SimpleCrypto
        var crypto = new SimpleCrypto.PBKDF2();

        //Find the user by the username entered
        Person u = TranslateEntityToBLLPerson(context.People.AsNoTracking().FirstOrDefault(p => p.Username == _username));

        //If the user exists
        if (u != null)
        {
            //check the account status
            if (u.AccountStatus == AccountStatus.PendingEmailVer)
            {
                //The credentials are not valid, display this to the user
                return null;
            }
            else
            {
                //Compare the users actual password to an encrypted version of what was entered
                if (u.Password == crypto.Compute(_password, u.Salt))
                {
                    u.LastUpdated = DateTime.Now;
                    u.LastUpdateBy = u.ID;
                    u.updatePerson(u);

                    return u;
                }
                else
                {
                    return null;
                }
            }
        }
        else
        {
            return null;
        }
    }
}
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Sidenote: Your methods should be in CamcelCase (to follow the general C# convention), and not the java convention with first letter in lower case. :-) \$\endgroup\$ – scheien May 9 '14 at 10:02
  • \$\begingroup\$ Can you tell me where is that AccountStatus enum located? in DAL or in BLL? \$\endgroup\$ – roozbeh S Nov 18 '18 at 22:47
1
\$\begingroup\$

imho the business layer should be unaware of the persistence layer. What you got there is a big pile of mess.

Here are a couple of issues:

  1. Why do the business layer ever know about the salt? It's information required for the persistence layer only.

  2. Encapsulation is important in the business layer. Only the class itself knows when it's in a correct state. By using public setters you for all other classes to know when a user is in a correct state. Use private setters and methods instead (which leads to the third point).

  3. Your class do not have any behavior. It's more of a DTO object than a business entity. The anti pattern is called "Anemic domain model".

  4. Move the translation methods to the DAL instead. Business layer should not depend on anything, all other layers should depend on the business. It makes it a lot easier to test and maintain the code.

  5. Split the rest into smaller classes with clear responsibilities. A user object is only responsible for a single user. A service class is responsible of coordinating several users, a repository is responsible of abstracting away the persistence logic etc etc.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the comments. Is there a suitable tutorial out there to point me in the right direction? Also a bit confused as to how to do the translation on the DAL? \$\endgroup\$ – dynamicuser May 9 '14 at 10:15
  • \$\begingroup\$ I've written an article about the repository pattern: blog.gauffin.org/2013/01/repository-pattern-done-right \$\endgroup\$ – jgauffin May 9 '14 at 10:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.