{"id":23122863,"url":"https://github.com/wissance/weathercontrol","last_synced_at":"2025-08-17T03:31:02.445Z","repository":{"id":65267716,"uuid":"485278210","full_name":"Wissance/WeatherControl","owner":"Wissance","description":"Asp Net Core Web API programming tutorial: Weather Control application that uses (netcore3.1/net6.0) with either EntityFramework and EdgeDb plus Wissance.WebApiToolkitpi","archived":false,"fork":false,"pushed_at":"2024-11-02T07:22:35.000Z","size":1363,"stargazers_count":4,"open_issues_count":5,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-02T08:19:39.743Z","etag":null,"topics":["aspnet-product","aspnet-web-api","csharp-tutorial","edgedb","edgedb-net","netcore-boilerplate","netcore-template","netcore-webapi","netcore31","programming-tutorial","rest-api","restful-api","weather-app","weather-forecast","webapi","webapi-tutorial"],"latest_commit_sha":null,"homepage":"https://wissance.github.io/WeatherControl/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Wissance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-25T08:06:04.000Z","updated_at":"2024-10-24T21:01:42.000Z","dependencies_parsed_at":"2024-11-02T08:19:13.383Z","dependency_job_id":"3455b5e9-cca7-44eb-aee7-4acae71ed6a1","html_url":"https://github.com/Wissance/WeatherControl","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWeatherControl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWeatherControl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWeatherControl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWeatherControl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Wissance","download_url":"https://codeload.github.com/Wissance/WeatherControl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230080775,"owners_count":18169619,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspnet-product","aspnet-web-api","csharp-tutorial","edgedb","edgedb-net","netcore-boilerplate","netcore-template","netcore-webapi","netcore31","programming-tutorial","rest-api","restful-api","weather-app","weather-forecast","webapi","webapi-tutorial"],"created_at":"2024-12-17T07:31:05.854Z","updated_at":"2024-12-17T07:31:10.551Z","avatar_url":"https://github.com/Wissance.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"## WeatherControl\n\n![Manage you WeatherData with Wissance](https://github.com/Wissance/WeatherControl/blob/master/docs/cover.png)\n\nThis project uses [`Wissance.WebApiToolkit`](https://github.com/Wissance/WebApiToolkit) so please give us a star! And for this project too!\n\n### 1. General description\n\nThis project from the one side is a **tutorial** about how to design `REST API` using `C#`, but from the other side it is also fully functional `Web API` (`REST`) for working with meteorological stations or with indoor conditions sensors.\n\n1. `REST API` with `EntityFramework` `ORM` - `Wissance.WeatherControl.WebApi` project\n2. `REST API` with `EdgeDb` `Graph DB` - `Wissance.WeatherControl.WebApi.V2` project\n\nThese 2 Project previously having had (\u003c `2.0`) different data Model, but starting from `2.0` they have the same data model.\n\nThese projects targets multiple platforms - `netcore 3.1`, `net6` and `net8`.\n\n### 2. Data Model\n\n#### 2.1 Glossary / Domain object\n\n* `Station` - weather station that has name, description, coordinates and it **can collect and store any number of parameters**, 1 measuring parameter - 1 `Sensor`;\n* `Sensor` - sensor that measures physical values like `Temparature`, `Pressure`, `Humidity` and so on, 1 sensor measures only one 1 physical value;\n* `MeasureUnit` - physical value itself that is measuring by `Sensor`.\n* `Measurement` - Sensor values = timestamp + numeric (decimal) value.\n\n### 3. REST API With Entity Framework\n\n#### 3.1 Application Overview\n\nWeb API (REST) service (.Net Core) could store weather data from multiple weather station with multiple (any number) sensors, typically meteo stations store/manage following physical value measurements getting from appropriate sensors:\n* `temperature`;\n* `atmosphere pressure`;\n* `humidity`;\n* `wind speed`;\n\nApplication has 4 `resources` = Domain objects\n\nApplication uses `MsSql` (`Sql Server`) as Database Server (this could be easily changed, but this required to re-generate migration).\n\n1. `Station`\n2. `Sensor`\n3. `MeasureUnit`\n4. `Measurement`\n \nHere is relations between objects in SQL database:\n![Relation between objects in SQL DB](docs/sql_db_model.png)\n\n#### 3.2 Overall usage scenario\n\nThis is a **very simple application (demo)**, if any feature is needed open new issue/request. Every `REST` resource described in a separate sub chapter.\n\n##### 3.2.1 Operation with MeasureUnit resource\n\nFirst we should configure what we would like to measure, we could do it via `POST` `~/api/MeasureUnit` with body i.e.:\n\n```json\n{\n  \"name\": \"Wind speed\",\n  \"description\": \"\",\n  \"abbreviation\": \"V, mm/s\"\n}\n```\n\n```bash\ncurl -X 'POST' \\\n  'http://127.0.0.1:8058/api/MeasureUnit' \\\n  -H 'accept: text/plain' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"Wind speed\",\n  \"description\": \"\",\n  \"abbreviation\": \"V, mm/s\"\n}'\n```\n\nWe could edit created MeasureUnit via `PUT` `~/api/MeasureUnit/{id}`, with new body, i.e.:\n```json\n{\n  \"name\": \"Wind speed\",\n  \"description\": \"Wind speed in mm/s\",\n  \"abbreviation\": \"V, mm/s\"\n}\n```\n\n```bash\ncurl -X 'PUT' \\\n  'http://127.0.0.1:8058/api/MeasureUnit/b23b25dc-49df-42e3-8374-08dcf37af278' \\\n  -H 'accept: text/plain' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"Wind speed\",\n  \"description\": \"Wind speed in mm/s\",\n  \"abbreviation\": \"V, mm/s\"\n}'\n```\n\nOr get multiple objects - `GET` `~/api/MeasureUnit`\n\n\n##### 3.2.2 Operations with Station resource\n\n1. Create Station:\n\n`POST http://localhost:8058/api/station`\n\n```json\n{\n\t\"name\": \"Ufa meteo station\",\n\t\"description\": \"Meteo station in Ufa city\",\n\t\"longitude\": \"55°96'0\\\"E\",\n\t\"latitude\": \"54°7'0\\\"N\",\n\t\"sensors\": []\n}\n```\n\n```bash\ncurl -X 'POST' \\\n  'http://127.0.0.1:8058/api/Station' \\\n  -H 'accept: text/plain' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n\t\"name\": \"Ufa meteo station\",\n\t\"description\": \"Meteo station in Ufa city\",\n\t\"longitude\": \"55°96'\\''0\\\"E\",\n    \"latitude\": \"54°7'\\''0\\\"N\",\n\t\"sensors\": []\n}'\n```\n\nWe got a Operation result response:\n```json\n{\n    \"success\": true,\n    \"message\": null,\n    \"data\": {\n        \"id\": \"7b5dab63-b9d9-4aac-dd19-08dcf42ec6b0\",\n        \"name\": \"Ufa meteo station\",\n\t    \"description\": \"Meteo station in Ufa city\",\n        \"longitude\": \"55°96'0\\\"E\",\n        \"latitude\": \"54°7'0\\\"N\",\n        \"sensors\": null\n    }\n}\n```\n\nExample of station creation postman (different from upper requests)\n![Result of running create station](docs/create_station_v1_example.png)\n\nExample of station **with sensors** creation in postman (different from upper requests)\n![Result of running create station](docs/create_station_v1_with_sensors_example.png)\n\n2. Station data update (could be updated name, description and coordinates):\n\n`PUT http://localhost:8058/api/station/9bea7375-b27a-4c48-ee97-08dcf4490c3f`\n\nBody and response are the same as at Create operation, unlike Sensors are not editable through `PUT`:\n```json\n{\n    \"name\": \"Nizniy Tagil meteostation\",\n    \"description\": \"Nizniy Tagil meteostation (Sverdlovskaya region)\",\n    \"longitude\": \"60°07'0\\\"E\",\n    \"latitude\": \"57°88'0\\\"N\",\n    \"sensors\": []\n}\n```\n\n```bash\ncurl -X 'PUT' \\\n  'http://127.0.0.1:8058/api/Station/9bea7375-b27a-4c48-ee97-08dcf4490c3f' \\\n  -H 'accept: text/plain' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"Nizniy Tagil meteostation\",\n  \"description\": \"Nizniy Tagil meteostation (Sverdlovskaya region)\",\n  \"longitude\": \"60°07'0\\\"E\",\n  \"latitude\": \"57°88'0\\\"N\",\n  \"sensors\": []\n}'\n```\n\nExample of running different station update in Postman:\n![Result of running update station](docs/update_station_v1_example.png)\n\n3. There are two get endpoints:\n\n* 3.1 to get `one by id` - `GET http://localhost:8058/api/station/{id}`\n* 3.2 to get `collection with paging` - `GET http://localhost:8058/api/station/?page=1\u0026size=10`\n\n  ![Result of running get stations](docs/get_station_v1_example.png)\n\n4. To delete station with id `9bea7375-b27a-4c48-ee97-08dcf4490c3f` use endpoint `DELETE http://localhost:8058/api/station/9bea7375-b27a-4c48-ee97-08dcf4490c3f`\n\n##### 3.2.4 Operations with Sensor resource\n\nOperation with Sensors are the same as for Sensor.\n\n1. Create Sensor - `POST ~/api/Sensor`, i.e.\n```bash\ncurl -X 'POST' \\\n  'http://127.0.0.1:8058/api/Sensor' \\\n  -H 'accept: text/plain' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"Temperature sensor ЕТ1\",\n  \"description\": \"\",\n  \"latitude\": \"\",\n  \"longitude\": \"\",\n  \"stationId\": \"7004ccf8-8c6a-47dd-dd18-08dcf42ec6b0\",\n  \"measureUnitId\": \"7f319181-31d3-44ce-8371-08dcf37af278\"\n}'\n```\n\n2. Update Sensor is similar to Create:\n```bash\ncurl -X 'PUT' \\\n  'http://127.0.0.1:8058/api/Sensor/cb018a8a-cb03-4dbd-2b3c-08dcf466fe52' \\\n  -H 'accept: text/plain' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"Temperature sensor ЕТ1\",\n  \"description\": \"Temperature sensor\",\n  \"latitude\": \"\",\n  \"longitude\": \"\",\n  \"stationId\": \"7004ccf8-8c6a-47dd-dd18-08dcf42ec6b0\",\n  \"measureUnitId\": \"7f319181-31d3-44ce-8371-08dcf37af278\"\n}'\n```\n\n3. There are two get endpoints:\n\n* 3.1 to get `one by id` - `GET http://localhost:8058/api/sensor/{id}`\n* 3.2 to get `collection with paging` - `GET http://localhost:8058/api/station/?page=1\u0026size=10` with 2 additional params:\n  - station (`Guid`) for filtering Sensors by relation to station\n  - mu (`Guid`) for filtering Sensors by MeasureUnit\n  \nExamples of usage:\n* Get multiple for MeasureUnit with id `7f319181-31d3-44ce-8371-08dcf37af278` - `http://127.0.0.1:8058/api/Sensor?mu=7f319181-31d3-44ce-8371-08dcf37af278`\n* Get multiple for MeasureUnit and Station `http://127.0.0.1:8058/api/Sensor?station=9694eacb-6470-46c2-dd1a-08dcf42ec6b0\u0026mu=7f319181-31d3-44ce-8371-08dcf37af278`\n\n##### 3.2.5 Operations with Measurement resource\n\n1. Create measurements\n\n`POST http://localhost:8058/api/measurement`\nOne measurement is one sample of measuring some MeasureUnit, Measurement directly relates to Sensor, not MeasureUnit (this could be discussed)\n\n```json\n{\n    \"SampleDate\": \"2024-10-25T12:10:00\",\n    \"Value\": 7.45,\n    \"SensorId\": \"cb018a8a-cb03-4dbd-2b3c-08dcf466fe52\"\n}\n```\n\nWe got following result in the output:\n```json\n{\n    \"success\": true,\n    \"message\": null,\n    \"data\": {\n        \"id\": \"af27b058-4f6b-46f3-ff6b-08dcf4c8e236\",\n        \"sampleDate\": \"2024-10-25T12:10:00+05:00\",\n        \"value\": 7.45,\n        \"sensorId\": \"cb018a8a-cb03-4dbd-2b3c-08dcf466fe52\"\n    }\n}\n```\n\n![Result of running create measurements](docs/create_measurement_v1_example.png)\n\n2. Update measurements: one or any number of weather parameters could be changed using \n   `PUT http://localhost:8058/api/measurements/af27b058-4f6b-46f3-ff6b-08dcf4c8e236` with same body and result as at create measurements operation.\n   \n![Result of running update measurements](docs/update_measurement_v1_example.png)\n   \n3. There are two get operations:\n\n* 3.1 to get one by id `GET http://localhost:8058/api/measurements/af27b058-4f6b-46f3-ff6b-08dcf4c8e236`\n* 3.2 to get collection with paging `GET http://localhost:8058/api/measurements/?page=1\u0026size=10`\n\n4. To delete measurements with id 1 use endpoint `DELETE http://localhost:8058/api/measurements/af27b058-4f6b-46f3-ff6b-08dcf4c8e236`\n\n### 4. REST API With EdgeDB\n\nHere we've got a `net6.0` `REST` Service that have the same model as previous service but persistent storage is EdgeDB not an SQL Server.\n\n* `MeasureUnit`\n* `Measurement`\n* `Sensor`\n* `Station`\n\nModel schema is here:\n![Relation between objects in SQL DB](docs/edge_db_model.png)\n\nData project is `Wissance.WeatherControl.GraphData`\n\n![Relation between Models]()\n\n\n#### 4.1 Configure Edge DB (Prerequisites)\n\n1. Start `Edgedb` instance from `Wissance.WeatherControl.GraphData` directory\n\n```ps1\nedgedb instance start -I Wissance_WeatherControl --foreground\n```\n\napply migration via\n\n```ps1\nedgedb -I Wissance_WeatherControl migrate\n```\n\n2. Configure Edgedb to allow pass own identifiers (necessary for object return after create)\n\n```ps1\nedgedb -I Wissance_WeatherControl configure set allow_user_specified_id true\n```\n\n2. Start edgedb ui:\n\n```ps1\nedgedb ui\n```\n\nOnce you loaded project you could use it in current application:\n\n1. Add proper Project Name in `appsettings.Development.json`config file:\n```json\n\"Application\": {\n    \"Database\": {\n      \"ProjectName\": \"Wissance_WeatherControl\"\n  }\n}\n```\n\nSee how configuration works via project Name [here](https://github.com/Wissance/EdgeDb.Net.Configurator)\n\nThis package build connnection string using following scheme: `edgedb://user:password@host:port/database`\nyou could see your project credential on:\n* `Windows` machine in a directory: `%USER_PROFILE%\\AppData\\Local\\EdgeDB\\config\\credentials`\n* `Linux` machine in a directory `$HOME/.config/edgedb/credential`\n\n#### 4.2 REST API With Edge DB\n\nWe are having following Key Items:\n\n1. `Controllers` - we are using base classes from a `Wissance.WebApiToollit`, in this lib we have\n   either controllers for read-only and for full `CRUD` resources.\n2. `Managers` - classes that are responsible for manage all business logic, in this project we have\n   only one manager class - `EdgeDbManager` that is common for `CRUD` operation over all resources \n3. `EqlResolver` - class that is responsible for association `model` (`resource`) with operation\n   (`read` , `create`, `update` or `delete`)\n4. `Factories` - static classes that constructs `DTO` from `Models` and params (dictionary for \n   `create` and `update` perform) from `DTO`.\n   \n   \n##### 4.2.1 REST API Controllers\n\nAll controllers are located in a folder `Controllers`, just look how simply look full `CRUD` Controller:\n\n```csharp\nnamespace Wissance.WeatherControl.WebApi.V2.Controllers\n{\n    public class MeasurementController : BasicCrudController\u003cMeasurementDto, MeasurementEntity, Guid, , MeasureUnitFilterable\u003e\n    {\n        public MeasurementController(EdgeDBClient edgeDbClient)\n        {\n            Manager = new EdgeDbManager\u003cMeasurementDto, MeasurementEntity, Guid\u003e(ModelType.Measurement, edgeDbClient,\n                MeasurementFactory.Create, MeasurementFactory.Create);\n        }\n    }\n}\n```\n\n\n###### 4.2.2 Manager\n\nWe have only one manager for all controllers due to the power of C# generics we just have to pass\nto `EdgeDbManager`:\n* `modelType` that is using to find appropriate eql statements from `EqlResolver`\n* `EdgeDbClient` client to `edgedb` database\n* and 2 delegates that describes how to create representation (`DTO`) from model and how to convert\n  `DTO` to parameters list for `insert` and `update` operations\n  \n##### 4.2.3 EqlResolver\n\nJust a set of dictionaries every dictionary for one operation:\n* get collection\n* get one\n* create\n* update\n* delete\n\n##### 4.2.4 Factories for objects transformation\n\nThey are static classes in a `Factories` directory, the looking quite simple:\n\n```csharp\nnamespace Wissance.WeatherControl.WebApi.V2.Factories\n{\n    public static class SensorFactory\n    {\n        public static SensorDto Create(SensorEntity entity)\n        {\n            SensorDto dto = new SensorDto()\n            {\n                Id = entity.Id,\n                Name = entity.Name,\n                Latitude = entity.Latitude,\n                Longitude = entity.Longitude\n            };\n\n            if (entity.Measurements.Any())\n            {\n                dto.Measurements = entity.Measurements.Select(m =\u003e MeasurementFactory.Create((m))).ToList();\n            }\n\n            return dto;\n        }\n        \n        public static IDictionary\u003cstring, object?\u003e Create(SensorDto dto, bool generateId)\n        {\n            IDictionary\u003cstring, object?\u003e dict = new Dictionary\u003cstring, object?\u003e()\n            {\n                {\"Name\", dto.Name},\n                {\"Latitude\", dto.Latitude},\n                {\"Longitude\", dto.Longitude},\n                {\"Measurements\", dto.Measurements.Where(m =\u003e m.Id.HasValue)\n                    .Select(m =\u003e m.Id.Value).ToArray()}\n            };\n            \n            // TODO(this if for further getting created object)\n            dict[\"id\"] = generateId ? Guid.NewGuid() : dto.Id;\n\n            return dict;\n        }\n    }\n}\n```\n\n### 5. Contributors\n\n\u003ca href=\"https://github.com/Wissance/WeatherControl/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=Wissance/WeatherControl\" /\u003e\n\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwissance%2Fweathercontrol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwissance%2Fweathercontrol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwissance%2Fweathercontrol/lists"}