.NET CORE
1.Microsoft Azure 微軟擁抱云計算
2..net core 是為云所生的技術
3.Net Framework缺點:
-
系統級別的安裝,互相影響
-
無法獨立部署
-
SAP.NET和IIS深度耦合
-
非云原生
4.NET Framework歷史包袱
-
基于拖控制元件之上的MVC
-
ASP.NET 底層不支持很好的單元測驗
5.NET Core的有點
-
支持獨立部署不相互影響
-
徹底模塊化
-
運行效率高
-
跨平臺
-
符合現代開發理念,依賴注入,單元測驗等
.NET Core .NET Framework 兩者根據標準實作 .NET Standard 規定的類 規定的方法 只有定義沒有實作 反編譯軟體ILSpy
驗證:.NET Standard只是標準,不是實作1)建.NET Standard類別庫專案,確認版本是 2.0,建一個類,方法中列印 typeof (FileStream).Assembly.Location,2分別建NET Framework和.NET Core的控制臺專案,添加對類別庫專案參考,并且呼叫,3用反編譯工具ILSpy盡管開源)分別反編譯VS中FileStream、.NET Framework和. NETCore運行中的,BeginRead方法實作以及定義有 不同,
.NET5開發工具:1).NET CLI:命令列 2)yisual Studio: Windows-Only(推薦)3) Visual Studio for Mac 4)Jetbrains Rider:收費 5)vS Code (Visual Studio, Code):跨平臺
.NET SDK、運行時、檔案----https:// dotnet.mi crosoft.com/ 可能VS自帶,但是在服務器上需要單獨安裝
dotnet —-version查看版本 dotnet new console當前檔案夾下創建控制臺專案 dotnet run構建并運行 詳細見官方檔案“.NET CLI”部分,
程式的發布, 1、部署模式:依賴框架;獨立(推薦)﹔ 2、目標運行時, 3、生成單個檔案, 4、ReadyToRun: AOT (ahead of-time)、JIT,缺點看檔案, 5、裁剪未使用的程式集,缺點看檔案, 自學就要養成把相關檔案“翻一翻”的意識,
WSL:Windows subsystem for linux SandBox
NugGet
注意:【默認專案】為目標專案, 1)安裝:Install-Package XXX.yersion指定版本, 可以看到把依賴組件都下載了, 版本檢測:嘗試把專案改為.NET core 1.0,然后再安裝Zack. EFCore.Batch試試, 2)卸載:Uninstall-Package XXX 3)更新到最新版:Update-Package XXX
1、NuGet你也可以頁獻,就三步, 2、和.NET Framework不同,.NET core絕大部分官方程式集也要到NuGet下載,模塊化! 3、少部分是收費的,如搜索“word file” 4、參差不齊,如何分辨質量, 5、內部部署NuGet服務mTo
異步編程1
不等 異步編程不能加快單個請求的回應速度,只是能處理更多的請求
C#關鍵字:async await 不能與多執行緒
“異步方法”:async關鍵字修飾的方法 1))異步方法的回傳值一般是Task<T>,T是真正的回傳值型別,Task<int>,慣例:異步方法名字以Async結尾, 2)即使方法沒有回傳值,也最好把回傳值宣告為非泛型的Task, 3)呼叫異步方法時,一般在方法前加上await,這樣拿到的回傳值就是泛型指定的T型別;4)異步方法的“傳染性”:一個方法中如果有await呼叫,則這個方法也必須修飾為async
static async Task Main(string[] args)
{
string fileName = "d:/1.txt" ;File. Delete(fileName) ;
File.WriteA11TextAsync(fileName,"hello async");string s= await File.ReadAllTextAsync(fileName) ; Console.WriteLine(s):
}
如果同樣的功能,既有同步方法,又有異步方法,那么首先使用異步方法,.NE5中,很多框架的方法也都支持異步:Main、WinForm事件處理函式, 對于不支持的異步方法怎么辦? Wait()(無回傳值);Result(有回傳值),風險:死鎖,盡不用
異步委托
//執行緒池 在子執行緒中執行的方法放到執行緒池
ThreadPool.QueueUserWorkItem(async(obj) =>
{
while (true)
{
await File.WriteAllTextAsync(@"D:\text\1.txt","aaaaaaaaaaa");
Console.WriteLine("------------------");
}
});
Console.Read();
·用ILSpy反編譯dll(.exe只是 windows下的啟動器)成C#4.0版本,就能看到容易理解的底層lL代碼,await、async是“語法糖”,最終編譯成“狀態機呼叫”.
總結: async的方法會被C#編譯器編譯成一個類,會主要根據await呼叫進行切分為多個狀態,對async方法的呼叫會被拆分為對MoveNext的呼叫, 用await看似是“等待”,經過編譯后,其實沒有“wait”,
//使用異步編程獲取百度網址頁面的代碼并且寫道檔案中 然后讀出來寫入到控制器
using (HttpClient httpContent = new HttpClient())
{
string html = await httpContent.GetStringAsync("https://www.baidu.com");
Console.WriteLine(html);
}
string txt = "hellow zyb";
string filename = @"D:\text\1.txt";
await File.WriteAllTextAsync(filename, txt);
Console.WriteLine("寫入成功");
string s = await File.ReadAllTextAsync(filename);
Console.WriteLine("檔案內容"+s);
await呼叫的等待期間,.NET會把當前的執行緒回傳給執行緒池,等異步方法呼叫執行完畢后,框架會從執行緒池再取出來一個執行緒執行后續的代碼,
Thread.CurrentThread.ManagedThreadId 獲得當前執行緒Id
驗證:在耗時異步(寫入大字串)操作前分別列印執行緒Id
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); //托管執行緒id
StringBuilder sb = new StringBuilder(); //大檔案
for (int i = 0; i < 2000; i++)
{
sb.Append("xxxxxxxxxxxxx"); //等待的程序中把當前的執行緒放到執行緒池,然后從執行緒池取出隨機執行緒執行別的方法
}
await File.WriteAllTextAsync(@"D:\text\1.txt",sb.ToString());
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); //托管執行緒id 兩次執行緒的id不一樣
異步編程不等于執行緒
異步方法的代碼并不會自動在新執行緒中執行,除非把代碼放到新的執行緒中執行
用Task.Run改造之前的例子,再看看執行緒變化
Console.WriteLine("之前", +Thread.CurrentThread.ManagedThreadId);
double r = await CalcAsync(5000);
Console.WriteLine($"r={r}");
Console.WriteLine("之前", +Thread.CurrentThread.ManagedThreadId);
public static async Task<double> CalcAsync(int n)
{//自動根據回傳值進行型別推斷 將下面代碼放到新的執行緒執行
return await Task.Run(() =>
{
Console.WriteLine("CalcAsync", +Thread.CurrentThread.ManagedThreadId);
double result = 0;
Random radn = new Random();
for (int i = 0; i < n * n; i++)
{
result += radn.NextDouble();
}
return result;
});
?
}
anync方法缺點
1.異步方法會生成一個類,運行效率沒有普通方法高,
2.可能會占用非常多的執行緒
只甩手Task,不拆完了再裝,反編譯上面的代碼,知識普通方法的呼叫
有點:運行效率高,不會造成執行緒浪費
回傳值為Task的不一定都要標注async,標注async知識讓我們可以更方便的await而已
如果一個異步的方法只是對別的方法呼叫的轉發,并沒有太多的復雜邏輯,(比如等待a的結果,再呼叫b,把a呼叫的回傳值拿到內部做一些處理再回傳),那么就可以去掉async關鍵字
?
//內部只是對方法的呼叫
static Task<string> ReadAsync(int num)
{
if (num == 1)
{
return File.ReadAllTextAsync(@"D:\text\1.txt");
}
else if (num == 2)
{
return File.ReadAllTextAsync(@"D:\text\2.txt");
}
else
{
throw new ArgumentException();
}
?
}
異步編程 不要用sleep
如果想在異步方法中暫停一段時間,不要用thread.sleep(),因為他會阻塞呼叫執行緒,而要用await task.Delay()
在控制臺中沒看到區別,但是放到winform程式中就能看到區別了,asp.net core中也看不到區別但是sleep()會降低并發
//這是winform的代碼塊
private async void button1_Click(object sender, EventArgs e)
{
using (HttpClient httpClient = new HttpClient())
{
string sl = await
httpClient.GetStringAsync("https://www.youzack.com");
textBox1.Text = sl.Substring(0,20);
//Thread.Sleep(3000);
await Task.Delay(3000);
string s2 = await
httpClient.GetStringAsync("https://www.baidu.com");
textBox1.Text = sl.Substring(0, 20);
}
}
異步編程CancellationToken
有時需要提前終止任務,比如:請求超時,用戶取消請求,跟多異步方法都有CancellationToken引數 用于獲得提前終止的信號
CancellationToken結構體None:空 bool lsCancellationRequested是否取消(*)Register(Action callback)注冊取消監聽ThrowlfCancellationRequested()如果任務被取消,執行到這句話就拋例外,
CancellationTokenSource CancelAfter()超時后發出取消信號Cancel()發出取消信號 CancellationToken Token
為“下載一個網址N次”的方法增加取消功能, 分別用GetStringAsync + IsCancellationRequested、 GetStringAsync+ ThrowlfCancellationRequested()、帶CancellationToken的GetAsync()分別實作,取消分別用超時、用戶敲按鍵(不能await)實作,
ASP.NET Core開發中,一般不需要自己處理CancellationToken、 CancellationTokenSource這些,只要做到“能轉發cancellationToken就轉發”即可,ASP.NET Core會對于用戶請求中斷進行處理,(*)演示一下ASP.NETCore中的使用:寫一個方法,Delay1000次,用 Debug.WriteLine(輸出,訪問中間跳到放到其他網站,
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(2000);
CancellationToken token = cts.Token;
await DownloadlAsync("https://www.baidu.com",1000,token);
static async Task DownloadlAsync(string url, int n,CancellationToken cancellationToken)
{
using (HttpClient client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
var resp = await client.GetAsync(url,cancellationToken);
string html = await resp.Content.ReadAsStringAsync();
File.WriteAllTextAsync(@"D:\text\1.txt", html,cancellationToken);
Console.WriteLine($"{DateTime.Now}:{html}");
//if (cancellationToken.IsCancellationRequested)
//{
// Console.WriteLine("請求被取消了");
// break;
//}
//cancellationToken.ThrowIfCancellationRequested();
}
}
}
//CancellationToken cancellationToken 這個引數是為了防止用戶訪問別的網站或者關閉瀏覽器服務端還在執行
static async Task DownloadlAsync(string url, int n, CancellationToken cancellationToken)
{
using (HttpClient client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
var resp = await client.GetAsync(url, cancellationToken);
string html = await resp.Content.ReadAsStringAsync();
Debug.WriteLine(html);
}
}
}
public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
await DownloadlAsync("https://www.baidu.com", 10000, cancellationToken);
return View();
}
異步編程WhenAll
Task類的重要方法: 1.Task<Task> WhenAny(lEnumerable<Task>tasks)等,任何一個Task完成,Task就完成2.Task<TResult[]> WhenAll<TResult>(paramsTask<TResult>[] tasks)等,所有Task完成,Task才完成,用于等待多個任務執行結束,但是不在乎它們的執行順序, 3.FromResult()創建普通數值的Task物件,
string[] file = Directory.GetFiles(@"D:\text\1.txt");
Task<int>[] counts = new Task<int>[file.Length];
for (int i = 0; i < file.Length; i++)
{
string fileName = file[i];
Task<int> t = ReadCharCount(fileName);
counts[i] = t;
}
int[] countTask = await Task.WhenAll(counts);
Console.WriteLine(countTask);
static async Task<int> ReadCharCount(string fileName)
{
string a = await File.ReadAllTextAsync(fileName);
return a.Length;
}
異步編程其他問題
介面中的異步方法: async是提示編譯器為異步方法中的await代碼進行分段處理的,而一個異步方法是否修飾了async對于方法的呼叫者來講沒區別的,因此對于介面中的方法或者抽象方法不能修飾為async,
interface IText
{
Task<int> GetCharCount(string file);
}
class Text : IText
{
public async Task<int> GetCharCount(string file)
{
string a = await File.ReadAllTextAsync(file);
return a.Length;
}
}
異步與yield: 復習: yield return不僅能夠簡化資料的回傳,而且可以讓資料處理“流水線化”,提升性能,
foreach (var s in Test2())
{
Console.WriteLine(s);
}
static IEnumerable<string> Test2()
{
yield return "hellow"; //這里直接回傳一條進行foreach回圈 取一條執行一條 方法內部拆分搞成狀態機
yield return "yzk";
yield return "youzack";
}
在舊版C#中,async方法中不能用yield,從C#8.0開始,把回傳值宣告為IAsyncEnumerable(不要帶Task),然后遍歷的時候用await foreach()即可,
await foreach (var s in Test3())
{
Console.WriteLine(s);
}
static async IAsyncEnumerable<string> Test3()
{
yield return "hellow";
yield return "yzk";
yield return "youzack";
}
為什么要學LING
為什么要學LINQ?讓資料處理變得簡單: 讓資料處理傻瓜化 統計一個字串中每個字母出現的頻率(忽略大小寫),然后按照從高到低的順序輸出出現頻率高于2次的單詞和其出現的頻率,
(復習)委托 1、委托是可以指向方法的型別,呼叫委托變數時執行的就是變數指向的方法,舉例, 2、.NET中定義了泛型委托Action(無回傳值)和Func(有回傳值),所以一般不用自定義委托型別,舉例,
static void Main(string[] args)
{
D1 d = F1;
d();
d = F2;
d();
D2 d2 = Add;
Console.WriteLine(d2(1,2));
Action a1 = F2;
a1();
Func<int, int, int> f = Add;
f(1,2);
Func<int, int, string> a = F33; //有回傳值委托
a(1,2);
Action<int, string> c = F44; //無回傳值委托
c(1,"zha");
}
static void F1()
{
Console.WriteLine("我是F1");
}
static void F2()
{
Console.WriteLine("我是F2");
}
static int Add(int a,int b)
{
return a + b;
}
static string F33(int i,int a)
{
return "hellow";
}
static void F44(int a,string b)
{
}
}
delegate void D1();
delegate int D2(int a,int b);
Lambda運算式
可以省略引數資料型別,因為編譯能根據委托型別推斷出引數的型別,用=>引出來方法體,
一步一步簡化
Action f1 = delegate ()
{
Console.WriteLine("我是方法");
};
Action f11 = () => Console.WriteLine("我是方法");
f1 (); //匿名方法無參無回傳值
Action<string, int> f2 = (string a, int b) => Console.WriteLine($"a={a},b={b}"); ;
f2("zyb",1); //匿名方法 兩個引數
Func<int, int, int> f3 = (int a, int b) => { return a + b; };
f3(1,2); //有參有回傳值 匿名方法
Func<int, int, int> f4 = (int i, int j) => i + j;
f4(1,2);
Func<int, int, int> f5 = ( i, j) =>
{
return i + j;
};
f5(1, 2);
Action<int> a1 = i => Console.WriteLine(i);
Func<int, bool> f66 = delegate (int i)
{
return i > 0;
};
Func<int, bool> f67 = i => i > 0;
Func<int, bool> f68 = delegate (int i)
{
return i > 5;
};
}
LING方法的背后
LING中提供了很多集合的擴展方法 配合lambda能簡化資料處理
static void Main(string[] args)
{
int[] nums = new int[] { 20,55,51,55,58,99,22};
//where方法會遍歷集合中每個元素 對于每個元素
//都呼叫a=>a>10 這個運算式判斷一下是否為true
//如果為true 則把這個放到回傳的集合中
//IEnumerable<int> result = nums.Where(a=>a>10);
var result = MyWhere2(nums,a => a > 10);
foreach (int item in result)
{
Console.WriteLine(item);
}
}
static IEnumerable<int> MyWhere(IEnumerable<int> items,Func<int,bool> f)
{
List<int> result = new List<int>();
foreach (int item in result)
{
if (f(item)==true)
{
result.Add(item);
}
}
return result;
}
static IEnumerable<int> MyWhere2(IEnumerable<int> items, Func<int, bool> f)
{
foreach (int item in items)
{
if (f(item) == true)
{
yield return item; //流水線處理 滿足條件直接回傳
}
}
}
}
LING常用擴展方法 IEnumberble<T> 擴展方法
獲取一條資料(是否帶引數的兩種寫法):Single:有且只有一條滿足要求的資料;SingleOrDefault:最多只有一條滿足要求的資料; First :至少有一條,回傳第一條; FirstOrDefault:回傳第一潭訓者默認值; 選擇合適的方法,“防御性編程”
LING解決面試
性能與面試 LINQ大部分時間不會影響性能,不過我曾經遇到過,講講, 面試時候的演算法題一般盡量避免使用正則運算式、LINQ等這些高級的類別庫,大飯店面試大廚的故事,
案例1 有一個用逗號分隔的表示成績的字串,如 "61,90,100,99,18,22,38,66,80,93,55,50,89",計算這些成績的平均值,
string s = "33,55,66,99";
double avg2 = s.Split(',').Select(e => Convert.ToInt32(e)).Average();
Console.WriteLine(avg);
案例2 統計一個字串中每個字母出現的頻率(忽略大小寫),然后按照從高到低的順序輸出出現頻率高于2次的單詞和其出現的頻率,
string s = "fjdssl fjsdj ejejejej dfaSFSDd";
var item = s.Where(c => char.IsLetter(c)).Select(c => char.ToLower(c)) //過濾掉空格逗號
.GroupBy(c => c).Select(g => new { g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count).Where(g => g.Count > 2);
foreach (var c in item)
{
Console.WriteLine(c);
}
依賴注入 控制反轉
概念 生活中的“控制反轉”:自己發電和用電網的電, 依賴注入(Dependency Injection,Dl)是控制反轉(lnversion of Control,IOC)思想的實作方式, 依賴注入簡化模塊的組裝程序,降低模塊之間的耦合度
自己發電的代碼
var connSettings =
ConfigurationManager.ConnectionStrings["connStr1"];
string connStr = connSettings.ConnectionString;SqIConnection conn = new
SqlConnection(connStr);缺點是?
//自己從組態檔取到連接字串 然后實體一個連接資料庫的連接把字串放進去
代碼控制反轉的目的 “怎樣創建XX物件"→“我要XX物件” 兩種實作方式: 1)服務定位器(ServiceLocator); 2)依賴注入(Dependency Injection,Dl);
DI幾個概念 服務(service):物件;注冊服務; 服務容器:負責管理注冊的服務;查詢服務:創建物件及關聯物件; 物件生命周期:Transient(瞬態);Scoped(范圍) ; Singleton(單例);
.NET中使用DI 2、根據型別來獲取和注冊服務, 可以分別指定服務型別(service type) 和實作類 型(implementation type),這兩者可能相同,也可能不同,服務型別可以是類,也可以是介面,建議面向介面編程,更靈活, 3、.NET控制反轉組件取名為Dependencylnjection,但它包含ServiceLocator的功能,
生命周期
生命周期 1、給類建構式中列印,看看不同生命周期的物件創建,使用serviceProvider.CreateScope()創建Scope, 2、如果一個類實作了IDisposable介面,則離開作用域之后容器會自動呼叫物件的Dispose方法, 3、不要在長生命周期的物件中參考比它短的生命周期的物件,在ASP.NET Core中,這樣做默認會拋例外, 4、生命周期的選擇:如果類無狀態,建議為Singleton;如果類有狀態,且有Scope控制,建議為Scoped,因為通常這種Scope控制下的代碼都是運行在同一個執行緒中的,沒有并發修改的問題;在使用Transient的時候要謹慎, 5、.NET注冊服務的多載方法很多,看著檔案琢磨吧,
//使用ioc容器
ServiceCollection services = new ServiceCollection();
services.AddTransient<TestSrvicelmpl>(); // 呼叫一次實體新的物件 瞬態
//services.AddSingleton<TestSrvicelmpl>(); //相同的物件 單例
//services.AddScoped<TestSrvicelmpl>(); //在范圍內拿到的是同一個物件
using (ServiceProvider sp = services.BuildServiceProvider()) //ServiceProvider相當于服務定位器
{
//TestSrvicelmpl T = sp.GetService<TestSrvicelmpl>();
//T.Name = "ZHAGNSNA";
//T.SayHi();
//TestSrvicelmpl T1 = sp.GetService<TestSrvicelmpl>();
//T1.Name = "放假啊可大幅度";
//T1.SayHi();
////比較兩個是否同一個物件 結果false
//Console.WriteLine(object.ReferenceEquals(T, T1));
//T.SayHi();
using (IServiceScope scope1 = sp.CreateScope())
{
TestSrvicelmpl T = scope1.ServiceProvider.GetService<TestSrvicelmpl>();
T.Name = "ZHAGNSNA";
T.SayHi();
TestSrvicelmpl T1 = scope1.ServiceProvider.GetService<TestSrvicelmpl>();
Console.WriteLine(object.ReferenceEquals(T, T1));
}
using (IServiceScope scope2 = sp.CreateScope())
{
TestSrvicelmpl T = scope2.ServiceProvider.GetService<TestSrvicelmpl>();
T.Name = "ZHAGNSNA";
T.SayHi();
TestSrvicelmpl T1 = scope2.ServiceProvider.GetService<TestSrvicelmpl>();
Console.WriteLine(object.ReferenceEquals(T, T1));
}
}
IServiceProvider的服務定位器方法:
T GetService<T>()如果獲取不到物件,則回傳null,object GetService(Type serviceType) T GetRequiredService<T>()如果獲取不到物件,則拋例外 object GetRequiredService(Type serviceType) lEnumerable<T> GetServices<T>()適用于可能有很多滿足條件的服務 lEnumerable<object> GetServices(Type serviceType)
ServiceCollection services = new ServiceCollection();
services.AddScoped<ITestServie, TestSrvicelmpl>();
services.AddScoped<ITestServie, TestSrvicelmpl2>();
services.AddSingleton(typeof(ITestServie),new TestSrvicelmpl());
using (ServiceProvider sp = services.BuildServiceProvider())
{
//GetService找不到服務回傳null
//ITestServie ts1 = sp.GetService<ITestServie>();
//ts1.Name = "tom";
//ts1.SayHi();
//Console.WriteLine(ts1.GetType());
//GetRequiredService 必須的如果找不到 直接拋例外
//ITestServie ts1 = sp.GetRequiredService<ITestServie>();
//ts1.Name = "tom";
//ts1.SayHi();
//Console.WriteLine(ts1.GetType());
//注冊多個服務
//IEnumerable<ITestServie> tets = sp.GetServices<ITestServie>();
// foreach (ITestServie item in tets)
// {
// Console.WriteLine(item.GetType());
// }
ITestServie test1 = sp.GetService<ITestServie>();
Console.WriteLine(test1.GetType());
}
DI魅力漸顯:依賴注入
1、依賴注入是有傳染性”的,如果一個類的物件是通過DI創建的,那么這個奚的建構式中宣告的所有服務型別的引數都會被DI賦值;但是如果一個物件是程式員手動創建的,那么這個物件就和DI沒有關系,它的建構式中宣告的服務型別引數就不會被自動賦值, 2、.NET的DI默認是建構式注入, 3、舉例:撰寫一個類,連接資料庫做插入操作,并且記錄日志(模擬的輸出),把Dao、日志都放入單獨的服務類,connstr見備注,
static void Main(string[] args)
{
//降低模塊之間的耦合
ServiceCollection services = new ServiceCollection();
services.AddScoped<Controller>();
services.AddScoped<ILog,LogImp1>();
services.AddScoped<IStorage,StorageImp1>();
services.AddScoped<IConfig,ConfiImp1>();
using (var sp = services.BuildServiceProvider())
{
var c = sp.GetService<Controller>();
c.Test();
}
Console.ReadKey();
}
class Controller
{
private readonly ILog log;
private readonly IStorage storage;
public Controller(ILog log, IStorage storage)
{
this.log = log;
this.storage = storage;
}
public void Test()
{
log.Log("開始上傳");
storage.Save("fddfs","1.txt");
log.Log("上傳完畢");
}
}
interface ILog
{
public void Log(string msg);
}
public class LogImp1 : ILog
{
public void Log(string msg)
{
Console.WriteLine($"日志:{msg}");
}
}
interface IConfig
{
public string GetValue(string name);
}
class ConfiImp1 : IConfig
{
public string GetValue(string name)
{
return "config";
}
}
interface IStorage
{
public void Save(string content,string name);
}
class StorageImp1 : IStorage
{
private readonly IConfig config;
public StorageImp1(IConfig config)
{
this.config = config;
}
public void Save(string content, string name)
{
string server = config.GetValue("server");
Console.WriteLine($"向服務器{server}的檔案名為{name}上傳{content}");
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/483226.html
標籤:.NET Core
上一篇:如何計算用戶在網站上花費的時間?
