[Day7] C# MVC Model模型連結資料庫,使用Entity Framework - C#&AspNetCore

在上回 [Day6] C# MVC Model模型驗證 - C#&AspNetCore ,我們學會利用Model建立驗證機制。
而這回將會介紹如何把Model與真實的資料庫連結再一起。

Entity Framework

Entity Framework (又稱ADO.NET Entity Framework) 是微軟以 ADO.NET 為基礎所發展出來的物件關聯對應 (O/R Mapping) 解決方案。
Entity Framework 利用了抽象化資料結構的方式,將每個資料庫物件都轉換成應用程式物件 (entity),而資料欄位都轉換為屬性 (property),關聯則轉換為結合屬性 (association),讓資料庫的 E/R 模型完全的轉成物件模型,如此讓程式設計師能用最熟悉的程式語言來呼叫存取。

簡言之,就是一個透過物件的方式去操作資料,而不透過原生SQL的作法。
(但我還是比較熟悉原生的SQL,因為換什麼語言寫都通,但在微軟架構下還是用他提供的會比較好)

安裝Entity Framework

安裝:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.Tools

過程:
1.Dependencies> Manage nuget packages

2.安裝Microsoft.EntityFrameworkCore

3.安裝Microsoft.EntityFrameworkCore.SqlServer

4.安裝Microsoft.EntityFrameworkCore.Design

5.安裝Microsoft.EntityFrameworkCore.Tools

使用Entity Framework

Entity Framework Core 有提供兩種開發方式

  • DB First:我已經有現有資料庫了,只是用程式去操作他。
  • Code First:我還沒有資料庫,想用程式碼去建立資料庫。
  • Model First:先定義好模型在產生資料表的方式。

以我在Laravel的架構下寫過的經驗,會覺得Code First會比較好,因為有更動歷程,也比較能知道異動。
而DB First則是把兩者分開來做,優點是很直接,缺點就是異動後很難知道改了哪。

而這回主要以DB First為主。

1.開啟package manager console

2.之後會出現命令列

3.之後把SQL Server打開,看目前資料庫,我們預計把Orders資料庫給整合進來

4.使用Scaffold-DbContext指令,來還原Orders資料庫的Models

1
Scaffold-DbContext "Server=.\SQLExpress;Database=Orders;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models

請自行替換字串的內容:

  • Database:資料庫名稱

之後Models資料夾就會自動產生Model了

再來至Startup.cs添加資料庫連線服務

Startup.cs
1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{

services.AddControllersWithViews();
// 資料庫配置
var connection = @"Server=.\SQLExpress;Database=Orders;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<OrdersContext>(options => options.UseSqlServer(connection));
}

再來至控制器HomeController.cs增加方法

HomeController.cs
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
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

// 添加Context
private readonly OrdersContext _context;


public HomeController(ILogger<HomeController> logger, OrdersContext context)
{
_logger = logger;
// 初始化Context
_context = context;
}

[HttpGet]
public IActionResult Create()
{
return View();
}

[HttpPost]
public IActionResult Create(UsersModel usersmodel)
{
if (ModelState.IsValid)
{
//如果連結資料庫在這進行儲存

//導回首頁
return RedirectToAction(nameof(Index));
}
return View(usersmodel);
}


public IActionResult Index()
{
// 透過Context取得資料
var model = _context.Customers.Select(b => new Customer
{
Name = b.Name,
Number = b.Number,
Tel = b.Tel
}).ToList();

// 取第一列資料出來
Customer m = model[0];

return View(m);
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

再來在View上修改

1
2
3
4
5
6
7
8
9
10
11
12
13
@model WebApplication1.Models.Customer;
@{
ViewData["Title"] = "Home Page";
}
<ul>
<li>@Model.Number</li>
<li>@Model.Name</li>
<li>@Model.Tel</li>
</ul>
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

接下來運行即可印出第一筆資料

總結

而現在已經能成功連結資料庫了!
微軟MVC資料庫連結方式比較麻煩,因為要自行找套件,不像Laravel已經配好,直接打帳號密碼就能用。
而現在迫切問題在Controller只能傳送一個Model,要如何讓多個Model能夠透過Controller傳送到View上。
所以,下個篇主要介紹的就是Controller。

參考資料
https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/intro?view=aspnetcore-5.0
https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/first-mvc-app/working-with-sql?view=aspnetcore-3.1&tabs=visual-studio
https://docs.microsoft.com/zh-tw/ef/core/cli/dotnet#dotnet-ef-dbcontext-scaffold
https://www.youtube.com/watch?v=2m_3SHGy-Rs
http://go.microsoft.com/fwlink/?LinkId=723263
https://www.uuu.com.tw/Public/content/article/20/20200413.htm
http://ikevin.tw/2019/08/04/asp-net-core-3-0-%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-database-first/

[Day6] C# MVC Model模型驗證 - C#&AspNetCore

在上回 [Day5] C# MVC Model模型使用 - C#&AspNetCore ,我們建立了基本的Model,並且示範讓資料藉由Model資料呈現在網頁上。
在這篇主要談如何Model模型上進行驗證。

Model的驗證

為了讓資料進到資料庫是符合我們要的格式,就必須在前面先擋過一層,而這層我們可以透過Model來達到。

常見的驗證屬性

  • [ValidateNever]: ValidateNeverAttribute 指出屬性或參數應該從驗證中排除。
  • [CreditCard]:驗證屬性是否具有信用卡格式。 需要 JQuery 驗證的其他方法。
  • [Compare]:驗證模型中的兩個屬性是否相符。
  • [EmailAddress]:驗證屬性具有電子郵件格式。
  • [Phone]:驗證屬性具有電話號碼格式。
  • [Range]:驗證屬性值是否落在指定的範圍內。像是最大值(Max)、最小值(min)
  • [RegularExpression]:驗證屬性值是否符合指定的正則運算式。
  • [Required]:驗證欄位不是 null。
  • [StringLength]:驗證字串屬性值不超過指定的長度限制。
  • [Url]:驗證屬性是否具有 URL 格式。
  • [Remote]:在伺服器上呼叫動作方法,以驗證用戶端上的輸入。
  • [Display]:顯示名稱為
  • [DataType]:資料型別

添加驗證屬性範例實作

我們先在UsersModel.cs增加屬性
需先添加using System.ComponentModel.DataAnnotations才可使用驗證屬性。

UsersModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace WebApplication1.Models
{
public class UsersModel
{
[Display(Name = "編號")]
public int ID { get; set; }

[Required(ErrorMessage = "姓名不可為空")]
[Display(Name = "姓名")]
public string Name { get; set; }

[Display(Name = "年齡")]
public int Age { get; set; }
}
}

接著在HomeController.cs裡面個添加一個HttpGet、HttpPost的Create方法
HttpGet是進頁面時會跑的方法
HttpPost是表單觸發發送後會跑的方法

HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[HttpGet]
public IActionResult Create()
{
return View();
}

[HttpPost]
public IActionResult Create(UsersModel usersmodel)
{
if (ModelState.IsValid)
{
//如果連結資料庫在這進行儲存

//導回首頁
return RedirectToAction(nameof(Index));
}
return View(usersmodel);
}

接著新增一個View頁面,在前台畫面上製作一個form表單。

Create.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@model WebApplication1.Models.UsersModel
@{
ViewData["Title"] = "Create Page";
}
<form asp-action="Create">
<div>
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
</div>
<div>
<label asp-for="Age"></label>
<input asp-for="Age" />
<span asp-validation-for="Age"></span>
</div>
<div>
<input type="submit" value="Save" />
</div>
</form>

注意,asp-validation-for="Name"表示後端回傳的錯誤訊息會傳送到這裡。

然後至網址:https://localhost:44378/Home/Create 查看後
名子不填儲存就會出現錯誤了

在程式碼上會發現跑到HttpPost的Create裡面

自訂驗證

系統預設的驗證肯定不夠,所以就來製作自己的驗證。

我們在UsersModel.cs裡面增加一個CheckValidAge的class並且繼承ValidationAttribute
並覆寫裡面IsValid方法。

UsersModel.cs
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace WebApplication1.Models
{
public class UsersModel
{
[Display(Name = "編號")]
public int ID { get; set; }

[Required(ErrorMessage = "姓名不可為空")]
[Display(Name = "姓名")]
public string Name { get; set; }

[CheckValidAge]
[Display(Name = "年齡")]
public int Age { get; set; }
}

public class CheckValidAge : ValidationAttribute
{

public CheckValidAge()
{
ErrorMessage = "年齡不可超過50歲";
}

public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
int age = (int)value;
if (age > 50)
{
return false;
}
else
{
return true;
}
}
}
}

如此一來,送出如果>50就會顯示出錯誤。

總結

雖然2021年的今天,大多數人都不使用這種form驗證,而是用API傳送。
因為頁面不用跳轉,用戶也能得到比較好的體驗。

然而,這種傳統的送資料方式還是有存在必要,就是系統追求安全性、穩定性,因為API方式會有比較難管理的問題。

此外,寫到這裡就開始有綁手綁腳的感覺
因為框架不那麼自由的讓你直接寫想寫的程式碼,而是照MVC架構
這就是一個好處,代表維護或後續開發的人就會照同條路走

參考資料
https://ithelp.ithome.com.tw/articles/10240340
https://docs.microsoft.com/zh-tw/aspnet/core/mvc/models/validation?view=aspnetcore-5.0

[Day5] C# MVC Model模型使用 - C#&AspNetCore

在上回 [Day4] C# MVC的程式架構淺談 - C#&AspNetCore ,我們談了整體的架構與檔案。
在這篇主要談如何在 ASP.NET Core MVC 中建立Model模型,並且使用。

Model定義

模型,是一個Class,在MVC裡是放資料的容器,通常會因為以下原因建立:

  1. 為了對應資料庫的table schema(欄位名稱)
  2. 呈現頁面資料的ViewModel

建立Model的過程

1.在Models資料夾 Add > New Item

2.選擇Class,然後自訂Model名稱

3.自己增加欄位ID、Name、Age、Address。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1.Models
{
public class UsersModel
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}

4.完成,建立一個Model就是這麼簡單,再來就是使用這個Model。

使用Model

再來我們透過HomeController使用他。

HomeController.cs

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
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
// 建立usersModel物件
UsersModel usersModel = new UsersModel()
{
ID = 1,
Name = "YU HSIANG",
Age = 25
};
// 傳入View
return View(usersModel);
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

23~31,透過模型Model建立規格,再把資料指派進去,傳進View中。

接下來要在View中使用我們傳進的Model

Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
@model WebApplication1.Models.UsersModel
@{
ViewData["Title"] = "Home Page";
}
<ul>
<li>@Model.ID</li>
<li>@Model.Name</li>
<li>@Model.Age</li>
</ul>
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

如此一來就能顯示我們的資料了。

此外,通常會建立ViewModel以對應特定的View畫面。

[補充]View型態主要分為兩種
Type View(強型別)-compiler時就必須過,有intellisense
Dynamic View(動態)-intellisense不會協助,在View具有不同class的model可以使用

總結

知道Model有ViewModel與table schema的用法,能夠使我們更清楚訂定需要的資料規格,讓維護或擴充時有一個良好的先天條件。@@
而在下篇中將會探討Model的資料驗證。

參考資料
https://ithelp.ithome.com.tw/articles/10240268

[Day4] C# MVC的程式架構淺談 - C#&AspNetCore

這篇主要檢討上回 [Day3] 建立C# MVC專案 - C#&AspNetCore 透過.net core 3.1版本建立出的程式碼架構。

檔案架構

  • wwwroot資料夾: 是放置靜態檔案如js、css等前台需要的檔案
  • Controllers資料夾: 是放置MVC的控制器Controller的地方,與使用者請求並給予回應有關
  • Models資料夾: 是放置MVC的控制器Models的地方,與資料庫有緊密關係
  • Views資料夾: 是放置MVC的控制器Views的地方,在頁面上和使用者互動的畫面
  • appsetting.json 設定檔案。
  • Program.cs 主要的進入點Main的所在,在此建立Host。
  • Startup.cs 在這裡可以設定網站的啟用設定,如添加中介層(Middleware)

程式碼探究

Program.cs

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
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
  • Main:程式碼進入點,並在裡面呼叫CreateHostBuilder,建立初始化的IHostBuilder。之後,Build建立實體,再透過Run運行。
  • CreateDefaultBuilder:預設為空參數,也可以自行添加。
  • ConfigureWebHostDefaults:其中包含用來裝載 web 應用程式的預設值
  • UseStartup:指定要供 web 主機使用的啟動類型,這裡套用Startup.cs的程式碼,也可以自訂Class然後套用至此。

小結:透過一個Console程式運行網站伺服器,可在此設定一開始要執行什麼、套用什麼設定。

Startup.cs

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
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// 添加服務
services.AddControllersWithViews();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

  • Startup:建構式,程式進入點,裡面設定configuration(如:appsetting.json的資料就會在此被帶進來)。
  • ConfigureServices:設定添加的服務。AddControllersWithViews,主要用來設定添加MVC服務,舊版是AddMvc。
  • Configure:設定HTTP request pipeline,針對http請求進行設定。
    • IsDevelopmen:套用開發版/上線版兩種模式。
    • UseHsts:將 HTTP Strict 傳輸安全性通訊協定 (HSTS) 標頭傳送給用戶端
    • UseDeveloperExceptionPage:使用開發者例外頁面
    • UseExceptionHandler:例外時導向某頁
    • UseHttpsRedirection:強制導向HTTPS
    • UseStaticFiles:會在中呼叫方法 Startup.Configure,以啟用靜態檔案的服務
    • UseRouting:將路由對應新增到Middleware中
    • UseAuthorization:授權使用端點路由傳送的資源時,此呼叫必須出現在對應用程式的呼叫之間 。UseRouting () 和 應用程式。UseEndpoints ( … ) ,讓中介軟體能夠正常運作。
    • UseEndpoints:Middleware提供了endpoints可以擴充一些方法去針對route去做設定

小結:添加哪些服務、設定一些初始架構(HTTP協定、開發版/上線版)、中介層設定都在這Startup.cs裡。

appsettings.json

1
2
3
4
5
6
7
8
9
10
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

小結:在此建立的值可以在Startup.cs被讀取,通常是一些外部字串設定檔,像是舊版的webconfig

HomeController.cs

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 Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
  • ILogger:提供寫入log的介面,在 ASP.NET Core 統一透過 ILogger 來記錄 Log。
  • Index()、Privacy():是Controller裡的action,用來載入的View資料夾的頁面。
  • ResponseCache:設定 Response 的暫存方式。並套用到要使用 Response 快取的 Controller 或 Action。以此就是套用到Error()。
  • Error():錯誤時返回的頁面,這邊回傳了ErrorViewModel模型格式的內容。
    [補充]C#問號用法:
    1
    2
    int? x = null;//定義可空型別變數
    int? y = x ?? 1000;//使用合併運算子,當變數x為null時,預設賦值1000

小結:控制器可依需求返回頁面、可在此紀錄LOG。

ErrorViewModel.cs

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

namespace WebApplication1.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}
  • ErrorViewModel:建立Class
  • RequestId:model中的string值
  • ShowRequestId:model中的bool值,返回一個lambda函式結果

給View用的Model,跟MVVM概念中所提到的ViewModel是不一樣的
在MVC的View中,一個View Binding一個Model,如果要使用多個不同的資料表就會用到View Model。

小結:在此建立一些模型規格,供頁面呈現使用。

Index.cshtml

1
2
3
4
5
6
7
8
@{
ViewData["Title"] = "Home Page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
  • ViewData:引用System.Web.Mvc.ViewDataDictionary,屬於Dictionary Object,能放Property或是一個Model的IEnumerable,使用Key/Value的概念存取。生命週期為一個頁面
  • @{}區塊:Razor語法,可以在裡面寫C#語法,但建議前台盡量少業務邏輯、只以純顯示為幕的比較好。

小結:在此可以寫html、前端畫面。

Privacy.cshtml

1
2
3
4
5
6
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's privacy policy.</p>
  • @ViewData:可用Razor語法叫出值顯示。

小結:同 Index.cshtml

_Layout.cshtml

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebApplication1</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2021 - WebApplication1 - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

  • @ViewData[“Title”]:底下頁面PASS過來的Title。
  • @RenderBody():把整塊子頁面放進來。
  • @RenderSection():建立放js區塊的程式碼區域,required表示是否一定要提供。

小結:共用的頁面主架構。

_ValidationScriptsPartial.cshtml

1
2
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

小結:放切出來的區塊。

Error.cshtml

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
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

@model:Razor語法,可以使用model的物件
小結:套用了ErrorViewModel,主要印出ErrorViewModel內的資料。

_ViewImports.cshtml

1
2
3
@using WebApplication1
@using WebApplication1.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

小結:設定使用那些程式模組

_ViewStart.cshtml

1
2
3
@{
Layout = "_Layout";
}

小結:頁面進入點套用。

總結

從程式碼片段中可以得知整個架構上如何運作。
從一開始的Console建置啟動程式、Startup設定,至單個頁面程式碼符合MVC架構的撰寫,最後運行起整個系統。

因為MVC所以知道這設計模式的人就能很快的熟悉這架構的寫法與邏輯。
如果是自幹的專案,就會有這地方是什麼架構、要怎麼改的才能達到功能的各種歪路。

預計下回會針對在Model、View、Controller上的細節程式碼撰寫。

參考資料
https://www.tutorialsteacher.com/core/aspnet-core-startup
https://blog.johnwu.cc/article/asp-net-core-3-application-lifetime.html
https://ithelp.ithome.com.tw/articles/10240077
https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.hosting.host.createdefaultbuilder?view=dotnet-plat-ext-5.0
https://ithelp.ithome.com.tw/articles/10243332
https://ithelp.ithome.com.tw/articles/10186520

[Day3] 建立C# MVC專案 - C#&AspNetCore

在上回 [Day2] 什麼是MVC,有什麼好處 - C#&AspNetCore 中我們理解到的MVC的核心概念。
在這回中我們要利用Visual Studio來建立一個C# MVC專案,並運行起來。

安裝

Visual Studio Community 2019:
https://visualstudio.microsoft.com/zh-hant/downloads/

建立專案

1.下載完後就可以開啟Visual Studio建立專案,New>Project

2.選ASP.NET Core Web App(Model-View-Controller),不然會建立出傳統沒有設計模式(Design Pattern)架構的專案。

3.設定專案名稱及資料夾

4.Enable Razor runtime compilation 執行期編譯要記得打勾,不然每次要重新執行才能看到修改的結果。

5.專案建立

6.可看見Model、View、Controller

7.點下Express執行

8.成功運行在網頁上了!

總結

建立MVC專案成功!
相對於PHP的LARAVEL框架來說,這套建立起來的MVC系統比較輕量,也就代表未來擴充的程式碼要設計好,否則量大起來加上多人協作可能就會有難以維護的問題。
為了在維護、co-work好改,之後也會注重在設計模式上。
最後,對於微軟程式只要能用IDE運行起來就成功一半了。因為程式碼除錯、追蹤功能強大。

基礎資料結構

資料結構:

  1. 如何在電腦中儲存資料。
  2. 是一種工具,如:演算法搭配適合的資料結構。

選擇不同的資料結構:

  1. 時間複雜度:非常頻繁拿、取,希望時間可以短一點。
  2. 空間複雜度:需要另外用空間來加速,那空間可能要比較多。
  3. coding複雜度:比如有兩種結構可以實作這程式,那選較容易、不容易出錯的那個。

常見操作

push放入一個元素、pop取出一個元素、query查詢

堆疊

堆疊(stack,FILO,first in last out),先進後出,如碟盤子。
實作搭配linked list
push:在head插入一個節點
pop:刪除head指向的節點
時間複雜度:
push:O(1)
pop:O(1)

佇列

佇列(queue,FIFO,first in first out),先進先出,如排隊。有環狀、單向之分。
實作搭配linked list或環狀佇列。
push:在end後面插入一個節點
pop:刪除head指向的節點
時間複雜度:
enqueue:O(1)
dequeue:O(1)

鏈結串列

鏈結串列(linked list)。
基本單元:Node(data+pointer)
將很多Node接起。
head指向第一個node,最後一個指標指向NULL。

性質:

  1. 每個元素只記錄他下一個元素,外部只記錄起點(head)
  2. 動態宣告記憶體,需要時就要,不需要就還給系統
  3. 分類:單向、雙向、環狀
  4. 不能隨機存取,因為外部只記錄起點(head),所以只能從頭去一個一個下去找,第一個告訴我第二個在哪,第二個告訴我第三個在哪…。

操作:
insert新增元素,在兩個元素間,或是頭、尾插入
delete刪除元素

時間複雜度:
insert:O(1),只要重新更改pointer指向
delete:O(1),只要重新更改pointer指向

為什麼不用陣列,還能隨機存取?
1.避免記憶體浪費,但array的dynamic array也能做到
2.可以快速刪除、插入節點,這array沒辦法做到。

下面兩個是延伸,不過要控制的資料變多,可能維護下來除起bug的機率會高。

雙向鏈結串列
環狀鏈結串列

時間複雜度比較

此外,兩個linked list合併速度很快,因為只要指向另一個的頭即可,只要O(1)。

總結

從這章中理解到為什麼要用資料結構,選用適合的資料結構可以改善程式的速度、程式碼難易度。在前面這幾篇中預計以理解原理為主,底層實作則未來有時間再另外拿出來寫。

如果是頻繁讀取,可以用array;如果頻繁新增、刪除,可以用linked list。

參考資料
https://www.youtube.com/watch?v=EH5XO2iYJvM

Big-O 複雜度分析

好程式的迷思

一個程式好不好通常看你要解決的問題。

  1. 時間花得少(運行速度) ex:網頁跑很快、資料載入快
  2. 空間花得少 ex:硬體不夠充裕,要使用比較少記憶體
  3. 正確率較高 ex:防毒軟體隊攻擊作防範、更高機率偵測
  4. 相容性較高 ex:好延伸擴充、裡面程式太複雜造成很難擴充
  5. 開發成本低 ex:開發軟體時,很快的做法但寫起來很困難,普通速度的作法但寫起來容易。

所以要適當的平衡,並不是哪種一定是好。
相容性、開發成本,通常仰賴當下開發者評估。
正確率則是在實作的細節上、Case量、理論分析上。

複雜度概念

把所有操作都視為一樣的時間,如:加減乘除、位運算、存取記憶體、判斷運算子…等
把所有操作次數計算出來。
看量級大小,來評估執行時間。有些程式容易TLE超時就是因為這問題,比較在意的是量級,而不是精確的值。

舉例:被指派長期作業,有兩種方案可選:

  1. 每天算10題。
  2. 第一天算1題,第二天算2題,第三天算3題…。

兩種方案怎麼選?
如果每算一題要5秒
第n天:

  1. 10 * n * 5 = 50n
  2. (n+1) * n/2 * 5 = 2.5(n^2+n)

n=5天時,

  1. 250秒
  2. 75秒

n=365天時,

  1. 18250秒
  2. 333975秒

可以發現操作數的量級(複雜)比較多,所以以長期來看會是方案1比較好。

量級概念

成長的速度不同等級。
如f(n)與g(n)兩個函數,當n趨近無窮大,誰會比較大?

  1. 3n^2+n+20 vs 100n 如果n = 10000,左邊會變成億;右邊只有百萬,所以左邊量級大
  2. n^100 vs 2^n
  3. n^2 vs 10nlogn
  4. 100^n vs n! 如果n=200,左邊100倍,右邊成長200倍,所以右邊量級大於左邊。
  5. 30*2^n vs 3^n
  6. 100n vs 200n 同一量級,因為極限的概念

極限概念

如果f(n)/g(n),n趨近無限大時趨近0,那麼說f(n)比g(n)小。
如果f(n)/g(n),n趨近無限大時趨近無限大,那麼說f(n)比g(n)大。
否則稱作兩者一樣量級。
所以,100n/200n = 1/2,所以不管n多大都一樣,稱作一樣量級。
但是,科技進步可以補100n與200n的差異,如平行運算可能可以把100n與200n的作法都視為一樣。但如果不是同一量級,還是會有科技進步彌補不了的,如上面的例子1。

量級的比較

以一台每秒可以進行10億次運算的電腦:

所以好算法、壞算法需要的時間是有很大差距的。

大歐符號 Big-O

記為O(f(n)),f(n)為複雜度量級的”上界”。所以實際結果可能會比他還要小。
常數會省略只寫一個n

  1. 3n^3+5n^2+10n+3 ∈ O(n^3)
  2. f(n) ∈ O(n^2),則f(n) ∈ O(n^3),因為是上界
  3. 1000 ∈ O(1)

ps.最緊上界:如果一個算法同時是O(n^2)算法、O(n^3)算法,通常會取比較小的O(n^2),以避免過度悲觀評估。<<=這句我不太懂

  1. 3n^2+n+20 vs 100n ;O(n^2) vs O(n)
  2. n^2 vs 10nlogn;O(n^2) vs O(nlogn)
  3. 100^n vs n! ;O(100^n) vs O(n!)
  4. 30*2^n vs 3^n ;O(2^n) vs O(3^n)
  5. 100n vs 200n ;O(n) vs O(n)

常見的時間複雜度函式:

如果時間不穩定

如一個人姓名存在資料庫,想找到那個人
如果是排第一個情況,O(1)
如果是排最後一個情況,O(n)

多數關注的是最壞的case,如:插入排序,最好是O(n);最壞是O(n^2)

總結

從參考資料中整理的一些對演算法複雜度的知識。
如果有機會寫到要追求「時間花得少」或「空間花得少」時就能派上用場,但這些通常使用在底層實作上,在應用的層面非常少用到,不過能透過理解複雜度,盡量選擇好的實踐方法。

此外,在應用層面上,相容性較高、開發成本低,我認為比較重要;底層函式庫則是追求時間花得少、空間花得少。所以,看是寫哪個層面的程式,如果兩者反過來會蠻可怕的。

參考資料

https://zh.wikipedia.org/wiki/%E5%A4%A7O%E7%AC%A6%E5%8F%B7
https://www.youtube.com/watch?app=desktop&v=_r7cfVrn28c
https://zh.wikipedia.org/wiki/%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6

無限階層留言規劃,含資料表設計

這個議題算是只要有留言系統,並且想實作無限層級的留言都會碰到的。
如:Facebook的留言、wordpress的留言。
當然也有簡單類的留言,只有一層的,如:古早的無名小站,但這不是我們今天要探討的。
就單刀直入了,如何實作「無限階層的留言」。

資料表

只要一張comments表加上parent_id即可,如下。
再來就可以利用程式方式(遞迴或迴圈)去印出他。

程式部分

把comments當主表,撈出資料SQL:

1
2
3
4
5
6
7
select
users.name,
comments.id,
comments.comment_parent_id,
comments.title,
comments.content
from comments left join users on comments.user_id = users.id

把上面資料表進行關聯後撈出後的資料:

1
2
3
4
5
6
7
[
{id:1,comment_parent_id:null,title:'留言1',content:'內容',name:'路人A'},
{id:2,comment_parent_id:null,title:'留言2',content:'內容',name:'路人B'},
{id:3,comment_parent_id:2,title:'留言3',content:'內容',name:'路人C'},
{id:4,comment_parent_id:3,title:'留言4',content:'內容',name:'路人D'},
{id:5,comment_parent_id:3,title:'留言5',content:'內容',name:'路人E'}
]

再來就可以實作程式碼了,我們要先把上面SQL撈出來的「打平結構」變成「樹狀結構」。
這會需要使用到遞迴。

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
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
</body>
</html>
<script>
const commentList = [
{id:1,comment_parent_id:null,title:'留言1',content:'內容',name:'路人A'},
{id:2,comment_parent_id:null,title:'留言2',content:'內容',name:'路人B'},
{id:3,comment_parent_id:2,title:'留言3',content:'內容',name:'路人C'},
{id:4,comment_parent_id:3,title:'留言4',content:'內容',name:'路人D'},
{id:5,comment_parent_id:3,title:'留言5',content:'內容',name:'路人E'}
]

// 建立樹狀階層
function buildTree(commentList){
// 根節點
const rootNode = commentList.filter(it=> it.comment_parent_id === null)
// 增加子節點
return addNode(rootNode,commentList)
}

// 添加子節點
function addNode(children,commentList){
// 找不到子節點返回
if(children.length === 0){
return []
}
// 添加子節點
const arr = []
// 遍歷當前children節點
for(let i=0;i<children.length;i++){
// 查詢底下的子節點
const childrenNode = commentList.filter(it=>
it.comment_parent_id === children[i].id)
// 把底下子節點添加進陣列
arr.push({
id:children[i].id,
name:children[i].name,
title:children[i].title,
content:children[i].content,
children:addNode(childrenNode,commentList)
})
}
// 返回子節點陣列
return arr
}
// 印出結果
console.log(JSON.stringify(buildTree(commentList)));

</script>

轉成樹狀階層後的資料:

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
[
{
"id": 1,
"name": "路人A",
"title": "留言1",
"content": "內容",
"children": []
},
{
"id": 2,
"name": "路人B",
"title": "留言2",
"content": "內容",
"children": [
{
"id": 3,
"name": "路人C",
"title": "留言3",
"content": "內容",
"children": [
{
"id": 4,
"name": "路人D",
"title": "留言4",
"content": "內容",
"children": []
},
{
"id": 5,
"name": "路人E",
"title": "留言5",
"content": "內容",
"children": []
}
]
}
]
}
]

光轉成上面那串,就想了快3小時的遞迴,然後上網找些範例參考才組出來。
我當時的疑問就是:「怎麼讓遞迴去回傳整包陣列」,所幸有找到參考,然後小改自己程式就成功了。

再來就是把上面那階層式資料在網頁上印出來。
還是要用遞迴。

網頁上內容呈現

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
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>

<div id="comment">
</div>

</body>
</html>
<script>
const commentList = [
{ id: 1, comment_parent_id: null, title: '留言1', content: '內容', name: '路人A' },
{ id: 2, comment_parent_id: null, title: '留言2', content: '內容', name: '路人B' },
{ id: 3, comment_parent_id: 2, title: '留言3', content: '內容', name: '路人C' },
{ id: 4, comment_parent_id: 3, title: '留言4', content: '內容', name: '路人D' },
{ id: 5, comment_parent_id: 3, title: '留言5', content: '內容', name: '路人E' }
]

// 建立樹狀階層
function buildTree(commentList) {
// 根節點
const rootNode = commentList.filter(it => it.comment_parent_id === null)
// 增加子節點
return addNode(rootNode, commentList)
}

// 添加子節點
function addNode(children, commentList) {
// 找不到子節點返回
if (children.length === 0) {
return []
}
// 添加子節點
const arr = []
// 遍歷當前children節點
for (let i = 0; i < children.length; i++) {
// 查詢底下的子節點
const childrenNode = commentList.filter(it =>
it.comment_parent_id === children[i].id)
// 把底下子節點添加進陣列
arr.push({
id: children[i].id,
name: children[i].name,
title: children[i].title,
content: children[i].content,
children: addNode(childrenNode, commentList)
})
}
// 返回子節點陣列
return arr
}

// 在網頁上呈現出結果
function generateCommentTemplate(children) {
let template = '<ul>'
for (let i = 0; i < children.length; i++) {
template += '<li>'
template += children[i].title + '<br>'
template += children[i].content + '<br>'
template += children[i].name + '<br>'

// 如果有子節點則遞迴再次呼叫
if (children[i].children.length !== 0) {
template += generateCommentTemplate(children[i].children)
}
template += '</li>'
}
template += '</ul>'
return template
}
// 建立樹狀結構
const tree = buildTree(commentList)
// 產生HTML模板
document.getElementById("comment").innerHTML = generateCommentTemplate(tree)
</script>

輸出結果:

總結

整篇大概花了5小時左右寫完,算是網頁系統裡最難的”遞迴”。
在網路上查,很難有一篇現成完整範例T_T,所以在這篇中就把整套都放上來。
同時練習對於樹的掌握。

流程如下:
1.規劃無限階層的留言 - 約10分鐘
2.樹的組合(遞迴) - 約3小時
3.轉譯成HTML模板(遞迴) - 約30分鐘

總之,有點成就感,因為真的不好寫。
如果後端一開始吐給你的是已經階層化的,那就很輕鬆了,因為那段的遞迴比較簡單。

ps.這留言的實作方法也能用來處理分類、類別、側邊欄,就內文改一下即可。

我有封裝了方法變成一隻.js函式庫,可以直接拿去用。
https://github.com/yuhsiang237/buildTree.js

參考資料
https://blog.51cto.com/dd118/2095671
https://kevintsengtw.blogspot.com/2013/07/aspnet-mvc-jquery-easy-ui-tree-json.html
https://ithelp.ithome.com.tw/articles/10028129
https://itw01.com/UBNZIEU.html

資訊系統的角色權限規劃,含資料表設計

寫後台系統時,有個很複雜的難點在於「角色權限」。
難點在如何變得好擴充,又能讓整個後台系統容易分權。
如:A進到一個頁面,在這頁面只能看,不能編輯。

思考的角度

如果同時思考「頁面權限」和「新刪修查權限」,很容易陷入混亂。
因此,建議先有個 「這個系統主要是要幹嘛」 的概念,說不定根本不用權限,因為使用者就那幾個,或是只到頁面上的分權,先有了使用者需求是什麼,再來確定要用哪種方法。

在此,我想探討的會是「頁面權限」到「新刪修查權限」的作法。
考量到使用者操作的直覺,以「模組>功能>操作權限」這樣排。

如此一來,我們功能只要對到「查看、新增、編輯、刪除」,先解決了細部的部分。
再來就只要把模組與功能分類關聯即可。

資料表規劃

users:使用者,上面綁角色role_id,每個使用者都有一種角色。
roles:角色,用來設定權限用。
module_permissions:模組權限,用來綁定底下功能權限。
func_permissions:功能權限,用來綁底層的權限。
permissions:權限,用來綁func_permissions與permission_types的表。
permission_types:權限分類,可自行定義分類,如:查看、新增、編輯、刪除…等。

ps.資料表上的name欄位也能說是code、key值,就是在網頁上你要抓檢查用的。

以下做的這張表就能進行分權。因為是以功能劃分,所以之後如果有APP或其他前端介面,只要照著「模組>功能」這樣去設計一樣能夠適用。

如果一個A使用者進到「基礎管理>角色管理功能」頁面,只要檢查他的檢視權限有沒有,沒有就返回首頁即可。只要有勾取功能權限,那必有檢視的權限。

如果更新角色時,把module_permissions、func_permissions、permissions有關聯的清空再重新建立即可。

[補充]*代表多,1代表1,可以看出是一對多還多對一
如果想知道更多:[Day7] SQL server資料庫關聯 - SQL Server資料庫入門

更多階層的權限?

如果是巢狀的話也是可以,func_permissions加個func_permissions_parent_id欄位,就能變成巢狀了。
不過以現有系統架構來講,也是能偷吃步直接在name上說是功能A的XXX項目也是可以的。

通常不會有超過5階以上的頁面,不然使用者會迷路、功能分則過細也不好用,即使遇到也是能用上面兩種方法去克服。

然後,建議權限這種不要做成動態的樹,因為哪天要改會很麻煩,建議這種階層還是寫死,變動次數通常不會太多,寫的簡單些未來也好改。

如果是API權限如RESTful API ?

目前我還沒有想到比較好的解法,目前只想到能在送出去前免強撈資料表判斷。
在新、刪、修時都卡,而查則是盡量了,因為RESTful本身就容易查出一些原本不相干的資料,所以不好分權。

總結

花了兩小時左右打完了這篇文跟設計表,實際規劃後覺得權限這部分不算難,只是要想清楚系統到底要做什麼,再來跟PM或SA確定一種可以的結構去開發就行。
如今,網路上也有不同解答,如果有共事的工程師也能一起討論方法,讓這系統能順利產製便可以,千萬別執迷不悟硬幹阿@@…孩子。

然後我是用 https://dbdiagram.io/ 來畫下面那張圖的,很方便。

此外,也可以參考”以角色為基礎的存取控制”(Role-based access control,RBAC)
https://zh.wikipedia.org/wiki/%E4%BB%A5%E8%A7%92%E8%89%B2%E7%82%BA%E5%9F%BA%E7%A4%8E%E7%9A%84%E5%AD%98%E5%8F%96%E6%8E%A7%E5%88%B6

參考資料
http://www.chanpin100.com/article/110136
https://ithelp.ithome.com.tw/questions/10190444
https://zh.wikipedia.org/wiki/%E4%BB%A5%E8%A7%92%E8%89%B2%E7%82%BA%E5%9F%BA%E7%A4%8E%E7%9A%84%E5%AD%98%E5%8F%96%E6%8E%A7%E5%88%B6

自己對自己程式的看法

自己算是一個寫程式的人,做過前端,也實習過後端。

我數學並不強,只會用比較簡單的概念去實作程式。所以在演算法、資料結構上偏向應用居多,我能知道在哪種情況下,可能要搭配的結構,但如果要空手寫出來底層,除非背過,不然要寫很久,有需要才會回去看、參考,然後才實作出來。

因此,我知道以自己的數學能力是沒辦法把程式弄到很頂尖的,可能前20%那種,而後面的80%或許還是有機會。
就是偏向應用、非演算法那塊。

我覺得自己優勢在整合能力,能夠把不同的東西拼起來,可以正常運行、滿足需求。到目前為止,我還沒碰過寫不出來的應用系統。

雖然看網路上很多文人相輕的評論,如:不會寫資結、演算法就不算工程師,但軟體工程師也分很多種,並不是每種都需要很強的資結、演算法。
也許,就只是要穩定的交付可運行的程式而已。

我遇過演算法很強但卻沒辦法在時程內完成案子的工程師,也遇過能力普普但能很穩定能夠完成案子的工程師。

總之,以我觀點來說就是能夠解決問題

而在這片領域,我能解的可能只有80%,而那20%個人天賦影響既然沒辦法改變,那就坦然接受吧!
(就像每人都會寫數學,但又有幾個能變得像高斯或愛因斯坦那樣的開創者)
說來說去,別看得太重而否定自己,不過是要口飯而已。