ASP.NET Core 快速入门(实战篇)
上篇講了《asp.net core在linux上的環(huán)境部署》。今天我們將做幾個(gè)小玩意實(shí)戰(zhàn)一下。用到的技術(shù)和工具有mysql、websocket、AngleSharp(爬蟲html解析)、nginx多站點(diǎn)部署。
NO1 留言板(mysql的使用)
演示:http://haojima.net
這個(gè)功能很簡(jiǎn)單。就是對(duì)數(shù)據(jù)庫(kù)的寫入和展示。如果在Windows下,相信大家分分鐘都可以搞定。而初次接觸.net core + mysql可能需要注意些細(xì)節(jié)。
首先打開vs2017新建一個(gè)asp.net core項(xiàng)目(選Web應(yīng)用程序),然后nuget 導(dǎo)入Microsoft.EntityFrameworkCore.Tools 1.1.1和MySql.Data.EntityFrameworkCore 8.0.8-dmr。
然后新建一個(gè)DbContext類。
?string str = @"Data Source=;Database=;User ID=;Password=;pooling=true;CharSet=utf8;port=3306;sslmode=none"; ? ?protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>optionsBuilder.UseMySQL(str); ?
?//下面就可以添加要加入數(shù)據(jù)庫(kù)的實(shí)體了//public DbSet<Message> Messages { get; set; }}
到此為止,我們已經(jīng)可以利用EF Core直接連接mysql進(jìn)行增刪改查操作了。注意:需要導(dǎo)入命名空間using Microsoft.EntityFrameworkCore; using MySQL.Data.EntityFrameworkCore.Extensions;
當(dāng)然。你會(huì)說(shuō),連接字符串不能硬編碼到代碼里面。我們也可以放配置文件。appsettings.json
{"Logging": {"IncludeScopes": false,"LogLevel": {"Default": "Warning"}},"ConnectionStrings": { "SqlServerConnection": "Data Source=;Database=;User ID=;Password=;pooling=true;CharSet=utf8;port=3306;sslmode=none" } }然后把上面的硬編碼注釋掉。在Startup.cs文件的ConfigureServices方法添加
var connection = Configuration.GetConnectionString("SqlServerConnection"); services.AddDbContext<DataContext>(options => options.UseMySQL(connection));【注意】項(xiàng)目名稱和路徑最好不要有中文,不然會(huì)出現(xiàn)些亂七八糟的問(wèn)題。
【完整代碼】:https://github.com/zhaopeiym/BlogDemoCode/tree/master/MessageBoard
NO2 聊天室(WebSocket的使用)
演示:http://socket.haojima.net
WebSocket是Html5新增的一個(gè)很酷的技術(shù)。下面我們簡(jiǎn)單講解下這個(gè)很酷的技術(shù)
創(chuàng)建了一個(gè)WebSocket對(duì)象后會(huì)觸發(fā)打開連接事件:
Socket.onopen = function(){ ?}除了onopen事件,還有其他三個(gè)事件:
Socket.onmessage ?//客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā)Socket.onerror ? ?//通信發(fā)生錯(cuò)誤時(shí)觸發(fā)Socket.onclose ? ?//連接關(guān)閉時(shí)觸發(fā)另外還有兩個(gè)方法:
Socket.send() ? //使用連接發(fā)送數(shù)據(jù)Socket.close() ?//關(guān)閉連接最后還有四個(gè)連接狀態(tài)屬性:
Socket.readyState0 - 表示連接尚未建立。 1 - 表示連接已建立,可以進(jìn)行通信。 2 - 表示連接正在進(jìn)行關(guān)閉。 3 - 表示連接已經(jīng)關(guān)閉或者連接不能打開。整個(gè)WebSocket常用功能知識(shí)點(diǎn)就四個(gè)事件、兩個(gè)方法、四種狀態(tài)。簡(jiǎn)單吧,下面我們看看asp.net core后臺(tái)的配合:
后臺(tái)添加一個(gè)SocketHandler類,并添加一個(gè)靜態(tài)方法Map:
/// <summary>/// 請(qǐng)求/// </summary>/// <param name="app"></param>public static void Map(IApplicationBuilder app){app.UseWebSockets(); //【注意】需要 nuget ? 導(dǎo)入 Microsoft.AspNetCore.WebSockets.Serverapp.Use(Acceptor); }然后新增對(duì)應(yīng)的Acceptor方法:
/// <summary>/// 接收請(qǐng)求
/// </summary>
/// <param name="httpContext"></param>
/// <param name="n"></param>/// <returns></returns>static async Task Acceptor(HttpContext httpContext, Func<Task> n){
需要在Startup.cs類里面的Configure方法里面加入
app.Map("/ws", SocketHandler.Map); ? //傳入我們剛才新建的靜態(tài)方法Map現(xiàn)在為止,基本的類和配置已經(jīng)完成。
我們主要操作,是在Acceptor方法里面接收和發(fā)送消息。
后臺(tái)關(guān)鍵代碼也就這三句,建立連接、等待接收、發(fā)送消息。
不過(guò)這里有一點(diǎn)需要理解。建立連接后,可以接收任意多次客戶端消息。所以ReceiveAsync等待接收這里需要死循環(huán)接收消息,直到連接斷開。(不用擔(dān)心真的死循環(huán),沒(méi)有消息發(fā)送的時(shí)候,代碼會(huì)阻塞在那里等待消息)
【完整實(shí)現(xiàn)】:https://github.com/zhaopeiym/ChatRoom
NO3 找工作(AngleSharp的使用)
演示:http://job.haojima.net
對(duì)于爬蟲抓包,我相信大家初次接觸都非常的熱衷于此。我也不例外。
那么在asp.net core下面是否也有這樣的插件呢?答案是肯定的。
http://www.cnblogs.com/linezero/p/5599611.html?HtmlAgilityPack HTML解析(感謝博主對(duì).net core的貢獻(xiàn))。不過(guò)xpath用起來(lái)超級(jí)惡心。
之前在.net下面有一款Jumony?http://www.cnblogs.com/Ivony/p/3447536.html(博客園大牛寫的)。支持CSS選擇和linq查詢。簡(jiǎn)直不要太爽。可是不支持.net core。(本人試了下遷移.net core,發(fā)現(xiàn)很多類在.net core沒(méi)有實(shí)現(xiàn))
最后還是到了一款支持.net core的解析組件。并可以媲美Jumony,同樣支持css選擇和linq查詢。那就是AngleSharp。
新建項(xiàng)目,nuget 安裝 AngleSharp。然后以下簡(jiǎn)單使用:
?var htmlString = await http.GetStringAsync(url); ?
??HtmlParser htmlParser = new HtmlParser();
?? ?var jobInfos = htmlParser.Parse(htmlString).QuerySelectorAll(".newlist_list_content table").Where(t => t.QuerySelectorAll(".zwmc a").FirstOrDefault() != null).Select(t => new JobInfo(){PositionName = t.QuerySelectorAll(".zwmc a").FirstOrDefault().TextContent,CorporateName = t.QuerySelectorAll(".gsmc a").FirstOrDefault().TextContent,Salary = t.QuerySelectorAll(".zwyx").FirstOrDefault().TextContent,WorkingPlace = t.QuerySelectorAll(".gzdd").FirstOrDefault().TextContent,.ToList(); ?
?? ? ?return jobInfos; }
看到?jīng)]有,就像jq一樣解析html。如果你說(shuō)不爽我都不信。
【完整實(shí)現(xiàn)】:https://github.com/zhaopeiym/JobWanted
部署多個(gè)站點(diǎn)
以上,這些項(xiàng)目都比較簡(jiǎn)單。關(guān)鍵技術(shù)點(diǎn)和難點(diǎn)都進(jìn)行的分析。我相信大家都可以動(dòng)起手練習(xí)起來(lái)了。
不過(guò)有個(gè)問(wèn)題,前面我們只說(shuō)了部署一個(gè)應(yīng)用程序。如果是多個(gè)該怎么部署呢?
首先我們把多個(gè)程序發(fā)布包放到服務(wù)器上。
然后修改nginx的配置文件/etc/nginx/conf.d/default.conf
?listen 80; ?
? ?server_name www.haojima.net; ?
? ?? ? ? ? #對(duì)應(yīng)的域名root /home/projects/messagBoard; ?
? ?? ? ? ? ? ? #程序路徑location / { ? ? ?
? ?? ? ? ? ? ? ?proxy_pass http://localhost:5000; ?#內(nèi)網(wǎng)端口
? ? ? ?proxy_http_version 1.1;
? ?? ? ? ? ? ? ?proxy_set_header Upgrade $http_upgrade; ? ?
? ?? ? ? ? ? ? ?proxy_set_header Connection keep-alive; ? ?
? ?? ? ? ? ? ? ?proxy_set_header Host $host; ? ? ?
? ?? ? ? ? ? ? ?proxy_cache_bypass $http_upgrade; ?
? ?? ? ? ? ? ? ?proxy_set_header X-real-ip $remote_addr; ? ?? ??
? ?? ? ? ? ? ? ?proxy_set_header Upgrade $http_upgrade; ? } }
有幾個(gè)程序就添加幾個(gè)server,不過(guò)需要修改你解析到的域名、程序路徑和內(nèi)網(wǎng)對(duì)應(yīng)的端口(看配置里的注釋) 。
然后修改supervisor的配置文件/etc/supervisor/conf.d/supervisord.conf
有幾個(gè)程序就往下復(fù)制幾份program。需要修改program名稱,只要名稱不重復(fù)就可以。然后修改 運(yùn)行程序的命令 對(duì)應(yīng)的dll和命令執(zhí)行的目錄(看配置文件的注釋)。
如此就可以部署多個(gè)程序了。
開始我還以為是在域名解析的時(shí)候,解析IP + 端口。原來(lái)是多個(gè)域名解析到同一個(gè)IP,然后nginx在內(nèi)部做域名和內(nèi)網(wǎng)端口分發(fā)。
一些其它的細(xì)節(jié)
部署阿里云
我們?cè)趌inux的防火墻開放了端口,發(fā)現(xiàn)在外面還是訪問(wèn)不了(可以telnet IP 端口 來(lái)測(cè)試)。有可能是阿里云攔截了。https://help.aliyun.com/document_detail/25471.html?在安全組添加某端口哪些IP可以訪問(wèn)。
mysql的客戶端
對(duì)于mysql,我們安裝好之后總不能每次命令操作吧。在Windows下面有個(gè)客戶端Navicat可以方便管理mysql。Navicat
獲取ip
用了nginx后發(fā)現(xiàn)取不到瀏覽器IP了。那是因?yàn)槲覀兂绦蚨际菫g覽器訪問(wèn)nginx,然后nginx轉(zhuǎn)發(fā)內(nèi)網(wǎng)程序端口。所以取到的IP都是內(nèi)網(wǎng)本機(jī)IP。如果需要取瀏覽器IP需要在nginx配置
server { ? ?listen 80; ?
?server_name www.haojima.net; ?
? ?root /home/projects/messagBoard; ?
? ? ?location / { ? ? ?
? ? ??proxy_pass http://localhost:5000;
? ? ??? ? ? ?proxy_http_version 1.1; ?? ? ?
? ? ??? ? ? ?proxy_set_header Upgrade $http_upgrade; ?
? ? ??? ? ? ??proxy_set_header Connection keep-alive; ?
? ? ??? ? ? ??proxy_set_header Host $host; ? ?
? ? ??? ? ? ??proxy_cache_bypass $http_upgrade; ?
? ? ??? ? ? ??proxy_set_header X-real-ip $remote_addr; ? ? # 新添加} }
然后代碼里面取IP:
var ip = HttpContext.Request.Headers["X-real-ip"].FirstOrDefault();WebSocket在nginx的配置
上面我們寫的WebSocket直接運(yùn)行發(fā)現(xiàn)沒(méi)有任何問(wèn)題,可是部署在nginx去跑不起來(lái)了。那是因?yàn)樾枰猲ginx支持WebSocket,需要配置。http://nginx.org/en/docs/http/websocket.html
server { ??listen 80; ?
??server_name job.haojima.net;
??? ?root /home/projects/jobWanted; ?
??? ??location / { ? ? ?
??? ???proxy_pass http://localhost:5002; ?
??? ???proxy_http_version 1.1; ? ?
??? ???proxy_set_header Upgrade $http_upgrade; ?
??? ???proxy_set_header Connection keep-alive;
??? ???proxy_set_header Host $host;
??? ???proxy_cache_bypass $http_upgrade;
??? ???proxy_set_header X-real-ip $remote_addr; ?
??? ???proxy_set_header Upgrade $http_upgrade;
??? ???# 新增proxy_set_header Connection "upgrade"; ? ? ?# 新增 } }
WebSocket心跳
經(jīng)過(guò)上面的配置,我們的WebSocket在nginx上跑起來(lái)了。萬(wàn)分歡喜的我們,發(fā)現(xiàn)一分鐘不發(fā)消息就自動(dòng)掉線了。郁悶至極到頭大。細(xì)心的同學(xué)通過(guò)上面的鏈接資料其實(shí)已經(jīng)有說(shuō)明:
By default, the connection will be closedif the proxied server does not transmit any data within 60 seconds. This timeout can be increased with the proxy_read_timeout directive. Alternatively, the proxied server can be configured to periodically send WebSocket ping frames to reset the timeout and check if the connection is still alive.
靠,英文實(shí)在太爛了。
默認(rèn)情況下,如果代理的服務(wù)器在60秒內(nèi)沒(méi)有傳輸任何數(shù)據(jù),則連接將被關(guān)閉。可以使用proxy_read_timeout指令增加此超時(shí) 。或者,代理服務(wù)器可以配置為定期發(fā)送WebSocket ping幀以重置超時(shí)并檢查連接是否仍然存在。nginx給出了兩種解決方案。第一種,修改proxy_read_timeout (默認(rèn)60秒)。第二種,瀏覽器客戶端定時(shí)發(fā)送心跳包(時(shí)間要短于proxy_read_timeout)。
我使用的是第二種方式。
第一種雖然簡(jiǎn)單粗暴,但是時(shí)間再長(zhǎng)也是一個(gè)值,還是會(huì)有超時(shí)的可能。再者,誰(shuí)能保證瀏覽器端不會(huì)new 很多個(gè)WebSocket出來(lái)?yè)v蛋。
第二種方式,瀏覽器定時(shí)發(fā)送一條消息,內(nèi)容和后臺(tái)約定下。如發(fā)送“心跳”,然后后臺(tái)接收消息是,判斷如果是“心跳”則不做任何處理。
中文編碼
在做“找工作”爬前程無(wú)憂的數(shù)據(jù)時(shí),發(fā)現(xiàn)他們使用的GBK編碼。而在.net core中默認(rèn)不支持這種格式,導(dǎo)致取到的數(shù)據(jù)都是亂碼。我們需要nuget安裝System.Text.Encoding.CodePages。然后在Startup.cs的Configure里面注冊(cè):
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);//注冊(cè)編碼提供程序使用:
var htmlBytes = await http.GetByteArrayAsync(url);var htmlString = Encoding.GetEncoding("GBK").GetString(htmlBytes);
asp.net core 端口分配
asp.net core 默認(rèn)端口都是5000。那么我們運(yùn)行第二個(gè)程序的時(shí)候就會(huì)提示5000端口被占用。這個(gè)時(shí)候,我們就需要為每個(gè)程序分配不同的端口了。
在根目錄新建一個(gè)json文件hosting.json
在Program.cs文件修改
public static void Main(string[] args){? ?var config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("hosting.json", optional: true).Build(); ? ?var host = new WebHostBuilder().UseKestrel().UseConfiguration(config).UseContentRoot(Directory.GetCurrentDirectory()).UseIISIntegration().UseStartup<Startup>().UseApplicationInsights().Build();host.Run(); }
爬拉勾數(shù)據(jù)
在爬拉勾網(wǎng)的時(shí)候沒(méi)有搞定,不知道是不是因?yàn)閔ttps的原因。
using (HttpClient http = new HttpClient()) { ? ?var url = "https://www.lagou.com/zhaopin/Java/?labelWords=label"; ? ?var htmlString = await http.GetStringAsync(url); }在.net core中報(bào)錯(cuò):An unhandled exception occurred while processing the request.
在.net 4.5 中抓到的數(shù)據(jù)是“頁(yè)面加載中...”。和瀏覽器訪問(wèn)的結(jié)果不一樣。
原因未知。如果有大佬解惑,感激不盡!
?
參考
http://www.runoob.com/html/html5-websocket.html
http://www.cnblogs.com/liguobao/p/6130121.html
http://www.cnblogs.com/linezero/p/5806814.html
演示
http://haojima.net
http://socket.haojima.net
http://job.haojima.net
源碼
https://github.com/zhaopeiym/JobWanted
https://github.com/zhaopeiym/ChatRoom
https://github.com/zhaopeiym/BlogDemoCode
原文地址:http://www.cnblogs.com/zhaopei/p/netcore2.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 快速入门(实战篇)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ASP.NET Core - Razor
- 下一篇: 如何在Linux上使用VIM进行.Net