Sayfalar

19 Eylül 2013 Perşembe

ASP.NET WEBFORMS DEPENDENCY INJECTION & UNIT TEST

Asp.Net Webforms ile dependency injection nasıl yapılır, örnek bir proje üzerinde görelim. Projede sadece tek tablo ile çalışacağız (Users tablosu). Test projesi içerisinde, bu tablo için Insert, Update, Delete işlerinin testini yapacağız. Yeni bir solution oluşturuyoruz. Bu solutiona, aşağıdaki projeleri ekliyoruz.

  • WebFormsProject.Website   (asp.net empty web forms project)
  • WebFormsProject.Data    (class library project)
  • WebFormsProject..Domain (class library project)
  • WebFormsProject.Services (class library project)
  • WebFormsProject.Test (test project)
Solution explorer görüntüsü;

Data projesi içerisine domain, service projesine data ve domain, web projesine, test dısındaki hepsi, test projesine de tüm projeleri referans ediyoruz. 

Web projesine ayrıca, Manage Nuget Packages kullanarak Unity.Webforms u yüklüyoruz. (WebFormsProject.Website > Sağ Tık > Manage Nuget Packages)

App_Start klasörü altında UnityWebFormsStart.cs diye bir sınıf oluşacak. Ayarlarımızı bu sınıf içerisinde yapacağız. Ama öncelikle, veritabanını oluşturalım. WebFormsProject adında bir veritabanı oluşturun ve Users diye bir tablo ekleyin. Daha sonra bu veritabanının connctionString ini web config dosyasına ekleyin.

Users tablosunun görünümü:

Web.config
<configuration>
  <connectionStrings>
    <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=WebFormsProjectDB;Integrated Security=True;" />
  </connectionStrings>
    <system.web>
      <compilation debug="true" targetFramework="4.5" />
      <httpRuntime targetFramework="4.5" />
    </system.web>
</configuration>

Users adında bir sınıf ekleyelim, bu sınıfı, Domain projesi altındaki, DomainModel kalasörüne ekliyoruz.

Users.cs
using System;
namespace WebForms.Domain.DomainModel.Entities
{
    public class Users
    {
        public int Id { getset; }
        public string UserName { getset; }
        public string DisplayName { getset; }
        public string Password { getset; }
        public string Email { getset; }
        public DateTime? LastLoginDate { getset; }
        public string LastLoginIP { getset; }
    }
}

Yine Domain projesi içerisinde, Interfaces klasörü altında 2 tane klasör oluşturup bunlarada Service ve Respository sınıflarını ekliyoruz.

IUserRepository.cs
using System.Data;
using WebForms.Domain.DomainModel.Entities;
 
namespace WebForms.Domain.Interfaces.Repositories
{
    public interface IUserRepository
    {
        /// <summary>
        /// Tüm kullanıcılar
        /// </summary>
        /// <returns></returns>
        DataTable GetAll();
 
        /// <summary>
        /// Role göre kullanıcılar
        /// </summary>
        /// <param name="roleName"></param>
        /// <returns></returns>
        DataTable GetAllByRoleName(string roleName);
 
        /// <summary>
        /// Kullanıcı bul
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        Users Find(int userId);
 
        /// <summary>
        /// Kullanıcı ekle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Eklenen kullanıcı</returns>
        Users Insert(Users user);
 
        /// <summary>
        /// Kullanıcı güncelle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Etkilenen satır sayısı</returns>
        int Update(Users user);
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="user">Kullanıcı</param>
        /// <returns>Etkilenen satır sayısı</returns>
        int Delete(Users user);
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="userId">Kullanıcı Id</param>
        /// <returns>Etkilenen satır sayısı</returns>
        int Delete(int userId);
    }
}

IUserService.cs
using System.Data;
using WebForms.Domain.DomainModel.Entities;
namespace WebForms.Domain.Interfaces.Services
{
    public interface IUserService
    {
        /// <summary>
        /// Tüm kullanıcılar
        /// </summary>
        /// <returns></returns>
        DataTable GetAll();
 
        /// <summary>
        /// Role göre kullanıcılar
        /// </summary>
        /// <param name="roleName"></param>
        /// <returns></returns>
        DataTable GetAllByRoleName(string roleName);
 
        /// <summary>
        /// Kullanıcı bul
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        Users Find(int userId);
 
        /// <summary>
        /// Kullanıcı ekle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Eklenen kullanıcı</returns>
        Users Insert(Users user);
 
        /// <summary>
        /// Kullanıcı güncelle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Etkilenen satır sayısı</returns>
        int Update(Users user);
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="user">Kullanıcı</param>
        /// <returns>Etkilenen satır sayısı</returns>
        int Delete(Users user);
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="userId">Kullanıcı Id</param>
        /// <returns>Etkilenen satır sayısı</returns>
        int Delete(int userId);
    }
}

Domain projesinin son hali;

Şimdi Data projesi içerisinde ana dizine iki tane sınıf ekleyeceğiz, bunlardan birisi, Ado.Net için yazdığımız extension metodları, digeri ise ortak bir veri erişim katmanı sağlayacak. Ado.net için yazdığımız extension sınıfı:

AdoExtensions.cs
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
 
namespace WebFormsProject.Data
{
    public static class AdoExtensions
    {
        // Null olabilecek parametreler için DBNull kullanılmalı, bunun kontolünü bu metod yapıyor
        // http://www.codeproject.com/Tips/310792/Handling-null-values-in-SqlParameter-parameter-obj
        /// <summary>
        /// Null olan parametreler için default değer ataması yapar.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="parameterName">Parametre ismi</param>
        /// <param name="value">Paremetre değeri</param>
        /// <param name="nullValue">Null ise, girilecek değer</param>
        /// <returns></returns>
        public static SqlParameter AddWithValue(this SqlParameterCollection target, string parameterName, object value, object nullValue)
        {
            if (value == null)
            {
                return target.AddWithValue(parameterName, nullValue ?? DBNull.Value);
            }
            return target.AddWithValue(parameterName, value);
        }
 
        public static string GetNullableString(this DbDataReader reader, string columnName)
        {
            object value = reader[columnName];
            if (value == DBNull.Value)
            {
                return null;
            }
            return value.ToString();
        }
 
        public static string GetString(this DbDataReader reader, string columnName)
        {
            object value = reader[columnName];
            return value.ToString();
        }
 
        public static T GetNullable<T>(this DbDataReader reader, string columnName)
        {
            object value = reader[columnName];
            if (value == DBNull.Value)
            {
                return default(T);
            }
            return Get<T>(reader, columnName);
        }
 
        public static T Get<T>(this DbDataReader reader, string columnName)
        {
            try
            {
                return (T)reader[columnName];
            }
            catch (InvalidCastException ex)
            {
                throw new InvalidCastException("Specified cast is not valid, field: " + columnName, ex);
            }
        }
 
        public static string GetNullableString(this DataRow dr, string columnName)
        {
            object value = dr[columnName];
            if (value == DBNull.Value)
            {
                return null;
            }
            return value.ToString();
        }
 
        public static T? GetNullableStruct<T>(this DataRow dr, string columnName) where T : structIConvertible
        {
            var value = dr[columnName];
            if (value == DBNull.Value)
            {
                return (T?)null;
            }
            if (typeof(T) == typeof(DateTime))
            {
                return (T)Convert.ChangeType(dr.GetDate(columnName), typeof(T));
            }
            if (typeof(T) == typeof(bool))
            {
                //Depending on the db engine it can come as a boxed boolean or a boxed int
                //Watch out for the ints!
                var stringValue = Convert.ToString(value);
                if (stringValue == "0")
                {
                    return (T)Convert.ChangeType(falsetypeof(T));
                }
                else if (stringValue == "1")
                {
                    return (T)Convert.ChangeType(truetypeof(T));
                }
            }
            return (T)value;
        }
 
        public static string GetString(this DataRow dr, string columnName)
        {
            return dr.GetNullable<string>(columnName);
        }
 
        public static T GetNullable<T>(this DataRow dr, string columnName)
        {
            object value = dr[columnName];
            if (value == DBNull.Value)
            {
                return default(T);
            }
            return Get<T>(dr, columnName);
        }
 
        /// <summary>
        /// Gets the date in UTC Kind
        /// </summary>
        /// <param name="dr"></param>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public static DateTime GetDate(this DataRow dr, string columnName)
        {
            DateTime date = (DateTime)dr[columnName];
            return DateTime.SpecifyKind(date, DateTimeKind.Utc);
        }
 
        public static T Get<T>(this DataRow dr, string columnName)
        {
            try
            {
                if (dr[columnName] == DBNull.Value)
                {
                    throw new NoNullAllowedException("Column " + columnName + " has a null value.");
                }
                Type type = typeof(T);
                if (type == typeof(DateTime))
                {
                    throw new ArgumentException("Date time not supported.");
                }
                else if (type == typeof(Guid))
                {
                    return (T)(object)new Guid(dr[columnName].ToString());
                }
                else if (type.IsEnum)
                {
                    return (T)Enum.Parse(type, dr[columnName].ToString());
                }
 
                return (T)dr[columnName];
            }
            catch (InvalidCastException ex)
            {
                throw new InvalidCastException("Specified cast is not valid, field: " + columnName + ", Type: " + typeof(T).FullName, ex);
            }
        }
 
        /// <summary>
        /// Safely opens the connection, executes and closes the connection
        /// </summary>
        /// <param name="comm"></param>
        /// <returns>The number of rows affected.</returns>
        public static int SafeExecuteNonQuery(this DbCommand comm)
        {
            int rowsAffected = 0;
            try
            {
                comm.Connection.Open();
                rowsAffected = comm.ExecuteNonQuery();
            }
            catch (Exception ex) { }
            finally
            {
                comm.Connection.Close();
            }
            return rowsAffected;
        }
 
        /// <summary>
        /// Safely opens the connection, executes and closes the connection
        /// </summary>
        /// <param name="comm"></param>
        /// <returns>The number of rows affected.</returns>
        public static int SafeExecuteScalar(this DbCommand comm)
        {
            int id = 0;
            try
            {
                comm.Connection.Open();
                id = Int32.Parse(comm.ExecuteScalar().ToString());
            }
            catch (Exception ex) { }
            finally
            {
                comm.Connection.Close();
            }
            return id;
        }
    }
}

Burada, ado.net te olan metodları genişletiyoruz. Örneğin ilk metod, AddWithValue metodu. Metodu incelerseniz göreceksinizki, eğer ekleyeceğimizi değer null ise bu değeri default bir değer atamak için genişletiyoruz.

İkinci sınıf ortak erişim sınıfı:

BaseDataAccess.cs
using System;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
 
namespace WebFormsProject.Data
{
    /// <summary>
    /// Represents a set of methods to get data from the db and convert into app entities
    /// </summary>
    public abstract class BaseDataAccess
    {
        private ConnectionStringSettings _connectionStringSetting;
        private DbProviderFactory _factory;
 
        /// <summary>
        /// Gets the forums connection string
        /// </summary>
        protected virtual ConnectionStringSettings ConnectionStringSetting
        {
            get
            {
                if (_connectionStringSetting == null)
                {
                    var connection = ConfigurationManager.ConnectionStrings["DefaultConnection"];
                    connection = EnsureProvider(connection);
                    _connectionStringSetting = connection;
                }
                return _connectionStringSetting;
            }
        }
 
        /// <summary>
        /// Ensures that the connection strings contains a provider
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        protected ConnectionStringSettings EnsureProvider(ConnectionStringSettings connection)
        {
            if (String.IsNullOrEmpty(connection.ProviderName))
            {
                //Fallback to default provider: sql server
                connection = new ConnectionStringSettings(connection.Name, connection.ConnectionString, "System.Data.SqlClient");
            }
            return connection;
        }
 
        /// <summary>
        /// The database provider factory to create the connections and commands to access the db.
        /// </summary>
        public virtual DbProviderFactory Factory
        {
            get
            {
                if (_factory == null)
                {
                    _factory = DbProviderFactories.GetFactory(ConnectionStringSetting.ProviderName);
                }
                return _factory;
            }
            set
            {
                _factory = value;
            }
        }
 
        /// <summary>
        /// Gets a new command for query executing.
        /// </summary>
        /// <param name="procedureName"></param>
        /// <returns></returns>
        protected SqlCommand GetCommand(string query)
        {
            var command = this.Factory.CreateCommand() as SqlCommand;
            command.Connection = GetConnection();
            command.CommandText = query;
 
            return command;
        }
 
        /// <summary>
        /// Gets a new connection.
        /// </summary>
        /// <returns></returns>
        public virtual SqlConnection GetConnection()
        {
            var connection = Factory.CreateConnection() as SqlConnection;
            connection.ConnectionString = ConnectionStringSetting.ConnectionString;
            return connection;
        }
 
        /// <summary>
        /// Gets a datatable filled with the first result of executing the command.
        /// </summary>
        protected DataRow GetFirstRow(DbCommand command)
        {
            DataRow dr = null;
            var dt = GetTable(command);
            if (dt.Rows.Count > 0)
            {
                dr = dt.Rows[0];
            }
            return dr;
        }
 
        /// <summary>
        /// Gets a datatable filled with the results of executing the command.
        /// </summary>
        protected DataTable GetTable(DbCommand command)
        {
            var dt = new DataTable();
            var da = this.Factory.CreateDataAdapter();
            da.SelectCommand = command;
            da.Fill(dt);
 
            return dt;
        }
 
        /// <summary>
        /// Disposes the reader.
        /// </summary>
        /// <param name="reader"></param>
        protected void SafeDispose(DbDataReader reader)
        {
            if (reader != null)
            {
                reader.Dispose();
            }
        }
 
        /// <summary>
        /// Safely opens the connection, executes and closes the connection
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        protected int SafeExecuteNonQuery(DbCommand command)
        {
            return command.SafeExecuteNonQuery();
        }
 
        /// <summary>
        /// Safely opens the connection, executes and closes the connection
        /// </summary>
        /// <param name="command"></param>
        /// <returns>The last inserted element id.</returns>
        protected int SafeExecuteScalar(DbCommand command)
        {
            return command.SafeExecuteScalar();
        }
    }
}

Repository sınıflarımız bu sınıftan kalıtım alacaklar, boylece, her repository içerisinde, ayrı ayrı, connection almaya veya acmaya kapatmaya gerek kalmayacak.

Data projemize en son olarak, Repositories klasörü altına, UserRespository ekliyoruz.

UserRespository.cs
using System;
using System.Data;
using System.Data.SqlClient;
using WebForms.Domain.DomainModel.Entities;
using WebForms.Domain.Interfaces.Repositories;
 
namespace WebFormsProject.Data.Repositories
{
    public class UserRepository : BaseDataAccessIUserRepository
    {
        /// <summary>
        /// Tüm kullanıcılar
        /// </summary>
        /// <returns></returns>
        public DataTable GetAll()
        {
            SqlCommand command = GetCommand("SELECT * FROM Users");
            DataTable dt = GetTable(command);
 
            return dt;
        }
 
        /// <summary>
        /// Role göre kullanıcılar
        /// </summary>
        /// <param name="roleName"></param>
        /// <returns></returns>
        public DataTable GetAllByRoleName(string roleName)
        {
            SqlCommand command = GetCommand("SELECT * FROM Users U, UserInRoles UR, Roles R WHERE (UR.RoleId = R.Id AND UR.UserId = U.Id AND R.Name = @roleName)");
            command.Parameters.AddWithValue("@roleName", roleName);
            DataTable dt = GetTable(command);
 
            return dt;
        }
 
        /// <summary>
        /// Kullanıcı bul
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Users Find(int userId)
        {
            Users user = null;
            SqlCommand command = GetCommand("SELECT TOP 1 * FROM Users WHERE Id = @UserId");
            command.Parameters.AddWithValue("@UserId", userId);
            DataRow dr = GetFirstRow(command);
 
            if (dr != null)
            {
                user.Id = dr.Get<int>("Id");
                user.UserName = dr.GetString("UserName");
                user.DisplayName = dr.GetString("DisplayName");
                user.Email = dr.GetString("Email");
                user.LastLoginDate = dr.GetNullable<DateTime?>("LastLoginDate");
                user.LastLoginIP = dr.GetString("LastLoginIP");
                user.Password = dr.GetString("Password");
            }
 
            return user;
        }
 
        /// <summary>
        /// Kullanıcı ekle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Eklenen kullanıcı</returns>
        public Users Insert(Users user)
        {
            SqlCommand command = GetCommand("INSERT INTO Users (UserName, DisplayName, Email, LastLoginDate, LastLoginIP, Password) VALUES (@UserName, @DisplayName, @Email, @LastLoginDate, @LastLoginIP, @Password); SELECT SCOPE_IDENTITY()");
            command.Parameters.AddWithValue("@UserName", user.UserName, null);
            command.Parameters.AddWithValue("@DisplayName", user.DisplayName, null);
            command.Parameters.AddWithValue("@Email", user.Email, null);
            command.Parameters.AddWithValue("@LastLoginDate", user.LastLoginDate, null);
            command.Parameters.AddWithValue("@LastLoginIP", user.LastLoginIP, null);
            command.Parameters.AddWithValue("@Password", user.Password, null);
 
            user.Id = SafeExecuteScalar(command);
 
            return user;
        }
 
        /// <summary>
        /// Kullanıcı güncelle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Etkilenen satır sayısı</returns>
        public int Update(Users user)
        {
            SqlCommand command = GetCommand("UPDATE Users SET UserName=ISNULL(@UserName,UserName), DisplayName=ISNULL(@DisplayName,DisplayName), Email=ISNULL(@Email,Email), LastLoginDate=ISNULL(@LastLoginDate,LastLoginDate), LastLoginIP=ISNULL(@LastLoginIP=LastLoginIP), Password=ISNULL(@Password,Password) WHERE Id=@UserId");
            command.Parameters.AddWithValue("@UserId", user.Id);
            command.Parameters.AddWithValue("@UserName", user.UserName, null);
            command.Parameters.AddWithValue("@DisplayName", user.DisplayName, null);
            command.Parameters.AddWithValue("@Email", user.Email, null);
            command.Parameters.AddWithValue("@LastLoginDate", user.LastLoginDate, null);
            command.Parameters.AddWithValue("@LastLoginIP", user.LastLoginIP, null);
            command.Parameters.AddWithValue("@Password", user.Password, null);
 
            return SafeExecuteNonQuery(command);
        }
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="user">Kullanıcı</param>
        /// <returns>Etkilenen satır sayısı</returns>
        public int Delete(Users user)
        {
            SqlCommand command = GetCommand("DELETE FROM Users WHERE Id = @UserId");
            command.Parameters.AddWithValue("@UserId", user.Id);
            return SafeExecuteNonQuery(command);
        }
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="userId">Kullanıcı Id</param>
        /// <returns>Etkilenen satır sayısı</returns>
        public int Delete(int userId)
        {
            SqlCommand command = GetCommand("DELETE FROM Users WHERE Id = @UserId");
            command.Parameters.AddWithValue("@UserId", userId);
            return SafeExecuteNonQuery(command);
        }
    }
}

Data projemizin son görüntüsü:

Service projesine de service sınıfımızı eklersek veri erişimlerimiz tamamlanmış olacak.

UserService.cs
using System.Data;
using WebForms.Domain.DomainModel.Entities;
using WebForms.Domain.Interfaces.Repositories;
using WebForms.Domain.Interfaces.Services;
 
namespace WebForms.Services
{
    public class UserService : IUserService
    {
        private readonly IUserRepository _userRepository;
 
        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }
 
        /// <summary>
        /// Tüm kullanıcılar
        /// </summary>
        /// <returns></returns>
        public DataTable GetAll()
        {
            return _userRepository.GetAll();
        }
 
        /// <summary>
        /// Role göre kullanıcılar
        /// </summary>
        /// <param name="roleName"></param>
        /// <returns></returns>
        public DataTable GetAllByRoleName(string roleName)
        {
            return _userRepository.GetAllByRoleName(roleName);
        }
 
        /// <summary>
        /// Kullanıcı bul
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Users Find(int userId)
        {
            return _userRepository.Find(userId);
        }
 
        /// <summary>
        /// Kullanıcı ekle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Eklenen kullanıcı</returns>
        public Users Insert(Users user)
        {
            return _userRepository.Insert(user);
        }
 
        /// <summary>
        /// Kullanıcı güncelle
        /// </summary>
        /// <param name="user"></param>
        /// <returns>Etkilenen satır sayısı</returns>
        public int Update(Users user)
        {
            return _userRepository.Update(user);
        }
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="user">Kullanıcı</param>
        /// <returns>Etkilenen satır sayısı</returns>
        public int Delete(Users user)
        {
            return _userRepository.Delete(user);
        }
 
        /// <summary>
        /// Kullanıcı sil
        /// </summary>
        /// <param name="userId">Kullanıcı Id</param>
        /// <returns>Etkilenen satır sayısı</returns>
        public int Delete(int userId)
        {
            return _userRepository.Delete(userId);
        }
    }
}

Service projemizin son hali:

Şimdi başta söylediğimiz Unity.WebForms ayarlarımızı yapalım. Website projeside, App_Start klasörü içerisindeki, UnityWebFormsStart sınıfını aşağıdaki gibi değiştirelim.

UnityWebFormsStart.cs
using System.Web;
using Microsoft.Practices.Unity;
using Unity.WebForms;
using WebForms.Domain.Interfaces.Repositories;
using WebForms.Domain.Interfaces.Services;
using WebForms.Services;
using WebFormsProject.Data.Repositories;
 
[assembly: WebActivator.PostApplicationStartMethod(typeof(WebFormsProject.Website.App_Start.UnityWebFormsStart), "PostStart")]
namespace WebFormsProject.Website.App_Start
{
    /// <summary>
    ///  Startup class for the Unity.WebForms NuGet package.
    /// </summary>
    internal static class UnityWebFormsStart
    {
        /// <summary>
        ///     Initializes the unity container when the application starts up.
        /// </summary>
        /// <remarks>
        ///  Do not edit this method. Perform any modifications in the
        ///  <see cref="RegisterDependencies" /> method.
        /// </remarks>
        internal static void PostStart()
        {
            IUnityContainer container = new UnityContainer();
            HttpContext.Current.Application.SetContainer(container);
 
            RegisterDependencies(container);
        }
 
        /// <summary>
        ///  Registers dependencies in the supplied container.
        /// </summary>
        /// <param name="container">Instance of the container to populate.</param>
        private static void RegisterDependencies(IUnityContainer container)
        {
            container.BindInRequestScope<IUserServiceUserService>();
 
            container.BindInRequestScope<IUserRepositoryUserRepository>();
        }
    }
 
    /// <summary>
    /// Bind the given interface in request scope
    /// </summary>
    public static class IOCExtensions
    {
        public static void BindInRequestScope<T1, T2>(this IUnityContainer container) where T2 : T1
        {
            container.RegisterType<T1, T2>(new HierarchicalLifetimeManager());
        }
 
        public static void BindInSingletonScope<T1, T2>(this IUnityContainer container) where T2 : T1
        {
            container.RegisterType<T1, T2>(new ContainerControlledLifetimeManager());
        }
    }
}

Artık Service ve Repository katmanlarımız Dependency Injection için hazır. Örneğin, bir sayfaya UserService i enjekte etmek için ;

Default.aspx.cs
using Microsoft.Practices.Unity;
using System;
using WebForms.Domain.Interfaces.Services;
 
namespace WebFormsProject.Website
{
    public partial class Default : System.Web.UI.Page
    {
        [Dependency]
        public IUserService _userService { getset; }
 
        protected void Page_Load(object sender, EventArgs e)
        {
 
        }
    }
}

Service içerisindeki tüm metodları artık sayfalarımızda kullanabiliriz. Biz örnek kullanımı, birim test üzerinde yapalım.

UnitTestUserService.cs
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WebForms.Domain.DomainModel.Entities;
using WebForms.Domain.Interfaces.Repositories;
using WebForms.Domain.Interfaces.Services;
using WebForms.Services;
using WebFormsProject.Data.Repositories;
 
namespace WebFormsProject.Test.Services
{
    [TestClass]
    public class UnitTestUserService
    {
        private IUnityContainer _unityContainer;
        private IUserService _userService;
        private IUserRepository _userRepository;
 
        [TestInitialize]
        public void TestInitialize()
        {
            _unityContainer = new UnityContainer();
            _unityContainer.RegisterType<IUserServiceUserService>();
            _unityContainer.RegisterType<IUserRepositoryUserRepository>();
 
            _userService = _unityContainer.Resolve<UserService>();
            _userRepository = _unityContainer.Resolve<UserRepository>();
        }
 
        [TestCleanup]
        public void TestCleanup()
        {
            _userService = null;
            _userRepository = null;
            _unityContainer = null;
        }
 
 
        [TestMethod]
        public void TestMethodAddUser()
        {
            var newUser = _userService.Insert(new Users { UserName = "test_user" });
            var insertedUser = _userService.Find(newUser.Id);
 
            // newUser eklenirken null değerler için, default değerler 
            // girilebileceğinden, insertedUser ile birebir uyuşmayacaktır
            // bunun için sadece tanımladığımız özelliği karşılaştırıyoruz
            Assert.AreEqual(insertedUser.UserName, newUser.UserName);
            Assert.AreEqual(1, _userService.Delete(newUser));
        }
 
        [TestMethod]
        public void TestMethodUpdateUser()
        {
            var newUser = _userService.Insert(new Users { UserName = "test_user", DisplayName = "display name", Email = "test_email@mail.com", Password = "1234" });
            newUser.UserName = "updated_test_user";
            _userService.Update(newUser);
 
            Assert.AreEqual(newUser.UserName, "updated_test_user");
            Assert.AreEqual(1, _userService.Delete(newUser));
        }
 
        [TestMethod]
        public void TestMethodDeleteUser()
        {
            var newUser = _userService.Insert(new Users { UserName = "test_user", DisplayName = "display name", Email = "test_email@mail.com", Password = "1234" });
            var affectedRow = _userService.Delete(newUser);
            var deletedUser = _userService.Find(newUser.Id);
 
            Assert.AreEqual(deletedUser, null);
            Assert.AreEqual(1, affectedRow);
        }
    }
}

Tabi test projesinin çalışması için Test projesi içerisine App.Config ekleyip, bunun içine de, connectionString tanımlamamız lazımdır.

Test projesinin görünümü

Projeyi github tan indirip, inceleyebilirsiniz.

https://github.com/alirizaadiyahsi/WebFormsBasicProject

Anlaşılmayan ve ya eksik kalan yerler için yorumlarınızı bekliyorum.

İyi Çalışmalar...

15 yorum:

  1. Güzel çalışma olmuş eline sağlık

    YanıtlaSil
  2. Hocam Elinize sağlık, bu AdoExtensions.cs ve BaseDataAccess.cs sınıflarını bir yere kaydetmek lazım. Bu iki sınıf sürekli işimize yarayacak.

    Böyle saf SQL ile çalışmak bazen gerekebiliyor. Çünkü, veritabanı firmanın kendisinde oluyor. Uygulama onaların veri tabananına bağlandığı için, ORM kullanmak bazen projeyi kırılgan yapabiliyor. Bundan dolayı çok değerli bir makale olmuş...

    YanıtlaSil
  3. Tabi birde özellikle Dependency Injection konusunu asp.net web forms la birlikte olunca baya iyi olmuş. Tekrar elinize sağlık.

    YanıtlaSil
  4. Merhaba, derslerinizi VS 2012 Express ile öğrenmek mümkün mü? Mümkünse ben nerede hata yapıyorum bilemiyorum. Mesela: "Asp.Net Webforms ile dependency injection nasıl yapılır" örnek projenizi uygulamaya çalıştım fakat şu hatalarla karşılaştım.

    UnitTestUserService.cs dosyası refaransına,

    using Microsoft.Practices.Unity
    using Microsoft.Practices.Unity.Configuration
    ekleyemiyorum.
    Acaba VS Express sürümü ile bunlar yapılamıyor mu?

    YanıtlaSil
    Yanıtlar
    1. Kodları kendiniz yazdıysanız problem olmaz. Ama benim yazdığım örnek uygulamayı VS2010 açamazsınız.Aldığınız hata, Unity dosyalarını referans etmediğinizden kaynaklanıyor. Aslına bakarsanız, test projesi içerisinde unity ile service ve repository oluşturulmaz. Sadece kullanımını göstermek için yazdımıştım. Ama yinede, test projenize, website projesi içerisindeki, bin dosyasından, unity dll lerini referans edebilirsiniz. Test projenize, Sağ tık>Add Refrence diyin, açılan pencereden, Browse diyerek, web projesi>bin>debug içerisindeki unity e ait dll leri ekleyin. En son resimde test projesinin görünümünü vermişim, refrences dosyasının içerisine bakarsanız, unity e ait dll ler mevcut. Bu şekilde sorununuz çözülür.

      İkinci bir yöntem doğrıdan, unity kullanmadan service ve repository sınıflarını tanımlayıp kullanmak. Yani;

      private IUserService _userService;
      private IUserRepository _userRepository;

      [TestInitialize]
      public void TestInitialize()
      {
      _userRepository = new UserRepository();
      _userService = new UserService(_userRepository);
      }

      şeklinde değiştirirseniz, unity kullanamaya gerek yok. Yani test projesinde injection yapmaya gerek kalmaz. Direk service sınıflarını test edebilirsiniz. Doğrusu bu, makaleyi de yakın zamanda bu şekilde güncelleyeceğim.

      Takıldığınız, anlamadığınız, anlaşılmayan ve eksik kalan yerleri sorabilirsiniz.

      Sil
    2. VS2012 ile çalışıyorum. Kodları anlatımınızdan yararlanarak kendim yazıyorum.
      Karşılaştığım sorun şu anladığım kadarıyla.
      WebFormsProject.Test
      Referances klasörüne sağ tıklayıp Add Reference dediğimde ne Assemblies ne de diğer başlıklarda,

      Microsoft.Practices.ServiceLocation
      Microsoft.Practices.Unity
      Microsoft.Practices.Unity.Configuration
      bulamıyorum. NuGet paketlerinde de bulamıyorum.
      Visual Studio'nun express sürümlerinde yok mu acaba diye düşündüm.
      Neyse önerdiğiniz ikinci yöntemle deneyeceğim.
      Çok teşekkürler

      Sil
    3. nuget, için expressle alaklı bir sorun olabilir ama, diğer türlü ekleyebilirsiniz. Web projesini build edince, bin dosyası içerisine unity dll leri atar.

      Sil
  5. Merhaba Hocam
    Öncelikle eline sağlık.Çok güzel bir yazı...

    Okurken gözüme birşey ilişti.
    UnityWebFormsStart sınıfı içersin de ,PostStart methodu içersinde conteiner'a oluşturuluyor ve tipler conteinera register ediliyor.

    PostStart methodunun açıklamasında "Initializes the unity container when the application starts up." yazıyor.
    Burdaki lifecycle'ı acaba nasıl olur merak ediyorum.
    Süreki gelişen bir projede, 1000'lerce sınıfın olduğu/olabileceği bir proje de acaba conteiner'a register etme performansı nasıl olur..
    Bu konuda tecrübesi olan arkadaşlar bizimle paylaşırsa sevinirim :)

    HttpContext.Current.Application içersine set ettiğimiz conteinerı, Default.aspx.cs içersinde kullanabilmemiz için, HttpContext.Current.Application içersindeki container dan kullanacağımız tipi resolve etmemiz gerekiyor.

    System.Web.UI.Page sınıfına, HttpContext.Current.Application içersindeki container dan istediğimiz tipi, resolve edip döndüren bir extension method yazsak nasıl olur :)

    takipteyiz :)

    YanıtlaSil
    Yanıtlar
    1. DI (Dependency Injection) yapılacak olan sınıflar sınırlıdır. Yani öyle 1000 lerce olmaz. Bundan dolayıda performansı çok etkileyeceğini sanmıyorum. Bunu neye göre söylüyorum; kendim test etmedim, ama birçok açık kaynak proje inceledim, DI yapılan sınıf sayıları da az. Ama tabi yine de bu konuda farklı fikri olan paylaşırsa iyi olur.

      Ayrıca Default.aspx.cs sayfasında resolve etmeye gerek yok. Injection işlemi [Dependency] public IUserService _userService { get; set; } şeklinde yapılıyor. Test metodu içerisinde kullanabilmek için resolve metodunu kullandım.

      Extension metod konusunda ise, Resolve metodu zaten dediğiniz işi yapıyor, dana neden extension yazılsın ki? UnityContainer nesnesi oluşturmamak için mi böyle düşündünüz?

      Sil
  6. Dependency attribute'ü _userService'i initialize ediyor ,
    yani otomatik bir resolve işlemi oluyor o zaman.Ben olmadığını düşündüm o zaman :)
    Tamam extension methoda gerek kalmadı o zaman orda hem fikiriz.
    Otomatik resolve etmesi bu çok iyi oldu. :)

    DI (Dependency Injection) yapılacak olan sınıflar sınırlıdır.Yani öyle 1000 lerce olmaz. demişsin hocam.
    Ama büyük bir projede birbiri ile ilişkili 1000'den bile daha fazla sınıf olabilir.

    O zaman nasıl yapıcaz ?
    O ilişkili sınıfları container 'a register etmeyecek miyiz ?
    Yanlış mı düşünüyorum. :)

    YanıtlaSil
    Yanıtlar
    1. Unity aslında kendi oluşturmadıgımız sınıflara DI yapmak için kullanılıyor. Mesela Default.aspx.cs .net framework içerisindeki PageFactory metod ile üretiliyor. Kendimiz bu sınıf için contructor metod yazmaya kalkarsak hata alırız. Bundan dolayı ya factory metodunu parametre alabilecek sekilde extend edecegiz ya da unity kullanacagız. Yani aslında unity, müdahale edemedigimiz sınıflara otomatik olarak parametre alan bir constructor metod eklemiş gibi oluyor.Yani kendi oluşturdugumuz sınıflara DI yapacaksak unity e gerek yok. Ayrıca sadece iş yapan sınıflar için DI yapılır. Bir kaç acık kaynak proje incelereniz sanırım ne demek istediğim anlasılır.

      Sil
    2. Hocam Merhabalar çok güzel bir örnek olmuş bu örnek de bahsettiğimiz yapıları kullanılan bir kaç örnek erkan yapabilir misiniz?

      Sil
    3. Zaman olursa olur inşallah. Bu arada Default.aspx.cs içerisindeki

      [Dependency]
      public IUserService _userService { get; set; }

      kod satırı aslında kullanıma hazır. Bu sınıf içerisinde artık, service içerisindeki metodları çağırabiliriz. Örneğin; page load içerisinde

      protected void Page_Load(object sender, EventArgs e)
      {
      gridView1.DataSource = _userService.GetAll();
      }

      gibi...

      Sil
  7. Selam
    Şurda bir proje buldum.
    https://github.com/MarlabsInc/SocialGoal

    inceliyorum

    YanıtlaSil
    Yanıtlar
    1. biraz inceledim. güzel paylaşım olmuş. bende indirip detaylı inceleyeceğim. ama sanki biraz hindu tarzı da var gibi (sağ elle sol kulagı tutma)

      Sil