Due to the pandemic Urban Engine is no longer holding CoWorking Night on a weekly basis. CoWorking Night is now held once a month and all scheduling is done via Google Calendar; therefore, this project is no longer needed. However, this code will be publicly archived for reference in the event any devs want to see how we planning to to handle this use case.
This section outlines information relating to configuring or deploying the UrbanEngineApi application.
For provisioning resources in Azure the following script is available if running locally:
From the root directory run the following command in PowerShell:
& ./scripts/UrbanEngineApi-deploy-azure.ps1
NOTE: You will need Azure PowerShell installed if you want to run this script. Documentation on this is available at Install Azure PowerShell.
In your local development environment most configuration settings will be managed via UserSecrets
in a local secrets.json
file. The Microsoft.Extensions.Configuration.UserSecrets
NuGet package enables this for us. There is a script located in scripts/set-secrets-local.ps1 that will set this up in your local development environment.
From the root folder of this repository run the following command in PowerShell:
& ./scripts/set-secrets-local.ps1
You may be prompted to specify certain values such as the database password.
In production we will be using Azure KeyVault
to store secrets. The set-secrets-prod.ps1
script can be used to set the configuration values in the KeyVault. This script will affect production; therefore, please be sure to only run this when ready.
From the root folder of this repository run the following command in PowerShell:
& ./scripts/set-secrets-prod.ps1
Also NOTE: In the appsettings.json the setting KeyVaultName
indicates the name of the KeyVault to use. Again, please make sure this settings is correct before deploying to production.
Be sure to update the set-secrets-local.ps1
and set-secrets-prod.ps1
scripts with any additional settings you add to the project. You can store any configuration values as secrets, they do not necessarily have to be just secrets.
This section will contain any notes to be aware of for developing Urban Engine API
To make things consistent objects in the Results folder are used. Use this to return responses from API endpoints to provide consistent return types
- FailureResult: used to indicate a failure occurred, for example an exception was thrown
- CommandResult: used to indicate result of a command such as an Insert, Update, Delete
- QueryResult: used to indicate result of a query that was performed, returns data and paging information if applicable
To provide a common response when exceptions occur and to ensure exceptions are logged and sensitive error messages are not returned to the client
an exception handler is added in the Startup.cs file. This allows the capture of any exceptions
thrown by controller endpoints without having to add try/catch
and logging
statements in your controllers themselves.
Example:
// see Configure method in Startup.cs
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
// ...see implementation details in Startup.cs file...
});
});
When the UrbanEngineApi starts up it is currently set to call Migrate
to update the database. Whether this is run or not is controll by an Environment Variable
called APPLY_MIGRATIONS
and if this is set to the value of True
then Migrations will be applied on application start up. This is good to have turned on for any
development or testing environments but should be disabled in Production and applied only when expected. To see the code where migrations are run go to
the Program.cs file in the UrbanEngineApi project and look for the CreateOrMigrateDatabase
method.
-
Open a command prompt
-
cd to the
src\UrbanEngine.Infrastructure
directory -
In the command window type
dotnet ef --help
to ensure you have the dotnet ef tool installed -
If EF tools are not installed you'll need to install them using
dotnet tool install --global dotnet-ef
-
Run the following command, change the
<yourmigrationname>
to be a name to indicate what is going into this migrationdotnet ef migrations add <yourmigrationname> --startup-project ../UrbanEngine.Web/UrbanEngine.Web.csproj --project UrbanEngine.Infrastructure.csproj --output-dir Data/Migrations
If working with the local SQL Lite database you'll have to delete the *.db file first as well
The SeedDataGenerator is used to seed the database with any initial data. Use this to add any data that should always be present in the database
A docker compose file exists to help spin up a docker container that runs PostgresSQL. Follow the steps to run the database locally in your dev environment.
-
Open a command prompt
-
cd to the
UrbanEngineApi
directory -
Ensure that Docker is running
-
Run the following command
& scripts\start-postgres-local.ps1
-
Open
pgAdmin
and ensure you can connect to the database locally -
To stop the database when you are finished run the following
& scripts\stop-postgres-local.ps1
- Go to the
UrbanEngine.Core
project - Go to Entities folder
- Create a new Entity to model what this will look like in the database, use the naming convention of suffix name of entity with
Entity
- Make sure your class inherits from
UrbanEngine.SharedKernel.Data.EntityBase
- Add any additional properties to your entity
- Go to
UrbanEngine.Infrastructure
project - Go to
Data\Configuration
- Create a new Configuration for EF Core
- Inherit from
EntityBaseConfiguration
- Override the
Configure
method and add in any mappings - NOTE: inheriting from EntityBaseConfiguration will map the table, primary key, and any common properties from
EntityBase
- Be sure to make the class internal
- Go to
UrbanEngine.Infrastructure
project - Go to `Data\Repository
- Create a new repository for your entity
- Inherit from
EfRepository<T>
- Add a constructor that inherits from base
- This step can come when you are completely done modeling your entity but should happen before you create a pull request.
- Follow the steps at EF Migrations
- Go to
UrbanEngine.Web
project and right click, go to properties. Click Debug, If it does not already exist add an environment variableAPPLY_MIGRATIONS
and set the value to betrue
- Make sure your database is running locally
- Run the UrbanEngine.Web project
- Go to
pgAdmin
and ensure your changes are refelected
Explore other options with dotnet ef
tools for additional options
- Go to
UrbanEngine.Core
- Under Specifications create a new Specification
- Inherit from
BaseSpecification<T>
- Create an interface for filtering, this is passed to the constructor of your Specification class
- Add any properties to the filter class that you would use to search on
- Look at other Specification classes for examples
- Go to
UrbanEngine.Core
- Go to Managers folder and create a new folder for your manager
- Name the new class with the convetion suffix the name with
Manager
- Inherit from the
ManagerBase<TEntity>
base class - Also add an associated interface for this manager and implement that
- Your interace should inherit from
IManager<TEntity>
- In the manager class generate the constructor that inherits from base
- Go to
UrganEngine.Core
- Under the Models folder create a new folder to store you models
- Create a both a list DTO and a detail DTO
- Use the convention to suffix with
DetailDTO
andListItemDTO
- Enter the necessary properties for each DTO. The ListItemDTO is only a few properties and what you need to see a list of that entity. The detail DTO gives you all the details about the DTO
- You can use your associated Entity to help define the properties you need. This is also a good place to do any transformations before sending to UI
- Remove any properties from the DTO that you don't want to expose to outside callers
- Go to
UrbanEngine.Core
- Under the Messages folder create a new folder associated with the messages you are creating
- Typically you will have 4 messages if implementing CRUD operations for your entity
- Examples (rename entity to be your entity)
- DeleteEntityMessage
- GetEntityByIdMessage
- GetEntityMessage
- SaveEntityMessage
- Each message will implement the Mediatr
IRequest
interface passing in the expected output to receive when the message is processed - Look at other messages for examples how each of these should be laid out. The idea behind a message is you are defining the inputs to be processed for some associated action
- Go to
UrbanEngine.Core
- Under the Handlers folder create a new folder associated with the handlers you are creating
- Typically you will have a handler per message you need to process. So, if implementing CRUD operations you may have 4 handlers
- Examples (rename entity to be your entity)
- DeleteEntityHandler
- GetEntityByIdHandler
- GetEntityHandler
- SaveEntityHandler
- Each Handler will implement the Mediatr
IRequestHandler
and specify the type of message your handler is to process and the expected output to return. The output must match the same output specified in your message forIRequest
- Look at other handlers for examples how each of these should be laid out. The idea behind the handler is you are defining the logic to perform when a certain message is received
- Your handler should have the associated manager passed in via dependency injection to this class
- Go to
UrbanEngine.Web
- Go to
Configuration\AutoMapperProfile
- You will need to add a mapping for each entity to the associated DTO
- Go
Startup.cs
, we need to add in Dependency Injection in theConfigureServices
method to register your repository and manager
- Go to
UrbanEngine.Web
- Under the Controllers folder add a new API Controller
- Look at other Controllers for examples how this should be implemented
- An important note is that you should pass in
IMediator
as dependency and let your controller endpoints receive messages and publish to Mediatr to process - Make sure your controller inherits from
ControllerBase
- Run
UrbanEngine.Web
and hitswagger
and test your your controller. If everything is wired up correctly you should be able to test out everything end to end
- Go to
UrbanEngine.Tests
- Under the appropriate folder create a new Tests class.
- Look at existing examples for how this should be implemented.
- Create in your test class a private class called DefaultScope that inherits from
TestScope<T>
where T is the class you want to write the test for - In the constructor of the DefaultScope mock any dependencies that your instance needs. For unit tests, all external dependencies should be mocked.
- Create test methods and decorate them with this attribute
[TestMethod, TestCategory(TestCategory.Unit)]
- Make sure a test has only one assertion and is testing one thing. Create multiple tests for different assertions.
- Follow the Arrange/Act/Assert pattern for writing your tests.
- Inside your test, create an instance of DefaultScope and use the InstanceUnderTest for testing your unit.
- Use TestExplorer to execute your tests.
- Write additional Unit Tests
- Recurring Events
- Authentication
- Authorization