Sinyi.Net協助開發人員快速建立Web站台、API服務和Console程式。提供參數管理、依賴注入、身分驗證等多種功能模組,用以簡化開發流程並滿足多變的商業需求。
Sinyi.Net架構主要分為
Presentation(表現層): 服務對外的介面,常見的API介面或是Web畫面呈現跟背景程式等等.
Domain(邏輯層): 負責整個服務的核心邏輯,以架構預設為
除了Context與Entity之外其他都是針對使用情境與條件做設計.
Access(資料存取層): 串接資料的階層,依據資料來源渠道進行命名.
外界透過表現層的接口執行動作,在邏輯層依據執行條件來匹配符合的邏輯。而在依據情境去使用對應的實體並產生行為,再透過行為去資料層進行所需的資料串接。
Sinyi.Net架構下運作是依靠兩個底層套件做運行,一個是放置於起始檔的Sinyi.AspNetCore/Sinyi.NetCore跟放置在需依賴注入物件的其他專案檔Sinyi.Registration。
依賴注入(DI)是一種設計模式,透過外部化依賴項目的方式,使程式碼更靈活、易測試、易維護。透過將依賴項目注入到類別中,提高了程式碼的鬆耦合性、可測試性、可維護性、靈活性和可擴展性。
而Sinyi.Net包裝並調整了原生.NetCore的依賴注入方式,透過設定檔的階層設計去進行註冊與調用。讓開發者專注於邏輯開發且透過靈活的注入方式為不同情境做架構設計。
我們先預設我們有一個負責建築服務的核心邏輯取名為BuildingContext,其方法GetHouse需使用為IHouseRepository的房子介面的實例MsSqlHouseRepository。可以得知其實例是從MsSql取得房子資訊。
關係如下圖:
在原生.NetCore會是在起始檔寫依賴注入的語法進行註冊。
using Sinyi.building;
using Sinyi.building.Accesses;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddTransient<BuildingContext>();
builder.Services.AddTransient< MsSqlHouseRepository>();
BuildingContext在建構子注入IhouseRepository的實例。
namespace Sinyi.building
{
public class BuildingContext
{
// Fields
private readonly IHouseRepository _houseRepository;
// Constructors
public BuildingContext(IHouseRepository houseRepository)
{
_houseRepository = houseRepository;
}
// Methods
public House GetHouse()
{
var house = this._houseRepository.GetHouse();
return house;
}
}
}
而在Sinyi.Net的依賴注入模式,底層會依據設定檔的階層進行解析並透過註冊的實例進行比對再做注入動作,以下為在Sinyi.Net下的依賴注入方法。
先依據config與對應環境在Sinyi.XXX.json內作階層組合。
以BuildingContext為例其在建構子注入參數中有個命名為houseRepository的實例,故在以BuildingContext為命名的階層內新增個key名為HouseRepository(大小寫無區別)且value設為要使用實例的型態。
而所需使用的實例也必須註冊,故需以其型態為命名組合它的階層。
{
//請以專案名稱命名,底層會
"Sinyi.building": {
// BuildingContext
"BuildingContext": {
//key為HouseRepository對應建構子內參數的名稱(不分大小寫),value為對應的實例type
"HouseRepository": "MsSqlHouseRepository"
},
// IHouseRepository
"MsSqlHouseRepository": {}
}
}
而在需註冊注入的實例需添加RegisterService屬性。
namespace Sinyi.building
{
//需添加RegisterService讓底層進行此類別得依賴注入註冊
[RegisterService<BuildingContext>()]
public class BuildingContext
{
// Fields
private readonly IHouseRepository _houseRepository;
// Constructors
public BuildingContext(IHouseRepository houseRepository)
{
// Default
_houseRepository = houseRepository;
}
// Methods
public House GetHouse()
{
var house = _houseRepository.GetHouse();
// Return
return house;
}
}
}
若實例以intetface進行註冊與使用,則需帶入interface的型態。
using Sinyi.Registration;
namespace Sinyi.building.Accesses
{
//需添加RegisterService讓底層進行此類別得依賴注入註冊
[RegisterService<IHouseRepository>()]
public class MsSqlHouseRepository : IHouseRepository
{
// Constructors
public MsSqlHouseRepository() { }
// Methods
public House GetHouse()
{
//sql command
var house = new House() { Name = "Sql House" };
return house;
}
}
}
假設我們的房子資訊除了從我們自己資料庫獲得之外,也需透過外面第三方的資料來源來匹配或更新邏輯。勢必須開一條渠道在BuildingContext做整合。
假設以從google的API獲取房子資訊,關係圖如下:
其情境在在原生.NetCore的依賴注入語法稍有修改:
using Sinyi.building;
using Sinyi.building.Accesses;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddTransient<MsSqlHouseRepository>();
builder.Services.AddTransient<FromGoogleHouseRepository>();
//把剛加的DI Services取出來
var serviceProvider = builder.Services.BuildServiceProvider();
var sqlHouse = serviceProvider.GetService<MsSqlHouseRepository>();
var googleHouse = serviceProvider.GetService<FromGoogleHouseRepository>();
//依據參數對應去注入符合情境的BuildingContext實例
builder.Services.AddTransient(sp =>
{
return new BuildingContext(sqlHouse, googleHouse);
});
其在BuildingContext的調整:
namespace Sinyi.building
{
public class BuildingContext
{
// Fields
private readonly IHouseRepository _sqlHouseRepository;
private readonly IHouseRepository _fromGoogleRepositroy;
// Constructors
public BuildingContext(IHouseRepository sqlHouseRepository, IHouseRepository fromGoogleRepositroy)
{
_sqlHouseRepository = sqlHouseRepository;
_fromGoogleRepositroy = fromGoogleRepositroy;
}
// Methods
public IList<House> GetHouse()
{
List<House> houselist = new();
var sqlHouse = _sqlHouseRepository.GetHouse();
houselist.Add(sqlHouse);
var googleHouse = _fromGoogleRepositroy.GetHouse();
houselist.Add(googleHouse);
return houselist;
}
}
}
雖然符合需求情境,但若專案隨著推行不斷增加須注入的模組或不調整程式,像是因環境不同而有不同的注入條件或是注入模組過多階層過於複雜將會導致開發者維護困難。
在Sinyi.Net的架構下,只需為須註冊的模組增添RegisterService屬性。
using Sinyi.Registration;
namespace Sinyi.building.Accesses
{
[RegisterService<IHouseRepository>()]
public class FromGoogleHouseRepository : IHouseRepository
{
public FromGoogleHouseRepository(){ }
public House GetHouse()
{
var house = new House() { Name = "Google House"};
return house;
}
}
}
在以上面BuildingContext的建構子參數為例,在對應環境的設定檔做階層命名:
{
"Sinyi.building": {
// BuildingContext
"BuildingContext": {
//key為BuildingContext參數的名稱,於value為下面註冊的實例名稱(型態名稱)
"sqlhouseRepository": "MsSqlHouseRepository",
"fromGoogleRepository": "FromGoogleHouseRepository"
},
// IHouseRepository
"MsSqlHouseRepository": {},
"FromGoogleHouseRepository": {}
}
若情境會需要根據不同環境有不同的注入實例,像是僅測試區不能使用到資料庫與第三方資料,須自產假資料作提供跑完服務。那在原生的案例會需要在專案起始檔的部分針對注入做一些條件判斷。
///get Environment
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddTransient<MsSqlHouseRepository>();
builder.Services.AddTransient<FromGoogleHouseRepository>();
//把剛加的DI Services取出來
var serviceProvider = builder.Services.BuildServiceProvider();
var sqlHouse = serviceProvider.GetService<MsSqlHouseRepository>();
var googleHouse = serviceProvider.GetService<FromGoogleHouseRepository>();
//測試區專用模擬資料
var mockHouse = serviceProvider.GetService<MockHouseRepository>();
//依據參數對應去注入符合情境的BuildingContext實例
builder.Services.AddTransient(sp =>
{
//若是測試區改用測試專用資料
if (env.Equals("Development"))
return new BuildingContext(mockHouse, mockHouse);
return new BuildingContext(sqlHouse, googleHouse);
});
在Sinyi.Net可以透過不同環境的設定檔設計對應的注入階層,服務會根據環境去讀取對應設定檔並做後續依賴注入動作。
在Sinyi.Net除了底層套件之外,也可以透過功能套件的擴充來協助專案開發。而套件的設定與使用也是透過設定檔注入方式做處理,設定檔分為依據環境分類的Sinyi.DevOps.Hosting.json跟預設的appsettings.json。
以MsSql連線套件Sinyi.Data.MsSqlClient.Hosting為例:
先根據環境資料夾的設定檔添加套件注入對應階層
//mssql連線套件
"Sinyi.Data.MsSqlClient": {
"MsSqlClientFactory": {
"ConnectionStrings": {
//建立不同的key在access層進行連線抽換
"DefaultConnection": "sql connection string",
"third-party": "sql connection string"
}
}
}
在使用的模組建構子直接增添套件物件:
[RegisterService<IHouseRepository>()]
public class MsSqlHouseRepository : IHouseRepository
{
private MsSqlClientFactory _sqlClientFactory;
// Constructors
//建構子加了MsSqlClientFactory進行資料庫連線動作
public MsSqlHouseRepository(MsSqlClientFactory sqlClientFactory)
{
_sqlClientFactory = sqlClientFactory;
}
// Methods
public House GetHouse()
{
//sql command
using (var cmd = _sqlClientFactory.CreateCommand("DefaultConnection"))
{
cmd.CommandText = "SELECT 1;";
var result = cmd.ExecuteNonQuery();
}
var house = new House() { Name = "Sql House" };
return house;
}
}
若Sinyi.Net提供的功能套件或本身的底層設定不夠滿足開發專案的需求,開發者也能透過Sinyi.AspNetCore.Hosting(用於AspNetCore架構) 與Sinyi.NetCore.Hosting(用於NetCore架構) 的注入套件來擴充自己服務。
假設情境是我們因為特殊需求需要擴充Newtonsoft.Json來替代原生的System.Text.Json,勢必先需取用其套件並進行設定。
在這之前我們可以建立一個專門放注入相關邏輯的並以專案名為開頭的命名,例如Sinyi.Building的專案因有擴充需求而有了Sinyi.Building.Hosting的專案檔。
並根據參考此專案的起始檔是否為AspNetCore而選擇注入套件。以此專案為例因為是開發API故選擇Sinyi.AspNetCore.Hosting來進行注入設計。
再在以此專案安裝Microsoft.AspNetCore.Mvc.NewtonsoftJson等等在注入端使用其設定方法。
專案檔安裝完所需套件後我們需要準備兩個檔案:
一個是以此套件名稱+builder的cs檔來作為此套件建立的來源,以目前例子來說我們建立一個NewtonsoftJsonBuilder.cs的檔案。
另一個可以是以此套件名稱+Setting的cs檔來作為此套件的設定檔,以目前例子我們建立一個NewtonsoftJsonSetting.cs的檔案。
我們在NewtonsoftJsonBuilder內去繼承注入套件中的ServiceBuilder,並在其泛型中放入NewtonsoftJsonSetting。
### NewtonJsonBuilder.cs檔 ###
using Microsoft.AspNetCore.Builder;
using Sinyi.AspNetCore.Hosting;
namespace Sinyi.Building.Hosting
{
public class NewtonJsonBuilder : ServiceBuilder<NewtonsoftJsonSetting>
{
//建構子必備且須帶入設定檔階層名稱
public NewtonJsonBuilder(string @namespace, string service = null) : base(@namespace, service)
{
}
//注入邏輯用來設定套件參數的地方
public override void ConfigureService(WebApplicationBuilder builder, NewtonJsonBuilderSetting setting)
{
throw new NotImplementedException();
}
}
}
在套件設定檔這我們設個調整時間格式的字串屬性
### NewtonJsonBuilderSetting.cs檔 ###
public class NewtonJsonBuilderSetting
{
public string DataFormat { get; set; }
}
再來我們決定此套件注入的設定檔:
我們在Sinyi.DevOps.Hosting.json或預設的appsettings.json根據這套件注入使否根據環境而有變異來決定注入設定放在哪。以此為例假設各環境的Json設定都一樣我們放在appsettings.json就好。
我們在設定檔組一個名為NewtonJson的階層並把剛剛於NewtonJsonBuilderSetting的屬性名稱設定為key並帶入我們想注入的value。(顧名思義設定檔階層內的內容須與builder那放在泛型的setting.cs內容屬性一樣)
"NewtonJson": {
"DataFormat": "MMMM dd, yyyy"
}
回到NewtonJsonBuilder我們開始把設定檔的設定與套件方法帶進來:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Sinyi.AspNetCore.Hosting;
namespace Sinyi.Building.Hosting
{
public class NewtonJsonBuilder : ServiceBuilder<NewtonJsonBuilderSetting>
{
//建構子必備且須帶入設定檔階層名稱
public NewtonJsonBuilder() : base("NewtonJson") {}
public override void ConfigureService(WebApplicationBuilder builder, NewtonJsonBuilderSetting setting)
{
//WebApplicationBuilder調用到ServiceCollection並利用AddControllers去使用AddNewtonsoftJson
builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateFormatString = setting.DataFormat;
});
}
}
}
最後在測試看看透過builder﹑setting與對應設定檔是否有將設定值傳到buillder內進行注入邏輯設定。
Sinyi.Net起始專案分為Api為主的專案與Web為主的專案,皆使用Sinyi.AspNetCore進行設定包裝與套件注入。其中差異在於Api須加上Swagger與ApiVersion的功能,而Web可使用StaffAuth的驗證服務等網站驗證功能。
API版本預設套用Sinyi.AspNetCore.SwaggerGen.Hosting與Sinyi.AspNetCore.Versioning.Hosting作為swagger與api版本控制的處理。
在控制器中必須添加Module屬性,用來分類服務網域
若Module沒有帶入任何值,則下圖網域首頁即為:Domain/Home/Index
假設Modile帶入City的話,則網域首頁即為: Domain/City/Home/Index
而在每個API的方法之上需添加ApiVersion(系統內建)的屬性,為該API作版本控制。
假設需求有同名方法且有不同api版本,需在其同名方法上添加屬性ActionName並帶入原本方法名,而同名方法須改可以辨別的名稱。
最後可以在Swagger中選擇不同api版本進行swagger測試:
Web版本跟API版本除了多了View的部分,也可以套用Sinyi.StaffAuth套件來進行員工驗證。
此功能會在另開一版Sinyi.StaffAuth服務作介紹。(未來把連結貼於此)
待補充(將逐一寫新文章詳細介紹)