WebApi(221)

webvideo本地枚举文件太恶心了,算了写个服务器来读吧,顺便把avi和rmvb解包写了.

Controller

  • 这里的Controller继承于ApiController,
  • IE和Firefox发送了不同的Accept报头,导致返回的数据可以是JSON,也可以是XML(感觉太强大了,这都做好了。客户端请求的“application/json”中的Accept报头)

路由:/api/{controller}/{id}id = RouteParameter.Optional情况下
controller匹配控制器名
{id} 匹配名称为id的方法参数(即函数的参数名叫id)

/api/products products匹配名为ProductsController的控制器。该请求是一个GET请求,因此框架在ProductsController上查找一个名称以GET…开头的方法。

进一步地,这个URI不包含可选的{id}片段,因此,框架查找的是一个不带参数的方法。于是,ProductsController::GetAllProducts满足所有这些需求

同理 如果是个POST``Put请求,会去找POST``Put开头的方法 以及Delete

/api/products/1 找到参数名叫id的函数,并把这个传入。
同理 /api/products/abc就不行,因为只有GetProductById有个叫id的参数,但是是int,无法转换为string

Post
js和c#的完美兼容,直接互相传对象,完全可以解析

1
2
3
var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
string uri = Url.Link("DefaultApi", new { id = item.Id }); //当服务器创建一个资源时,它应当在响应的Location报头中包含新资源的URI。
response.Headers.Location = new Uri(uri);

WebapiClient

添加库,用HttpClient设置base uri,设置请求报头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
HttpResponseMessage response = client.GetAsync("api/products").Result; // Blocking call(阻塞调用)!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
// 解析响应体。阻塞!
var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
foreach (var p in products)
{
Console.WriteLine("{0}\t{1};\t{2}", p.Name, p.Price, p.Category);
}
}
//异步和阻塞注意
//自动构造结构数据很智能:比如Product有个属性和服务器的不对应,那那个属性将不被赋值,只赋值对应的属性
---
//post的时候
// 创建JSON格式化器。
MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
// Use the JSON formatter to create the content of the request body.
// 使用JSON格式化器创建请求体内容。
HttpContent content = new ObjectContent<Product>(product, jsonFormatter);
// Send the request.
// 发送请求。
var resp = client.PostAsync("api/products", content).Result;
//或者client.PostAsJsonAsync("api/products", gizmo).Result;

上面讲了client.GetAsync("api/products").Result 是阻塞函数,并不会把控制返回给调用者,这导致一直阻塞(ui线程阻塞)。
给出2个解决方法:

  1. asyncawait

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    private async void GetProducts(object sender, RoutedEventArgs e)
    {
    try
    {
    btnGetProducts.IsEnabled = false;
    var response = await client.GetAsync("api/products");
    response.EnsureSuccessStatusCode(); // Throw on error code(有错误码时报出异常).
    var products = await response.Content.ReadAsAsync<IEnumerable<Product>>();
    _products.CopyFrom(products);
    }
    catch (Newtonsoft.Json.JsonException jEx)
    {
    // This exception indicates a problem deserializing the request body.
    // 这个异常指明了一个解序列化请求体的问题。
    MessageBox.Show(jEx.Message);
    }
    catch (HttpRequestException ex)
    {
    MessageBox.Show(ex.Message);
    }
    finally
    {
    btnGetProducts.IsEnabled = true;
    }
    }
  2. 安装Async Targeting Pack

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    client.GetAsync("api/products/2").ContinueWith((t) =>
    {
    if (t.IsFaulted)
    {
    MessageBox.Show(t.Exception.Message);
    btnGetProducts.IsEnabled = true;
    }
    else
    {
    var response = t.Result;
    if (response.IsSuccessStatusCode)
    {
    response.Content.ReadAsAsync<IEnumerable<Product>>().
    ContinueWith(t2 =>
    {
    if (t2.IsFaulted)
    {
    MessageBox.Show(t2.Exception.Message);
    btnGetProducts.IsEnabled = true;
    }
    else
    {
    var products = t2.Result;
    _products.CopyFrom(products);
    btnGetProducts.IsEnabled = true;
    }
    }, TaskScheduler.FromCurrentSynchronizationContext());
    }
    }
    }, TaskScheduler.FromCurrentSynchronizationContext());

HttpClient消息处理器

通过这个我可以把自定义的头处理掉

路由变异

id = RouteParameter.Optional情况下

当路由为 api/{controller}/{action}/{id} 时,

1
2
3
4
5
6
7
8
9
10
11
12
public class ProductsController : ApiController
{
[HttpGet] //需要显式的指定映射
public string Details(int id); //api/products/details/1
[HttpPost]
[ActionName("Thumbnail")] //覆盖动作,这样就转到api/products/thumbnail/id
public void AddThumbnailImage(int id);
}
//除此外还有个: [NonAction]属性可以修饰动作,这样框架就不会映射到那个函数
//[AcceptVerbs("GET", "HEAD")] 允许此方法对get和head的http方法(put/delete等同理)

### 异常处理
总而言之(exception和error都用最好):

1
2
3
var message = string.Format("Product with id = 0 not found");
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, message));

自定义消息过滤

这个要继承DelegatingHandler,服务器在config.MessageHandlers加,客户端在HttpClient加。

唯一的注意http头的加法,客户端随便加,服务器一般如此

1
2
3
4
5
6
return base.SendAsync(request, cancellationToken)
.ContinueWith( (task) => {
HttpResponseMessage response = task.Result;
response.Headers.Add(_header, "Server");
return response;
});

Post数据

有俩种,复合类型和简单类型

  • 复合类型(传和接受都是自定义的结构体)

服务器对对这类型没啥要求,传入参数是自定义类或结构体就好。

客户端代码必须传application/json格式数据(我用postman测试了下,选raw,传json字符串可以的)

1
2
3
4
5
6
string seri = JsonConvert.SerializeObject(
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries" });
HttpResponseMessage response = client.PostAsync(
"api/products/PostPro1/"
, new StringContent(seri,
Encoding.Unicode, "application/json")).Result;
  • 简单类型(服务器接收的是string int这种类型)

服务器要求用[FromBody]修饰参数

客户端要求发送=value的数据(代码和测试真心发俩种数据)

客户端代码,不知道为何必须用{ "": ["update one", "update two", "update three"] }格式数据 且x-www-form-urlencoded它才认同

ajax只需要传上面的格式就好了

1
2
3
4
5
6
7
8
9
var conte = new FormUrlEncodedContent(
new[]
{
new KeyValuePair<string, string>("", "login")
}
);
HttpResponseMessage response = client.PostAsync(
"api/products/PostPro1/"
, conte).Result;

然而我用postmanraw,传=rety就可以传入rety字符进去。
angular的js没试过,放以后把

Upload Form Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public async Task<HttpResponseMessage> PostFormData()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(
System.Net.HttpStatusCode.UnsupportedMediaType
);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);//读取后,会自动在根目录保存临时文件
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
HttpContext.Current.Request.Files[0].SaveAs(root+"//webservertt.txt");//重新按文件名保存
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}

限制数据

如下对结构进行限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public decimal Price { get; set; }
[Range(0,999)]
public double Weight { get; set; }
}
//当webapi对一复合类型进行自动转换时,可以用此来判断结构体是否符合限制
public HttpResponseMessage Post(Product product)
{
if (ModelState.IsValid)
{
// Do something with the product (not shown).
// 用product做一些事(未表示出来)
return new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}

Under-Posting和Over-Posting

Under-Posting 当客户端提交时遗漏某些属性,json格式化器会默认给缺失赋值(0)
Over-Posting 当客户端提交时属性过多时,格式化器会忽略多余的属性

NuGet和Self-Host

  1. Microsoft.AspNet.WebApi.OwinSelfHost是自驻留服务安装的
  2. controller接口一定要public,这个搞了我好久哦。

单个webapi自己的话(weapi.core是必须的),要加Microsoft.AspNet.WebApi.WebHost
这个模式下直接用Global.asax做入口是可以的.(遇见403不怕,只是页面没添加,api接口还是好的)

后来翻资料,还有其它方法可以用Microsoft.Owin.Host.SystemWeb and Microsoft.AspNet.WebApi.Owin;建立Startup入口是可以的。
(完全想不明白,为何俩台电脑反应如此不同)

self的要加Microsoft.AspNet.WebApi.OwinSelfHost (这个官方介绍是有俩种方式的,我个人喜欢用owin)
xcopy /y "$(TargetDir)$(ProjectName).*" "$(ProjectDir)\..\WebHostServerC\bin\Debug"

content root和web root

要用content root要安装Microsoft.AspNetCore.StaticFiles (Microsoft.AspNetCore.Hosting里面有PhysicalFileProviderd的类)

路由属性和重定向

一直看他们用RouteRoutePrefix来指定路由,发现调用 MapHttpAttributeRoutes就可以使用了。

https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

路由属性的好处是,可以使用匹配路由([Route(“~/api/authors/{authorId:int}/books”)]public IEnumerable GetByAuthor(int authorId) { … })和父路由

先把重定向代码贴了。

1
2
3
4
5
6
7
8
//[Route(""), HttpGet]
//[ApiExplorerSettings(IgnoreApi = true)]
//public HttpResponseMessage RedirectToSwaggerUi()
//{
// var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.Found);
// httpResponseMessage.Headers.Location = new Uri("/app", UriKind.Relative);
// return httpResponseMessage;
//}

静态资源访问

Microsoft.Owin.StaticFiles
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/static-files

// //