前后端分離大概是指是HTML和服務器代碼的分離,因為瀏覽器中解釋執行的HTML+JS+CSS代碼混合着<%%>包含的內容確實是不友好,於是就有人發明了代碼分離的技術,比如asp.net的基於事件的codebehind,struts的mvc方式。再后來,為了更徹底地分離,前端直接做成了獨立的程序包或應用,比如基於瀏覽器的angular、react之類的。這種前后端分離的好處是顯而易見的,即增加了代碼的可讀性,增加了可維護性,同時也容易分配開發任務,前后端可以並行開發,互不干擾。
在實際開發中,新的項目,只要是正式的項目,肯定是盡量代碼分離的,況且現在主流的程序框架都是MVC結構的,你不想分離都難,唯一糾結的地方就是選擇服務端渲染還是客戶端渲染的問題。服務端渲染和客戶端渲染的比較大的差別大概有兩點,一個是響應速度上,服務端渲染每次返回的一個完整的HTML代碼或是部分HTML代碼,而客戶端渲染則所有界面邏輯都在客戶端,變更界面不用每次都向服務器請求。由於客戶端渲染每次操作只請求純數據的部分,這樣無論響應速度還是網絡流量都會比服務端渲染好點。當然服務端渲染也有服務端渲染的好處,就是生成頁面連同數據一同發過來的,不用復雜的Dom操作,這樣有利於開發的效率,畢竟服務端的調試要比客戶調試來得方便。
我們可以用代碼的方式來比較一下這兩者的不同。假設我們要開發一個網店系統,其中有一功能就是商品的管理,商品有一個分類屬性,商品和分類是從屬關系,一般設計成多對一的關系。比如以下關於分類和商品實體的定義代碼:
/// <summary>
/// 分類實體
/// </summary>
[Table("CategoryInfo")]
public class CategoryInfo
{
public Guid Id { get; set; }
public string Title { get; set; }
public override string ToString()
{
return Title;
}
}
/// <summary>
/// 商品實體
/// </summary>
[Table("CommodityInfo")]
public class CommodityInfo
{
public Guid Id { get; set; }
public string Title { get; set; }
public float Price { get; set; }
public Guid CategoryInfoId { get; set; }
public CategoryInfo Category { get; set; }
public override string ToString()
{
return Title;
}
}
然后,我們在DbContext里邊加上這兩行:
public DbSet<CategoryInfo> CategoryInfos { get; set; }
public DbSet<CommodityInfo> CommodityInfos { get; set; }
因為我們只做簡單的CRUD操作,所以這個示例中免去了業務層代碼邏輯,我們將直接在Controller中進行Linq查詢,無論是MVC項目,還是WEB API項目,查詢邏輯是一樣的。
//MVC中的Action
public IActionResult Index()
{
return View(dbContext.CommodityInfos.ToList());
}
//API中的查詢
[HttpGet()]
public CommodityInfo[] All()
{
return dbContext.CommodityInfos.ToArray();
}
接下來就有區別了,MVC中我們只要定義一個View視圖。
@model List<RelationData.CommodityInfo>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<table class="table">
<tr><th>分類</th><th>商品名稱</th></tr>
@foreach (var p in Model)
{
<tr><td>@p.Category</td><td>@p</td></tr>
}
</table>
而客戶端渲染要先建一個Angular(當然也可以是其它的),我們可以在VS中創建,也可以命令行創建。為了方便,我們用DevExpress。
HTML部分:
<dx-data-grid id="gridContainer"
[dataSource]="dataSource"
[remoteOperations]="false"
[allowColumnReordering]="true"
[rowAlternationEnabled]="true"
[showBorders]="true"
(onContentReady)="contentReady($event)">
<dxo-paging [pageSize]="10"></dxo-paging>
<dxo-pager
[showPageSizeSelector]="true"
[allowedPageSizes]="[10, 25, 50, 100]"
></dxo-pager>
<dxo-search-panel
[visible]="true"
[highlightCaseSensitive]="true"
></dxo-search-panel>
<dxo-group-panel
[visible]="true"
></dxo-group-panel>
<dxo-grouping
[autoExpandAll]="false"
></dxo-grouping>
<dxi-column dataField="Category.Title" caption="分類"></dxi-column>
<dxi-column caption="Title" dataField="商品名稱"></dxi-column>
</dx-data-grid>
組件部分:
import { NgModule, Component, Pipe, PipeTransform, enableProdMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { DxDataGridModule,
DxBulletModule,
DxTemplateModule } from 'devextreme-angular';
import DataSource from 'devextreme/data/data_source';
import { Service } from './app.service';
@Component({
selector: 'app-counter-component',
templateUrl: './commodity.component.html'
})
export class CommodityComponent {
dataSource: DataSource;
constructor(service: Service) {
this.dataSource = service.getDataSource();
}
}
數據服務:
import { Injectable } from '@angular/core';
import 'devextreme/data/odata/store';
import DataSource from 'devextreme/data/data_source';
@Injectable()
export class Service {
getDataSource() {
return new DataSource({
store: {
url: 'https://localhost/api/commodity/all'
}
});
}
}
相較而言,兩者似乎差不多,區別是服務器端渲染是內存傳遞對象,而客戶端渲染是通過json來傳遞對象。另外就是客戶端增加了一個技能棧,當然你也可以不用Angular框架,而是直接用ajax的方式直接操作Dom樹。
前后端分離,我們還可以再擴展一下。現在的客戶端框架,其實就是將原來的瘦端進行了富化,現在有了豐富的第三方UI組件,原來需要桌面程序才能實現的東西,現在通過純網頁也可以實現了,因而前端並不限於HTML,不包括桌面應用,手機APP、小程序等不直接處理業務邏輯的各類終端。
Xamarin中獲取Json數據:
var uri = new Uri (string.Format ("http://localhost/api/all", string.Empty));
...
var json = JsonConvert.SerializeObject (item);
var content = new StringContent (json, Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
if (isNewItem)
{
response = await _client.PostAsync (uri, content);
}
...
if (response.IsSuccessStatusCode)
{
Debug.WriteLine (@"\tTodoItem successfully saved.");
}
...