.NET是.NET Framework、.NETCore、Xamarin/Mono的统称。
.NET FrameWork框架只能开发Windows平台的应用程序,也就是说用.NET FrameWork框架做出来的程序只能运行在Windows系统下。
.NET Core框架是免费、开源、跨平台的,可以运行在Linux、Macos、Windows平台下。
异步方法:用async关键字修饰的方法。
await File.WriteAllTextAsync(filename,html);
体验下异步编程,如:
public class Program
{
static async Task Main(string[] args)
{
string fileName = @"D:\1.txt";//该行也可以写成string fileName = "D:/1.txt";
File.Delete(fileName);//删除文件
StringBuilder str = new StringBuilder();
for(int i = 0; i < 10000; i++)
{
str.Append(",Hello");
}
File.WriteAllTextAsync(fileName,str.ToString());
string s=await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
}
运行程序会报错,因为File.WriteAllTextAsync(fileName,str.ToString())前没有await关键字,程序运行到此行时,不会跳过此行,会一直执行此行知道此行执行结束,但是由于下一行中的File.ReadAllTextAsync(fileName)前有await,因此运行此代码会报错(报错原因:同一个文件不能同时被一个进程读和另外一个进程写)。
如下图,在File.WriteAllTextAsync(fileName,str.ToString())方法前加一个await关键字就可以了:
public class Program
{
static async Task Main(string[] args)
{
string fileName = @"D:\1.txt";//该行也可以写成string fileName = "D:/1.txt";
File.Delete(fileName);//删除文件
StringBuilder str = new StringBuilder();
for(int i = 0; i < 10000; i++)
{
str.Append(",Hello");
}
await File.WriteAllTextAsync(fileName,str.ToString());
string s=await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
}
1.自定义两个异步方法,一个不带返回值,一个带返回值,如下:
public class Program
{
public static async Task Main(string[] args)
{
await DownloadHtmlAsync1("https://www.youzack.com", @"D:/1.txt");
Console.WriteLine("Ok");
Console.WriteLine("**************");
Console.WriteLine("**************");
Console.WriteLine("**************");
int len2= await DownloadHtmlAsync2("https://www.youzack.com", @"D:/2.txt");
Console.WriteLine("Ok"+len2);
}
///
/// 自定义一个异步方法(不带返回值),用于从网页上下载html文件,并写在本地文件中
///
///
/// html文件的下载地址
///
///
/// 下载的html文件保存在本地哪个文件中
///
///
public static async Task DownloadHtmlAsync1(string url, string filename)
{
HttpClient httpClient = new HttpClient();
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(filename, html);
}
///
/// 自定义一个异步方法(带返回值),用于从网页上下载html文件,并写在本地文件中
///
///
/// html文件的下载地址
///
///
/// 下载的html文件保存在本地哪个文件中
///
///
public static async Task<int> DownloadHtmlAsync2(string url, string filename)
{
HttpClient httpClient = new HttpClient();
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(filename, html);
return html.Length;
}
}
}
2.如果同样的功能,既有同步方法,又有异步方法,那么优先使用异步方法。
3.异步lambda表达式(委托)
例如:
public class Program
{
public static void Main()
{
ThreadPool.QueueUserWorkItem(async (obj) =>
{
while (true)
{
await File.WriteAllTextAsync(@"D:/3.txt", "sdfffffffffffffffffff");
}
});
Console.ReadKey();
}
}
异步方法中的代码并不会自动在新线程中执行,除非把代码放到新线程中执行(通过Task.Run方法将代码放到新的线程中执行)。
看下不使用Task.Run方法和使用Task.Run方法的效果:
不使用Task.Run方法:
public class Program
{
public static async Task Main()
{
Console.WriteLine("之前" + Thread.CurrentThread.ManagedThreadId);
double r = await CalcAsync(5000);
Console.WriteLine("之后" + Thread.CurrentThread.ManagedThreadId);
}
public static async Task<double> CalcAsync(int n)
{
Console.WriteLine("CalcAsync" + Thread.CurrentThread.ManagedThreadId);
double result = 0;
Random random = new Random();
for(var i = 0; i < n * n; i++)
{
result+=random.NextDouble();
}
return result;
}
}
使用Task.Run方法:
public class Program
{
public static async Task Main()
{
Console.WriteLine("之前" + Thread.CurrentThread.ManagedThreadId);
double r = await CalcAsync(5000);
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 random = new Random();
for (var i = 0; i < n * n; i++)
{
result += random.NextDouble();
}
return result;
});
}
}
1.async方法的缺点:
public class Program
{
public static async Task Main()
{
string s = await ReadAsync(1);
Console.WriteLine(s);
}
public static Task<string> ReadAsync(int n)
{
if (n == 1)
{
return File.ReadAllTextAsync(@"D:/1.txt");
}else if (n == 2)
{
return File.ReadAllTextAsync(@"D:/2.txt");
}
else
{
throw new ArgumentException();
}
}
}
1.Thread.Sleep()方法阻塞的是当前线程,如果当前线程是主线程,那么调用Sleep方法就会阻塞主线程。如果想在异步方法中暂停一段时间,不要用Thread.Sleep()方法,因为它会阻塞调用线程,而要用await Task.Delay()。举例:下载一个网址,3秒后下载另外一个。
例如,新建一个Winfrom程序(选用winform程序而不采用控制台为例子是因为在控制台中看不到区别,但是在winfrom程序中就可以看到区别,在ASP.NET Core中也看不到区别,但是Sleep()方法会降低并发,因此不要用Sleep()方法):
在后台的代码如下:
private async void button1_Click(object sender, EventArgs e)
{
HttpClient httpClient=new HttpClient();
string s1= await httpClient.GetStringAsync("https://www.youzack.com");
textBox1.Text = s1.Substring(0, 2000);
//Thread.Sleep(3000);
await Task.Delay(3000);
string s2 = await httpClient.GetStringAsync("https://www.baidu.com");
textBox1.Text = s2.Substring(0, 200);
}
1.有时需要提前终止任务,比如:请求超时、用户取消请求。很多异步方法都有CancellationToken参数,用于获得提前终止执行的信号。
2.CancellationToken结构体:
public class Program
{
public static async Task Main(string[] args)
{
//await Download1Async("https://www.youzack.com", 100);
CancellationTokenSource cts=new CancellationTokenSource();
cts.CancelAfter(5000);
CancellationToken cToken=cts.Token;
await Download2Async("https://www.youzack.com",100,cToken);
}
///
/// 不带CancellationToken的方法
///
///
///
///
public static async Task Download1Async(string url,int n)
{
HttpClient client = new HttpClient();
for(int i = 0; i < n; i++)
{
string html=await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
}
}
///
/// 带CancellationToken的方法
///
///
///
///
public static async Task Download2Async(string url, int n, CancellationToken cancellationToken)
{
HttpClient client = new HttpClient();
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("请求被取消");
break;
}
}
}
}
4.在ASP.NET Core开发中,一般不需要自己处理cancellationToken、CancellationTokenSource 这些,只要能做到“能转发cancellationToken”即可。在ASP.NET Core会对用户请求中断进行处理。
1.Task类的重要方法:
public class Program
{
public static async Task Main(string[] args)
{
string[] files=Directory.GetFiles(@"D:/Test");
Task<int>[] countTasks=new Task<int>[files.Length];
for(int i=0; i<files.Length; i++)
{
string filename=files[i];
Task<int> t = ReadCharsCount(filename);
countTasks[i] = t;
}
int[] counts=await Task.WhenAll(countTasks);
int c = counts.Sum();//计算数组中所有元素的和
Console.WriteLine(c);
}
static async Task<int> ReadCharsCount(string filename)
{
string s=await File.ReadAllTextAsync(filename);
return s.Length;
}
}
async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲是没区别的,因此对于接口中的方法或抽象方法不能修饰为async。
如:
public interface Itest
{
Task<int> GetCharCount(string file);
}
public class Test : Itest
{
public async Task<int> GetCharCount(string file)
{
string s = await File.ReadAllTextAsync(file);
return s.Length;
}
}
yield return不仅能够简化数据的返回,而且可以让数据处理"流水线化",提升性能。
如:
public class Program
{
public static async Task Main(string[] args)
{
IEnumerable<string> lists= Test();
foreach(var item in lists)
{
Console.WriteLine(item);
}
}
public static IEnumerable<string> Test()
{
yield return "hello1";
yield return "hello2";
yield return "hello3";
}
}
1.答案是让数据处理变得简单。
比如,现在有这样一个需求:统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。用LINQ的知识很容易就可以写出来:
var items = s.Where(c => char.IsLetter(c)) //过滤非字母
.Select(c => char.ToLower(c)) //大写字母转化为小写
.GroupBy(c => c)//根据字母进行分组
.Where(g => g.Count() > 2)//过滤掉出现次数小于2
.OrderByDescending(g => g.Count())//按照出现次数排序
.Select(g => new { Char = g.Key, Count = g.Count() });
2.要想把LINQ学好,需要按照委托—lambda—LINQ的顺序来,即先把委托学好,再学好委托中lamda,最后学习LINQ。
LINQ中提供了大量类似于Where的扩展方法,简化数据处理,大部分都在System.Linq命名空间中。
如:
bool b1 = List.Any(e => e.Salary > 8000);
bool b2 = List.Where(e => e.Salary > 8000).Any();
可以在Order()、OrderByDescending()后继续写ThenBy () 、ThenByDescending()。
案例:优先按照Age排序,如果Age相同再按照Salary排序。
list.OrderBy(e => e.Age).ThenByDescending(e => e.Salary),
千万不要写成list.OrderBy(e => e.Age). OrderByDescending (e => e.Salary)。
Skip(n)跳过n条数据,Take(n)获取n条数据。
案例:跳过前2条数据并开始获取之后的3条数据(即获得第3、4、5条数据):
var orderedItems1 = list.Skip(2).Take(3);
Skip()、Take()也可以单独使用。
Max()、Min () 、Average () 、Sum () 、Count ()。
LINQ中所有的扩展方法几乎都是针对IEnumerable接口的,而几乎所有能返回集合的都返回IEnumerable,所以是可以把几乎所有方法“链式使用”的。
list.Where(e => e.Age > 30).Min(e=>e.Salary)。
代码举例:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
int maxAge = list.Max(e => e.Age);
Console.WriteLine($"最大年龄:{maxAge}");
long minId = list.Min(e => e.Id);
Console.WriteLine($"最小Id:{minId}");
double avgSalary = list.Average(e => e.Salary);
Console.WriteLine($"平均工资:{avgSalary}");
int sumSalary = list.Sum(e => e.Salary);
Console.WriteLine($"工资总和:{sumSalary}");
int count = list.Count();
Console.WriteLine($"总条数:{count}");
int minSalary2 = list.Where(e => e.Age > 30).Min(e => e.Salary);
Console.WriteLine($"大于30岁的人群中的最低工资:{minSalary2}");
}
record Employee
{
public long Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool Gender { get; set; }
public int Salary { get; set; }
}
GroupBy()方法参数是分组条件表达式,根据条件将一个大点的集合分成多个小集合。
例子:根据年龄分组,获取每组人数、最高工资、平均工资:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
var items = list.GroupBy(e => e.Age);
foreach (var item in items)
{
int age = item.Key;
int count = item.Count();
int maxSalary = item.Max(e => e.Salary);
double avgSalary = item.Average(e => e.Salary);
Console.WriteLine($"年龄{item.Key},人数{count},最高工资{maxSalary},平均工资{avgSalary}");
}
}
通过Select方法把集合中的每一项转换为另外一种类型。
IEnumerable ages = list.Select(e => e.Age);
IEnumerable names = list.Select(e=>e.Gender?“男”:“女”);
var dogs = list.Select(p=>new Dog{NickName=e.Name,Age=e.Age});
代码如下:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
var ages = list.Select(e => e.Age);
Console.WriteLine(string.Join(",", ages));
var names = list.Select(e => e.Gender ? "男" : "女");
Console.WriteLine(string.Join(",", names));
var items1 = list.Select(e => new { e.Name, e.Age, XingBie = e.Gender ? "男" : "女" });
foreach (var item in items1)
{
string name = item.Name;
int age = item.Age;
string xingbie = item.XingBie;
Console.WriteLine($"名字={name},年龄={age},性别={xingbie}");
}
var items2 = list.GroupBy(e => e.Gender).Select(g => new
{
Gender = g.Key,
Count = g.Count(),
AvgSalary = g.Average(e => e.Salary),
MinAge = g.Min(e => e.Age)
});
foreach (var item in items2)
{
Console.WriteLine($"性别{item.Gender},人数{item.Count},平均工资{item.AvgSalary:F},最小年龄{item.MinAge}");
}
}
var p = new {Name=“tom”,Id=1};
var p1 = new {name,Id=1,p.Age};
通过反编译看匿名类型原理。
var items = list.Select(e=>new {e.Name,e.Age,XingBie= e.Gender ? “男” : “女”});
var items = list.GroupBy(e => e.Gender).Select(g=>new { Gender=g.Key,Count=g.Count(),AvgSalary= g.Average(e => e.Salary),MinAge= g.Min(e => e.Age)});
有一些地方需要数组类型或者List类型的变量,我们可以用ToArray()方法和ToList()分别把IEnumerable转换为数组类型和List类型。
Where、Select、OrderBy、GroupBy、Take、Skip等返回值都是IEnumerable类型,所以可以链式调用。例子:“获取Id>2的数据,然后按照Age分组,并且把分组按照Age排序,然后取出前3条,最后再投影取得年龄、人数、平均工资”。
代码举例:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
var items = list.Where(e => e.Id > 2).GroupBy(e => e.Age).OrderBy(g => g.Key).Take(3)
.Select(g => new { Age = g.Key, Count = g.Count(), AvgSalary = g.Average(e => e.Salary) });
foreach (var item in items)
{
Console.WriteLine($"年龄:{item.Age},人数:{item.Count},平均工资:{item.AvgSalary}");
}
例如,有如下这样一段字符串:
sdfgsdgdffffreqqwertyuiopasdfghjklzxcvbnm,yhndhsfdsgfeybxnzzzzzzzzcyweferoqweyrei./,\]-=sadjsfhjdsfjuewxbcvc
现在要把所有的非字母过滤掉,可以很方便地用LINQ中的方法实现:
string str = @"sdfgsdgdffffreqqwertyuiopasdfghjklzxcvbnm,yhndhsfdsgfeybxnzzzzzzzzcyweferoqweyrei./,\]-=sadjsfhjdsfjuewxbcvc";
var result = str.Where(c => char.IsLetter(c));//选择字母,即过滤掉非字母
foreach(var item in result)
{
Console.Write(item+" ");
}
习题1:
有一个用逗号分隔的表示成绩的字符串,如"61,90,100,99,18,22,38,66,80,93,55,50,89",计算这些成绩的平均值。
public class Program
{
static void Main(string[] args)
{
List<int> lists = new List<int>() { 61, 90, 100, 99, 18, 22, 38, 66, 80, 93, 55, 50, 89 };
var results = lists.Average();//求出集合中的平均数
Console.WriteLine(results);
}
}
习题2:
统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。
public class Program
{
static void Main(string[] args)
{
string str = @"sdfgsdgdffffreqqwertyuiopasdfghjklzxcvbnm,yhndhsfdsgfeybxnzzzzzzzzcyweferoqweyrei./,\]-=sadjsfhjdsfjuewxbcvc";
var result = str.Where(c => char.IsLetter(c))//选择字母,即过滤掉非字母
.Select(c => char.ToLower(c))//大写字母全部转换为小写
.GroupBy(c => c)//根据字母进行分组
.Where(c => c.Count() > 2)//过滤掉字母出现频率低于2的
.OrderByDescending(c => c.Count())//根据出现次数降序排序(即出现次数多的排在前面)
.Select(c => new { Key = c.Key, Count = c.Count() });//实例化一个匿名对象,并赋值字符的值和出现的次数
foreach(var item in result)
{
Console.WriteLine(item);
}
}
}
大佬写的很详细的关于依赖注入和控制反转的博客
IDbConnection conn = ServiceLocator.GetService<IDbConnection>();
- 依赖注入
class Demo
{
public IDbConnection Conn { get; set; }
public void InsertDB()
{
IDbCommand cmd = Conn.CreateCommand();
}
}
服务(service):对象;
注册服务;
服务容器:负责管理注册的服务;
查询服务:创建对象及关联对象;
对象生命周期:Transient(瞬态); Scoped(范围); Singleton(单例);
1.根据类型来获取和注册服务。
2.可以分别指定服务类型(service type)和实现类型(implementation type)。这两者可能相同,也可能不同。服务类型可以是类,也可以是接口,建议面向接口编程,更灵活。
3.NET控制反转组件取名为DependencyInjection,但它包含ServiceLocator的功能。
4.使用DI的步骤:
5.一个简单的例子:
using System;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp1
{
public interface ITestService
{
public string Name { get; set; }
public void SayHi();
}
public class TestServiceImpI: ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine($"Hi,I'm{Name}");
}
}
public class TestServiceImpI2 : ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine($"你好,我是{Name}");
}
}
public class Program
{
public static void Main()
{
//ITestService t = new TestServiceImpI();
//t.Name = "tom";
//t.SayHi();
ServiceCollection services = new ServiceCollection();
services.AddTransient<TestServiceImpI>();
using (ServiceProvider sp = services.BuildServiceProvider())
{
TestServiceImpI t= sp.GetService<TestServiceImpI>();
t.Name = " Lily";
t.SayHi();
};
Console.ReadKey();
}
}
}
1.给类构造函数中打印,看看不同生命周期的对象创建,使用serviceProvider.CreateScope()创建Scope。
2.如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法。
3.不要在长生命周期的对象中引用比它短的生命周期的对象。在ASP.NET Core中,这样做默认会抛异常。
4.生命周期的选择:如果类无状态,建议为Singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。
5.NET注册服务的重载方法很多,看着文档琢磨吧。
1.依赖注入是有“传染性”的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。
2…NET的DI默认是构造函数注入。
3.案例:编写一个类,记录日志(模拟的输出),把Dao、日志都放入单独的服务类。
代码如下:
using System;
using Microsoft.Extensions.DependencyInjection;
namespace DI会传染
{
public class Program
{
static void Main(string[] args)
{
ServiceCollection services = new ServiceCollection();
services.AddScoped<Controller>();
services.AddScoped<ILog,LogImpl>();
services.AddScoped<IStorage, StorgeImpl>();
services.AddScoped<IConfig, ConfigImpl>();
using(var sp = services.BuildServiceProvider())
{
var c=sp.GetRequiredService<Controller>();
c.Test();
}
Console.ReadKey();
}
}
public 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()
{
this.log.Log("开始上传");
this.storage.Save("33333333333", "3.txt");
this.log.Log("上传完毕");
}
}
///
/// 日志相关
///
public interface ILog
{
public void Log(string msg);
}
public class LogImpl : ILog
{
public void Log(string msg)
{
Console.WriteLine($"日志:{msg}");
}
}
///
/// 配置相关
///
public interface IConfig
{
public string GetValue(string name);
}
public class ConfigImpl : IConfig
{
public string GetValue(string name)
{
return "你好";
}
}
///
/// 云存储相关
///
public interface IStorage
{
public void Save(string content,string name);
}
public class StorgeImpl : IStorage
{
private readonly IConfig config;
public StorgeImpl(IConfig config)
{
this.config = config;
}
public void Save(string content, string name)
{
string server = config.GetValue("server");
Console.WriteLine($"向服务器{server}的文件名为{name}上传{content}");
}
}
}
前后端的代码被放到同一个项目中,前端人员负责编写页面的模板,而后端开发人员负责编写控制器和模型的代码并且“套模板”。缺点:互相依赖;耦合性强;责任划分不清。
前端开发人员和后端开发人员分别负责前端和后端代码的开发,各自在自己的项目中进行开发;后端人员只写Web API接口,页面由前端人员负责。
需求变动越来越大、交付周期越来越短、多端支持。
优点:独立开发,不互相依赖;耦合性低;责任划分清晰;前后端分别部署,可以针对性运维(扩容等)。
缺点:对团队的沟通能力要求更高,提前沟通好接口和通知接口变更;不利于SEO(可以用“服务器端渲染”SSR);对运维要求更高。
Web API两种风格:面向过程(RPC)、面向REST(REST)
RPC:“控制器/操作方法“的形式把服务器端的代码当成方法去调用。把HTTP当成传输数据的通道,不关心HTTP谓词。通过QueryString、请求报文体给服务器传递数据。状态码。比如:/Persons/GetAll、/Persons/GetById?id=8、/Persons/Update、/Persons/DeleteById/8。
REST:按照HTTP的语义来使用HTTP协议:
RPC:业务驱动,自然。
REST:要求开发人员对REST原则更了解、并且有更多的设计能力。
REST的优点:
1、通过URL对资源定位,语义更清晰;
2、通过HTTP谓词表示不同的操作,接口自描述;
3、可以对GET、PUT、DELETE请求进行重试;
4、可以用GET请求做缓存;
5、通过HTTP状态码反映服务器端的处理结果,统一错误处理机制。
6、网关等可以分析请求处理结果。
REST的缺点:
1、真实系统中的资源非常复杂,很难清晰地进行资源的划分,对技术人员的业务和技术水平要求高。
2、不是所有的操作都能简单地对应到确定的HTTP谓词中。
3、系统的进化可能会改变幂等性。
4、通过URL进行资源定位不符合中文用户的习惯。
5、HTTP状态码个数有限。
6、有些环节会篡改非200响应码的响应报文。
7、有的客户端不支持PUT、DELETE请求。
选择:
1、REST是学术化的概念,仅供参考。为什么AWS、ES等比较RESTful。为什么阿里、腾讯等很多系统不RESTful?
2、根据公司情况,进行REST的选择和裁剪。
HTTP传递参数的三种方式:
URL(资源定位):适合定位;长度限制。
QueryString(URL之外的额外数据):灵活;长度限制。
请求报文体(供PUT、POST提供数据):灵活;长度不限制;不支持GET、Delete。
实施指南:
1)对于保存、更新类的请求POST、PUT请求,把全部参数都放到请求报文体中;
2)对于DELETE请求,要传递的参数就是一个资源的id,因此把参数放到QueryString中即可;
3)对于GET请求,一般参数的内容都不会太长,因此统一通过QueryString传递参数就可以。对于极少数参数内容超过URL限制的请求,由于GET、PUT请求都是幂等的,因此我们把请求改成通过PUT请求,然后通过报文体来传递参数。