Ii. Summary of Help documentation comments
Our comment is the description or description of the Help document, then this feature is installed helppage directly have, here in two ways.
1: When creating a project is a direct selection of the Web API, then this feature is configured when the initialization project is created.
2: When you create a project, you select Empty, the core reference chosen by the Web API is not available for this feature.
There is a part of the project code generated for mode 1 that we do not need, and we can do subtraction to delete unnecessary files.
For mode 2, you need to install helppage within NuGet, you need to cancel the configuration comments within the file ~/areas/helppage/helppageconfig.cs, depending on your needs.
and set the output in the project's generated properties, tick the XML document file, set the value and ~/areas/helppage/helppageconfig.cs
Consistent with the configuration within.
and register it in the Global.asax file Application_Start method.
Arearegistration.registerallareas ();
The Help document is now available, but it has no style. You can choose to manually copy the required CSS and JS into the areas folder. and add Files
public class bundleconfig{ //For more information about bindings, visit http://go.microsoft.com/fwlink/? linkid=301862 public static void Registerbundles (Bundlecollection bundles) { bundles. ADD (New Scriptbundle (' ~/bundles/jquery '). Include ( ' ~/areas/helppage/scripts/jquery-{version}.js ')); Use the development version of the MODERNIZR to be used for development and learning. Then, when you're ready for//production, use the build tool on http://modernizr.com to select only the tests you want. bundles. ADD (New Scriptbundle (' ~/bundles/modernizr '). Include ( ' ~/areas/helppage/scripts/modernizr-* ')); Bundles. ADD (New Scriptbundle (' ~/bundles/bootstrap '). Include ( ' ~/areas/helppage/scripts/bootstrap.js ', ' ~/areas/helppage/scripts/respond.js ')); Bundles. ADD (New Stylebundle (' ~/content/css '). Include ( ' ~/areas/helppage/content/bootstrap.css ', ' ~/areas/helppage/content/site.css '));} }
and register it in the Global.asax file Application_Start method.
Bundleconfig.registerbundles (Bundletable.bundles);
Finally, change ~/areas/helppage/views/shared/_layout.cshtml to
@using system@using system.web.optimization<! DOCTYPE html>
At this point, you will see the following document.
The corresponding routing structure is as follows
Viewing the route can find its AllowMultiple = True means that we can define multiple different routes for the same action, but it also means that the action only allows defined routing access.
such as the Get method here, when the browser can only access Http://localhost:11488/api/product/{id} in this way
Use Http://localhost:11488/api/product?id={id} will throw 405, as follows.
In order to support multiple ways we will increase the number of routes as follows.
The document then generates both routes.
There is a principle here that the same type of request and the same type of response do not allow the same route to be defined, such as httpget and the same response type.
<summary>/// Get all products///</summary>[httpget, Route (')]public ienumerable<product> get () { return _products;} <summary>/// Gets the first three products//</summary>[httpget, Route (')]public ienumerable<product> GetTop3 ( ) { return _products. Take (3);}
Accessing http://localhost:11488/api/product at this point will find a 500 error, prompting for matching to multiple actions, and viewing the Help document at this time will only show a matching action if you do not specify the route's Order property )。
There are some basic restrictions that can be made within a route, and we'll transform the Top3 method above to determine the top number based on the parameters passed in, and at least the first three bars.
<summary>/// Get the first few products//</summary>[httpget, Route (' Top/{count:min (3)} ')]public ienumerable< product> GetTop (int count) { return _products. Take (3);}
At this point the access to HTTP://LOCALHOST:11488/API/PRODUCT/TOP/1 or HTTP://LOCALHOST:11488/API/PRODUCT/TOP/2 will be thrown 404
But I want direct access to the HTTP://LOCALHOST:11488/API/PRODUCT/TOP default of the first 3, then the direct access will be 405, because there is no route ("Top") to define the routing, we modified the next
<summary>/// Get the first few products//</summary>[httpget, Route (' Top/{count:min (3): int=3} ')]public ienumerable<product> GetTop (int count) { return _products. Take (3);}
The first 3 are returned by default when accessing Http://localhost:11488/api/product/Top, but there are some definitions including the regular can see here and here.
Routed documents related to the basic of these, where there are omissions are welcomed to point out.
The next step is the request and response documentation for the single interface, and let's take a look at our separate request and response.
First look at the display of Api/product/all this interface, will be found in two categories.
Api/product This interface itself does not require any parameters, so it is none.
Put Api/product?id={id} This interface is actually all inclusive. His definition is as follows.
<summary>/// Editing products///</summary>///<param name= ' ID ' > Product code </param>///<param name= ' Request ' > edited product </param>[httpput, Route ('), route (' {ID} ')]public string Put (int ID, product request) { var model = _products. FirstOrDefault (x = x.id.equals (Id)); if (model = null) return ' did not find the product '; Model. Name = Request. Name; Model. Price = Request. Price; Model. Description = Request. Description; return ' OK ';}
In fact, in practice we may only use get and post to do all of our operations. Therefore, it will be get only the URI Parameters and the post only shows the body Parameters.
You can see that description is our comment on the property, type is the property, and additional information is the description of the "constraint", as we will constrain the requested parameters which are required which are optional and which values have a scope of use.
Let's say we change product.
<summary>/// Products//</summary>public class product{ //<summary>///number/ </summary> [Required] public int Id {get; set;} <summary> //////</summary> [Required, MaxLength] public string name {get; set;} <summary> ///Price///</summary> [Required, Range (0, 99999999)] public Decimal price {get; set;} <summary>//description//// </summary> public string Description {get; set;}}
You can see that the corresponding "constraint information" is changed.
One might say that I have customized some of the constraints to be displayed, and then we define a minimum constraint minattrbute.
<summary>///min characteristic///</summary>[attributeusage (Attributetargets.property, AllowMultiple = False)] public class minattribute:validationattribute{//<summary>//min//</summary> public I NT MinimumValue {get; set;} <summary>///constructors///</summary>//<param name= ' minimun ' ></param> public Mi Nattribute (int minimun) {minimumvalue = Minimun; }///<summary>///Authentication logic///</summary>//<param name= ' value ' > value to be validated </param>/ <returns> whether to pass validation </returns> public override bool IsValid (object value) {int intvalue; if (value! = null && int. TryParse (value. ToString (), out Intvalue) {return (intvalue >= minimumvalue); } return false; }///<summary>///Format error message///</summary>//<param name= ' name ' > property name </param> <retuRns> error message </returns> public override string Formaterrormessage (string name) {return string. Format (' {0} minimum value {1} ', name, MinimumValue); }}
Add it to the price property and set the minimum value to 10.
<summary>/// Price//</summary>[required, Min (Ten)]public decimal prices {get; set;}
At this time through the postman to request, you will find that the validation is passed, and there is no expected error prompt. That's because we don't have the attribute to enable the validation attribute.
We customize a validatemodelattribute with the available range specified as class and method, and do not allow multiple times and add it to just the put interface.
///<summary>///Validate Model Filter///</summary>[attributeusage ( AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = False, inherited = True)]public class Validatemodelattribute: actionfilterattribute{//<summary>//action Pre-validation///</summary>//<param name= ' action Context ' >the action context.</param> public override void OnActionExecuting (Httpactioncontext actioncontext) {if (ActionContext.ActionArguments.Any (kv = kv). Value = = null)) {actioncontext.response = ActionContext.Request.CreateErrorResponse (Httpstatuscode.badr Equest, ' parameter cannot be null '); } if (ActionContext.ModelState.IsValid) return; Actioncontext.response = ActionContext.Request.CreateErrorResponse (Httpstatuscode.badrequest, Actioncontext.modelstate); }}
<summary>/// Editing products///</summary>///<param name= ' ID ' > Product code </param>///<param name= ' Request ' > edited product </param>[httpput, Route ('), route (' {ID} ')][validatemodel]public string Put (int ID, product Request) { var model = _products. FirstOrDefault (x = x.id.equals (Id)); if (model = null) return ' did not find the product '; Model. Name = Request. Name; Model. Price = Request. Price; Model. Description = Request. Description; return ' OK ';}
This is where we use the postman request and the validation prompt appears.
But by the time we look at our help document, Price's "constraint information" leaves only required.
So I'm going to show the constraint information for the custom Minattribute. The generated code of the observation document can be found to be generated within the definition of Annotationtextgenerator in the Areas.HelpPage.ModelDescriptions.ModelDescriptionGenerator class.
That would be a good job, and I would add my own custom.
Modify this to support more data annotation attributes.private readonly idictionary<type, Func<object, string> > annotationtextgenerator = new Dictionary<type, Func<object, string>>{{typeof (RequiredAttribute), a = > ' Required '}, {typeof (Rangeattribute), a = = {Rangeattribute range = (rangeattribute) A; Return String.Format (CultureInfo.CurrentCulture, ' range:inclusive between {0} and {1} ', Range. Minimum, Range. Maximum); }}, {typeof (Maxlengthattribute), a = = {Maxlengthattribute MaxLength = (Maxlengthattribute) A; Return String.Format (CultureInfo.CurrentCulture, ' Max length: {0} ', maxlength.length); }}, {typeof (Minlengthattribute), a = = {Minlengthattribute MinLength = (Minlengthattribute) A; Return String.Format (CultureInfo.CurrentCulture, ' Min length: {0} ', minlength.length); }}, {typeof (Stringlengthattribute),A = {Stringlengthattribute strlength = (Stringlengthattribute) A; Return String.Format (CultureInfo.CurrentCulture, ' String length:inclusive between {0} and {1} ', Strlength.minimumlength, strlength.maximumlength); }}, {typeof (Datatypeattribute), a = = {Datatypeattribute DataType = (Datatypeattribute) A; Return String.Format (CultureInfo.CurrentCulture, ' Data type: {0} ', Datatype.customdatatype?? datatype.datatype.t Ostring ()); }}, {typeof (Regularexpressionattribute), a = = {Regularexpressionattribute RegularExpression = (Regularexpressionattribute) A; Return String.Format (CultureInfo.CurrentCulture, ' Matching regular expression pattern: {0} ', Regularexpression.pattern); }}, {typeof (Minattribute), a = = {Minattribute Minattribute = (Minattribute) A; Return String.Format (cultureinfo.currentculture, ' minimum value: {0} ', MinattribuTe. MinimumValue); } },};
Then we look at the document and our "constraint information" comes out.
This is basically what the request section is all about. Response part of the content is not much, mainly is the display of sample will have a problem, if you write a step-by-step here to see the Help document sample will have three, respectively, is
Application/json,text/json Application/xml,text/xml application/x-www-from-urlencoded
Here we will find that it cannot generate application/x-www-form-urlencoded because we cannot use Jquerymvcformurlencodefomatter to format our class. As for why, I did not go to look for, because besides Application/json is I need, the rest I do not need.
Interested friends can find out why. Then let's tell you what we don't need to remove here, as follows.
public static class webapiconfig{public static void Register (httpconfiguration config) { //Web API Configuration and services C3/>config. Formatters.remove (config. Formatters.xmlformatter); Web API Routing config. Maphttpattributeroutes (); }}
This only removes xmlformatter, because application/x-www-form-urlencoded we need it at the request, but I don't want him to show up in the document, so ...
Within the Getsample method in the Areas.HelpPage.SampleGeneration.HelpPageSampleGenerator class, the
foreach (Var formatter in formatters)
Change to
foreach (Var formatter in formatters. Where (x = X.gettype ()! = typeof (Jquerymvcformurlencodedformatter)))
Then, the document is clean, is it a neat freak ...
Iii. using multiple projects to generate an XML file to display the Help document
It's finally here, we first webapi2postman.webmodel the product as a project and referencing him, and see the documentation below.
You will find that your comment is not a description of the attribute. Before you open app_data/xmldocument.xml file comparison p did not move the roduct XML file indeed the description of the product class did not, because the Xmldocument.xml file here is the project's build profile, not in this project
The defined files are not generated within this file, and the real requirement is that we really need to define all requests and response separately in one project for other project references, perhaps unit tests may also be our encapsulated WEBAPI client (as described in the next article).
With this question found this article http://stackoverflow.com/questions/21895257/ How-can-xml-documentation-for-web-api-include-documentation-from-beyond-the-main
This article provides 3 ways to only describe what I think is reasonable, that is, we need to webapi2postman.webmodel the generated properties of the XML document file, but also to generate an XML document, while expanding a new XML document loading method
Add a class named MultiXmlDocumentationProvider.cs under Directory ~/areas/helppage/.
Using system;using system.linq;using system.reflection;using system.web.http.controllers;using System.web.http.description;using Xlobo.rechargeservice.areas.helppage.modeldescriptions;namespace xlobo.rechargeservice.areas.helppage{//<summary>a custom <see cref= ' Idocumentationprovider '/> That Reads the API documentation from a collection of XML documentation files.</summary> public class Multixmldocument Ationprovider:idocumentationprovider, Imodeldocumentationprovider {/********* * * Properties * * * //////<summary>the internal documentation providers for specific files.</summary> private ReadOnly xmldocumentationprovider[] Providers; /********* * * Public methods *********///<summary>construct an instance.</summary> <param name= ' paths ' >the physical paths to the XML documents.</param> public Multixmldocumentati Onprovider (params string[] paths) {this. Providers = paths. Select (p = new Xmldocumentationprovider (p)). ToArray (); }//<summary>gets the documentation for a subject.</summary>//<param name= ' subject ' >t He subject to document.</param> public string getdocumentation (MemberInfo subject) {return This. Getfirstmatch (P = p.getdocumentation (subject)); }//<summary>gets the documentation for a subject.</summary>//<param name= ' subject ' >t He subject to document.</param> public string getdocumentation (Type subject) {return this. Getfirstmatch (P = p.getdocumentation (subject)); }//<summary>gets the documentation for a subject.</summary>//<param name= ' subject ' >t He subject to document.</param> public string getdocumentation (httpcontrollerdescriptor subject) { return this. GetfirStmatch (P = p.getdocumentation (subject)); }//<summary>gets the documentation for a subject.</summary>//<param name= ' subject ' >t He subject to document.</param> public string getdocumentation (httpactiondescriptor subject) { return this. Getfirstmatch (P = p.getdocumentation (subject)); }//<summary>gets the documentation for a subject.</summary>//<param name= ' subject ' >t He subject to document.</param> public string getdocumentation (httpparameterdescriptor subject) { return this. Getfirstmatch (P = p.getdocumentation (subject)); }//<summary>gets the documentation for a subject.</summary>//<param name= ' subject ' >t He subject to document.</param> public string getresponsedocumentation (httpactiondescriptor subject) { return this. Getfirstmatch (p = P.getdocumentatiOn (subject)); }/********* * * Private methods *********///<summary>get The first valid result from The collection of XML documentation providers.</summary>///<param name= ' expr ' >the method to INVOKE.&L T;/param> private String Getfirstmatch (Func<xmldocumentationprovider, string> expr) {RE Turn this. Providers. Select (expr). FirstOrDefault (p =!) String.isnullorwhitespace (p)); } }}
Then replace the configuration within the original ~/areas/helppage/helppageconfig.cs.
Config. Setdocumentationprovider (New Xmldocumentationprovider (HttpContext.Current.Server.MapPath (' ~/app_data/ Xmldocument.xml ')); config. Setdocumentationprovider (New Multixmldocumentationprovider (HttpContext.Current.Server.MapPath (' ~/app_data/ Xmldocument.xml '), HttpContext.Current.Server.MapPath (' ~/app_data/webapi2postman.webmodel.xmldocument.xml '));
So here you can select multiple document XML to be placed in different locations or under App_Data that are placed under the WEBAPI project.
To facilitate our WEBAPI project, this refers to Webapi2postman, which adds a build event to it
Copy $ (SolutionDir) WebAPI2PostMan.WebModelApp_DataXmlDocument.xml $ (ProjectDir) App_ DataWebAPI2PostMan.WebModel.XmlDocument.xml
After each build succeeds, copy the WebAPI2PostMan.WebModel.XmlDocument.xml file to the App_Data directory of the Webapi2postman project and rename it to WebAPI2PostMan.WebModel.XmlDocument.xml.
Now, rebuild the project, and our description is back again ~
This article if the patient will see the changes in fact, there is no need to spend so much space to explain, but for the people who need a little help.
For convenience, the source code is still in: Https://github.com/yanghongjie/WebAPI2PostMan, if you have seen here, easy to click "Recommended" bar ('? ') ).