基于 WebAPI 的 API 实现
本文基于
- WebAPI
- OData (微软发起的一个格式标准,其中一个比较有意思的是可以直接在 Excel 中填入 API 就可以展示了)
- Swashbuckle.OData(把 API 生成一个测试页面)
项目结构
代码
- SwaggerConfig
1 [assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")] 2 3 namespace ODataWebAPI 4 { 5 /// <summary> 6 /// SwaggerConfig 7 /// </summary> 8 public class SwaggerConfig 9 { 10 /// <summary> 11 /// SwaggerConfig Register 12 /// </summary> 13 public static void Register() 14 { 15 GlobalConfiguration.Configuration.EnableSwagger(c => 16 { 17 // By default, the service root url is inferred from the request used to access the docs. 18 // However, there may be situations (e.g. proxy and load-balanced environments) where this does not 19 // resolve correctly. You can workaround this by providing your own code to determine the root URL. 20 // 21 //c.RootUrl(req => GetRootUrlFromAppConfig()); 22 23 // If schemes are not explicitly provided in a Swagger 2.0 document, then the scheme used to access 24 // the docs is taken as the default. If your API supports multiple schemes and you want to be explicit 25 // about them, you can use the "Schemes" option as shown below. 26 // 27 //c.Schemes(new[] { "http", "https" }); 28 29 // Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to 30 // hold additional metadata for an API. Version and title are required but you can also provide 31 // additional fields by chaining methods off SingleApiVersion. 32 // 33 c.SingleApiVersion("v1", "Sample API for Swashbuckle.OData") 34 .Contact(contactBuilder => contactBuilder 35 .Url("http://localhost:53515/swagger/ui/index#/")); 36 37 // If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion". 38 // In this case, you must provide a lambda that tells Swashbuckle which actions should be 39 // included in the docs for a given API version. Like "SingleApiVersion", each call to "Version" 40 // returns an "Info" builder so you can provide additional metadata per API version. 41 // 42 //c.MultipleApiVersions( 43 // (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion), 44 // (vc) => 45 // { 46 // vc.Version("v2", "Swashbuckle Dummy API V2"); 47 // vc.Version("v1", "Swashbuckle Dummy API V1"); 48 // }); 49 50 // You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API. 51 // See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details. 52 // NOTE: These only define the schemes and need to be coupled with a corresponding "security" property 53 // at the document or operation level to indicate which schemes are required for an operation. To do this, 54 // you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties 55 // according to your specific authorization implementation 56 // 57 //c.BasicAuth("basic") 58 // .Description("Basic HTTP Authentication"); 59 // 60 //c.ApiKey("apiKey") 61 // .Description("API Key Authentication") 62 // .Name("apiKey") 63 // .In("header"); 64 // 65 //c.OAuth2("oauth2") 66 // .Description("OAuth2 Implicit Grant") 67 // .Flow("implicit") 68 // .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog") 69 // //.TokenUrl("https://tempuri.org/token") 70 // .Scopes(scopes => 71 // { 72 // scopes.Add("read", "Read access to protected resources"); 73 // scopes.Add("write", "Write access to protected resources"); 74 // }); 75 76 // Set this flag to omit descriptions for any actions decorated with the Obsolete attribute 77 //c.IgnoreObsoleteActions(); 78 79 // Each operation be assigned one or more tags which are then used by consumers for various reasons. 80 // For example, the swagger-ui groups operations according to the first tag of each operation. 81 // By default, this will be controller name but you can use the "GroupActionsBy" option to 82 // override with any value. 83 // 84 //c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString()); 85 86 // You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate 87 // the order in which operations are listed. For example, if the default grouping is in place 88 // (controller name) and you specify a descending alphabetic sort order, then actions from a 89 // ProductsController will be listed before those from a CustomersController. This is typically 90 // used to customize the order of groupings in the swagger-ui. 91 // 92 //c.OrderActionGroupsBy(new DescendingAlphabeticComparer()); 93 94 // If you annotate Controllers and API Types with 95 // Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate 96 // those comments into the generated docs and UI. You can enable this by providing the path to one or 97 // more Xml comment files. 98 // 99 var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; 100 var commentsFileName = Assembly.GetExecutingAssembly().GetName().Name + ".XML"; 101 var commentsFile = Path.Combine(baseDirectory, "App_Data", commentsFileName); 102 c.IncludeXmlComments(commentsFile); 103 104 // Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types 105 // exposed in your API. However, there may be occasions when more control of the output is needed. 106 // This is supported through the "MapType" and "SchemaFilter" options: 107 // 108 // Use the "MapType" option to override the Schema generation for a specific type. 109 // It should be noted that the resulting Schema will be placed "inline" for any applicable Operations. 110 // While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not. 111 // It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only 112 // use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a 113 // complex Schema, use a Schema filter. 114 // 115 //c.MapType<ProductType>(() => new Schema { type = "integer", format = "int32" }); 116 // 117 // If you want to post-modify "complex" Schemas once they've been generated, across the board or for a 118 // specific type, you can wire up one or more Schema filters. 119 // 120 //c.SchemaFilter<ApplySchemaVendorExtensions>(); 121 122 // Set this flag to omit schema property descriptions for any type properties decorated with the 123 // Obsolete attribute 124 //c.IgnoreObsoleteProperties(); 125 126 // In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique 127 // Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this 128 // works well because it prevents the "implementation detail" of type namespaces from leaking into your 129 // Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll 130 // need to opt out of this behavior to avoid Schema Id conflicts. 131 // 132 //c.UseFullTypeNameInSchemaIds(); 133 134 // In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers. 135 // You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given 136 // enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different 137 // approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings. 138 // 139 //c.DescribeAllEnumsAsStrings(); 140 141 // Similar to Schema filters, Swashbuckle also supports Operation and Document filters: 142 // 143 // Post-modify Operation descriptions once they've been generated by wiring up one or more 144 // Operation filters. 145 // 146 //c.OperationFilter<AddDefaultResponse>(); 147 // 148 // If you've defined an OAuth2 flow as described above, you could use a custom filter 149 // to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required 150 // to execute the operation 151 // 152 //c.OperationFilter<AssignOAuth2SecurityRequirements>(); 153 154 // Post-modify the entire Swagger document by wiring up one or more Document filters. 155 // This gives full control to modify the final SwaggerDocument. You should have a good understanding of 156 // the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md 157 // before using this option. 158 // 159 c.DocumentFilter<ApplyResourceDocumentation>(); 160 161 // In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL 162 // to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions 163 // with the same path (sans query string) and HTTP method. You can workaround this by providing a 164 // custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs 165 // 166 //c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); 167 168 // Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an 169 // alternative implementation for ISwaggerProvider with the CustomProvider option. 170 // 171 c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c).Configure(odataConfig => 172 { 173 // Set this flag to include navigation properties in your entity swagger models 174 // 175 //odataConfig.IncludeNavigationProperties(); 176 })); 177 }) 178 .EnableSwaggerUi(c => 179 { 180 // Use the "InjectStylesheet" option to enrich the UI with one or more additional CSS stylesheets. 181 // The file must be included in your project as an "Embedded Resource", and then the resource's 182 // "Logical Name" is passed to the method as shown below. 183 // 184 //c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css"); 185 186 // Use the "InjectJavaScript" option to invoke one or more custom JavaScripts after the swagger-ui 187 // has loaded. The file must be included in your project as an "Embedded Resource", and then the resource's 188 // "Logical Name" is passed to the method as shown above. 189 // 190 //c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js"); 191 192 // The swagger-ui renders boolean data types as a dropdown. By default, it provides "true" and "false" 193 // strings as the possible choices. You can use this option to change these to something else, 194 // for example 0 and 1. 195 // 196 //c.BooleanValues(new[] { "0", "1" }); 197 198 // By default, swagger-ui will validate specs against swagger.io's online validator and display the result 199 // in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the 200 // feature entirely. 201 //c.SetValidatorUrl("http://localhost/validator"); 202 //c.DisableValidator(); 203 204 // Use this option to control how the Operation listing is displayed. 205 // It can be set to "None" (default), "List" (shows operations for each resource), 206 // or "Full" (fully expanded: shows operations and their details). 207 // 208 //c.DocExpansion(DocExpansion.List); 209 210 // Use the CustomAsset option to provide your own version of assets used in the swagger-ui. 211 // It's typically used to instruct Swashbuckle to return your version instead of the default 212 // when a request is made for "index.html". As with all custom content, the file must be included 213 // in your project as an "Embedded Resource", and then the resource's "Logical Name" is passed to 214 // the method as shown below. 215 // 216 //c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html"); 217 218 // If your API has multiple versions and you've applied the MultipleApiVersions setting 219 // as described above, you can also enable a select box in the swagger-ui, that displays 220 // a discovery URL for each version. This provides a convenient way for users to browse documentation 221 // for different API versions. 222 // 223 //c.EnableDiscoveryUrlSelector(); 224 225 // If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to 226 // the Swagger 2.0 specification, you can enable UI support as shown below. 227 // 228 //c.EnableOAuth2Support("test-client-id", "test-realm", "Swagger UI"); 229 }); 230 } 231 } 232 }
-
WebApiConfig
1 public static class WebApiConfig 2 { 3 /// <summary> 4 /// WebApiConfig Register 5 /// </summary> 6 /// <param name="config"></param> 7 public static void Register(HttpConfiguration config) 8 { 9 ODataModelBuilder builder = new ODataConventionModelBuilder(); 10 builder.EntitySet<UserModel>("Users"); 11 config.MapODataServiceRoute( 12 routeName: "ODataRoute", 13 routePrefix: null, 14 model: builder.GetEdmModel()); 15 } 16 }
- UsersController
1 public class UsersController : ODataController 2 { 3 /// <summary> 4 /// 查询用户 5 /// </summary> 6 /// <returns></returns> 7 [EnableQuery] 8 public List<UserModel> Get() 9 { 10 return new List<UserModel>() 11 { 12 new UserModel { Id = 1, Name = "Zhang San" }, 13 new UserModel { Id = 2, Name = "Li Si" } 14 }; 15 } 16 }
- AesourceDocumentation
1 public class ApplyResourceDocumentation : IDocumentFilter 2 { 3 /// <summary> 4 /// Apply 5 /// </summary> 6 /// <param name="swaggerDoc"></param> 7 /// <param name="schemaRegistry"></param> 8 /// <param name="apiExplorer"></param> 9 public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer) 10 { 11 swaggerDoc.tags = new List<Tag> 12 { 13 new Tag { name = "Users", description = "a RESTier resource" } 14 }; 15 } 16 }
-
UserModelMap
1 public class UserModelMap : EntityTypeConfiguration<UserModel> 2 { 3 /// <summary> 4 /// UserModelMap 5 /// </summary> 6 public UserModelMap() 7 { 8 //this.ToTable("dbo.User"); 9 this.HasKey(u => u.Id); 10 11 this.Property(u => u.Name).IsRequired(); 12 } 13 }
-
UserModel
1 public class UserModel 2 { 3 /// <summary> 4 /// User Id 5 /// </summary> 6 public int Id { get; set; } 7 /// <summary> 8 /// User Name 9 /// </summary> 10 public string Name { get; set; } 11 }
-
EfContext
1 public class EfContext : DbContext 2 { 3 /// <summary> 4 /// Users 5 /// </summary> 6 public DbSet<UserModel> Users { get; set; } 7 8 /// <summary> 9 /// EfContext ctor 10 /// </summary> 11 public EfContext() 12 : base("name=EfContext") 13 { 14 } 15 16 /// <summary> 17 /// This method is called when the model for a derived context has been initialized, but 18 /// before the model has been locked down and used to initialize the context. The default 19 /// implementation of this method does nothing, but it can be overridden in a derived class 20 /// such that the model can be further configured before it is locked down. 21 /// </summary> 22 /// <remarks> 23 /// Typically, this method is called only once when the first instance of a derived context 24 /// is created. The model for that context is then cached and is for all further instances of 25 /// the context in the app domain. This caching can be disabled by setting the ModelCaching 26 /// property on the given ModelBuidler, but note that this can seriously degrade performance. 27 /// More control over caching is provided through use of the DbModelBuilder and DbContextFactory 28 /// classes directly. 29 /// </remarks> 30 /// <param name="modelBuilder">The builder that defines the model for the context being created. </param> 31 protected override void OnModelCreating(DbModelBuilder modelBuilder) 32 { 33 base.OnModelCreating(modelBuilder); 34 35 modelBuilder.Configurations.Add(new UserModelMap()); 36 } 37 }
Tips:
- 53515 是项目设置 Web 选项里的端口号,具体请查询真实的端口号
- 勾选项目设置 Build 选项里的XML documentation file,因为 Swagger 会用 xml 里的注释作为说明,请把代码中的 /// 注释补全,这样才能生成说明
运行结果
由于测试直接返回的固定数据,所以,当运行后就可以通过 API 得到结果了,并且可以通过 Swagger 测试页面看到 API 的信息,并可以测试。
此例中的 API 为
http://localhost:53515/Users?$orderby=Id desc (请求的 API)
http://localhost:53515/swagger/ui/index#/ (Swagger 生成的测试页面)