使用 WCF REST、Web Services 作为提供者以及 JQuery、Javascript 作为消费者的商业网站数据库 CRUD 操作:零回发
本文将介绍如何在 Web 应用程序中使用 WCF REST 服务和 ASMX 服务作为提供者,以及 jQuery 或 javascript 作为消费者来执行 CRUD 操作,从而完全消除回发。
引言
本文展示了如何从 Web 应用程序使用 jQuery、javascript 作为客户端,以及 WCF RESTful 服务、ASP.NET Web Services 作为服务提供者来执行数据库 CRUD(创建、检索、更新、删除)操作,所有操作均实现零回发。本文帮助您了解 RESTful API 的优势、jQuery 的强大功能、javascript 和脚本服务的细致使用。本文还展示了如何在不进行任何回发的情况下实现所有业务功能。
背景
要完全理解本文内容,您需要先阅读我之前在 ZeroPostbackSite.aspx 上的文章。在我之前提到的文章中,我已经解释了如何从 jQuery 调用 ASP.NET Web Services 并随后执行 UI Manipulations。在这里,我将详细介绍如何使用 WCF RESTful 服务执行插入、更新、删除操作,以及如何从 .NET 代码和 jQuery 代码中消费它们。祝您阅读愉快...
使用代码
废话不多说,我将直接开始讲解代码。首先,我们需要创建 RESTful WCF 服务。在这方面,很多网站都提供了各种方法,但它们往往会让你感到困惑,以至于在阅读 REST API 后你会觉得需要“休息”(rest)一下。在这里,我将消除所有这些痛苦,并向你展示一切运作的万无一失的方法。你可以通过做三件事将现有的 WCF 服务公开为 REST API 或 RESTful WCF 服务:
a) 使用 [WebInvoke] 属性装饰你的操作,我将展示如何操作。
b) 在你的 WCF 服务的 ABC(Address, Binding, Contract)中,使用 webHttpBinding。
c) 在你的 endpoint behavior 配置中声明 <webHttp/> 元素。
现在,让我们来看接口(契约)。
[ServiceContract(Namespace="StudentContract", SessionMode=SessionMode.Allowed)]
public interface IStudent
{
[OperationContract]
[WebInvoke(
Method = "GET",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped
)
]
List<ListItem> GetStudents();
[OperationContract]
[WebInvoke(
Method = "POST",
RequestFormat= WebMessageFormat.Json,
ResponseFormat=WebMessageFormat.Json,
BodyStyle=WebMessageBodyStyle.Wrapped
)
]
int InsertStudent(StudentRecord stuRec);
[OperationContract]
[WebInvoke(
Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped
)
]
void InsertMark(StudentMark stuMark);
[OperationContract]
[WebInvoke(
Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped
)
]
void UpdateStudent(StudentRecord stuRec);
[OperationContract]
[WebInvoke(
Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped
)
]
void DeleteStudent(StudentRecord stuRec);
[OperationContract]
[WebInvoke(
Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped
)
]
void UpdateMarks(StudentMark stuMark);
}
您可以看到如何指定 POST 方法,指定请求和响应格式为 Json,以及指定一个包装的 body style。如果您传输二进制数据(如图像),则 body style 应为 bare。现在,让我们看一下上面契约的实现。
public class StudentBase
{
public List<ListItem> GetStudents()
{
string connStr = ConfigurationManager.ConnectionStrings["KovairSiteConnectionString"].ToString();
SqlConnection conn = new SqlConnection(connStr);
SqlCommand comm = new SqlCommand();
comm.CommandText = "SELECT RollNo, Name From dbo.StuRec";
comm.Connection = conn;
SqlDataAdapter da = new SqlDataAdapter(comm);
DataSet ds = new DataSet();
conn.Open();
da.Fill(ds, "StudentRecord");
conn.Close();
List<ListItem> stuList = new List<ListItem>();
if (ds.Tables != null)
{
int recordCount = ds.Tables["StudentRecord"].Rows.Count;
if (recordCount > 0)
{
DataTable dtab = ds.Tables["StudentRecord"];
for (int i = 0; i < recordCount; i++)
{
stuList.Add(new ListItem(
dtab.Rows[i]["Name"].ToString(),
dtab.Rows[i]["RollNo"].ToString()
));
}
}
}
comm.Dispose();
return stuList;
}
public int InsertStudent(StudentRecord stuRec)
{
string connStr = ConfigurationManager.ConnectionStrings["KovairSiteConnectionString"].ToString();
SqlConnection conn = new SqlConnection(connStr);
SqlCommand comm = new SqlCommand();
string name = stuRec.Name;
comm.CommandText = "INSERT INTO dbo.StuRec(Name) VALUES (@stuName); SELECT SCOPE_IDENTITY()";
comm.Parameters.Add(new SqlParameter("@stuName", SqlDbType.VarChar)).Value = name;
comm.Connection = conn;
conn.Open();
object roll = comm.ExecuteScalar();
conn.Close();
comm.Dispose();
return int.Parse(roll.ToString());
}
public void UpdateMarks(StudentMark stuMark)
{
string connStr = ConfigurationManager.ConnectionStrings["KovairSiteConnectionString"].ToString();
SqlConnection conn = new SqlConnection(connStr);
SqlCommand comm = new SqlCommand();
int roll = stuMark.RollNo;
int mark = stuMark.Mark;
comm.CommandText = "UPDATE dbo.StuMark SET Marks = @mark WHERE RollNo = @stuRoll";
comm.Parameters.Add(new SqlParameter("@mark", SqlDbType.Int)).Value = mark;
comm.Parameters.Add(new SqlParameter("@stuRoll", SqlDbType.Int)).Value = roll;
comm.Connection = conn;
conn.Open();
comm.ExecuteNonQuery();
conn.Close();
comm.Dispose();
}
public void UpdateStudent(StudentRecord stuRec)
{
string connStr = ConfigurationManager.ConnectionStrings["KovairSiteConnectionString"].ToString();
SqlConnection conn = new SqlConnection(connStr);
SqlCommand comm = new SqlCommand();
int roll = stuRec.RollNo;
string name = stuRec.Name;
comm.CommandText = "UPDATE dbo.StuRec SET Name = @stuName WHERE RollNo = @stuRoll";
comm.Parameters.Add(new SqlParameter("@stuName", SqlDbType.VarChar)).Value = name;
comm.Parameters.Add(new SqlParameter("@stuRoll", SqlDbType.Int)).Value = roll;
comm.Connection = conn;
conn.Open();
comm.ExecuteNonQuery();
conn.Close();
comm.Dispose();
}
public void InsertMark(StudentMark stuMark)
{
string connStr = ConfigurationManager.ConnectionStrings["KovairSiteConnectionString"].ToString();
SqlConnection conn = new SqlConnection(connStr);
SqlCommand comm = new SqlCommand();
int roll = stuMark.RollNo;
int mark = stuMark.Mark;
comm.CommandText = "INSERT INTO dbo.StuMark(RollNo,Marks) VALUES (@stuRoll, @stuMarks)";
comm.Parameters.Add(new SqlParameter("@stuRoll", SqlDbType.Int)).Value = roll;
comm.Parameters.Add(new SqlParameter("@stuMarks", SqlDbType.Int)).Value = mark;
comm.Connection = conn;
conn.Open();
comm.ExecuteNonQuery();
conn.Close();
comm.Dispose();
}
public void DeleteStudent(StudentRecord stuRec)
{
string connStr = ConfigurationManager.ConnectionStrings["KovairSiteConnectionString"].ToString();
SqlConnection conn = new SqlConnection(connStr);
SqlCommand comm = new SqlCommand();
int roll = stuRec.RollNo;
comm.CommandText = "DELETE FROM dbo.StuMark WHERE RollNo = @rollNo";
comm.Parameters.Add(new SqlParameter("@rollNo", SqlDbType.Int)).Value = roll;
comm.Connection = conn;
conn.Open();
comm.ExecuteNonQuery();
comm.CommandText = "DELETE FROM dbo.StuRec WHERE RollNo = @rollNo";
comm.ExecuteNonQuery();
conn.Close();
comm.Dispose();
}
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Student : StudentBase, IStudent
{
}
看看我在上面使用的技巧。我首先在 StudentBase 类中实现了所有方法,然后让 Student 继承自 StudentBase 和 IStudent。还请注意,我在契约中启用了 session,并在服务类 Student 中启用了 AspNetCompatibilityRequirements。
现在,相应地修改 .svc 文件。
<%@ ServiceHost
Language="C#"
Debug="true"
Service="StudentService.Student"
CodeBehind="StudentService.svc.cs"
%>
您可以看到我在 Service 属性中提到了类的完全限定名。现在,让我们看一下我们 web.config 文件中的重要部分。
<system.serviceModel>
<services>
<service name="StudentService.Student" behaviorConfiguration="StudentService.ServiceStudentBehavior">
<!-- Service Endpoints -->
<endpoint address="https:///StudentService/StudentService.svc" binding="webHttpBinding" contract="Kovair.Interface.IStudent" behaviorConfiguration="WebHttpBehavior"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="StudentService.ServiceStudentBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="WebHttpBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
您可以看到,对于 binding,我指定了 webHttpBinding,对于 behavior,我为 endpoint 指定了 <webHttp/> 元素。我还启用了与 ASP.NET 的兼容性,以便在需要时可以利用 HttpContext.Current.Session。不过,WCF 服务也有 ServiceContext。
现在,让我们转向客户端部分,在本例中,它仍然是一个 Web 应用程序。看看我如何在 .master 文件的 <ScriptManager> 标签内添加了我的 WCF RESTful 服务的引用。
<asp:ScriptManager ID="KovairScriptManager" runat="server" EnableScriptGlobalization="true"
EnablePageMethods="true">
<Services>
<asp:ServiceReference Path="https:///StudentService/StudentService.svc" />
</Services>
</asp:ScriptManager>
这里我添加了 asp:ServiceReference 元素,以便脚本能够“感知”RESTful 服务。现在,StudentService 的 GetStudents() 操作在页面初始加载时(非回发)以及在 jQuery 的内部 $.ajax 调用期间都被调用。下面是 Page_Load 方法。
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string webUri = ConfigurationManager.AppSettings["WcfServiceUri"].ToString();
WebChannelFactory<IStudent> wcf = new WebChannelFactory<IStudent>
(
new WebHttpBinding(),
new Uri(webUri)
);
IStudent channel = wcf.CreateChannel();
List<ListItem> items = channel.GetStudents();
ListItem newItem = new ListItem();
newItem.Value = "-1";
newItem.Text = "Select a value";
ddlStudents.Items.Add(newItem);
foreach (ListItem item in items)
{
ddlStudents.Items.Add(item);
}
}
}
我调用了我的 RESTful 服务,而没有使用代理。这是最好的方法。对于普通的 WCF 服务,我们使用 ChannelFactory 类,但在这里你需要使用 WebChannelFactory 类,因为 WebChannelFactory 类能够识别 [WebInvoke] 属性。另外,您也可以看到我在这里使用了 WebHttpBinding。现在,看看我如何从我的 jQuery / javascript 代码中调用服务。这是不言自明的,前提是您已经阅读了本文开头提到的我之前的文章。
<script type = "text/javascript">
$(function() {
$('#ModDiv').hide();
$('#<%=btnModify.ClientID%>').hide();
$('#<%=btnDelete.ClientID%>').hide();
});
function GetStudentDetail() {
$('#<%=btnModify.ClientID%>').hide();
$('#<%=btnDelete.ClientID%>').hide();
var control = $get('<%= this.ddlStudents.ClientID %>');
var NewStudent = {};
NewStudent.RollNo = 22;
NewStudent.Name = "Jacob";
// Create a data transfer object (DTO) with the proper structure.
var DTO = { 'stu': NewStudent };
var rollNo = control.options[control.selectedIndex].value;
var name = control.options[control.selectedIndex].text;
NewStudent.RollNo = rollNo;
NewStudent.Name = name;
$.ajax({
type: 'POST',
url: 'https:///StudentService/StudentWebService.asmx/GetStudentDetail',
data: JSON.stringify(DTO),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response, status) {
var list = (typeof response.d) == 'string' ? eval('(' + response.d + ')') : response.d;
var oup = "RollNo: " + list.RollNo.toString() + " Marks: " + list.Mark.toString();
$('#<%=lblResult.ClientID %>').html(oup);
},
failure: function(response) {
alert(response.d);
}
}
);
$('#<%=btnModify.ClientID%>').toggle(500);
$('#<%=btnDelete.ClientID%>').toggle(500);
return false;
}
function GetStudents() {
$('#<%=ddlStudents.ClientID %>').empty().append('<option selected="selected" value="0">Loading...</option>');
$.ajax({
type: 'GET',
url: 'https:///StudentService/StudentService.svc/GetStudents',
data: {},
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response, status) {
var list = response;
var control = $get('<%=ddlStudents.ClientID %>');
control.options.length = 0;
var newOption = new Option("Please Select ", "-1");
control.options[0] = newOption;
var i = 1;
for (i = 1; i <= list.GetStudentsResult.length; i++) {
newOption = new Option(list.GetStudentsResult[i - 1].Text, list.GetStudentsResult[i - 1].Value);
control.options[i] = newOption;
}
},
failure: function(response) {
alert(response.d);
}
}
);
return false;
}
function ShowMod(isNew) {
$('#ModDiv').toggle(500);
if (isNew) {
$get('<%= this.txtName.ClientID %>').focus();
}
else {
var control = $get('<%= this.ddlStudents.ClientID %>');
var name = control.options[control.selectedIndex].text;
var marks = $get('<%= this.lblResult.ClientID %>').outerText;
var gotMarks = marks.substring(marks.lastIndexOf(':') + 1);
$get('<%= this.txtName.ClientID %>').value = name;
$get('<%= this.txtMarks.ClientID %>').value = gotMarks;
$get('<%= this.txtName.ClientID %>').focus();
}
return false;
}
function InsertMarks(roll) {
var NewMarks = {};
NewMarks.RollNo = roll;
NewMarks.Mark = $get('<%= this.txtMarks.ClientID %>').value;
var DTO2 = { 'stuMark': NewMarks };
$.ajax({
type: 'POST',
url: 'https:///StudentService/StudentService.svc/InsertMark',
data: JSON.stringify(DTO2),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response) {
setTimeout("GetStudents()", 100);
},
failure: function(response) {
alert(response.d);
}
}
);
$get('<%= this.txtName.ClientID %>').value = "";
$get('<%= this.txtMarks.ClientID %>').value = "";
}
function SaveData(isNew) {
var txtControl = $get('<%= this.txtName.ClientID %>');
var rollNo = -99;
var NewStudent = {};
NewStudent.RollNo = rollNo;
NewStudent.Name = txtControl.value;
var DTO1 = { 'stuRec': NewStudent };
if (isNew) {
$.ajax({
type: 'POST',
url: 'https:///StudentService/StudentService.svc/InsertStudent',
data: JSON.stringify(DTO1),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response) {
rollNo = response.InsertStudentResult;
setTimeout(function() { InsertMarks(rollNo) }, 100);
},
failure: function(response) {
alert(response.d);
}
}
);
}
else {
var control = $get('<%= this.ddlStudents.ClientID %>');
rollNo = control.options[control.selectedIndex].value;
NewStudent.RollNo = rollNo;
// Create a data transfer object (DTO) with the proper structure.
DTO = { 'stuRec': NewStudent };
$.ajax({
type: 'POST',
url: 'https:///StudentService/StudentService.svc/UpdateStudent',
data: JSON.stringify(DTO),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response) { UpdateMarks(); },
failure: function(response) {
alert(response.d);
}
}
);
$('#<%=btnModify.ClientID%>').toggle(500);
}
$('#ModDiv').toggle(500);
$('#<%=lblResult.ClientID %>').html("");
return false;
}
function UpdateMarks() {
var control = $get('<%= this.ddlStudents.ClientID %>');
rollNo = control.options[control.selectedIndex].value;
var NewMark = {};
NewMark.RollNo = rollNo;
NewMark.Mark = $get('<%= this.txtMarks.ClientID %>').value;
var DTO4 = { 'stuMark': NewMark };
$.ajax({
type: 'POST',
url: 'https:///StudentService/StudentService.svc/UpdateMarks',
data: JSON.stringify(DTO4),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response) { setTimeout("GetStudents()", 100); },
failure: function(response) {
alert(response.d);
}
}
);
}
function DeleteStudent() {
var rollNo = $get('<%= this.lblResult.ClientID %>').outerText;
var gotRoll = rollNo.substring(rollNo.indexOf(':') + 1, rollNo.indexOf('M') - 1);
var roll = parseInt(gotRoll.trim());
var NewStudents = {};
NewStudents.RollNo = roll;
NewStudents.Name = "Jacob";
// Create a data transfer object (DTO) with the proper structure.
var DTO3 = { 'stuRec': NewStudents };
$.ajax({
type: 'POST',
url: 'https:///StudentService/StudentService.svc/DeleteStudent',
data: JSON.stringify(DTO3),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(response) {
setTimeout("GetStudents()", 100);
},
failure: function(response) {
alert(response.d);
}
}
);
$('#<%=btnModify.ClientID%>').toggle(500);
$('#<%=btnDelete.ClientID%>').toggle(500);
$('#<%=lblResult.ClientID %>').html("");
$('#<%=ddlStudents.ClientID %>').val("-1");
return false;
}
</script>
现在,您可以看到我如何在连续的 jQuery $.ajax 调用之间精细地使用了 javascript 的 setTimeout 函数。$.ajax 调用只要能“调用”WCF 服务就会成功,它没有耐心等待服务执行完成,而且服务是在另一个线程中执行的,调用线程会返回。因此,如果一个服务调用的结果将在下一个服务中使用,就需要等待一定的毫秒数以实现同步。
兴趣点
我展示了如何使用 RESTful 服务和用 [ScriptService] 属性装饰的 Web Services(上一篇文章)来执行数据库 CRUD 操作,所有操作均实现零回发。我还展示了同一个 RESTful WCF 服务既可以从 C# 代码调用,也可以从 jQuery / javascript 代码调用。