title: "End-to-End Solution: Building a Secure Business App with Azure + PowerApps + SharePoint + .NET"
date: 2025-04-07
category: Deep Dive
tags: [Azure, PowerApps, SharePoint, .NET, Integration, Architecture, Security, ALM]
author: Vladimir Luis
description: "Design, build, secure, monitor, and scale a production-grade business application spanning Azure, Power Platform, SharePoint, and .NET."
featured: true
draft: false
readTime: 25-30 min
End-to-End Solution: Building a Secure Business App with Azure + PowerApps + SharePoint + .NET
Executive Summary
This deep dive walks through architecting and implementing a secure, enterprise-ready business application that unifies Azure backend services, PowerApps for user experience, SharePoint for collaborative content & metadata, and .NET (ASP.NET Core + Azure Functions) for extensible logic. You will provision infrastructure, structure data, implement reusable UI components, enforce least-privilege security, integrate automation, instrument observability, and establish CI/CD & ALM practices for sustainable operations and scale.
The Business Challenge
Organizations frequently stitch together ad hoc solutions (manual lists, siloed scripts, fragmented forms) resulting in data duplication, inconsistent security, limited auditing, and low agility. This unified architecture solves:
- Fragmented business process tracking across departments.
- Lack of governed extensibility for custom logic and integration.
- Poor visibility (telemetry, KPIs, operational health).
- Inconsistent security & compliance posture (secrets, PII access, audit trails).
- Manual deployments and environment drift.
Solution Architecture
Key principles: Domain-driven data segregation, least-privilege, resilient integration, layered telemetry, cost-aware design, automated lifecycle.
SharePoint
- Modern SharePoint Communication Site for documentation & structured lists
- Site Collection Admin for initial configuration
Developer Tooling
- VS Code, Azure CLI, Power Platform CLI (PAC), GitHub CLI
- Node.js LTS & .NET 8 SDK
System(appInsights, "App Insights", "Telemetry & health monitoring")
System(keyVault, "Azure Key Vault", "Secrets & key management")
}
Rel(businessUser, powerApp, "Uses")
Rel(manager, powerApp, "Uses for approvals")
Rel(powerApp, dataverse, "CRUD")
Rel(powerApp, sharePoint, "Reads/Writes lists & docs")
Rel(powerApp, azureFuncs, "Invokes for advanced logic")
Rel(azureFuncs, keyVault, "Retrieves secrets")
Rel(azureFuncs, apiCore, "Calls internal APIs")
Rel(azureFuncs, extCRM, "Fetches enrichment")
Rel(azureFuncs, extPayments, "Payment operations")
Rel(apiCore, dataverse, "Aggregated queries")
Rel(apiCore, appInsights, "Logs & metrics")
Rel(powerApp, appInsights, "Telemetry (custom events)")
Prerequisites
-------------
Azure Resources
---------------
- Azure Subscription (Enterprise Agreement or Pay-As-You-Go)
- Resource Group: `rg-business-app-prod`
- Services: Storage Account, Key Vault, Application Insights, Service Bus (Premium if high throughput), Event Grid, Azure Functions, Azure API Management (optional), Azure Static Web Apps (optional for admin portal)
Power Platform
--------------
- Environment Strategy: Dev β Test β Prod (Managed solutions promoted)
- Appropriate licenses: Per-App or Per-User for PowerApps; Power Automate capacity; Dataverse database provisioned
SharePoint
---------
- Modern SharePoint Communication Site for documentation & structured lists
- Site Collection Admin for initial configuration
Developer Tooling
-----------------
- VS Code, Azure CLI, Power Platform CLI (PAC), GitHub CLI
- Node.js LTS & .NET 8 SDK
Part 1: SharePoint Foundation
-----------------------------
Step 1: Data Structure
----------------------
Lists:
- `Operations` (Columns: Title, Status Choice, Priority Choice, Owner Person, DueDate Date, DataverseRecordId GUID)
- `Documents` (Metadata: Category Choice, Sensitivity Choice, LinkedOperation Lookup)
JSON Column Formatting Example (Status badge):
```json
{
"$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
"elmType": "div",
"attributes": {
"class": "=if(@currentField == 'Critical', 'sp-field-severity--severeWarning', if(@currentField=='High','sp-field-severity--warning','sp-field-severity--good'))"
},
"txtContent": "@currentField"
}
Step 2: Permissions & Governance
- Break inheritance only for restricted document libraries (avoid widespread unique permissions)
- Use Azure AD groups mapped to SharePoint groups:
BusinessApp-Contributors,BusinessApp-Approvers,BusinessApp-Readers - Enable retention & sensitivity labeling via Purview classifications for
Documentslibrary
Part 2: Dataverse Domain Model
Tables:
Operation(OperationId GUID PK, Title, Status OptionSet, Owner Lookup(User), DueDate, Priority OptionSet, CreatedOn, ModifiedOn)AuditLog(EntryId GUID, Source Text, Actor Lookup(User), Action Text, Timestamp, CorrelationId Text)PaymentTransaction(TxnId GUID, OperationId Lookup(Operation), Amount Currency, Status OptionSet, GatewayRef Text)
Delegation Strategy:
// Fast filtering & paging
With({src: Operation},
FirstN(
SortByColumns(
Filter(src, Status <> "Closed" And DueDate >= Today()),
"DueDate", Ascending
),
200
)
)
Part 3: Azure Backend Services
Provision Core Resources
az group create --name rg-business-app-prod --location eastus
az storage account create --name bizappstoreprod --resource-group rg-business-app-prod --sku Standard_LRS
az keyvault create --name kv-bizapp-prod --resource-group rg-business-app-prod --location eastus --enable-rbac-authorization true
az monitor app-insights component create --app bizapp-ai --location eastus --resource-group rg-business-app-prod --application-type web
az functionapp create --name func-bizapp-prod --resource-group rg-business-app-prod --consumption-plan-location eastus --runtime node --storage-account bizappstoreprod
az servicebus namespace create --name sb-bizapp-prod --resource-group rg-business-app-prod --location eastus --sku Premium
Function: Operation Enrichment (Node.js)
// /OperationEnrich/index.js
const { DefaultAzureCredential } = require('@azure/identity');
const { SecretClient } = require('@azure/keyvault-secrets');
const fetch = require('node-fetch');
module.exports = async function (context, req) {
const op = req.body;
const credential = new DefaultAzureCredential();
const kv = new SecretClient(process.env.KEY_VAULT_URI, credential);
const crmKey = await kv.getSecret('CrmApiKey');
const resp = await fetch(`${process.env.CRM_URL}/enrich?id=${op.OperationId}`, {
headers: { 'x-api-key': crmKey.value }
});
const data = await resp.json();
return { body: { success: true, enrichment: data } };
};
API (.NET 8 Minimal Endpoint)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
var app = builder.Build();
app.MapGet("/operations/summary", async (TelemetryClient telemetry) => {
// Query Dataverse via service principal (pseudo-code)
telemetry.TrackEvent("OperationsSummaryRequested");
return Results.Ok(new { open = 42, overdue = 5, highPriority = 11 });
});
app.Run();
Prerequisites
Azure Resources
- Azure Subscription (Enterprise Agreement or Pay-As-You-Go)
- Resource Group:
rg-business-app-prod - Services: Storage Account, Key Vault, Application Insights, Service Bus (Premium if high throughput), Event Grid, Azure Functions, Azure API Management (optional), Azure Static Web Apps (optional for admin portal)
Power Platform
- Environment Strategy: Dev β Test β Prod (Managed solutions promoted)
- Appropriate licenses: Per-App or Per-User for PowerApps; Power Automate capacity; Dataverse database provisioned
Delegation Strategy:
// Fast filtering & paging
With({src: Operation},
FirstN(
SortByColumns(
Filter(src, Status <> "Closed" And DueDate >= Today()),
"DueDate", Ascending
),
200
)
)
SharePoint
- Modern SharePoint Communication Site for documentation & structured lists
- Site Collection Admin for initial configuration
Developer Tooling
- VS Code, Azure CLI, Power Platform CLI (PAC), GitHub CLI
- Node.js LTS & .NET 8 SDK
Part 1: SharePoint Foundation
### Provision Core Resources
```bash
az group create --name rg-business-app-prod --location eastus
az storage account create --name bizappstoreprod --resource-group rg-business-app-prod --sku Standard_LRS
az keyvault create --name kv-bizapp-prod --resource-group rg-business-app-prod --location eastus --enable-rbac-authorization true
az monitor app-insights component create --app bizapp-ai --location eastus --resource-group rg-business-app-prod --application-type web
az functionapp create --name func-bizapp-prod --resource-group rg-business-app-prod --consumption-plan-location eastus --runtime node --storage-account bizappstoreprod
az servicebus namespace create --name sb-bizapp-prod --resource-group rg-business-app-prod --location eastus --sku Premium
```
Step 1: Data Structure
Lists:
Operations(Columns: Title, Status Choice, Priority Choice, Owner Person, DueDate Date, DataverseRecordId GUID)Documents(Metadata: Category Choice, Sensitivity Choice, LinkedOperation Lookup)
JSON Column Formatting Example (Status badge):
{
"$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
"elmType": "div",
"attributes": {
"class": "=if(@currentField == 'Critical', 'sp-field-severity--severeWarning', if(@currentField=='High','sp-field-severity--warning','sp-field-severity--good'))"
},
"txtContent": "@currentField"
}
Step 2: Permissions & Governance
Break inheritance only for restricted document libraries (avoid widespread unique permissions)
Function: Operation Enrichment (Node.js)
// /OperationEnrich/index.js const { DefaultAzureCredential } = require('@azure/identity'); const { SecretClient } = require('@azure/keyvault-secrets'); const fetch = require('node-fetch'); module.exports = async function (context, req) { const op = req.body; const credential = new DefaultAzureCredential(); const kv = new SecretClient(process.env.KEY_VAULT_URI, credential); const crmKey = await kv.getSecret('CrmApiKey'); const resp = await fetch(`${process.env.CRM_URL}/enrich?id=${op.OperationId}`, { headers: { 'x-api-key': crmKey.value } }); const data = await resp.json(); return { body: { success: true, enrichment: data } }; };Use Azure AD groups mapped to SharePoint groups:
BusinessApp-Contributors,BusinessApp-Approvers,BusinessApp-ReadersEnable retention & sensitivity labeling via Purview classifications for
Documentslibrary
Part 2: Dataverse Domain Model
Tables:
Operation(OperationId GUID PK, Title, Status OptionSet, Owner Lookup(User), DueDate, Priority OptionSet, CreatedOn, ModifiedOn)AuditLog(EntryId GUID, Source Text, Actor Lookup(User), Action Text, Timestamp, CorrelationId Text)PaymentTransaction(TxnId GUID, OperationId Lookup(Operation), Amount Currency, Status OptionSet, GatewayRef Text)
Delegation Strategy:
// Fast filtering & paging
### API (.NET 8 Minimal Endpoint)
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
var app = builder.Build();
app.MapGet("/operations/summary", async (TelemetryClient telemetry) => {
// Query Dataverse via service principal (pseudo-code)
telemetry.TrackEvent("OperationsSummaryRequested");
return Results.Ok(new { open = 42, overdue = 5, highPriority = 11 });
});
app.Run();
```
With({src: Operation},
FirstN(
SortByColumns(
Filter(src, Status <> "Closed" And DueDate >= Today()),
"DueDate", Ascending
),
200
)
)
Part 3: Azure Backend Services
Provision Core Resources
az group create --name rg-business-app-prod --location eastus
az storage account create --name bizappstoreprod --resource-group rg-business-app-prod --sku Standard_LRS
az keyvault create --name kv-bizapp-prod --resource-group rg-business-app-prod --location eastus --enable-rbac-authorization true
az monitor app-insights component create --app bizapp-ai --location eastus --resource-group rg-business-app-prod --application-type web
az functionapp create --name func-bizapp-prod --resource-group rg-business-app-prod --consumption-plan-location eastus --runtime node --storage-account bizappstoreprod
az servicebus namespace create --name sb-bizapp-prod --resource-group rg-business-app-prod --location eastus --sku Premium
Function: Operation Enrichment (Node.js)
// /OperationEnrich/index.js
const { DefaultAzureCredential } = require('@azure/identity');
const { SecretClient } = require('@azure/keyvault-secrets');
const fetch = require('node-fetch');
module.exports = async function (context, req) {
const op = req.body;
const credential = new DefaultAzureCredential();
const kv = new SecretClient(process.env.KEY_VAULT_URI, credential);
const crmKey = await kv.getSecret('CrmApiKey');
const resp = await fetch(`${process.env.CRM_URL}/enrich?id=${op.OperationId}`, {
headers: { 'x-api-key': crmKey.value }
});
const data = await resp.json();
return { body: { success: true, enrichment: data } };
};
API (.NET 8 Minimal Endpoint)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
var app = builder.Build();
app.MapGet("/operations/summary", async (TelemetryClient telemetry) => {
// Query Dataverse via service principal (pseudo-code)
telemetry.TrackEvent("OperationsSummaryRequested");
return Results.Ok(new { open = 42, overdue = 5, highPriority = 11 });
});
app.Run();
Part 4: PowerApps Implementation
Screens
- Dashboard (KPIs, status filters)
- OperationDetail (Edit form, enrichment panel)
- Transactions (Payment tracking)
- Admin (Feature flags, environment diagnostics)
Component Library Patterns
// Component: StatusBadge - Input StatusText
Switch( StatusText,
"Critical", RGBA(204,0,0,1),
"High", RGBA(255,140,0,1),
RGBA(0,128,0,1)
)
Audit Logging Wrapper
Set(_corr, GUID());
Patch(AuditLog,
Defaults(AuditLog),
{ Source: "CanvasApp", Actor: User().Email, Action: "OperationUpdate", Timestamp: Now(), CorrelationId: _corr }
);
Patch(Operation, ThisItem, { ModifiedOn: Now() })
Part 5: Automation (Power Automate)
Flows:
- Operation Created β Enrichment Function call β Update Dataverse fields.
- Daily Overdue Digest β Email Teams adaptive card to Managers.
- Payment Status Poller β External gateway API β Update PaymentTransaction + raise Event Grid event.
Part 6: Security & Compliance
| Layer | Control | Implementation |
|---|---|---|
| Identity | Conditional Access | Entra ID policy enforcing MFA & compliant device |
| Secrets | Centralized vault | Key Vault with RBAC & rotation policies |
| Data | Least privilege | Service principals with scoped Dataverse security roles |
| App | Feature flags | Environment variables + admin screen toggles |
| Audit | Unified log | Dataverse AuditLog + App Insights custom events |
| Compliance | Retention | Purview retention labels on SharePoint libraries |
Part 7: Observability & Telemetry
Custom event pattern:
// Track user action
Collect(_Telemetry,
{ Event: "OpSave", User: User().Email, OperationId: varOpId, DurationMs: DateDiff(varStart, Now(), Milliseconds) }
);
Export via custom connector batched every 30 records.
App Insights Queries (Kusto):
customEvents
| where name == "OpSave"
| summarize avg(duration) by bin(timestamp, 1h)
Part 8: Performance Optimization
- Use
ShowColumns()to trim Dataverse payloads in galleries. - Staged loading (Concurrent for KPI + list data).
- Cache enrichment responses locally for session via global variables.
- Avoid non-delegable functions in gallery filter (no
IsBlank()nested combosβprefer explicit comparisons).
Part 9: Cost Management
- Service Bus Premium only if > 1000 msg/day; otherwise Standard.
- Consolidate Functions into logical apps to reduce cold starts & management overhead.
- Leverage Dataverse API concurrency budgeting: prefer batch operations.
- Monitor ingestion vs retention in App Insights; set daily cap & sampling.
Part 10: Deployment & ALM
GitHub Actions (Solution Export β Build β Import)
name: power-platform-alm
on:
workflow_dispatch:
push:
paths:
- powerplatform/solution/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: microsoft/powerplatform-actions/authenticate@v1
with:
tenant-id: ${{ secrets.TENANT_ID }}
app-id: ${{ secrets.CLIENT_ID }}
client-secret: ${{ secrets.CLIENT_SECRET }}
environment-url: ${{ secrets.PP_DEV_URL }}
- name: Export Unmanaged
uses: microsoft/powerplatform-actions/export-solution@v1
with:
solution-name: BusinessApp
solution-output-file: BusinessApp.zip
- name: Convert to Source
uses: microsoft/powerplatform-actions/convert-solution-to-source@v1
with:
solution-file: BusinessApp.zip
source-folder: powerplatform/solution
- name: Pack Managed
uses: microsoft/powerplatform-actions/pack-solution@v1
with:
solution-folder: powerplatform/solution
solution-file: BusinessApp_managed.zip
solution-type: Managed
- name: Publish Artifacts
uses: actions/upload-artifact@v4
with:
name: managed-solution
path: BusinessApp_managed.zip
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: managed-solution
- uses: microsoft/powerplatform-actions/authenticate@v1
with:
tenant-id: ${{ secrets.TENANT_ID }}
app-id: ${{ secrets.CLIENT_ID }}
client-secret: ${{ secrets.CLIENT_SECRET }}
environment-url: ${{ secrets.PP_PROD_URL }}
- name: Import Managed
uses: microsoft/powerplatform-actions/import-solution@v1
with:
solution-file: BusinessApp_managed.zip
- name: Publish Customizations
uses: microsoft/powerplatform-actions/publish-solution@v1
Environment Variables (Feature Flags)
pac env var set --name Feature_EnablePayments --value true --environment $PP_DEV_URL
Part 11: Testing & Quality
- Canvas App: Use Test Studio for critical flows (save operation, approve, enrich).
- API: xUnit integration tests with Testcontainers for local emulated DB.
- Functions: Durable Functions unit tests (if orchestrations added) using
@azure/functionstest harness.
Part 12: Resilience & Reliability
- Retry strategy: Exponential backoff for external CRM; circuit-breaker pattern in API.
- Queue-based load leveling for payment processing.
- Event Grid for change notifications to downstream analytics.
Part 13: Troubleshooting Matrix
| Symptom | Likely Cause | Diagnostic Action | Resolution | Preventative Measure |
|---|---|---|---|---|
| Slow gallery load | Non-delegable formulas | App Insights perf events | Refactor filters to delegable ops | Governance review of formula patterns |
| API 401 errors | Expired secret | Check Key Vault version history | Rotate secret & update CI/CD | Automate rotation policy |
| Function cold starts | Excessive app segmentation | Metrics: cold start count | Consolidate functions / use premium plan | Periodic architecture review |
| Payment sync failures | Gateway API throttling | Inspect response headers | Implement backoff + caching | Capacity planning & SLA monitoring |
| Missing audit entries | Patch wrapper bypassed | Query AuditLog delta | Enforce wrapper use (component) | Code review checklist |
Key Takeaways
- Unified architecture accelerates feature delivery while preserving governance.
- Layered security (identity, secrets, RBAC, data) minimizes breach blast radius.
- Telemetry-first design enables proactive optimization & SLA tracking.
- ALM automation reduces drift and deployment friction across environments.
Next Steps
- Add real-time notifications via SignalR + PowerApps component.
- Implement analytics workspace & KPI dashboards in Power BI.
- Introduce AI enrichment (Azure OpenAI + Dataverse plugin) for operation classification.
Additional Resources
- https://learn.microsoft.com/azure/architecture/
- https://learn.microsoft.com/power-apps/developer/data-platform/
- https://learn.microsoft.com/sharepoint/
- https://learn.microsoft.com/azure/key-vault/
- https://learn.microsoft.com/azure/azure-monitor/
What integration or governance challenge do you want to solve next? Share below!