延續昨天讀取IT鐵人賽文章的程式,今天要在miniblog同步IT鐵人賽的文章,遇到以下問題:
.NET Core如何在非Controller、Action類別的方法取得依賴注射的service singleton物件?
舉例:
資料存儲都使用IBlogService類別,並且在ConfigureServices方法使用AddSingleton註冊到asp.net core web服務。
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBlogService, MSSqlBlogService>();
}
假設現在有一個IT鐵人賽Service類別,想要使用BlogService的功能,但是找不到接口取得該singleton物件。
解決方式
在S.O查到文章...Static Factory Class,發現可以在Startup類別Configure方法下,使用IApplicationBuilder物件呼叫ApplicationServices.GetService<注射類別>()
取得singleton物件;
舉例
前面注射IBlogService類別,可以使用以下範例取得:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBlogService, MSSqlBlogService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var blogservice = app.ApplicationServices.GetService<IBlogService>();
}
.NET Core如何取得依賴注射的設定檔物件?
跟service取得singleton方式不一樣,ApplicationServices沒有提供方法可以取得設定檔資料物件 ,需要換一個類別物件使用,就是Startup下的IConfiguration屬性
。
它會在Startup物件一開始建立的時候,就將appsettings.json
從字串轉換成Configuration物件保存起來,我們只需要使用Get<設定檔類別>
就可以取得設定檔物件。
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) => Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
services.Configure<BlogSettings>(section);
/*取得Blog設定檔物件*/
var sectionBlog = Configuration.GetSection("blog").Get<BlogSettings>();
}
}
解決上述問題後,只需要應用昨天的例子,做成一個同步兩邊的API。
使用Azure Function API Code概念:"要使用API功能,需要有正確的Code參數值",建立"ITIronManKeyCode"屬性
假如沒有填寫正確code值返回400錯誤,不給使用功能。
API Code:
[Route("/api/ITIromManPostSync/")]
public IActionResult ITIromManPostSync(string Code)
{
if (Code == null || Code != _settings.Value.ITIronManKeyCode)
return new BadRequestResult();
ITIronManSyncPostService.SyncPost();
return View("~/Views/Blog/Index.cshtml");
}
將想要查詢的文章系列連接以ITIronManArticleURI
Json陣列保存起來,在Service foreach查詢網頁資料
Service部分:
邏輯綜合昨天的程式,將讀取到的文章更新到miniblog的資料庫或是XML文件內
using AngleSharp.Parser.Html;
using Miniblog.Core.Helper;
using Quartz;
using Quartz.Impl;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Miniblog.Core.Services
{
public class ITIronManSyncPostService
{
private static readonly HtmlParser _parser = new HtmlParser();
public IList<Post> Posts { get; set; } = new List<Post>();
private string _url { get; set; }
public static BlogSettings sectionBlog;
public static IBlogService blogService;
public static void SyncPost()
{
foreach (var item in sectionBlog.ITIronManArticleURI)
{
var lstITposts = ITIronManSyncPostService.GetITIronManPosts(item).Result;
foreach (var itpost in lstITposts)
{
//檢查有沒有資料,如果有資料更新動作
var id = itpost.link.Replace("https://ithelp.ithome.com.tw/articles/", "");
var post = blogService.GetPostById(id).Result;
if (post != null)
{
post.Content = itpost.Content;
post.Excerpt = $"{itpost.link}" + HtmlHelper.HtmlInnerText(itpost.Content);
post.Title = itpost.Title;
post.Categories = new string[] { itpost.Article };
}
//檢查有沒有資料,如果沒有資料做新增動作
else
{
post = new Miniblog.Core.Models.Post()
{
ID = id,
Categories = new string[] { itpost.Article },
Content = itpost.Content,
Excerpt = $"{itpost.link}" + HtmlHelper.HtmlInnerText(itpost.Content),
IsMarkDown = true,
PubDate = itpost.PubDate,
Slug = id,
IsPublished = true,
Title = itpost.Title
};
}
blogService.SavePost(post);
};
}
}
public async static Task<IList<Post>> GetITIronManPosts(string url)
{
var itironman = new ITIronManSyncPostService();
itironman._url = url;
await itironman.ExecuteAsync();
return itironman.Posts;
}
private async Task ExecuteAsync()
{
//因為IT鐵人賽只需要三十篇文章,每頁10篇文章,抓取頁數取4頁就好
for (int i = 1; i < 4; i++)
await GetITIronManPostsAsync(_url + $"?page={i}");
}
private async Task GetITIronManPostsAsync(string url)
{
var htmlContent = (await GetAsync(url));
var document = _parser.Parse(htmlContent);
//獲取鐵人賽主題
var article = document.QuerySelector(".qa-list__title--ironman");
article.RemoveChild(article.QuerySelector("span"));/*移除系列文字*/
var articleText = article.TextContent.Trim();
//獲取鐵人賽:發布日期、標題、內容、連結
var allpost = document.QuerySelectorAll(".profile-list__content");
foreach (var postInfo in allpost)
{
var post = new Post();
var titleAndLinkDom = postInfo.QuerySelector(".qa-list__title>a");
post.Title = titleAndLinkDom.InnerHtml.Trim();
post.link = titleAndLinkDom.GetAttribute("href").Trim();
post.Content = GetPostContentAsync(post.link).Result.Trim();
post.PubDate = DateTime.Parse(postInfo.QuerySelector(".qa-list__info>.qa-list__info-time").GetAttribute("title").Trim());
post.Article = articleText;
Posts.Add(post);
}
}
private async Task<string> GetPostContentAsync(string posturl)
{
var htmlContent = (await GetAsync(posturl));
var document = _parser.Parse(htmlContent);
return document.QuerySelectorAll(".markdown__style").FirstOrDefault().InnerHtml;
}
public async Task<string> GetAsync(string uri)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
return await reader.ReadToEndAsync();
}
}
public class Post
{
public string Title { get; set; }
public string link { get; set; }
public string Content { get; set; }
public string Article { get; set; }
public DateTime PubDate { get; set; }
}
}
}