如何让 Web API 统一回传格式以及例外处理
发布网友
发布时间:2022-05-12 05:52
我来回答
共2个回答
热心网友
时间:2023-11-23 22:07
1.所以首先我们需要先自定义一个 Model 来当作我们的包装的容器,其类别的定义如下:
public class ApiResultModel
{
public HttpStatusCode Status { get; set; }
public object Data { get; set; }
public string ErrorMessage { get; set; }
}
2.相信写过 ASP.NET MVC 的朋友一定会知道,一般我们会将一些在 Action 中固定的逻辑,利用 Filter 来套用到每一个 Action 上面,例如:Authorize。如果你对 Filter 不是很熟悉可以参考一下网络上前辈所写的文章:[VS2010] ASP.NET MVC with Action Filters。 所以这边我们也需要使用同样的技巧来重新打包我们回传的数据格式,我们先新增一个 ApiResultAttribute.cs 的档案,且继承 System.Web.Http.Filters.ActionFilterAttribute,并且复写 OnActionExecuted 的方法,如下:
public class ApiResultAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
}
3.而 OnActionExecuted 会在 Action 执行之后呼叫,也表示我们将资料送进这个方法里面,接着处理我们主要打包的程序逻辑,程序代码如下:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
ApiResultModel result = new ApiResultModel();
// 取得由 API 返回的状态代码
result.Status = actionExecutedContext.ActionContext.Response.StatusCode;
// 取得由 API 返回的资料
result.Data = actionExecutedContext.ActionContext.Response.Content.ReadAsAsync<object>().Result;
// 重新封装回传格式
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(result.Status, result);
}
4.而为了要让所有的 Web API 都能套用我们自定义的 Filter,所以我们需要到 App_Start → WebApiConfig.cs → Register 来注册全局的 Web API Filter。(注意:若要注册 Web API 的 Filter 需在 WebApiConfig.cs 中注册,而非 FilterConfig.cs 中)
config.Filters.Add(new ApiResultAttribute());
5.重新建置之后我们再重新执行一次原先的 Web API 程序,就会看到回传的格式已经变成我们自定义的格式了:
"Status": 200,
"Data": [
{
"Account": "taxi",
"Mark": "",
"Name": "王大明",
"Telephone": "0986540123",
"AccountStatus": true
},
{
"Account": "taxi2",
"Mark": "",
"Name": "方大同",
"Telephone": "0922335111",
"AccountStatus": true
},
{
"Account": "q121234567",
"Mark": null,
"Name": "0000",
"Telephone": "0972334334",
"AccountStatus": true
}
],
"ErrorMessage": null
例外处理
前面我们已经将讯息打包成我们要的格式了,不过我们还没确切地去处理有关例外的程序代码,一般当程序发生错误产生例外时,我们当然也希望接收端能知道程序发生错误,进而显示该显示的讯息,而不是活生生地看着程序 Crash 或是停顿,这样将带给你的客户不好的体验,而在 ASP.NET MVC 中也有提供专门处理例外的 ExceptionFilterAttribute,所以接着来看看该如何打包我们的例外讯息吧。
1.新增一个 ApiErrorHandleAttribute.cs 并且继承 System.Web.Http.Filters.ExceptionFilterAttribute,接着复写 OnException 当例外发生时执行的方法,程序代码如下:
public class ApiErrorHandleAttribute : System.Web.Http.Filters.ExceptionFilterAttribute
{
public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
base.OnException(actionExecutedContext);
}
}
2.透过 OnException 的方法能让我们捕捉当例外发生时要处理的事情,一般系统我们也会在这边将发生错误的时间、登入的用户以及错误的状况记录下来 (例如:系统事件、存入数据库、写入 .txt 档 … 等),不过这边不是我们讨论的重点,我们先来看看该如何打包我们的例外讯息,程序代码如下:
public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
base.OnException(actionExecutedContext);
// 取得发生例外时的错误讯息
var errorMessage = actionExecutedContext.Exception.Message;
var result = new ApiResultEntity()
{
Status = HttpStatusCode.BadRequest,
ErrorMessage = errorMessage
};
// 重新打包回传的讯息
actionExecutedContext.Response = actionExecutedContext.Request
.CreateResponse(result.Status, result);
}
3.而因为程序丢出例外后会先回到 OnActionExcuted 在进到例外的处理,所以我们稍微修改一下原本的 OnActionExcuted 这个方法,让发生例外时就直接跳过不再这边打包我们的讯息,程序代码如下:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// 若发生例外则不在这边处理
if (actionExecutedContext.Exception != null)
return;
base.OnActionExecuted(actionExecutedContext);
ApiResultModel result = new ApiResultModel();
// 取得由 API 返回的状态代码
result.Status = actionExecutedContext.ActionContext.Response.StatusCode;
// 取得由 API 返回的资料
result.Data = actionExecutedContext.ActionContext.Response.Content.ReadAsAsync<object>().Result;
// 重新封装回传格式
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(result.Status, result);
}
3.接着我们一样要将此自定义的 Filter 注册到程序当中,所以一样到 App_Start → WebApiConfig.cs → Register 来注册我们的 Filter:
config.Filters.Add(new ApiErrorHandleAttribute());
4.重新建置后,当我们程序发生例外时也会依照我们的格式回传给使用者:
{
"Status": 400,
"Data": null,
"ErrorMessage": "尝试以零除。"
}
总结
就这样我们又成功解决了一个简单的案例,不过这边也需要提醒一下读者,一般在处理例外这边是不会直接将例外讯息回传给用户的,因为如果假设你今天丢出的例外有包含了一些比较敏感的信息,例如:数据库名称或数据表名称…等等,这样一来你的程序就间接的有了漏洞了,所以如果真的要用此程序代码记得后面例外捕捉那边还要在包装一下。
热心网友
时间:2023-11-23 22:07
在使用Web Api的时候,有时候只想返回JSON;实现这一功能有多种方法,这里提供两种方式,一种传统的,一种作者认为是正确的方法。
JSON in Web API – the formatter based approach
只支持JSON最普遍的做法是:首先清除其他所有的formatters,然后只保留JsonMediaTypeFormatter。
有了HttpConfiguration的实例,将会很简单的清除所有formatters,然后重新添加JsonMediaTypeFormatter。
实现代码如下:
configuration.Formatters.Clear();
configuration.Formatters.Add(new JsonMediaTypeFormatter());这种方式虽然可以实现功能,但是所有的conent negotiation还是会发生,这就会产生以下额外的开销了。因为,已经知道要返回的结果了,也只想返回Json,其他的content negotiation都不需要了。
下面的方法可以很好的解决这个问题。
JSON in Web API – the conneg based approach
最好的方法是使用自定义的只返回Json Result的content negotiation代替Web Api中默认的content negotiation。
Conneg通过实现IContentNegotiator的Negotiator方法实现扩展。Negotiator方法返回ContentNegotiationResult(它包装了选择的headers和formatter)。
下面的方法通过传递一个JsonMediaTypeFormatter给自定义的conneg negotiator,让它一直返回applicaton/json 的content-type以及JsonMediaTypeFormatter。这种方法避免了每次请求都要重新创建一次formatter。
代码如下:
public class JsonContentNegotiator : IContentNegotiator
{
private readonly JsonMediaTypeFormatter _jsonFormatter;
public JsonContentNegotiator(JsonMediaTypeFormatter formatter)
{
_jsonFormatter = formatter;
}
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
var result = new ContentNegotiationResult(_jsonFormatter, new MediaTypeHeaderValue("application/json"));
return result;
}
}接下来,需要在HttpConfiguration实例上注册新的实现机制:
var jsonFormatter = new JsonMediaTypeFormatter();
//optional: set serializer settings here
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
通过替换默认的DefaultContentNegotiator,使用自定义的JsonContentNegotiator,它只支持Json,而且可以马上返回。
通过使用自定义的JsonContentNegotiator替换系统默认的DefaultContentNegotiator,很好的实现Web Api只返回Json的功能,而且没有额外的开销。