本篇接着上一篇博文,繼續講解如何在 SAP 系統外部,方便地對 SAP 的數據庫表進行增刪改查操作。推薦的方式:
- SAP 暴露 OData 服務供外部調用
- SAP 暴露 Restful Service 供外部調用
總的來說,OData 是比較新的 Restful Service 規範,在 SAP 端編寫代碼相對容易,但早期版本不支持 OData; Restful Service 基本上較早的版本也可以實現。關於 OData 對 Netweaver 版本要求,請參考我另外一篇博文:
SAPUI5 (34) - OData Model 連接後端 SAP 系統 (上)
對於異構系統的接口,我比較喜歡服務化這個表述,Restful Service 能夠很好地體現服務化思想。站在第三方系統的角度,不管是 push 還是 pull 都能實現。
本文介紹基於 .net 平臺的 WinForm 框架如何實現 SAP 表的維護界面。在前端技術日新月異的今天,WinForm 少有人用,但 WinForm 作爲 Microsoft 早期的平臺技術,技術成熟,使用簡單,而且 Office 的 開發技術 VSTO ,允許使用 WinForm 框架,所以可以在 Office 中編寫界面,這也是極爲有用的一個應用場景。
RestSharp
我選擇開源的 RestSharp 框架作爲調用 Restful Service 的技術,RestSharp 使用起來靈活、強大。爲了減少後續代碼量,我對 HTTP 的 GET / POST / PUT / DELETE 請求進行封裝,認證方式選擇 Http Basic Authentication:
using RestSharp;
using RestSharp.Authenticators;
using System;
namespace RestSharpCRUD
{
public class RestSharpHelper
{
private String username;
private String password;
public String BaseUrl { get; set; }
private RestClient client;
public RestSharpHelper(String baseUrl, String username, String password)
{
this.BaseUrl = baseUrl;
this.username = username;
this.password = password;
// construct RestClient object
client = new RestClient(this.BaseUrl)
{
Authenticator = new HttpBasicAuthenticator(username, password)
};
}
public IRestResponse Get(String resource)
{
var req = new RestRequest(resource, Method.GET);
var resp = client.Execute(req);
return resp;
}
public IRestResponse Post(String resource, String payload)
{
var req = new RestRequest(resource, Method.POST);
req.RequestFormat = DataFormat.Json;
req.AddJsonBody(payload);
req.AddParameter("application/json", payload, ParameterType.RequestBody);
var resp = client.Execute(req);
return resp;
}
public IRestResponse Put(String resource, String payload)
{
var req = new RestRequest(resource, Method.PUT);
req.RequestFormat = DataFormat.Json;
req.AddJsonBody(payload);
req.AddParameter("application/json", payload, ParameterType.RequestBody);
var resp = client.Execute(req);
return resp;
}
public IRestResponse Delete(String resource)
{
var req = new RestRequest(resource, Method.DELETE);
var resp = client.Execute(req);
return resp;
}
}
}
Model 類
Model 類比較簡單,代碼如下:
using System;
namespace RestSharpCRUD {
public class EmpEntity {
public EmpEntity() {
MANDT = "001";
}
public String MANDT { get; set; }
public String EMPID { get; set; }
public String EMPNAME { get; set; }
public String EMPADDR { get; set; }
}
}
調用 Restful Service
封裝了 Http 方法後,接下來編寫一個類,實現通過調用 Restful Service 來對 SAP zemployee 表增刪改查操作:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace RestSharpCRUD {
public class EmpService {
private String baseUrl = "http://sapecc6:8000";
private String username = "stone";
private String password = "123456";
private RestSharpHelper restSharpHelper;
public EmpService() {
restSharpHelper = new RestSharpHelper(baseUrl, username, password);
}
public IList<EmpEntity> ListAll() {
IList<EmpEntity> employees = null;
var resp = restSharpHelper.Get("/zrest/employees");
if (!resp.IsSuccessful) {
throw new Exception(resp.ErrorMessage);
}
employees = JsonConvert.DeserializeObject<List<EmpEntity>>(resp.Content);
return employees;
}
public bool Create(EmpEntity emp) {
String payload = JsonConvert.SerializeObject(emp);
// Call POST method
var resp = restSharpHelper.Post("/zrest/employees/create", payload);
if (!resp.IsSuccessful) {
throw new Exception(resp.Content);
}
return true;
}
public bool Update(EmpEntity emp) {
String payload = JsonConvert.SerializeObject(emp);
// Call POST method
var resource = String.Format("/zrest/employees/{0}", emp.EMPID);
var resp = restSharpHelper.Put(resource, payload);
if (!resp.IsSuccessful) {
throw new Exception(resp.Content);
}
return true;
}
public bool Delete(String empId) {
bool rv = false;
var resource = String.Format("/zrest/employees/{0}", empId);
var resp = restSharpHelper.Delete(resource);
if (resp.IsSuccessful) rv = true;
return rv;
}
}
}
界面實現
我通過兩個 Form 的配合來實現增刪改查操作。第一個 Form 羅列所有的 employee,允許在 Form 中進行導航,雙擊跳轉到維護的 Form,可以在這個 Form 中進行刪除操作。設計時的界面如下:
代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
namespace RestSharpCRUD
{
public partial class EmpListForm : Form
{
public EmpListForm()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// 加載數據
IList<EmpEntity> employees = null;
try {
employees = LoadEmployees();
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
return;
}
// 數據綁定到控件
// List綁定到DataGridView不能進行增刪改查,所以將List轉換爲BindingList
// DataGridView.DataSource = new BindingList<T>(List<T>);
bindingSource1.DataSource = new BindingList<EmpEntity>(employees);
dataGridView1.DataSource = bindingSource1;
bindingNavigator1.BindingSource = bindingSource1;
}
private void DataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
// 雙擊打開EmpSingleForm
var empSingleForm = new EmpSingleForm(bindingSource1);
empSingleForm.ShowDialog();
}
private void BindingNavigatorAddNewItem_Click(object sender, EventArgs e)
{
this.bindingSource1.AddNew();
var empSingleForm = new EmpSingleForm(bindingSource1, true);
empSingleForm.ShowDialog();
}
private void BindingNavigatorDeleteItem_Click(object sender, EventArgs e)
{
DoDelete();
}
private IList<EmpEntity> LoadEmployees()
{
var empService = new EmpService();
IList<EmpEntity> employees = empService.ListAll();
return employees;
}
private void DoDelete()
{
if (bindingSource1.Current == null) return;
if (MessageBox.Show("確定刪除這個員工嗎?", "刪除",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.Yes) {
var empId = (bindingSource1.Current as EmpEntity).EMPID;
var empService = new EmpService();
bool rv = empService.Delete(empId);
if (rv) {
bindingSource1.RemoveCurrent(); // 保持界面同步
}
}
}
}
}
UI 層與 Service 層交互,使用的是 IList<T> 格式,使用這種格式,在界面中要將 IList<T>,轉換爲 BindingList<T>,否則不支持 CRUD 操作。
在 EmpSingleForm 表單中,對單筆記錄進行更新和保存,表單設計時界面如下:
兩個表單都基於 BindingSource 控件對數據進行綁定,並且通過 BindingSource 控件在 Form 中交換數據,從而減少代碼量,爲此,在 EmpSingleForm 中,特意實現另外兩個構造函數:
#region constructors
public EmpSingleForm()
{
InitializeComponent();
}
public EmpSingleForm(BindingSource bs) : this()
{
empBs = bs;
//設置數據綁定
SetBinding();
}
public EmpSingleForm(BindingSource bs, bool addNew) : this(bs)
{
isAddNewMode = addNew;
}
#endregion
SetBinding()
方法負責數據的綁定:
private void SetBinding()
{
txtEmpID.DataBindings.Add("Text", empBs, "EMPID", true);
txtName.DataBindings.Add("Text", empBs, "EMPNAME", true);
txtAddress.DataBindings.Add("Text", empBs, "EMPADDR", true);
}
將更新的數據保存到 SAP 後端,無非就是調用 EmpService 類的方法:
private void BtnSave_Click(object sender, System.EventArgs e)
{
bool rv = false; // return value
var emp = new EmpEntity
{
MANDT = "001",
EMPID = txtEmpID.Text.Trim(),
EMPNAME = txtName.Text.Trim(),
EMPADDR = txtAddress.Text.Trim()
};
var empService = new EmpService();
if (isAddNewMode) {
try {
rv = empService.Create(emp);
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
else {
rv = empService.Update(emp);
}
if (rv) {
empBs.EndEdit();
this.Close();
}
}
以下是 EmpSingleForm 的完整代碼:
using System;
using System.Windows.Forms;
namespace RestSharpCRUD
{
public partial class EmpSingleForm : Form
{
private bool isAddNewMode = false;
#region constructors
public EmpSingleForm()
{
InitializeComponent();
}
public EmpSingleForm(BindingSource bs) : this()
{
empBs = bs;
//設置數據綁定
SetBinding();
}
public EmpSingleForm(BindingSource bs, bool addNew) : this(bs)
{
isAddNewMode = addNew;
}
#endregion
private void EmpSingleForm_FormClosed(object sender, FormClosedEventArgs e)
{
this.empBs.CancelEdit();
}
private void BtnSave_Click(object sender, System.EventArgs e)
{
bool rv = false; // return value
var emp = new EmpEntity
{
MANDT = "001",
EMPID = txtEmpID.Text.Trim(),
EMPNAME = txtName.Text.Trim(),
EMPADDR = txtAddress.Text.Trim()
};
var empService = new EmpService();
if (isAddNewMode) {
try {
rv = empService.Create(emp);
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
else {
rv = empService.Update(emp);
}
if (rv) {
empBs.EndEdit();
this.Close();
}
}
private void EmpSingleForm_Load(object sender, EventArgs e)
{
txtEmpID.Enabled = (isAddNewMode == true);
}
private void SetBinding()
{
txtEmpID.DataBindings.Add("Text", empBs, "EMPID", true);
txtName.DataBindings.Add("Text", empBs, "EMPNAME", true);
txtAddress.DataBindings.Add("Text", empBs, "EMPADDR", true);
}
}
}
程序源碼
該工程完整的代碼在 github。