.NET6_MVC開發三層式架構開發part1_搭配Autofac來實作IOC_專案功能模組架構前期準備

 






這邊我要來透過三層式架構創建專案
那基本上會拆分成

(1).UI顯示層

(2).業務邏輯層(BLL)

(3).資料庫訪問層(DAL)



這裡會透過將三層都進行interface設計
透過IOC容器來去將interface與具體的實踐做串接








新建好空方案
並新建四個folder各自分別命名






準備好資料庫與相應表


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use master;
create database SchoolDb;

use SchoolDb;
create table Student
(
  [Id] [uniqueidentifier] primary key not null,
  [Name] [nvarchar](20) not null,
  [Age] [int] not null,
  [Sex] [bit] not null,	--1:男生,0:女生
  [Address] [nvarchar](500) not null,
  [CardId] [varchar](12) not null	--身分證字號
)

use SchoolDb;
create table Course
(
  [Id] [uniqueidentifier] primary key not null,
  [Name] [nvarchar](200) not null,	--課程名稱
  [Summary] [nvarchar] (500) null	--課程描述
)


--成績屬於哪位學生修的哪門課

use SchoolDb;
create table Score
(
  [Id] [uniqueidentifier] primary key not null,
  [Mark] [numeric](5,2) not null,	--成績(分數)
  [CourseId] [uniqueidentifier] foreign key references [Course](Id) not null,	--課程ID 成績對應哪門課
  [Student] [uniqueidentifier] foreign key references [Student](Id) not null,	--學生ID 成績對應哪位學生
  [Semester] [nvarchar](200) not null
)

針對這些表去產生相應的Model Class於DataModel目錄下



學生

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using System;

public class Student
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public bool Sex { get; set; }

    public string Address { get; set; }

    public string CardId { get; set; }

}

成績

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using System;

public class Score
{
    public Guid Id { get; set; }

    public decimal Mark { get; set; }

    public Guid CourseId { get; set; }

    public Guid Student { get; set; }

    public string Semester { get; set; }

}

課程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using System;

public class Course
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public string Summary { get; set; }

}


針對UI顯示層

新增一個asp.net core mvc專案


配置連線字串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolDbConn": "Server=.\\SQLEXPRESS;Database=SchoolDb;uid=sa;pwd=rootroot"
  }
}










針對資料庫訪問層(DAL)

新增一個Class Library Project



把預設的Class刪除






Nuget補安裝Microsoft.Data.SqlClient
ado.net最新微軟釋出的存取套件








建立ISysDataAccess的類別庫
用於DB訪問的介面,負責定義對DB訪問操作與資料存取相應規範。

可以定義 IBaseSysDataAccess 抽象的CRUD操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace My3TierAdoApp.ISysDataAccess
{
    public interface IBaseSysDataAccess
    {
        int Create(string sql);

        SqlDataReader Query(string sql);

        int Update(string sql);

        int Remove(string sql);
    }
}


建立SysDataAccess的類別庫
並新增SysDataAccess的Class
之後負責實踐ISysDataAccess,具體相應操作實踐。


配置nuget套件
Microsoft.Extensions.Configuration.Json

添加過Microsoft.Data.SqlClient套件
添加ISysDataAccess的類別庫參考
由於SysDataAccess類別庫專案要去繼承ISysDataAccess類別庫專案當中的IBaseSysDataAccess





在此類別庫專案定義一個SqlDbHelper的類別

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace My3TierAdoApp.SysDataAccess
{
    public static class SqlDbHelper
    {
        private static string connStr;
        static SqlDbHelper()
        {
            //創建組態Builder物件並相應獲取appsetting.json檔案
            var configuration = new ConfigurationBuilder().Add(
                new JsonConfigurationSource { Path="appsetting.json",ReloadOnChange=true}
            ).Build();
            connStr = configuration.GetConnectionString("SchoolDbConn");
        }

        /// <summary>
        /// 可傳入sql字串並參數化方式進行Insert,Delete,Update並回傳影響筆數
        /// </summary>
        /// <param name="strSQL"></param>
        /// <param name="htParams"></param>
        /// <returns></returns>
        public static int ExecuteNonQuery(string strSQL, Hashtable htParams = null)
        {
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                SqlCommand sqlCommand = conn.CreateCommand();
                sqlCommand.CommandText = strSQL;
                sqlCommand.CommandType = CommandType.Text;
                PrepareParams(htParams, sqlCommand);
                return sqlCommand.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 透過SQL語法回傳資料表中資料採用ReadOnly Data Reader方式獲取
        /// </summary>
        /// <param name="strSQL"></param>
        /// <param name="htParams"></param>
        /// <returns></returns>
        public static SqlDataReader GetSqlDataReader(string strSQL, Hashtable htParams = null)
        {
            SqlConnection conn = new SqlConnection(connStr);
            conn.Open();
            SqlCommand sqlCommand = conn.CreateCommand();
            sqlCommand.CommandText = strSQL;
            sqlCommand.CommandType = CommandType.Text;
            PrepareParams(htParams, sqlCommand);
            //設置CommandBehavior.CloseConnection代表當關閉Data Read後也同時將連線關閉
            return sqlCommand.ExecuteReader(CommandBehavior.CloseConnection);
        }

        /// <summary>
        /// 回傳首橫列首直行的值(first row and first column value)
        /// ex: select top 1 , select sum() , select count(*)
        /// </summary>
        /// <param name="strSQL"></param>
        /// <param name="htParams"></param>
        /// <returns></returns>
        public static object ExecuteScalar(string strSQL, Hashtable htParams = null)
        {
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                SqlCommand sqlCommand = conn.CreateCommand();
                sqlCommand.CommandText = strSQL;
                sqlCommand.CommandType = CommandType.Text;
                PrepareParams(htParams, sqlCommand);
                return sqlCommand.ExecuteScalar();
            }
        }

        private static void PrepareParams(Hashtable htParams, SqlCommand sqlCommand)
        {
            if (htParams != null)
            {
                foreach (DictionaryEntry entry in htParams)
                {
                    sqlCommand.Parameters.AddWithValue(entry.Key.ToString(), entry.Value);
                }
            }
        }

    }
}


新定義一個BaseSysDataAccess的類別(用於具體實作IBaseSysDataAccess)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Microsoft.Data.SqlClient;
using My3TierAdoApp.ISysDataAccess;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace My3TierAdoApp.SysDataAccess
{
    public class BaseSysDataAccess : IBaseSysDataAccess
    {
        public int Create(string sql)
        {
            return SqlDbHelper.ExecuteNonQuery(sql);
        }

        public SqlDataReader Query(string sql)
        {
            return SqlDbHelper.GetSqlDataReader(sql);
        }

        public int Remove(string sql)
        {
            return SqlDbHelper.ExecuteNonQuery(sql);
        }

        public int Update(string sql)
        {
            return SqlDbHelper.ExecuteNonQuery(sql);
        }
    }
}





建立SysDataAccessContainer的IOC容器類別庫


建立SysDataAccessContainer
用於作為倉儲容器,透過Autofac來做依賴注入容器
實踐ISysDataAccess 和 SysDataAccess 的相應串聯,負責SysDataAccess物件的實體化
針對SysDataAccessContainer類別庫專案新增nuget套件 "Autofac"


針對SysDataAccessContainer類別庫專案
新增IOCContainer.cs的class

由於在IOC容器中需要去建立ISysDataAccess 和 SysDataAccess之間的關聯。
因此需要額外補添加
上面兩個類別庫專案參考




撰寫IOC容器 類別 IOCContainer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using Autofac;
using Autofac.Core.Activators;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace My3TierAdoApp.SysDataAccessContainer
{

    /// <summary>
    /// 後續BLL要呼叫資料存取訪問層可以不用透過new來實體化SysDataAccess相關物件,而是
    /// 透過IOC容器中來間接獲取物件實體並映射至對應interface
    /// 換言之,有點類似統一入口,避免過多入口存取呼叫導致混亂,較難管理。
    /// </summary>
    public class IOCContainer
    {
        public static IContainer Container = null;

        public static T Resolve<T>()
        {
            try
            {
                if (Container == null)
                {
                    Instance();
                }
            }
            catch (Exception ex)
            {
                throw new Exception("IOC Initial instance fail," + ex.Message);
            }
            //Resolve方法可確保Instance()只生成一次並根據傳過來的泛型T(任一種類型)於容器中提取相應實體。
            return Container.Resolve<T>();
        }

        public static void Instance()
        {
            var builder = new ContainerBuilder();
            //使用RegisterType來註冊組件
            //組件部分統一關係皆為 具體的Class 
            //與相應繼承(要實作)的抽象Interace 
            //builder.RegisterType<具體的Class>().As<相應實作的抽象Interace>().InstancePerLifetimeScope();
            Container = builder.Build();
        }
    }
}


針對業務邏輯層(BLL)

新建一個IService類別庫專案



新建一個IBaseService
主要用來規範一些業務邏輯的共用方法
在泛型interface這邊約束T必須為引用類型(reference type)
並定義4個業務邏輯操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace My3TierAdoApp.IService
{
    public interface IBaseService<T> where T : class
    {
        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        bool Add(T t);
        /// <summary>
        /// 刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        bool Delete(string id);
        /// <summary>
        /// 獲取單一筆資料
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        T GetSingle(string id);
        /// <summary>
        /// 資料更新
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        bool Update(T t);
        /// <summary>
        /// 資料查詢
        /// </summary>
        /// <param name="keyword"></param>
        /// <returns></returns>
        List<T> GetList(string keyword);
    }
}

那這裡藉由泛型好處就在於
你可以傳遞int,double,string之類任一種類型

若以多個(column)屬性的資料要一起提交給資料庫
就可以使用model作為參數

如果只是單一個值提交給資料庫則使用單個參數
IBaseService這個介面服務目的在於
去實踐以物件實體來傳遞資料

在和DAL對接時則需要將Data Model 轉為T-SQL

新建Service類別庫專案

有了抽象的IService介面也要有相應具體的實踐

那這個專案由於涉及到諸多專案參考
需要添加
My3TierAdoApp.IService
My3TierAdoApp.ISysDataAccess
My3TierAdoApp.SysDataAccessContainer


那並不包含具體實作的這個類別
My3TierAdoApp.SysDataAccess
在這邊會藉由My3TierAdoApp.SysDataAccessContainer定義的容器
來建立具體的SysDataAccess 在賦予給ISysDataAccess

藉由IOC來取得相應實體,改善傳統三層式架構於業務邏輯層會去實體化DAL物件
導致有強依賴的缺點。

那在這專案中並不需要再去建立IBaseService的介面沒必要了
只要所有功能模組有相應繼承(實作)IBaseService去實作參數實體化
於Service專案中就可讓功能模組的class繼承對應功能模組的介面
即可實踐專用的業務邏輯處理。



新建ServiceContainer類別庫專案

於BLL中也需要有一個IOC容器的專案
用於針對UI層要去存取BLL中方法時候,不會去依賴Service 物件實體
而是透過IOC容器來獲取有實作 IService 的Service物件實體

這裡於專案中需要補添加
IService與Service兩項專案參考


一樣需要nuget補安裝Autofac套件



撰寫IOC容器 類別 IOCContainer
可以將功能模組中的業務邏輯操作的class與interface建立關係
並供 UI層去呼叫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using Autofac;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace My3TierAdoApp.ServiceContainer
{
    public class IOCContainer
    {
        public static IContainer Container = null;

        public static T Resolve<T>()
        {
            try
            {
                if (Container == null)
                {
                    Instance();
                }
            }
            catch (Exception ex)
            {
                throw new Exception("IOC Initial instance fail," + ex.Message);
            }
            return Container.Resolve<T>();
        }

        public static void Instance()
        {
            var builder = new ContainerBuilder();
            //builder.RegisterType<具體的Class>().As<相應實作的抽象Interace>().InstancePerLifetimeScope();
            Container = builder.Build();
        }
    }
}














留言

這個網誌中的熱門文章

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header

經得起原始碼資安弱點掃描的程式設計習慣培養(三)_7.Cross Site Scripting(XSS)_Stored XSS_Reflected XSS All Clients

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題