在新出的MVC4中,增加了WebAPI,用于提供REST风格的WebService,新生成的WebAPI项目和典型的MVC项目一样,包含主要的Models、Views、Controllers等文件夹和Global.asax文件。Views对于WebAPI来说没有太大的用途,Models中的Model主要用于保存Service和Client交互的对象,这些对象默认情况下会被转换为Json格式的数据迚行传输,Controllers中的Controller对应于WebService来说是一个Resource,用于提供服务。和普通的MVC一样,Global.asax用于配置路由规则。 环境准备 建议使用VS2012以上版本创建WebAPI,如果是使用VS2010,需要安装VS2010 SP1升级包,MVC4升级包,打开VS2012创建如下: 第一步:新建ASP.NET Web应用程序
第二步:建议WebAPI
新生成的WebAPI项目和典型的MVC项目一样,包含主要的Models,Views,Controllers等文件夹和Global.asax文件
注意:再次强调Views对于WebAPI来说没有太大的用途,Models中的Model主要用于保存Service和Client交互的对象,这些对象默认情况下会被转换为Json格式的数据进行传输,Controllers中的Controller对应于WebService来说是一个Resource,用于提供服务。和普通的MVC一样,Global.asax用于配置路由规则
(二)Models 和WCF中的数据契约形成鲜明对比的是,MVC WebAPI中的Model就是简单的POCO,没有任何别的东西,如,你可以创建如下的Model
public class UserModel
{
public int Id { get; set; }
public string UserName { get; set; }
public string PassWord { get; set; }
}
注意:Model必须提供public的属性,用于json或xml反序列化时的赋值 (三)Controllers MVC WebAPI中的Controllers和普通MVC的Controllers类似,不过不再继承于Controller,而改为继承API的ApiController,一个Controller可以包含多个Action,这些Action响应请求的方法与Global中配置的路由规则有关,在后面结束Global时统一说明 (四)Global 默认情况下,模板自带了两个路由规则,分别对应于WebAPI和普通MVC的Web请求,默认的WebAPI路由规则如下
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
可以看到,默认路由使用的固定的api作为Uri的先导,按照微软官方的说法,用于区分普通Web请求和WebService的请求路径: 可以看到,默认的路由规则只指向了Controller,没有指向具体的Action,因为默认情况下,对于Controller中的Action的匹配是和Action的方法名相关联的:具体来说,如果使用上面的路由规则,对应下面的Controller:
public class UserController : ApiController
{
public List<UserModel> allModeList = new List<UserModel>() {
new UserModel(){ Id=1,UserName="zhang", PassWord="123"},
new UserModel(){ Id=2,UserName="lishi", PassWord="123456"},
new UserModel(){ Id=3,UserName="wang", PassWord="1234567"}
};
//Get api/User/
public IEnumerable<UserModel> GetAll()
{
return allModeList;
}
//Get api/User/1
public IEnumerable<UserModel> GetOne(int id) {
return allModeList.FindAll((m) => { return m.Id == id; });
}
//POST api/User/
public bool PostNew(UserModel user)
{
try
{
allModeList.Add(user);
return true;
}
catch {
return false;
}
}
//Delete api/User/
public int DeleteAll()
{
return allModeList.RemoveAll((mode) => { return true; });
}
//Delete api/User/1
public int DeleteOne(int id) {
return allModeList.RemoveAll((m) => { return m.Id == id; });
}
//Put api/User
public int PutOne(int id, UserModel user)
{
List<UserModel> upDataList = allModeList.FindAll((mode) => { return mode.Id == id; });
foreach (var mode in upDataList)
{
mode.PassWord = user.PassWord;
mode.UserName = user.UserName;
}
return upDataList.Count;
}
}
则,会有下面的对应关系:
URL | HttpMethod | 对应的Action名 |
---|---|---|
/api/User | GET | GetALL |
/api/User/1 | GET | GetOne |
/api/User | POST | PostNew |
/api/User/1 | DELETE | DeleteOne |
/api/User | DELETE | DeleteALL |
/api/User | PUT | PutOne |
客户端JS调用
function getAll() {
$.ajax({
url: "api/User/",
type: 'GET',
success: function (data) {
document.getElementById("modes").innerHTML = "";
$.each(data, function (key, val) {
var str = val.UserName + ': ' + val.PassWord;
$('<li/>', { html: str }).appendTo($('#modes'));
});
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
function find() {
$.ajax({
url: "api/User/1" ,
type: 'GET',
success: function (data) {
document.getElementById("modes").innerHTML = "";
$.each(data, function (key, val) {
var str = val.UserName + ': ' + val.PassWord;
$('<li/>', { html: str }).appendTo($('#modes'));
});
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
function add() {
$.ajax({
url: "api/User/",
type: "POST",
dataType: "json",
data: { "Id":4,"UserName": "admin", "PassWord": "666666"},
success: function (data) {
getAll();
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
function removeUser() {
$.ajax({
url: "api/User/3",
type: 'DELETE',
success: function (data) {
document.getElementById("modes").innerHTML = "";
getAll();
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
function removeAll() {
$.ajax({
url: "api/User/",
type: 'DELETE',
success: function (data) {
document.getElementById("modes").innerHTML = "";
getAll();
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
function udpate() {
$.ajax({
url: "api/User/1",
type: 'PUT',
dataType: "json",
data: { Id: 1, "UserName": "admin", "PassWord": "666666" },
success: function (data) {
document.getElementById("modes").innerHTML = "";
getAll();
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
这样就实现了最基本的CRUD操作。 扩展需求 问题1:我想按照用户名称(UserName)进行查询,怎么办? 办法:第一步:在UserController类中加一个方法名称叫:GetUserByName,如下所示:
public UserModel GetUserByName(string userName) {
return allModeList.Find((m) => { return m.UserName.Equals(userName); });
}
第二步:在客户端index.cshtml中调用
function getUserByName() {
$.ajax({
url: "api/User/zhang",
type: 'GET',
success: function (data) {
document.getElementById("modes").innerHTML = "";
var str = data.UserName + ': ' + data.PassWord;
$('<li/>', { html: str }).appendTo($('#modes'));
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
如果URL是: url: "api/User/zhang",将会报错:Bad Request 原因是他会自动调用我们的GetOne(int id) 这个方法,类型转换出错 解决办法: 改变URL为: url: "api/User/?userName=zhang", 问题2:我想按用户名称(UserName) 和用户密码(PassWord)一起来进行查询,怎么办? 解决办法 第一步:UserController类中,可以重载一个GetUserByName的方法,如下所示:
public UserModel GetUserByName(string userName) {
return allModeList.Find((m) => { return m.UserName.Equals(userName); });
}
第二步:客户端调用:
function getUserByName() {
$.ajax({
url: "api/User/?userName=zhang&passWord=123", //这里尤其需要注意
type: 'GET',
success: function (data) {
document.getElementById("modes").innerHTML = "";
var str = data.UserName + ': ' + data.PassWord;
$('<li/>', { html: str }).appendTo($('#modes'));
}
}).fail(
function (xhr, textStatus, err) {
alert('Error: ' + err);
});
}
路由规则扩展 和普通的MVC一样,MVC WebAPI支持自定义的路由规则,如:在上面的操作中,路由规则使用 "api/{controller}/{id}" 则限定了使用GET方式利用URL来传值时,controller后面的接收参数名为id,但是在Controller中,如果GetOne方法的接收参数名为key,是不会被匹配的,这是只需要新增一个新的路由规则,或修改原先的路由规则为: "api/{controller}/{key}",如下所示:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{key}",
defaults: new { key = RouteParameter.Optional }
);
当然,可以对路由进行更深的扩展,如:扩展成和普通MVC一样的路由: "api/{controller}/{action}/{id}" 这样,就要求同时使用Action和HTTP方法进行匹配当然,根据微软的说法,这种使用是不被推荐的,因为这不符合大家对WebService的一般认知: 使用Attribute声明HTTP方法
[HttpGet]
public IEnumerable<TestUseMode> FindAll()
[HttpGet]
public IEnumerable<TestUseMode> FindByKey(string key)
[HttpPost]
public bool Add(TestUseMode mode)
[HttpDelete]
public int RemoveByKey(string key)
[HttpDelete]
public int RemoveAll()
[HttpPut]
public int UpdateByKey(string key, string value)
[NonAction]
public string GetPrivateData()
当然,我只列出了方法名,而不是这些方法真的没有方法体...方法体是不变的,NoAction表示这个方法是不接收请求的,即使以GET开头。如果感觉常规的GET,POST,DELETE,PUT不够用,还可以使用AcceptVerbs的方式来声明HTTP方法,如:
[AcceptVerbs("MKCOL", "HEAD")]
public int UpdateByKey(string key, string value)
{
List<TestUseMode> upDataList = allModeList.FindAll((mode) => { if (mode.ModeKey == key) return true; return false; });
foreach(var mode in upDataList)
{
mode.ModeValue = value;
}
return upDataList.Count;
}
附:什么是REST风格? 参考:什么是REST风格 http://hi.baidu.com/yankaiwei/item/1f0b37dd922d53ef3cc2cb69 第二部分:综合示例:应用ASP.NET MVC4+WebAPI+FluentData开发Web应用 第一步:创建数据库 NorthWind数据库的Customers表
Create DataBase NorthWind
Go
Use NorthWind
Go
CREATE TABLE [dbo].[Customers](
[CustomerID] [nchar](5) NOT NULL,
[CompanyName] [nvarchar](40) NOT NULL,
[ContactName] [nvarchar](30) NULL,
[ContactTitle] [nvarchar](30) NULL,
[Address] [nvarchar](60) NULL,
[City] [nvarchar](15) NULL,
[Region] [nvarchar](15) NULL,
[PostalCode] [nvarchar](10) NULL,
[Country] [nvarchar](15) NULL,
[Phone] [nvarchar](24) NULL,
[Fax] [nvarchar](24) NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
(
[CustomerID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
第二步:创建 FluentData.Entity层,创建Customer实体类
namespace FluentData.Entity
{
public class Customer
{
public string CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
}
}
第三步:利用FluentData做数据的持久化 首先引入FluentData.cs (见附件) 其次:创建DBHelper类,代码如下:
public class DBHelper
{
public static IDbContext Context() {
//return new DbContext().ConnectionString("server=127.0.0.1;uid=sa;pwd=sa;database=TestDB", new SqlServerProvider());
return new DbContext().ConnectionStringName("connString", new SqlServerProvider());
}
}
然后不要忘记修改ASP.NET MVC层所在的Web.config,加入数据库连结字符串:
<connectionStrings>
<add name="connString" connectionString="server=127.0.0.1;database=Northwind;uid=sa;pwd=sa;"/>
</connectionStrings>
第三步:创建 CustomerService数据持久化类,代码如下:
public class CustomerService
{
private IDbContext context = DBHelper.Context();
public Customer Select(string customerId){
return context.Select<Customer>("*").From("Customers").Where("CustomerID=@0").Parameters(customerId)
.QuerySingle();
}
public List<Customer> SelectAll() {
return context.Select<Customer>("*").From("Customers").QueryMany();
}
public List<Customer> SelectAll(string sortExpression) {
if (String.IsNullOrEmpty(sortExpression)) return null;
return context.Select<Customer>("*").From("Customers").OrderBy(sortExpression).QueryMany();
}
public List<Customer> SelectAll(int currentPageIndex,int maxRows, string sortExpression)
{
var select = context.Select<Customer>("*").From("Customers");
if (maxRows > 0) {
if (currentPageIndex == 0) currentPageIndex = 1;
select.Paging(currentPageIndex, maxRows);
}
if (!string.IsNullOrEmpty(sortExpression)) {
select.OrderBy(sortExpression);
}
return select.QueryMany();
}
public int CountAll() {
return context.Sql("select count(*) from Customers").QuerySingle<int>();
}
public int Insert(Customer customer) {
return context.Insert<Customer>("Customers", customer).Execute();
}
public int Update(Customer customer) {
return context.Update<Customer>("Customers", customer).Where("CustomerID", customer.CustomerID).Execute();
}
public int Delete(string customerId) {
return context.Delete("Customers").Where("CustomerID", customerId).Execute();
}
public int Delete(Customer customer)
{
return this.Delete(customer.CustomerID);
}
}
第四步:Web API,创建CustomerController 注意要引用:FluentData.Entity及FluentData.DAL 程序集
public class CustomerController : ApiController
{
private CustomerService customerService = new CustomerService();
//Select All
public IEnumerable<Customer> Get()
{
return customerService.SelectAll();
}
//Select By Id
public Customer Get(string id)
{
return customerService.Select(id);
}
//Insert
public void Post(Customer customer)
{
customerService.Insert(customer);
}
//Update
public void Put(string id, Customer obj)
{
customerService.Update(obj);
}
//Delete
public void Delete(string id)
{
customerService.Delete(id);
}
}
第五步:View层代码
namespace MyWebApI.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Test() {
return View();
}
public ActionResult CustomerManager() {
return View();
}
}
}
然后创建View
<table id="customerTable" border="1" cellpadding="3" style="700px">
<tr>
<th>Customer ID</th>
<th>Company Name</th>
<th>Contact Name</th>
<th>Country</th>
<th>Actions</th>
</tr>
<tr>
<td><input type="text" id="txtCustomerId" style="100px" size="5"/></td>
<td><input type="text" id="txtCompanyName" style="150px" /></td>
<td><input type="text" id="txtContactName" style="150px"/></td>
<td><input type="text" id="txtCountry" style="150px"/></td>
<td><input type="button" name="btnInsert" value="Insert" style="150px"/></td>
</tr>
</table>
<script type="text/javascript">
$(function () {
$.getJSON("api/Customer", LoadCustomers);
});
function LoadCustomers(data) {
$("#customerTable").find("tr:gt(1)").remove();
$.each(data, function (key, val) {
var tableRow = '<tr>' +
'<td>' + val.CustomerID + '</td>' +
'<td><input type="text" value="' + val.CompanyName + '" /></td>' +
'<td><input type="text" value="' + val.ContactName + '" /></td>' +
'<td><input type="text" value="' + val.Country + '" /></td>' +
'<td><input type="button" name="btnUpdate" value="修改" /> <input type="button" name="btnDelete" value="删除" /></td>' +
'</tr>';
$('#customerTable').append(tableRow);
});
$("input[name='btnInsert']").click(OnInsert);
$("input[name='btnUpdate']").click(OnUpdate);
$("input[name='btnDelete']").click(OnDelete);
}
function OnInsert(evt) {
var customerId = $("#txtCustomerId").val();
var companyName = $("#txtCompanyName").val();
var contactName = $("#txtContactName