├── .vs └── slnx.sqlite ├── Models ├── ErrorViewModel.cs └── User.cs ├── DataAccess ├── Utility │ ├── ICosmosConnection.cs │ └── CosmosConnection.cs ├── ICosmosDataAdapter.cs └── CosmosDataAdapter.cs ├── appsettings.json ├── appsettings.Development.json ├── Program.cs ├── Controllers ├── HomeController.cs └── CosmosController.cs ├── Startup.cs └── CosmosConnection.cs /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/azurecosmoscrudoperation/HEAD/.vs/slnx.sqlite -------------------------------------------------------------------------------- /Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Cosmos_CRUD.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } -------------------------------------------------------------------------------- /DataAccess/Utility/ICosmosConnection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Documents.Client; 2 | using System.Threading.Tasks; 3 | 4 | namespace Cosmos_CRUD.DataAccess.Utility 5 | { 6 | 7 | public interface ICosmosConnection 8 | { 9 | Task InitializeAsync(string collectionId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "Cosmos": { 8 | "AccountURL": "https://cosmos-db-test-cp.documents.azure.com:443/", 9 | "AuthKey": "yourauthkey==", 10 | "DatabaseId": "cosmos-db-demo", 11 | "CollectionId": "emp-collection" 12 | }, 13 | "AllowedHosts": "*" 14 | } 15 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "Cosmos": { 10 | "AccountURL": "https://cosmos-db-test-cp.documents.azure.com:443/", 11 | "AuthKey": "yourauthkey", 12 | "DatabaseId": "cosmos-db-demo" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Cosmos_CRUD 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DataAccess/ICosmosDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using Cosmos_CRUD.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Cosmos_CRUD.DataAccess 8 | { 9 | public interface ICosmosDataAdapter 10 | { 11 | Task UpsertUserAsync(UserInfo user); 12 | 13 | Task CreateDatabase(string name); 14 | Task CreateCollection(string dbName, string name); 15 | Task CreateDocument(string dbName, string name, UserInfo userInfo); 16 | 17 | Task PlaceOrder(string dbName, string name, Order order); 18 | 19 | Task GetData(string dbName, string name); 20 | Task DeleteUserAsync(string dbName, string name, string id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Cosmos_CRUD.Models; 8 | 9 | namespace Cosmos_CRUD.Controllers 10 | { 11 | public class HomeController : Controller 12 | { 13 | public IActionResult Index() 14 | { 15 | return View(); 16 | } 17 | 18 | public IActionResult About() 19 | { 20 | ViewData["Message"] = "Your application description page."; 21 | 22 | return View(); 23 | } 24 | 25 | public IActionResult Contact() 26 | { 27 | ViewData["Message"] = "Your contact page."; 28 | 29 | return View(); 30 | } 31 | 32 | public IActionResult Privacy() 33 | { 34 | return View(); 35 | } 36 | 37 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 38 | public IActionResult Error() 39 | { 40 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Models/User.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Cosmos_CRUD.Models 8 | { 9 | public class UserInfo 10 | { 11 | public string id { get; set; } 12 | public string FirstName { get; set; } 13 | public string LastName { get; set; } 14 | 15 | public override string ToString() 16 | { 17 | return JsonConvert.SerializeObject(this); 18 | } 19 | } 20 | 21 | public class Product 22 | { 23 | public string id { get; set; } 24 | public string ProductName { get; set; } 25 | public string ProductDescription { get; set; } 26 | 27 | public override string ToString() 28 | { 29 | return JsonConvert.SerializeObject(this); 30 | } 31 | } 32 | 33 | public class Order 34 | { 35 | public string id { get; set; } 36 | public Product Product { get; set; } 37 | public UserInfo User { get; set; } 38 | 39 | public bool IsPaymentDone { get; set; } 40 | 41 | public override string ToString() 42 | { 43 | return JsonConvert.SerializeObject(this); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Cosmos_CRUD.DataAccess; 6 | using Cosmos_CRUD.DataAccess.Utility; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.HttpsPolicy; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.DependencyInjection; 14 | 15 | namespace Cosmos_CRUD 16 | { 17 | public class Startup 18 | { 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public IConfiguration Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | services.Configure(options => 30 | { 31 | // This lambda determines whether user consent for non-essential cookies is needed for a given request. 32 | options.CheckConsentNeeded = context => true; 33 | options.MinimumSameSitePolicy = SameSiteMode.None; 34 | }); 35 | 36 | 37 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 38 | 39 | services.AddTransient(); 40 | services.AddTransient(); 41 | } 42 | 43 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 44 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 45 | { 46 | if (env.IsDevelopment()) 47 | { 48 | app.UseDeveloperExceptionPage(); 49 | } 50 | else 51 | { 52 | app.UseExceptionHandler("/Home/Error"); 53 | app.UseHsts(); 54 | } 55 | 56 | app.UseHttpsRedirection(); 57 | app.UseStaticFiles(); 58 | app.UseCookiePolicy(); 59 | 60 | app.UseMvc(routes => 61 | { 62 | routes.MapRoute( 63 | name: "default", 64 | template: "{controller=Home}/{action=Index}/{id?}"); 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Controllers/CosmosController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Cosmos_CRUD.DataAccess; 6 | using Cosmos_CRUD.Models; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace Cosmos_CRUD.Controllers 11 | { 12 | //[Route("api/[controller]")] 13 | [ApiController] 14 | public class CosmosController : ControllerBase 15 | { 16 | /// 17 | /// 18 | ICosmosDataAdapter _adapter; 19 | public CosmosController(ICosmosDataAdapter adapter) 20 | { 21 | _adapter = adapter; 22 | } 23 | 24 | // GET: api/Cosmos/5 25 | [HttpGet("createdb")] 26 | public async Task CreateDatabase() 27 | { 28 | var result = await _adapter.CreateDatabase("test-db"); 29 | return Ok(result); 30 | } 31 | 32 | [HttpGet("createcollection")] 33 | public async Task CreateCollection() 34 | { 35 | var result = await _adapter.CreateCollection("test-db", "test-collection"); 36 | return Ok(result); 37 | } 38 | 39 | [HttpPost("createdocument")] 40 | public async Task CreateDocument([FromBody] UserInfo user) 41 | { 42 | var result = await _adapter.CreateDocument("test-db", "test-collection", user); 43 | return Ok(result); 44 | } 45 | 46 | [HttpPost("placeorder")] 47 | public async Task Post([FromBody] Order order) 48 | { 49 | var result = await _adapter.PlaceOrder("test-db", "test-collection", order); 50 | return Ok(); 51 | } 52 | [HttpGet("get")] 53 | public async Task Get() 54 | { 55 | var result = await _adapter.GetData("test-db", "test-collection"); 56 | return Ok(result); 57 | } 58 | 59 | // POST: api/Cosmos 60 | [HttpPost("save")] 61 | public async Task Post([FromBody] UserInfo user) 62 | { 63 | var result = await _adapter.UpsertUserAsync(user); 64 | return Ok(); 65 | } 66 | 67 | // PUT: api/Cosmos/5 68 | [HttpPut("{id}")] 69 | public void Put(int id, [FromBody] string value) 70 | { 71 | } 72 | 73 | // DELETE: api/ApiWithActions/5 74 | [HttpDelete("{id}")] 75 | public async Task Delete(string id) 76 | { 77 | var result = await _adapter.DeleteUserAsync("test-db", "test-collection", id); 78 | return Ok(result); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /DataAccess/CosmosDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using Cosmos_CRUD.DataAccess.Utility; 2 | using Cosmos_CRUD.Models; 3 | using Microsoft.Azure.Documents; 4 | using Microsoft.Azure.Documents.Client; 5 | using Microsoft.Extensions.Configuration; 6 | using System; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Threading.Tasks; 10 | 11 | namespace Cosmos_CRUD.DataAccess 12 | { 13 | public class CosmosDataAdapter : ICosmosDataAdapter 14 | { 15 | private readonly DocumentClient _client; 16 | private readonly string _accountUrl; 17 | private readonly string _primarykey; 18 | 19 | public CosmosDataAdapter( 20 | ICosmosConnection connection, 21 | IConfiguration config) 22 | { 23 | 24 | _accountUrl = config.GetValue("Cosmos:AccountURL"); 25 | _primarykey = config.GetValue("Cosmos:AuthKey"); 26 | _client = new DocumentClient(new Uri(_accountUrl), _primarykey); 27 | } 28 | 29 | 30 | public async Task CreateDatabase(string name) 31 | { 32 | try 33 | { 34 | await _client.CreateDatabaseIfNotExistsAsync(new Database { Id = name }); 35 | return true; 36 | } 37 | catch(Exception ex) 38 | { 39 | return false; 40 | } 41 | } 42 | 43 | public async Task CreateCollection(string dbName, string name) 44 | { 45 | try 46 | { 47 | await _client.CreateDocumentCollectionIfNotExistsAsync 48 | (UriFactory.CreateDatabaseUri(dbName), new DocumentCollection { Id = name }); 49 | return true; 50 | } 51 | catch 52 | { 53 | return false; 54 | } 55 | } 56 | 57 | public async Task CreateDocument(string dbName, string name, UserInfo userInfo) 58 | { 59 | try 60 | { 61 | userInfo.id = "d9e51c1e-1474-41d1-8f32-96deedd8f36a"; 62 | await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(dbName, name), userInfo); 63 | return true; 64 | } 65 | catch 66 | { 67 | return false; 68 | } 69 | } 70 | 71 | public async Task PlaceOrder(string dbName, string name, Order order) 72 | { 73 | try 74 | { 75 | await _client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(dbName, name), order); 76 | return true; 77 | } 78 | catch 79 | { 80 | return false; 81 | } 82 | } 83 | 84 | public async Task GetData(string dbName, string name) 85 | { 86 | try 87 | { 88 | 89 | var result = await _client.ReadDocumentFeedAsync(UriFactory.CreateDocumentCollectionUri(dbName, name), 90 | new FeedOptions { MaxItemCount = 10 }); 91 | 92 | return result; 93 | } 94 | catch(Exception ex) 95 | { 96 | return false; 97 | } 98 | } 99 | 100 | 101 | public async Task UpsertUserAsync(UserInfo user) 102 | { 103 | ResourceResponse response = null; 104 | try 105 | { 106 | response = null;//await _client.UpsertDocumentAsync(_collectionUri, user); 107 | } 108 | catch (DocumentClientException ex) when (ex.StatusCode == HttpStatusCode.NotFound) 109 | { 110 | return null; 111 | } 112 | catch (Exception ex) 113 | { 114 | throw; 115 | } 116 | 117 | return (dynamic)response.Resource; 118 | } 119 | 120 | public async Task DeleteUserAsync(string dbName, string name, string id) 121 | { 122 | try 123 | { 124 | var collectionUri = UriFactory.CreateDocumentUri(dbName, name, id); 125 | 126 | var result = await _client.DeleteDocumentAsync(collectionUri); 127 | 128 | return (dynamic)result.Resource; 129 | } 130 | catch (DocumentClientException ex) when (ex.StatusCode == HttpStatusCode.NotFound) 131 | { 132 | return null; 133 | } 134 | catch (Exception ex) 135 | { 136 | throw; 137 | } 138 | } 139 | 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /CosmosConnection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Documents; 2 | using Microsoft.Azure.Documents.Client; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Serialization; 7 | using System; 8 | using System.Collections.ObjectModel; 9 | using System.Net; 10 | using System.Threading.Tasks; 11 | 12 | namespace Cosmos_CRUD.DataAccess.Utility 13 | { 14 | /// 15 | public class CosmosConnection : ICosmosConnection 16 | { 17 | 18 | /// 19 | protected string DatabaseId { get; } 20 | 21 | 22 | /// 23 | protected string CollectionId { get; set; } 24 | 25 | 26 | 27 | private DocumentClient _client; 28 | private readonly string _endpointUrl; 29 | private readonly string _authKey; 30 | private readonly Random _random = new Random(); 31 | private const string _partitionKey = "test"; 32 | private readonly ILogger _logger; 33 | /// 34 | /// Config-based constructor. 35 | /// 36 | /// Config file is used to access DatabaseId, AccountURL, and AuthKey 37 | /// /// Logger for logging 38 | public CosmosConnection(IConfiguration config, ILogger logger) 39 | { 40 | DatabaseId = config.GetValue("Cosmos:DatabaseId"); 41 | _endpointUrl = config.GetValue("Cosmos:AccountURL"); 42 | _authKey = config.GetValue("Cosmos:AuthKey"); 43 | _logger = logger; 44 | } 45 | 46 | /// 47 | public async Task InitializeAsync(string collectionId) 48 | { 49 | CollectionId = collectionId; 50 | 51 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings 52 | { 53 | ContractResolver = new CamelCasePropertyNamesContractResolver() 54 | }; 55 | 56 | var connectionPolicy = new ConnectionPolicy 57 | { 58 | ConnectionMode = ConnectionMode.Gateway, 59 | ConnectionProtocol = Protocol.Https 60 | }; 61 | 62 | if (_client == null) 63 | _client = new DocumentClient( 64 | new Uri(_endpointUrl), _authKey, connectionPolicy); 65 | 66 | await VerifyDatabaseCreated(); 67 | await VerifyCollectionCreated(); 68 | return _client; 69 | } 70 | 71 | /// 72 | /// Verify if database is already created or it need to be created. 73 | /// 74 | /// 75 | private async Task VerifyDatabaseCreated() 76 | { 77 | 78 | var database = await _client.CreateDatabaseIfNotExistsAsync( 79 | new Database 80 | { 81 | Id = DatabaseId 82 | } 83 | ); 84 | 85 | if (database.StatusCode == HttpStatusCode.Created) 86 | { 87 | _logger.LogInformation($"Created DocumentDB database: {DatabaseId}"); 88 | return true; 89 | } 90 | else if (database.StatusCode == HttpStatusCode.OK) 91 | { 92 | _logger.LogInformation($"DocumentDB database already exists: {DatabaseId}"); 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | /// 99 | /// Verify if collection is already created or we need to create the collection 100 | /// 101 | /// 102 | private async Task VerifyCollectionCreated() 103 | { 104 | if (string.IsNullOrEmpty(CollectionId)) 105 | { 106 | throw new Exception("No collection id was set before accessing the CosmosConnection's Initialize method"); 107 | } 108 | 109 | var databaseUri = UriFactory.CreateDatabaseUri(DatabaseId); 110 | var collection = await _client.CreateDocumentCollectionIfNotExistsAsync( 111 | databaseUri, new DocumentCollection 112 | { 113 | Id = CollectionId, 114 | PartitionKey = new PartitionKeyDefinition 115 | { 116 | Paths = new Collection { $"/{_partitionKey}" } 117 | } 118 | }); 119 | 120 | if (collection.StatusCode == HttpStatusCode.Created) 121 | { 122 | _logger.LogInformation($"Created DocumentDB collection: {CollectionId}"); 123 | return true; 124 | } 125 | else if (collection.StatusCode == HttpStatusCode.OK) 126 | { 127 | _logger.LogInformation($"DocumentDB collection already exists: {CollectionId}"); 128 | return true; 129 | } 130 | 131 | return false; 132 | 133 | } 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /DataAccess/Utility/CosmosConnection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Documents; 2 | using Microsoft.Azure.Documents.Client; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Serialization; 7 | using System; 8 | using System.Collections.ObjectModel; 9 | using System.Net; 10 | using System.Threading.Tasks; 11 | 12 | namespace Cosmos_CRUD.DataAccess.Utility 13 | { 14 | /// 15 | public class CosmosConnection : ICosmosConnection 16 | { 17 | 18 | /// 19 | protected string DatabaseId { get; } 20 | 21 | 22 | /// 23 | protected string CollectionId { get; set; } 24 | 25 | 26 | 27 | private DocumentClient _client; 28 | private readonly string _endpointUrl; 29 | private readonly string _authKey; 30 | private readonly Random _random = new Random(); 31 | private const string _partitionKey = "test"; 32 | private readonly ILogger _logger; 33 | /// 34 | /// Config-based constructor. 35 | /// 36 | /// Config file is used to access DatabaseId, AccountURL, and AuthKey 37 | /// /// Logger for logging 38 | public CosmosConnection(IConfiguration config, ILogger logger) 39 | { 40 | DatabaseId = config.GetValue("Cosmos:DatabaseId"); 41 | _endpointUrl = config.GetValue("Cosmos:AccountURL"); 42 | _authKey = config.GetValue("Cosmos:AuthKey"); 43 | _logger = logger; 44 | } 45 | 46 | /// 47 | public async Task InitializeAsync(string collectionId) 48 | { 49 | CollectionId = collectionId; 50 | 51 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings 52 | { 53 | ContractResolver = new CamelCasePropertyNamesContractResolver() 54 | }; 55 | 56 | var connectionPolicy = new ConnectionPolicy 57 | { 58 | ConnectionMode = ConnectionMode.Gateway, 59 | ConnectionProtocol = Protocol.Https 60 | }; 61 | 62 | if (_client == null) 63 | _client = new DocumentClient( 64 | new Uri(_endpointUrl), _authKey, connectionPolicy); 65 | 66 | await VerifyDatabaseCreated(); 67 | await VerifyCollectionCreated(); 68 | return _client; 69 | } 70 | 71 | /// 72 | /// Verify if database is already created or it need to be created. 73 | /// 74 | /// 75 | private async Task VerifyDatabaseCreated() 76 | { 77 | 78 | var database = await _client.CreateDatabaseIfNotExistsAsync( 79 | new Database 80 | { 81 | Id = DatabaseId 82 | } 83 | ); 84 | 85 | if (database.StatusCode == HttpStatusCode.Created) 86 | { 87 | _logger.LogInformation($"Created DocumentDB database: {DatabaseId}"); 88 | return true; 89 | } 90 | else if (database.StatusCode == HttpStatusCode.OK) 91 | { 92 | _logger.LogInformation($"DocumentDB database already exists: {DatabaseId}"); 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | /// 99 | /// Verify if collection is already created or we need to create the collection 100 | /// 101 | /// 102 | private async Task VerifyCollectionCreated() 103 | { 104 | if (string.IsNullOrEmpty(CollectionId)) 105 | { 106 | throw new Exception("No collection id was set before accessing the CosmosConnection's Initialize method"); 107 | } 108 | 109 | var databaseUri = UriFactory.CreateDatabaseUri(DatabaseId); 110 | var collection = await _client.CreateDocumentCollectionIfNotExistsAsync( 111 | databaseUri, new DocumentCollection 112 | { 113 | Id = CollectionId, 114 | PartitionKey = new PartitionKeyDefinition 115 | { 116 | Paths = new Collection { $"/{_partitionKey}" } 117 | } 118 | }); 119 | 120 | if (collection.StatusCode == HttpStatusCode.Created) 121 | { 122 | _logger.LogInformation($"Created DocumentDB collection: {CollectionId}"); 123 | return true; 124 | } 125 | else if (collection.StatusCode == HttpStatusCode.OK) 126 | { 127 | _logger.LogInformation($"DocumentDB collection already exists: {CollectionId}"); 128 | return true; 129 | } 130 | 131 | return false; 132 | 133 | } 134 | 135 | } 136 | } 137 | --------------------------------------------------------------------------------