后端分離的項目開發策略已經不是什么新鮮東西了,網上介紹這方面的文章非常多。我自己是在14年的時候接觸到的,對這種開發策略一直愛不釋手,不管新老項目都會首先用前后端分離的思維先去思考一番。從14年到現在在前后分離上面也實踐了近3年的時間,項目大大小小的也差不多4,5個吧,但是卻從來沒有一個是自己覺得很滿意的,其中的原由和心酸可能只有自己才能體會了。
前后端分離實踐有感
現在到處都是前后端分離的實踐。然而一些項目在從一體化 Web 設計轉向前后端分離的架構時,仍然會碰見各種各樣的問題。由于層出不窮的問題,甚至會有團隊質疑,一體化好好的,為什么要前后端分離?
首先看看前后端分離是什么?
“前端”通常指的是,相對來說更接近用戶的一端,例如:APP,網頁、桌面程序等,在現實開發中大部分情況可以理解為“客戶端”;
“后端”相對來說就更泛化了,可以理解為是為前端提供服務的一端。
”分離“顧名思義就是將”前端“和”后端進行分開“,但是這里的分開主要從下面幾個緯度進行分離
下面分別是一體式web架構示意圖和前后分離式web架構。
一體式 Web 架構示意
一體式 Web 架構示意
下面講講什么時候需要前后端分離,即前后端分離的應用場景。
說起這個問題,我想到了多年前,公司在以 .NET 開發團隊為主的基礎上擴展了 Java 團隊,兩個團隊雖然是在做不同的產品,但是仍然存在大量重復性的開發,比如用 ASP.NET WebPage 寫了組織機構相關的頁面,用 JSP 又要再寫一遍。在這種情況下,團隊就開始思考這樣一個方案:如果前端實現與后端技術無關,那頁面呈現的部分就可以共用,不同的后端技術只需要實現后端業務邏輯就好。
方案根本要解決的問題是把數據和頁面剝離開來。應對這種需求的技術是現成的,前端采用靜態網頁相關的技術,HTML + CSS + JavaScript,通過 AJAX 技術調用后端提供的業務接口。前后端協商好接口方式通過 HTTP 提供,統一使用 POST 謂詞。接口數據結構使用 XML 實現,前端 jQuery 解析 XML 很方便,后端對 XML 的處理工具就更多了如JSON 。
這種架構從本質上來說就是 SOA(面向服務的架構)。當后端不提供頁面,只是純粹的通過 Web API 來提供數據和業務交互能力之后,Web 前端就成了純粹的客戶端角色,與 WinForm、移動終端應用屬于同樣的角色,可以把它們合在一起,統稱為前端。以前的一體化架構需要定制頁面來實現 Web 應用,同時又定義一套
WebService/WSDL 來對 WinForm 和移動終端提供服務。轉換為新的架構之后,可以統一使用 Web API 形式為所有類型的前端提供服務。至于某些類型的前端對這個 Web API 進行的 RPC 封裝,那又是另外一回事了。
通過這樣的架構改造,前后端實際就已經分離開了。拋開其它類型的前端不提,這里只討論 Web 前端和后端。由于分離,Web 前端在開發的時候壓根不需要了解后端是用的什么技術,只需要后端提供了什么樣的接口可以用來做什么事情就好。前后端分離之后,由于技術和業務都更專注,開發效率也提高了。分離帶來的好處漸漸體現出來:
1. 前后職責分離
前端傾向于呈現,著重處理用戶體驗相關的問題;后端則傾處于業務邏輯、數據處理和持久化等。在設計清晰的情況下,后端只需要以數據為中心對業務處理算法負責,并按約定為前端提供 API 接口;而前端使用這些接口對用戶體驗負責。
2. 前后技術分離
前端可以不用了解后端技術,也不關心后端具體用什么技術來實現,只需要會 HTML/CSS/JavaScript 就能入手;而后端只需要關心后端開發技術。
3. 前后分離帶來了用戶用戶體驗和業務處理解耦
前端可以根據用戶不同時期的體驗需求迅速改版,后端對此毫無壓力。同理,后端進行的業務邏輯升級,數據持久方案變更,只要不影響到接口,前端可以毫不知情。
任何技術方案都不是萬能的,前后端分離帶來了好處,同時也帶來矛盾。我們在實踐初期,由于前端團隊力量相對薄弱,同時按照慣例,所有業務處理幾乎都是由后端來設計的,前端處理過程中常常發現接口定義不符合用戶操作流程等問題。畢竟后端思維和前端思維還是有所不同——前端思維傾向于用戶體驗,而后端思維則更傾向于業務的技術實現。
除此之外,由于前后分離本質上是一種 SOA 架構,所以在授權上也需要按 SOA 架構的方式來思考。Cookie/Session 的方式不是特別合適,相對來說,基于 Token 的認證則更適合一些。采用基于 Token 的認證就意味著后端的認證部分需要重寫……后端當然不想重寫,于是會將皮球踢給前端……于是前端開始報怨(悲?。?/p>
誰來主導
這些矛盾的出現,歸根結底在于設計不夠清晰明確。一般在開發過程中,主導者應該是架構師。然而大部分場景中,架構師往往也是開發人員,所以他們的主要技術棧會極大的影響前后端在整個項目中的主次作用。這位骨干處于哪端,開發的便捷性就會向哪端傾斜。這是一個不好的現象。
如果沒有良好的流程規范,多數應用產品的開發通常前端接觸的到角色會比后端更多。
換句話說,前端可以成為項目溝通的中心,因此前端比后端更合適承擔主導的角色。
接口設計
接口分后端服務實現和前端調用兩個部分,技術上并不難,因為都是成熟的技術,接口設計才是難點。前面提到前后端會產生一些矛盾。從前端的角度來看,重點關注的是用戶體驗;而從后端的角度來看,重點關注的是數據完整、有效、安全。解決這些矛盾的著眼點就是接口設計。
接口設計時,其粒度的大小往往代表了前后端工作量的大?。航涌诹6忍?,前端要處理的事情就多,尤其是對各種異步處理就可能會感到應接不暇;粒度太大,就會出現高耦合,降低靈活性和擴展性,當然這種情況下后端的工作就輕松不了。業務層面的東西涉及到具體的產品,這里不多做討論。這里主要討論一點點技術層面的東西。
就形式上來說,Web API 可以定義成 REST,也可以是 RPC,只要前后端商議確定下來就行。更重要的是在輸入參數和輸出結果上,最好一開始就有相對固定的定義,一般來說取決于前端架構或采用的 UI 框架。
常見請求參數的數據形式如下所示:
而服務器響應的數據形式就更多了,通常一個完整的響應需要包括狀態碼、消息、數據三個部分的內容,其中
我們在實踐中使用 JSON 形式,最初定義了下面這種形式
code 主要用于指導前端進行一些特殊的操作,比如 0 表示 API 調用成功,非0 表示調用失敗,其中 1 表示需要登錄、2 表示未獲取授權……對于這個定義,前端拿到響應之后,就可以在應用框架層進行一些常規處理,比如當 code 為 1 的時候,彈出登錄窗口請用戶在當前頁面登錄,而當 code 為 2 的時候,則彈出消息提示并后附鏈接引導用戶獲取授權。
一開始這樣做并沒有什么問題,直到前端框架換用了 jQuery EasyUI。以 EasyUI 為例的好多 UI 庫都支持為組件配置數據 URL,它會自動通過 AJAX 來獲取數據,但對數據結構有要求。如果仍然采用之前設計的響應結構,就需要為組件定義數據過濾器(filter)來處理響應結果,這樣做寫 filter 以及為組件聲明 filter 的工作量也是不小的。為了減少這部分工作量我們決定改一改接口。
新的接口是一種可變結構,正常情況下返回 UI 需要的數據結構,出錯的情況則響應一個類型于原定結構的數據結構:
對于新響應數據結構,前端框架只需要判斷一下是否存在 error 屬性,如果存在,檢查其 identity 屬性是否為指定的特殊值,然后再使用其 code 和 message 屬性處理錯誤。這個錯誤判斷過程略為復雜一些,但可以由前端應用框架統一處理。
使用 RESTful 風格的接口,部分狀態碼可以用 HTTP 狀態碼代替,比如 401 表示需要登錄,403 就可以表示沒有獲得授權,當然,雖然 HTTP 狀態碼與 RESTful 風格更配,但是非 RESTful 風格也可以使用 HTTP 狀態碼來代替 error.code。
認證方案很多,比如 Cookie/Session 在某些環境下仍然可行、也可以使用基于 Token 和 OAuth 或者 JWT,下面是幾種方案的介紹。
a.基于 OAuth 的認證方案
目前各大網站的開放式接口都是 SOA 架構,如果把這些開放式接口看作提供服務方(服務端),而把使用這些開放式接口的應用看作客戶端,那么就可以產生這樣一種和前后分離對應的關系:
所以,開放式接口廣泛使用的 OAuth 方案用于前后分離是可行的,但在具體實施上卻并不是那么容易。尤其是在安全性上,由于前端是完全暴露在外的,與 OAuth 通常實施的環境(后端?服務端)相比,要注意的是首次認證不是使用已注冊的 AppID 和 AppToken,而是使用用戶名和密碼。
b.基于 Token/JWT 的認證方案
雖然這個方案放在最后,但這個方案卻是目前前后端分離最適合的方案。基于 Token 的認證方案,各種討論由來已久,而 JWT 是相對較為成熟,也得到多數人認可的一種。從網上可以找到各種技術棧的 JWT 實現,應用起來也比較方便。
前后分離之后,前端的測試將以用戶體驗測試和集成測試為主,而后端則主要是進行單元測試和 Web API 接口測試。與一體化的 Web 應用相比,多了一層接口測試,這一層測試可以完全自動化,一旦完成測試開發,就能在很大程度上控制住業務處理和數據錯誤。這樣一來,集成測試的工作量會相對單一也容易得多。
前端測試的工作相對來說減輕不了多少,前后分離之后的前端部分承擔了原來的集成測試工作。但是在假設 Web API 正確的情況下進行集成測試,工作量是可以減輕不少的,用例可以只關注前端體驗性的問題,比如呈現是否正確,跳轉是否正確,用戶的操作步驟是否符合要求以及提示信息是否準確等等。
對于用戶輸入有效性驗證這部分工作在項目時間緊迫的情況下甚至都可以完全拋給 Web API 去處理。不管是否前后端分離,Web 開發中都有一個共識:永遠不要相信前端!既然后端必須保證數據的安全性和有效性,那么前端省略這一步驟并不會對后端造成什么實質性的威脅,最多只是用戶體驗差一點。但是,如果前后端都要做數據有效性驗證,那一定要嚴格按照文檔來進行,不然很容易出現前后端數據驗證不一致的情況(這不是前后分離的問題,一體化架構同樣存在這個問題)。
小結
總的來說,前后分離所帶來的好處還是很明顯的。但是具體實施的時候需要一個全新的思考方式,而不是基于原有一體化 Web 開發方式來進行思考。
作者:Alukar
鏈接:https://www.jianshu.com/p/3c7dafdc3576
來源:簡書
在使用了Spring Boot數月之后, 我發覺ASP.NET Core中缺失了對面向切面編程(AOP)的默認支持。
維基百科中針對AOP的定義:
面向切面編程(AOP)是一種編程范例,其旨在通過允許跨領域關注點的分離來提高模塊化。它通過“切入點”規范指定要修改的代碼,不修改源代碼本身的情況下,向現有代碼提供額外行為,例如使用日志的方式記錄為所有以"set"開頭的方法調用記錄。使用該方式,你可以向核心業務邏輯中追加一些不太重要的功能(例如日志),而不會使代碼混亂。AOP為面向切換的軟件開發奠定了基礎。
以下是AOP的一些常用場景
代理模式(Proxy Pattern)也常用于Mocking(例如Moq, NSubstitute等)和延時加載(Lazy Loading)(例如EF Core, NHierante等)
C#中其實已經支持AOP了,你可以快速Google搜索一下,AOP的實現方式有2種 RealProxy 真實代理和 MarshalByRefObject .技術上講,他們都可以在本地和遠程使用,它看起來非常的美好,直到你明白的你的所有目標對象都必須繼承 MarshalByRefObject 。僅此一點,就讓大部分人不會考慮這種實現方式。
幸運的是,我們在C#中可以使用一種更好的方式創建代理對象,即使用 Castle.DynamicProxy 庫。
Castle.DynamicProxy 是一個用于在運行時生成輕量級.NET代理的庫。生成代理對象允許你在不修改原始代碼的情況下攔截對對象成員的調用,只有virtual對象成員才能被攔截。- Castle Project
使用Castle提供的動態代理,你可以為抽象類、接口(同時提供實現)以及帶有virtual方法/屬性的普通類創建代理對象。
以下是一個例子,這里我們假設創建了一個處理博客文章的服務應用。
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool Disabled { get; set; }
public DateTime Created { get; set; }
}
public interface IBlogService
{
void DisablePost(BlogPost post);
BlogPost GetPost(int id);
}
public class BlogService : IBlogService
{
public BlogPost GetPost(int id)
{
return new BlogPost
{
Id=id,
Title="Test",
Description="Test",
Disabled=false,
Created=DateTime.UtcNow
};
}
public void DisablePost(BlogPost post)
{
post.Disabled=true;
}
}
通常,你會將 BlogService 類注冊為 IBlogService 接口的實現,一切都運轉的非常正常。但是現在,你希望代理這個接口,當接口中任何方法被調用的時候,做點什么事情。
這里,我們首先創建一個攔截器對象以便攔截方法調用,就像 RealProxy 一樣
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"正在調用方法 {invocation.TargetType}.{invocation.Method.Name}.");
invocation.Proceed(); // 執行當前被攔截的方法
}
}
然后,我們將使用一個代理生成器生成代理對象。
var generator=new ProxyGenerator();
var actual=new BlogService();
var proxiedService=(IBlogService)proxyGenerator.CreateInterfaceProxyWithTarget(typeof(IBlogService), actual, new LoggingInterceptor());
// 使用proxiedService對象和你平常使用IBlogService對象是一樣的
現在我們就創建出了一個實現了 IBlogService 接口的代理對象,其中包含了內部實現 BlogService 。當任何一個接口方法被調用的時候, LoggingInterceptor.Intercept 方法就會被調用,當攔截器調用 invocation.Proceed() 方法時,它在 BlogService 類中的具體實現方法就會被調用。
在ASP.NET Core中使用Castle實現AOP的實現思路是, 始終使用ASP.NET Core的IOC容器來創建代理服務。雖然Castle項目中包含它自己的IOC容器 Castle Windor , 使得注入代理更加的容易,但是我們暫時不使用它。
這里,我們首先為我們的 LoggingInterceptor 添加一個簡單的依賴以展示我們如何使用ASP.NET Core自帶的DI來處理依賴問題。因為現實中,你的大部分攔截器都是需要一個或多個依賴項的。
public class LoggingInterceptor : IInterceptor
{
private readonly ILogger<LoggingInterceptor> _logger;
public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
{
_logger=logger;
}
public void Intercept(IInvocation invocation)
{
_logger.LogDebug($"Calling method {invocation.TargetType}.{invocation.Method.Name}.");
invocation.Proceed();
}
}
第二步,我們在依賴注入容器中注冊一個單例的 ProxyGenerator 對象,以及我們即將使用的所有的攔截器對象
services.AddSingleton(new ProxyGenerator());
services.AddScoped<IInterceptor, LoggingInterceptor>();
最后,我們創建一個擴展方法 AddProxiedScoped , 并使用它注冊其他所有服務。
public static class ServicesExtensions
{
public static void AddProxiedScoped<TInterface, TImplementation>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
services.AddScoped<TImplementation>();
services.AddScoped(typeof(TInterface), serviceProvider=>
{
var proxyGenerator=serviceProvider.GetRequiredService<ProxyGenerator>();
var actual=serviceProvider.GetRequiredService<TImplementation>();
var interceptors=serviceProvider.GetServices<IInterceptor>().ToArray();
return proxyGenerator.CreateInterfaceProxyWithTarget(typeof(TInterface), actual, interceptors);
});
}
}
// In ConfigureServices
services.AddProxiedScoped<IBlogService, BlogService>();
這里,讓我們看看它是如何工作的
現在,我們無論何時需要一個 IBlogService 接口對象,都可以通過依賴注入容器得到一個代理對象,這個代理對象會先經過所有的攔截器,然后調用 BlogService 中定義的實際方法。
但是這里,相較與 Spring ,在ASP.NET Core中實現AOP還不夠簡單直接,但是我們可以輕松將其轉換為簡單的“框架”,我們可以使用 Castle.DynamicProxy 的一些特定方法,來執行一些更高級的操作。
原文地址: ASPECT ORIENTED PROGRAMMING USING PROXIES IN ASP.NET CORE
https://blog.zhaytam.com/2020/08/18/aspnetcore-dynamic-proxies-for-aop/
原文作者:ZANID HAYTAM
譯文地址: 如何在ASP.NET Core中實現面向切面編程(AOP)
https://www.cnblogs.com/lwqlun/p/aop_in_asp_net_core.html
譯文作者:Lamond Lu
spose.Words for .NET提供了一套完整的功能,用于在多個.NET應用程序中操作和轉換MS Word文檔。您可以在桌面或Web應用程序中創建新的或編輯現有的Word文檔。
在本文中,將展示如何利用Aspose.Words for .NET的字處理功能,以及如何在ASP.NET MVC中創建基于Web的MS Word編輯器。
Aspose.Words for .NET已升級至V20.4,如果你還沒有用過Aspose.Words可以點擊文末“了解更多”下載最新版測試。
為了演示,將在此應用程序中使用了基于JavaScript的Suneditor WYSIWYG編輯器。您可以使用相同的內容,也可以選擇其他任何適合您要求的HTML編輯器。以下是創建ASP.NET Word編輯器的步驟。
在Visual Studio中創建一個新的ASP.NET Core Web應用程序。
選擇 Web應用程序(模型-視圖-控制器) 模板。
下載所見即所得編輯器的文件,并將其保存在 wwwroot 目錄中。
打開NuGet軟件包管理器,然后安裝Aspose.Words for .NET軟件包。
在index.cshtml 視圖中添加以下腳本。
@{ ViewData["Title"]="Word Editor in ASP.NET"; } <div class="row"> <div class="col-md-12"> <form asp-controller="Home" asp-action="UploadFile" method="post" class="form-inline" enctype="multipart/form-data"> <br /> <div class="form-group"> <input type="file" name="file" accept=".doc, .docx" class="form-control custom-file-input" /> div> <div class="form-group"> <button type="submit" class="form-control btn btn-primary">Openbutton> div> <div class="form-group" style="position:relative; float :right"> <button type="button" id="download" class="form-control btn btn-success" value="Save and Download">Save and Downloadbutton> div> form> <br /> <form method="post" asp-action="Index" id="formDownload"> <textarea name="editor" id="editor" rows="80" cols="100"> @if (ViewBag.HtmlContent==null) { <p>Write something or open an existing Word document. p> } else { @ViewBag.HtmlContent; } textarea> form> div> div> <link href="~/suneditor/dist/css/suneditor.min.css" rel="stylesheet"> <script src="~/suneditor/dist/suneditor.min.js">script> <script> var suneditor=SUNEDITOR.create('editor', { display: 'block', width: '100%', height: '30%', popupDisplay: 'full', buttonList: [ ['font', 'fontSize', 'formatBlock'], ['paragraphStyle', 'blockquote'], ['bold', 'underline', 'align', 'strike', 'subscript', 'superscript', 'horizontalRule', 'list'], ['table', 'link', 'image'], ['align', 'horizontalRule', 'list', 'lineHeight'], ['codeView'] ], placeholder: 'Start typing something...' }); script> <script> $(document).ready(function () { $("#download").click(function () { suneditor.save(); $("#formDownload").submit(); }); }); script>
在HomeController.cs 控制器中添加以下方法 。
[HttpPost] public FileResult Index(string editor) { try { // Create a unique file name string fileName=Guid.NewGuid() + ".docx"; // Convert HTML text to byte array byte[] byteArray=Encoding.UTF8.GetBytes(editor.Contains("") ? editor : "" + editor + ""); // Generate Word document from the HTML MemoryStream stream=new MemoryStream(byteArray); Document Document=new Document(stream); // Create memory stream for the Word file var outputStream=new MemoryStream(); Document.Save(outputStream, SaveFormat.Docx); outputStream.Position=0; // Return generated Word file return File(outputStream, System.Net.Mime.MediaTypeNames.Application.Rtf, fileName); } catch (Exception exp) { return null; } } [HttpPost] public ViewResult UploadFile(IFormFile file) { // Set file path var path=Path.Combine("wwwroot/uploads", file.FileName); using (var stream=new FileStream(path, FileMode.Create)) { file.CopyTo(stream); } // Load Word document Document doc=new Document(path); var outStream=new MemoryStream(); // Set HTML options HtmlSaveOptions opt=new HtmlSaveOptions(); opt.ExportImagesAsBase64=true; opt.ExportFontsAsBase64=true; // Convert Word document to HTML doc.Save(outStream, opt); // Read text from stream outStream.Position=0; using(StreamReader reader=new StreamReader(outStream)) { ViewBag.HtmlContent=reader.ReadToEnd(); } return View("Index"); }
在您喜歡的瀏覽器中生成并運行該應用程序。
以下是如何在ASP.NET Word編輯器中創建或編輯Word文檔的演示。
創建一個Word文檔
編輯Word文檔
如果您有任何疑問或需求,請隨時加入Aspose技術交流群(642018183),我們很高興為您提供查詢和咨詢。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。