前后端分离大概是指是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 (@" TodoItem successfully saved.");
}
...