The Air Web app is operated to collect and organize the data required to operate an efficient air quality regulatory program for the State of Georgia Environmental Protection Division (EPD) Air Protection Branch (APB).
The application is under active development to replace similar functionality currently housed in the Integrated Air Information Platform (IAIP). As each new module is developed, it will be removed from the IAIP until all functionality has been migrated and the IAIP can be retired.
This long-term project began with the Small Business Environmental Assistance Program which was migrated into a standalone application.
The current effort focuses on the Stationary Source Compliance Program, specifically the compliance monitoring and enforcement modules (which are also used by the EPD District Offices). This effort will also require updates to our ICIS-Air data flows.
The remaining IAIP modules are described in this discussion topic.
The overall project is owned by the Air Protection Branch. Various modules are owned by the appropriate Programs within the Branch.
Complete the following tasks when the application is ready for deployment.
- Create server-specific settings and config files and add copies to the "app-config" repository.
- Create Web Deploy Publish Profiles for each web server using the "Example-Server.pubxml" file as an example.
- Configure the following external services as needed:
- Azure App registration to manage employee authentication. (Add configuration settings in the "AzureAd" section in a server settings file.) When configuring the app in the Azure Portal, add optional claims for "email", "family_name", and "given_name" under "Token configuration".
- Raygun for crash reporting and performance monitoring. (Add the API key to the " RaygunSettings" section in a server settings file.)
- SonarCloud for code quality and security scanning. (Update the project key in the "sonarcloud-scan.yml" workflow file and in the badges above.)
- Better Uptime for site uptime monitoring. (No app configuration needed.)
The solution contains the following projects:
- Domain β A class library containing the data models, business logic, and repository interfaces.
- AppServices β A class library containing the services used by an application to interact with the domain.
- IaipDataService β A class library implementing data services for IAIP data.
- LocalRepository β A class library implementing the repositories and data stores using static in-memory test data (for local development).
- EfRepository β A class library implementing the repositories and data stores using Entity Framework and a database (as specified by the configured connection string).
- WebApp β The front end web application and API.
- TestData β A class library containing test data for development and testing.
There are also corresponding unit test projects for each (not counting the TestData project).
flowchart BT
I[IaipDataServices]
D[Domain] --> I
A[AppServices] ----> D
T[TestData] --> D
E[EfRepository] --> T
L[LocalRepository] --> T
W[WebApp] ---> L
W ---> E
W --> A
The following settings section configures the data stores, authentication, and other settings for development purposes.
To work with these settings, add an appsettings.Development.json file in the root of the WebApp folder with a
DevSettings section, and make your changes there. Here's a sample appsettings.Development.json file to start out:
{
"DevSettings": {
"UseDevSettings": true,
"BuildDatabase": false,
"UseEfMigrations": false,
"ConnectToIaipDatabase": false,
"EnableTestUser": true,
"TestUserIsAuthenticated": true,
"TestUserRoles": [
"GeneralStaff"
],
"EnableSecurityHeaders": false,
"EnableWebOptimizer": false
}
}- UseDevSettings β Indicates whether the following Dev settings should be applied.
- BuildDatabase
- When
true, theEfRepositorydata store is used. A SQL Server database is created, and data is seeded from theTestDataproject (unless Entity Framework migrations are enabled). - When
false, theLocalRepositorydata store is used. In-memory data is initialized from theTestDataproject.
- When
- UseEfMigrations β (Only applies if
BuildDatabaseistrue.)- When
true, applies Entity Framework database migrations as needed. Test data is not seeded. - When
false, the database is deleted and recreated based on theDbContext. Data is seeded from theTestDataproject.
- When
- ConnectToIaipDatabase
- When
true, the IAIP data services will connect to an existing SQL Server database defined by theIaipConnectionconnection string. (A new database will not be created or seeded with data.) - When
false, test IAIP data is used (located insrc/IaipDataService/TestData).
- When
- EnableTestUser β If
true, a test user account will be available for development purposes. - TestUserIsAuthenticated β Simulates a successful login with a test account when
true. Simulates a failed login whenfalse. (Only applies ifEnableTestUserisfalse.) - TestUserRoles β Adds the listed App Roles to the logged-in account. (Only applies if
TestUserIsAuthenticatedistrue.)
- EnableSecurityHeaders β Sets whether to include HTTP security headers.
- EnableWebOptimizer β Sets whether to enable the WebOptimizer middleware for bundling and minification of CSS and JavaScript files.
In a production or staging environment, UseDevSettings is automatically set to false regardless of what is specified
in the appsettings.json file.
Connection Strings for both a DefaultConnection and a MigrationConnection must be specified.
- The
DefaultConnectionis used for normal app connectivity. The account only requires DML rights on the database. - The
MigrationConnectionis only used for applying Entity Framework migrations. The account requires DDL plus select and insert rights on the database.
The login providers must be enabled and configured. Currently, Okta and (Azure) Entra ID are available in the application.
- To enable authentication using Entra ID, the app must be registered in the Azure portal and configured in the
AzureAdsettings section.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"CallbackPath": "/signin-oidc",
"TenantId": "[Enter the Directory (tenant) ID from the Azure portal]",
"ClientId": "[Enter the Application (client) ID from the Azure portal]"
}
}- To enable Okta, the app must be registered in the Okta portal and configured in the
Oktasettings section.
{
"Okta": {
"OktaDomain": "https://${yourOktaDomain}",
"ClientId": "${clientId}",
"ClientSecret": "${clientSecret}",
"AuthorizationServerId": "default"
}
}- Finally, the login providers must be enabled in the
EnabledLoginProviderssection along with the allowed Okta organization ID or Entra Tenant ID.
{
"EnabledLoginProviders": [
{
"Name": "EntraId",
"Id": "tenant-1-id"
},
{
"Name": "EntraId",
"Id": "tenant-2-id"
},
{
"Name": "Okta",
"Id": "okta-id"
}
]
}User roles can be seeded using the SeedUserRoles setting. The roles are added to the user's account the first time
they log in. For example:
{
"SeedUserRoles": [
{
"User": "user1@example.com",
"Roles": [
"UserAdmin",
"Staff"
]
}
]
}Here's a visualization of how the settings configure data storage at runtime.
flowchart LR
subgraph SPL["'BuildDatabase' = false"]
direction LR
D[Domain]
T["Test Data (in memory)"]
R[Local Repositories]
A([App Services])
A --> R
R --> T
T --> D
end
flowchart LR
subgraph SPB["'BuildDatabase' = true"]
direction LR
D[Domain]
T[Test Data]
R[EF Repositories]
A([App Services])
B[(Database)]
R --> B
A --> R
T -->|Seed| B
B --> D
end
flowchart LR
subgraph SPD["Production or staging environment"]
direction LR
D[Domain]
R[EF Repositories]
A([App Services])
B[(Database)]
A --> R
R --> B
B --> D
end
Here's a visualization of how the settings configure the IAIP data services at runtime.
flowchart LR
subgraph SPL["'ConnectToIaipDatabase' = false"]
direction LR
T["Test Data (in memory)"]
A[IAIP App Services]
W([Web App])
W --> A
A --> T
end
flowchart LR
subgraph SPB["Production/staging environment <br> or 'ConnectToIaipDatabase' = true"]
direction LR
A[IAIP App Services]
W([Web App])
B[(Database)]
W --> A
A --> B
end
