(点击
上方蓝字
cnblogs.com/loogn/p/6275659.html
此篇是写给新手的Demo,用于参阅和学习,用于发散思路。老鸟能够疏忽了。
自己常常有这种状况,遇到一个新东西或难题,在了解和处理之前总是说“等搞定了必定要写篇文章记载下来”,可是当把握了之后,就感觉好简略呀不值得写下来了。
一、恳求呼应的规划
RESTFul风格嘹亮很久了,可是我没用过,今后也不计划用。当体系略微杂乱时,为了契合RESTFul要费劲地创立一些不直观的名词,这不是我的风格。所以此文规划的不是RESTFul风格,是只最常用的POST和GET恳求。
恳求部分便是调用API的参数,笼统出一个接口如下:
public interface IRequest
{
ResultObject Validate();
}
这儿边只界说了一个Validate()办法,用于验证恳求参数的有效性,回来值是呼应里的东西,下面会讲到。
关于恳求目标,传递到事务逻辑层,乃至是数据拜访层都能够,由于它本身便是用来传输数据的,俗语叫DTO(Data Transfer Object),不过界说多层传输目标,然后仿制来仿制去也是能够的~。可是有时分事务处理睬需求当时登录人的信息,而这个信息我并不期望直接从接口层向下传递,所以这儿我再笼统一个UserRequestBase,用于附加登录人相关信息:
public abstract class UserRequestBase : IRequest
{
public int ApiUserID { get; set; }
public string ApiUserName { get; set; }
// ......能够增加其他要专递的登录用户相关的信息
public abstract ResultObject Validate();
}
ApiUserID和ApiUserName这样的字段是不需求客户端传递的,咱们会依据登录人信息主动填充。
依据实践中的经历,咱们往往会做分页查询,会用到页码和每页条数,所为咱们再界说个PageRequestBase:
public abstract class PageRequestBase : UserRequestBase
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
}
由于.net只能承继单个父类,并且有些分页查询或许需求用户信息,所以咱们挑选承继UserRequestBase。
呼应的规划分为两部分,第一个是实践呼应部分,第二个会把呼应包装一下,加上code和msg,用于表明调用状况和过错信息(好老的办法了,咱们都懂)。
呼应接口IResponse里什么也没有,便是一个符号接口,不过咱们也能够笼统出来两个常用的共用类用于呼应列表和分页数据:
public class ListResponseBaseT : IResponse
{
public ListT List { get; set; }
}
public class PageResponseBaseT: ListResponseBaseT
{
/// summary
/// 页码数
/// /summary
public int PageIndex { get; set; }
/// summary
/// 总条数
/// /summary
public long TotalCount { get; set; }
/// summary
/// 每页条数
/// /summary
public int PageSize { get; set; }
/// summary
/// 总页数
/// /summary
public long PageCount { get; set; }
}
包装呼应的时分,有两种状况,第一种是操作类接口,比方增加产品,这些接口是不必呼应目标的,只需回来是否成功就行了,第二种查询类,这个时分必需求回来一些详细的东西了,所以呼应包装规划成两个类:
public class ResultObject
{
/// summary
/// 等于0表明成功
/// /summary
public int Code { get; set; }
/// summary
/// code不为0时,回来过错音讯
/// /summary
public string Msg { get; set; }
}
public class ResultObjectTResponse : ResultObject where TResponse : IResponse
{
public ResultObject()
{
}
public ResultObject(TResponse data)
{
Data = data;
}
/// summary
/// 回来的数据
/// /summary
public TResponse Data { get; set; }
}
IRequest接口的Validate()办法回来值便是第一个ResultObject,当恳求参数验证不通过的时分,肯定是没有数据回来了。
再事务逻辑层,我挑选以包装类作为回来类型,由于有许多过错都会在事务逻辑层呈现,咱们的接口是需求这些过错信息的。
二、恳求的Content-Type和模型绑定
可是前端恳求后端的时分还有许多是以form表单办法,也便是恳求的Content-
Type为:application/x-www-form-urlencoded,恳求体为id=23name=loogn这样的字符串,假如数据格局杂乱了,前端欠好传,后端解析起来也费事。
$.ajax({
type: "POST",
url: "/product/editProduct",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({id:1,name:"name1"}),
success: function (result) {
console.log(result);
}
})
除了contentType,还要留意运用了JSON.stringify把目标转化成了字符串。其实ajax运用的XmlHttpRequest目标只能处理字符串(json字符串呀,xml字符串呀,text纯文本呀,base64呀)。这些数据到了后端之后,从恳求流里读出来便是json办法的字符串了,可直接反序列化成后端目标。
但是这些考虑,.net mvc结构现已帮咱们做好了,这都要归功于DefaultModelBinder。
关于Form表单办法的恳求,能够拜见这位园友的文章:你从未知道如此强壮的ASP.NET MVC DefaultModelBinder(http://www.cnblogs.com/leotsai/p/ASPNET-MVC-DefaultModelBinder.html)
你告诉我你送过来一只猫,而实践上是一只狗,我以对待猫的办法对待狗当然就有被咬一口的风险了(肯定会报错)。
三、自界说ApiResult和ApiControllerBase
由于我不需求RESTFul风格,也不需求依据客户端的志愿回来json或xml,所以我挑选AsyncController作为操控器的基类。
AsyncController是直接承继Controller的,并且支撑异步处理,详细Controller和ApiController的差异,想了解的同学能够看这篇文章difference-between-apicontroller-and-controller-in-asp-net-mvc(http://stackoverflow.com/questions/9494966/difference-between-apicontroller-and-controller-in-asp-net-mvc)或许直接阅览源码。
Controller里的Action需求回来一个ActionResult目标,结合上面的呼应包装目标ResultObject,我决议自界说一个ApiResult作为Action的回来值,一起在这儿处理jsonp调用、跨域调用、序列化的小驼峰命名和时刻格局问题。
/// api回来成果,操控jsonp、跨域、小驼峰命名和时刻格局问题
public class ApiResult : ActionResult
{
/// summary
/// 回来数据
/// /summary
public ResultObject ResultData { get; set; }
/// summary
/// 回来数据编码,默许utf8
/// /summary
public Encoding ContentEncoding { get; set; }
/// summary
/// 是否承受Get恳求,默许答应
/// /summary
public JsonRequestBehavior JsonRequestBehavior { get; set; }
/// summary
/// 是否答应跨域恳求
/// /summary
public bool AllowCrossDomain { get; set; }
/// summary
/// jsonp回调参数名
/// /summary
public string JsonpCallbackName = "callback";
public ApiResult() : this(null)
{
}
public ApiResult(ResultObject resultData)
{
this.ResultData = resultData;
ContentEncoding = Encoding.UTF8;
JsonRequestBehavior = JsonRequestBehavior.AllowGet;
AllowCrossDomain = true;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
var request = context.HttpContext.Request;
response.ContentEncoding = ContentEncoding;
response.ContentType = "text/plain";
if (ResultData != null)
{
string buffer;
if ((JsonRequestBehavior == JsonRequestBehavior.DenyGet) string.Equals(context.HttpContext.Request.HttpMethod, "GET"))
{
buffer = "该接口不答应Get恳求";
}
else
{
var jsonpCallback = request[JsonpCallbackName];
if (string.IsNullOrWhiteSpace(jsonpCallback))
{
//假如能够跨域,写入呼应头
if (AllowCrossDomain)
{
WriteAllowAccessOrigin(context);
}
response.ContentType = "application/json";
buffer = JsonConvert.SerializeObject(ResultData, JsonSetting.Settings);
}
else
{
//jsonp
if (AllowCrossDomain) //这个判别或许非有必要
{
response.ContentType = "text/javascript";
buffer = string.Format("{0}({1});", jsonpCallback, JsonConvert.SerializeObject(ResultData, JsonSetting.Settings));
}
else
{
buffer = "该接口不答应跨域恳求";
}
}
}
try
{
response.Write(buffer);
}
catch (Exception exp)
{
response.Write(exp.Message);
}
}
else
{
response.Write("ApiResult.Data为null");
}
response.End();
}
/// summary
/// 写入跨域恳求头
/// /summary
/// param name="context"/param
private void WriteAllowAccessOrigin(ControllerContext context)
{
var origin = context.HttpContext.Request.Headers["Origin"];
if (true) //能够保护一个答应跨域的域名调集,类判别是否能够跨域
{
context.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", origin ?? "*");
}
}
}
里边都是一些惯例的逻辑,不做说明晰,其间的JsonSetting便是设置序列化的小驼峰和日期格局的:
public class JsonSetting
{
public static JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
DateFormatString = "yyyy-MM-dd HH:mm:ss",
};
}
这个时分有个问题,假如一个时刻字段需求"yyyy-MM-dd"这种格局怎么办呢?这个时分要界说一个JsonConverter的子类,来完成自界说日期格局:
/// summary
/// 日期格局化器
/// /summary
public class CustomDateConverter : DateTimeConverterBase
{
private IsoDateTimeConverter dtConverter = new IsoDateTimeConverter { };
public CustomDateConverter(string format)
{
dtConverter.DateTimeFormat = format;
}
public CustomDateConverter() : this("yyyy-MM-dd") { }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return dtConverter.ReadJson(reader, objectType, existingValue, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
dtConverter.WriteJson(writer, value, serializer);
}
}
在需求的呼应特点上加上 [JsonConverter(typeof(CustomDateConverter))] 或 [JsonConverter(typeof(CustomDateConverter),"yyyy年MM月dd日")] 即可。
ApiResult界说好了,再界说一个操控器基类,意图是便于处理ApiResult:
/// summary
/// API操控器基类
/// /summary
public class ApiControllerBase : AsyncController
{
public ApiResult ApiTRequest(TRequest request, FuncTRequest, ResultObject handle)
{
try
{
var requestBase = request as IRequest;
if (requestBase != null)
{
//处理需求登录用户的恳求
var userRequest = request as UserRequestBase;
if (userRequest != null)
{
var loginUser = LoginUser.GetUser();
if (loginUser != null)
{
userRequest.ApiUserID = loginUser.UserID;
userRequest.ApiUserName = loginUser.UserName;
}
}
var validResult = requestBase.Validate();
if (validResult != null)
{
return new ApiResult(validResult);
}
}
var result = handle(request); //处理恳求
return new ApiResult(result);
}
catch (Exception exp)
{
//反常日志:
return new ApiResult { ResultData = new ResultObject { Code = 1, Msg = "体系反常:" + exp.Message } };
}
}
public ApiResult Api(FuncResultObject handle)
{
try
{
var result = handle();//处理恳求
return new ApiResult(result);
}
catch (Exception exp)
{
//反常日志
return new ApiResult { ResultData = new ResultObject { Code = 1, Msg = "体系反常:" + exp.Message } };
}
}
/// summary
/// 异步api
/// /summary
/// typeparam name="TRequest"/typeparam
/// param name="request"/param
/// param name="handle"/param
/// returns/returns
public TaskApiResult ApiAsyncTRequest, TResponse(TRequest request, FuncTRequest, TaskTResponse handle) where TResponse : ResultObject
{
return handle(request).ContinueWith(x =
{
return Api(() = x.Result);
});
}
}
最常用的应该便是第一个ApiTRequest办法,里边处理了恳求参数的验证,把用户信息赋给需求的恳求目标,反常记载等。
第二个办法是对没有恳求参数的api调用处理。第三个办法是异步处理,能够对异步IO处理做一些优化,比方你供给的这个接口是调用的另一个网络接口的状况。
↓↓↓