In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >
Share
Shulou(Shulou.com)06/01 Report--
This article is written for novice Demo, for reference and reference, for divergent ideas. The old bird can be ignored.
I often have this situation. When I encounter a new thing or problem, I always say, "be sure to write an article and write it down when it is done" before understanding and solving it, but when you master it, it feels so simple that it's not worth writing down. In fact, this article is the same. I decided to write it down because I want to do one more serious thing before the Spring Festival.
Table of contents:
Design of request response
Requested Content-Type and model binding
Customize ApiResult and ApiControllerBase
Permission verification
Model generation
Document generation
I. the design of request response
The style of RESTFul has been loud for a long time, but I haven't used it, and I'm not going to use it in the future. When the system is a little more complex, it's not my style to struggle to create some unintuitive nouns in order to conform to RESTFul. So this article is not designed in RESTFul style, but the most commonly used POST and GET requests.
The request part is to call the parameters of API. An API is abstracted as follows:
Public interface IRequest {ResultObject Validate ();}
Only one Validate () method is defined, which is used to verify the validity of the request parameters, and the return value is something in the response, as we'll see below.
For the request object, you can pass it to the business logic layer or even the data access layer, because it is used to transfer data. As the saying goes, it is called DTO (Data Transfer Object), but it is possible to define multi-tier transfer objects and copy them back and forth. However, sometimes business processing requires the information of the current login person, and I do not want to pass this information directly down the interface layer, so here I abstract a UserRequestBase to add information about the login person:
Public abstract class UserRequestBase: IRequest {public int ApiUserID {get; set;} public string ApiUserName {get; set;} / /. You can add other information related to logged-in users to be delivered public abstract ResultObject Validate ();}
Fields such as ApiUserID and ApiUserName do not need to be passed by the client, and we will automatically populate them based on the login information.
Based on practical experience, we often do paging queries, using the page number and the number of pages per page, so define a PageRequestBase for us:
Public abstract class PageRequestBase: UserRequestBase {public int PageIndex {get; set;} public int PageSize {get; set;}}
Because. Net can only inherit a single parent class, and some paging queries may require user information, we choose to inherit UserRequestBase.
Of course, you can also abstract more common classes according to your actual situation, not one by one here.
The design of the response is divided into two parts, the first is the actual response, and the second wraps the response, plus code and msg, to represent the call status and error messages (the old method is known to all).
There is nothing in the response interface IResponse, just a tagging interface, but we can also abstract two common classes for responding to lists and paging data:
Public class ListResponseBase: IResponse {public List List {get; set;}} public class PageResponseBase: ListResponseBase {/ Page number / public int PageIndex {get; set;} / Total number / public long TotalCount {get; set } / number of pages per page / public int PageSize {get; set;} / Total pages / public long PageCount {get; set;}}
When packaging a response, there are two cases. The first is the operation class interface, such as adding goods. These interfaces do not need to respond to the object, as long as you return whether the response is successful or not. The second query class, you have to return something specific at this time, so the response wrapper is designed into two classes:
If public class ResultObject {/ equals 0 indicates success / public int Code {get; set;} / code is not 0, an error message / public string Msg {get; set will be returned } public class ResultObject: ResultObject where TResponse: IResponse {public ResultObject () {} public ResultObject (TResponse data) {Data = data;} / returned data / public TResponse Data {get; set;}}
The Validate () method of the IRequest interface returns the first ResultObject, and when the request parameter verification fails, there must be no data returned.
In the business logic layer, I choose the wrapper class as the return type, because a lot of errors occur in the business logic layer, and our interface needs these error messages.
Second, the requested Content-Type and model binding
Nowadays, the separation of front and rear ends is very popular. What we do in the back end usually returns the JSON format to the front end, the response Content-Type is application/json, and the front end can be directly used as a js object through some frameworks. However, when the frontend requests the backend, there are many form forms, that is, the Content-Type of the request is: application/x-www-form-urlencoded, and the body of the request is a string such as id=23&name=loogn. If the data format is complex and the frontend is difficult to pass, the backend is also troublesome to parse. Others pass the json string directly with a fixed parameter, such as json= {id:23,name:'loogn'}, and the back end takes it out with form ['json'] and then deserializes it. These methods are all good, but not good enough. The best way is to send json directly to the front end. Fortunately, many web servers now support that the requested Content-Type is application/json. At this time, the requested parameters will be passed in the form of Payload, such as using the ajax of jQuery to request:
$.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);}})
In addition to contentType, note that JSON.stringify is used to convert objects into strings. In fact, the XmlHttpRequest object used by ajax can only handle strings (json string, xml string, text plain text, base64). After the data reaches the backend, it is read as a string in the form of json from the request stream, which can be directly deserialized into a backend object.
However, these considerations,. Net mvc framework has been done for us, thanks to DefaultModelBinder.
For requests in the form of Form forms, see this gardener's article: you have never known such a powerful ASP.NET MVC DefaultModelBinder.
What I want to say here is that DefaultModelBinder is smart enough that we don't need to do anything ourselves. It parses the request in different ways according to the contentType of the request, and then binds to the object. When the contentType is application/json, we deserialize the object directly. When we encounter application/x-www-form-urlencoded, we bind the object in the form of form form. The only thing to pay attention to is the front-end classmate. Just don't confuse the contentType of the request with the actual content of the request. You told me that you sent me a cat, but it was actually a dog, and I was in danger of being bitten when I treated the dog the way I treated the cat.
III. Custom ApiResult and ApiControllerBase
Because I don't need the RESTFul style, and I don't need to return json or xml according to the client's wishes, I choose AsyncController as the base class for the controller. AsyncController inherits Controller directly and supports asynchronous processing. Students who want to know the difference between Controller and ApiController can read this article difference-between-apicontroller-and-controller-in-asp-net-mvc or read the source code directly.
The Action in Controller needs to return an ActionResult object. Combined with the above response wrapper object ResultObject, I decided to customize an ApiResult as the return value of Action, while dealing with jsonp calls, cross-domain calls, serialized hump naming and time format issues.
/ api returns results to control jsonp, cross-domain, small hump naming and time format problems / public class ApiResult: ActionResult {/ return data / public ResultObject ResultData {get; set } / return data encoding. Default utf8 / public Encoding ContentEncoding {get; set;} / whether to accept Get request or not. Default allows / public JsonRequestBehavior JsonRequestBehavior {get; set } / whether to allow cross-domain requests / public bool AllowCrossDomain {get; set;} / jsonp callback parameter name / 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 requests are not allowed on this interface";} else {var jsonpCallback = request [JsonpCallbackName] If (string.IsNullOrWhiteSpace (jsonpCallback)) {/ / if cross-domain, write the response header if (AllowCrossDomain) {WriteAllowAccessOrigin (context) } response.ContentType = "application/json"; buffer = JsonConvert.SerializeObject (ResultData, JsonSetting.Settings) } else {/ / jsonp if (AllowCrossDomain) / / this judgment may not be necessary {response.ContentType = "text/javascript" Buffer = string.Format ("{0} ({1});", jsonpCallback, JsonConvert.SerializeObject (ResultData, JsonSetting.Settings));} else {buffer = "Cross-domain requests are not allowed on this interface" } try {response.Write (buffer);} catch (Exception exp) {response.Write (exp.Message) }} else {response.Write ("ApiResult.Data is null");} response.End () } / write cross-domain request header / private void WriteAllowAccessOrigin (ControllerContext context) {var origin = context.HttpContext.Request.Headers ["Origin"] If (true) / / can maintain a collection of domain names that allow cross-domain, and the class determines whether cross-domain {context.HttpContext.Response.Headers.Add ("Access-Control-Allow-Origin", origin? "*");}
It's full of general logic, without explanation, where JsonSetting is formatted for serialized humps and dates:
Public class JsonSetting {public static JsonSerializerSettings Settings = new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver (), DateFormatString = "yyyy-MM-dd HH:mm:ss",};}
At this point, there is a question: what if a time field needs the format "yyyy-MM-dd"? At this point, a subclass of JsonConverter is defined to implement the custom date format:
/ / date formatter / 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);}}
Add [JsonConverter (typeof (CustomDateConverter))] or [JsonConverter (typeof (CustomDateConverter), "MM month dd day")] to the desired response attribute.
ApiResult is defined, and then a controller base class is defined to make it easy to handle ApiResult:
/ API controller base class / public class ApiControllerBase: AsyncController {public ApiResult Api (TRequest request, Func handle) {try {var requestBase = request as IRequest If (requestBase! = null) {/ / process requests requiring login users 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); / / process request return new ApiResult (result) } catch (Exception exp) {/ / exception log: return new ApiResult {ResultData = new ResultObject {Code = 1, Msg = "system exception:" + exp.Message}} }} public ApiResult Api (Func handle) {try {var result = handle (); / / processing request return new ApiResult (result) } catch (Exception exp) {/ / exception log return new ApiResult {ResultData = new ResultObject {Code = 1, Msg = "system exception:" + exp.Message}} }} / Asynchronous api / public Task ApiAsync (TRequest request Func handle) where TResponse: ResultObject {return handle (request) .ContinueWith (x = > {return Api () = > x.Result) );}}
The most commonly used is the first Api method, which deals with the validation of request parameters, assigning user information to the desired request object, exception records, and so on. The second method is to handle api calls with no request parameters. The third method is asynchronous processing, which can be optimized for asynchronous IO processing, such as if the interface you provide is another network interface that you call.
IV. Authority verification
On this issue, I posted some code in an article, in fact, as long as you know what's going on, you can play as much as you want, and the following does not involve the permissions of the characters.
Based on past experience, we can divide the permissions of resources (that is, an interface) into three levels (the second point of red is very important, which will greatly simplify the work of background permission management):
1, publicly accessible
2. Logged in users can access
3. Login users with permission can access
So here's how we design the filter to validate:
Public class AuthFilterAttribute: ActionFilterAttribute {/ anonymous accessible / public bool AllowAnonymous {get; set;} / logged in users can visit / public bool OnlyLogin {get; set } / the resource permission name used, for example, multiple APIs can use the same resource permission. Default is / ControllerName/ActionName / public string PowerName {get; set } public sealed override void OnActionExecuting (ActionExecutingContext filterContext) {/ / when cross-domain, the client will probe the server if with OPTIONS request (filterContext.HttpContext.Request.HttpMethod = = "OPTIONS") {var origin = filterContext.HttpContext.Request.Headers ["Origin"] If (true) / / can maintain a collection of domain names that allow cross-domain, and the class determines whether cross-domain {filterContext.HttpContext.Response.Headers.Add ("Access-Control-Allow-Origin", origin?? "*");} filterContext.Result = new EmptyResult (); return } if (AllowAnonymous) return; var user = LoginUser.GetUser (); if (user = = null) {filterContext.Result = new ApiResult {ResultData = new ResultObject {Code =-1, Msg = "not logged in"}, JsonRequestBehavior = JsonRequestBehavior.AllowGet} Return;} if (OnlyLogin) return; var url = PowerName; if (string.IsNullOrEmpty (url)) {url = "/" + filterContext.ActionDescriptor.ControllerDescriptor.ControllerName + "/" + filterContext.ActionDescriptor.ActionName;} var hasPower = true / / you can judge whether you have permission based on information such as user and url. If (! hasPower) {filterContext.Result = new ApiResult {ResultData = new ResultObject {Code =-2, Msg = "No permission"}, JsonRequestBehavior = JsonRequestBehavior.AllowGet} }
The functions of AllowAnonymous attribute and OnlyLogin attribute have been mentioned. Anonymous access is public, and a system always needs such an interface. Login access is generally low security, such as dictionary data acquisition. As long as you log in, you can access it, and you don't need to configure it in permission management.
What are the reasons for the attributes of PowerName? Sometimes, the permission levels of the two interfaces are bound together. For example, the add and modify interfaces of a commodity can be set to the same resource permission, so they can both be set to / product/edit, so that in permission management, we only need to maintain / product/edit, instead of maintaining / product/add and / product/update respectively. (examples may not be appropriate, because many times, adding and modifying are already the same interface. However, this situation does exist, and PowerName is also set up to simplify rights management in the background.
For cross-domain cases, the above code is also commented. The client will use the OPTIONS action to detect the server. In addition to the above code, you also need to configure it in web.config:
I deliberately keep the line commented out in the configuration because there is a corresponding place in the code, which can only be configured as "*" or a specific domain name in the configuration. We need to be more flexible, so we can control it in the program and allow a collection of domain names.
The logic of LoginUser is similar to the code in the connection above. It is no longer posted, and it is also available in the download. ApiToken can be obtained from both cookie and http headers, so that app can be called no matter whether it is a web page with the same domain name or cross-domain.
5. Model generation
There were a lot of model makers in the past, and now there are a lot of people using T4 templates, and there are T4 templates built into VS. But I don't like using T4 very much (mainly because there are no smart tips). I think the Razor engine is quite good and can be used to generate models. An ORM written by myself has added two new methods to obtain the metadata of the database table. Currently, MSSql and MySql are supported. A little code can be written to generate the model. The following is the content of cshtml. The screenshot is to show the code highlighting effect. Haha (the complete code can be downloaded at the bottom).
So sometimes, it's good to do it yourself. In fact, all web languages can be generated, jsp,php,nodejs, and dynamically generated pages returned to the client is the same, this is just written to the file.
VI. Document generation
Naturally, we are talking about API documents, which are different from the above generation model. Although the generation is basically "template + data = result", it is a bit difficult to obtain data. First, take a look at the effect image:
The importance of automatic generation of api documents must be well known, but if you still write word or excel manually, it is difficult to maintain consistency, not to mention the heavy workload.
1. Asp.net webapi comes with a Help Page that you can learn if you are interested.
2. Swagger is a framework for generating api, which is very powerful and supports interface testing, but it seems that swagger under .net can only be used in webapi, and general mvc is not good, and those who are interested can understand it.
The following is mainly about the realization of this wheel. Getting an object graph of that type from a type is easier to implement without rigor, mainly using reflection and recursion.
The C# class in the screenshot above:
Public class GetProductRequest: IRequest {/ item number / public int? ProductID {get; set;} public ResultObject Validate () {if (ProductID = = null | | ProductID.Value
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.