Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Power Apps Samples
https://github.com/microsoft/powerapps-samples
Admin
This repository contains sample code for Power Apps, covering Dataverse, model-driven apps, canvas
...
Tokens:
181,454
Snippets:
2,263
Trust Score:
9.9
Update:
3 days ago
Context
Skills
Chat
Benchmark
48
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Power Apps Samples This repository is Microsoft's official collection of sample code for Power Apps development, covering the entire Power Platform ecosystem. It provides production-ready examples for building custom controls with the Power Apps Component Framework (PCF), integrating with Microsoft Dataverse through both the Organization Service SDK and Web API, automating platform administration with PowerShell, and leveraging AI Builder for machine learning scenarios. The samples span multiple technology stacks including TypeScript/React for frontend controls, C# for backend integrations (both legacy .NET Framework 4.7.2 and modern .NET 6.0+), and PowerShell for administrative automation. The repository is organized by technology area: `component-framework/` contains 29 PCF controls demonstrating UI patterns from simple increment buttons to complex data grids; `dataverse/` provides comprehensive SDK samples for CRUD operations, file handling, security, and metadata management; `ai-builder/` includes training datasets for prediction models; `portals/` shows OAuth token validation and Web API integration; and `powershell/` offers Data Loss Prevention (DLP) policy management scripts. Each sample is self-contained with its own build configuration, making it easy to extract and adapt individual components for production use. --- ## Power Apps Component Framework (PCF) Controls ### Standard Control Lifecycle PCF controls implement a standard lifecycle interface with init, updateView, getOutputs, and destroy methods. This pattern enables data binding between the control and Power Apps/Model-driven apps. ```typescript // component-framework/IncrementControl/IncrementControl/index.ts import { IInputs, IOutputs } from "./generated/ManifestTypes"; export class IncrementControl implements ComponentFramework.StandardControl<IInputs, IOutputs> { private _value: number; private _notifyOutputChanged: () => void; private label: HTMLInputElement; private button: HTMLButtonElement; private _container: HTMLDivElement; constructor() {} public init( context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement ): void { // Create UI elements this.label = document.createElement("input"); this.label.setAttribute("type", "label"); this.label.addEventListener("blur", this.onInputBlur.bind(this)); this.button = document.createElement("button"); this.button.innerHTML = context.resources.getString("IncrementControl_ButtonLabel"); this.button.classList.add("SimpleIncrement_Button_Style"); this._notifyOutputChanged = notifyOutputChanged; this.button.addEventListener("click", this.onButtonClick.bind(this)); this._container = document.createElement("div"); this._container.appendChild(this.label); this._container.appendChild(this.button); container.appendChild(this._container); } private onButtonClick(event: Event): void { this._value = this._value + 1; this._notifyOutputChanged(); // Notify framework of change } private onInputBlur(event: Event): void { const inputNumber = Number(this.label.value); this._value = isNaN(inputNumber) ? (this.label.value as unknown as number) : inputNumber; this._notifyOutputChanged(); } public updateView(context: ComponentFramework.Context<IInputs>): void { this._value = context.parameters.value.raw!; this.label.value = this._value != null ? this._value.toString() : ""; // Handle validation errors if (context.parameters.value.error) { this.label.classList.add("SimpleIncrement_Input_Error_Style"); } else { this.label.classList.remove("SimpleIncrement_Input_Error_Style"); } } public getOutputs(): IOutputs { return { value: this._value }; } public destroy(): void {} } ``` ### React-Based PCF Control React controls use the ReactControl interface and render React components in updateView. This example shows a Fluent UI choices picker with responsive form factor handling. ```typescript // component-framework/ChoicesPickerReactControl/ChoicesPickerReact/index.ts import { IInputs, IOutputs } from "./generated/ManifestTypes"; import * as React from "react"; import { initializeIcons } from "@fluentui/react"; import { ChoicesPickerComponent } from "./ChoicesPickerComponent"; initializeIcons(undefined, { disableWarnings: true }); const SmallFormFactorMaxWidth = 350; export class ChoicesPickerReact implements ComponentFramework.ReactControl<IInputs, IOutputs> { private notifyOutputChanged: () => void; private selectedValue: number | undefined; private context: ComponentFramework.Context<IInputs>; public init( context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary ): void { this.notifyOutputChanged = notifyOutputChanged; this.context = context; this.context.mode.trackContainerResize(true); // Enable responsive behavior } public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement { const { value, configuration } = context.parameters; let disabled = context.mode.isControlDisabled; let masked = false; if (value.security) { disabled = disabled || !value.security.editable; masked = !value.security.readable; } if (value?.attributes && configuration) { return React.createElement(ChoicesPickerComponent, { label: value.attributes.DisplayName, options: value.attributes.Options, configuration: configuration.raw, value: value.raw, onChange: this.onChange, disabled: disabled, masked: masked, formFactor: context.client.getFormFactor() == 3 || context.mode.allocatedWidth < SmallFormFactorMaxWidth ? "small" : "large", }); } return React.createElement("div"); } private onChange = (newValue: number | undefined): void => { this.selectedValue = newValue; this.notifyOutputChanged(); }; public getOutputs(): IOutputs { return { value: this.selectedValue } as IOutputs; } public destroy(): void {} } ``` ### DataSet Control for Grids DataSet controls work with collections of records, enabling custom grid experiences. They support paging, sorting, and record navigation. ```typescript // component-framework/DataSetGrid/DataSetGrid/index.ts import { IInputs, IOutputs } from "./generated/ManifestTypes"; import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi; type DataSet = ComponentFramework.PropertyTypes.DataSet; export class DataSetGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> { private contextObj: ComponentFramework.Context<IInputs>; private mainContainer: HTMLDivElement; private gridContainer: HTMLDivElement; private loadPageButton: HTMLButtonElement; public init( context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement ): void { context.mode.trackContainerResize(true); this.mainContainer = document.createElement("div"); this.gridContainer = document.createElement("div"); this.gridContainer.classList.add("DataSetControl_grid-container"); this.loadPageButton = document.createElement("button"); this.loadPageButton.innerText = context.resources.getString("PCF_DataSetControl_LoadMore_ButtonLabel"); this.loadPageButton.addEventListener("click", this.onLoadMoreButtonClick.bind(this)); this.mainContainer.appendChild(this.gridContainer); this.mainContainer.appendChild(this.loadPageButton); container.appendChild(this.mainContainer); } public updateView(context: ComponentFramework.Context<IInputs>): void { this.contextObj = context; this.toggleLoadMoreButtonWhenNeeded(context.parameters.dataSetGrid); if (!context.parameters.dataSetGrid.loading) { const columnsOnView = this.getSortedColumnsOnView(context); if (!columnsOnView || columnsOnView.length === 0) return; while (this.gridContainer.firstChild) { this.gridContainer.removeChild(this.gridContainer.firstChild); } this.gridContainer.appendChild( this.createGridBody(columnsOnView, context.parameters.dataSetGrid) ); } } private getSortedColumnsOnView(context: ComponentFramework.Context<IInputs>): DataSetInterfaces.Column[] { if (!context.parameters.dataSetGrid.columns) return []; const columns = context.parameters.dataSetGrid.columns .filter((col: DataSetInterfaces.Column) => col.order >= 0); columns.sort((a, b) => a.order - b.order); return columns; } private createGridBody(columnsOnView: DataSetInterfaces.Column[], gridParam: DataSet): HTMLDivElement { const gridBody = document.createElement("div"); for (const currentRecordId of gridParam.sortedRecordIds) { const gridRecord = document.createElement("div"); gridRecord.classList.add("DataSetControl_grid-item"); gridRecord.setAttribute("rowRecId", gridParam.records[currentRecordId].getRecordId()); gridRecord.addEventListener("click", this.onRowClick.bind(this)); columnsOnView.forEach((columnItem) => { const labelPara = document.createElement("p"); labelPara.textContent = `${columnItem.displayName}: `; const valuePara = document.createElement("p"); valuePara.textContent = gridParam.records[currentRecordId] .getFormattedValue(columnItem.name) || "-"; gridRecord.appendChild(labelPara); gridRecord.appendChild(valuePara); }); gridBody.appendChild(gridRecord); } return gridBody; } private onRowClick(event: Event): void { const rowRecordId = (event.currentTarget as HTMLTableRowElement).getAttribute("rowRecId"); if (rowRecordId) { const entityReference = this.contextObj.parameters.dataSetGrid.records[rowRecordId].getNamedReference(); void this.contextObj.navigation.openForm({ entityName: entityReference.name, entityId: entityReference.id.guid, }); } } private onLoadMoreButtonClick(): void { this.contextObj.parameters.dataSetGrid.paging.loadNextPage(); } private toggleLoadMoreButtonWhenNeeded(gridParam: DataSet): void { this.loadPageButton.style.display = gridParam.paging.hasNextPage ? "block" : "none"; } public getOutputs(): IOutputs { return {}; } public destroy(): void {} } ``` ### Web API Control for CRUD Operations This control demonstrates using the PCF WebAPI to perform Create, Retrieve, Update, and Delete operations directly from a custom control. ```typescript // component-framework/WebAPIControl/WebAPIControl/index.ts import { IInputs, IOutputs } from "./generated/ManifestTypes"; export class WebAPIControl implements ComponentFramework.StandardControl<IInputs, IOutputs> { private _context: ComponentFramework.Context<IInputs>; private _container: HTMLDivElement; private static _entityName = "account"; private static _requiredAttributeName = "name"; public init( context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement ): void { this._context = context; this._container = document.createElement("div"); container.appendChild(this._container); } // Create a new account record private createButtonOnClickHandler(event: Event): Promise<void> { const currencyValue = parseInt( (event.target as Element)?.attributes?.getNamedItem("buttonvalue")?.value ?? "0" ); const recordName = `Web API Sample_${Date.now()}`; const data: ComponentFramework.WebApi.Entity = { name: recordName, revenue: currencyValue }; return this._context.webAPI.createRecord("account", data).then( (response: ComponentFramework.LookupValue) => { console.log(`Created account with ID: ${response.id}`); }, (errorResponse) => { console.error(`Error: ${(errorResponse as { message: string }).message}`); } ); } // Delete a record using lookup dialog private deleteButtonOnClickHandler(): Promise<void> { const lookUpOptions = { entityTypes: ["account"] }; return this._context.utils.lookupObjects(lookUpOptions).then( (data: ComponentFramework.LookupValue[]) => { if (data?.[0]) { return this._context.webAPI.deleteRecord(data[0].entityType, data[0].id).then( (response) => console.log(`Deleted: ${response.id}`), (error) => console.error(error) ); } } ); } // Retrieve records using FetchXML aggregate query private calculateAverageButtonOnClickHandler(): Promise<void> { let fetchXML = "<fetch distinct='false' mapping='logical' aggregate='true'>"; fetchXML += "<entity name='account'>"; fetchXML += "<attribute name='revenue' aggregate='avg' alias='average_val' />"; fetchXML += "<filter><condition attribute='revenue' operator='not-null' /></filter>"; fetchXML += "</entity></fetch>"; return this._context.webAPI.retrieveMultipleRecords("account", `?fetchXml=${fetchXML}`).then( (response: ComponentFramework.WebApi.RetrieveMultipleResponse) => { const averageVal = response.entities[0].average_val as number; console.log(`Average revenue: ${averageVal}`); } ); } // Retrieve records using OData query private refreshRecordCountButtonOnClickHandler(): Promise<void> { const queryString = "?$select=revenue&$filter=contains(name,'Web API')"; return this._context.webAPI.retrieveMultipleRecords("account", queryString).then( (response: ComponentFramework.WebApi.RetrieveMultipleResponse) => { console.log(`Found ${response.entities.length} records`); } ); } public updateView(context: ComponentFramework.Context<IInputs>): void {} public getOutputs(): IOutputs { return {}; } public destroy(): void {} } ``` ### PCF Control Manifest The ControlManifest.Input.xml defines control metadata, properties, and resources. ```xml <!-- component-framework/IncrementControl/IncrementControl/ControlManifest.Input.xml --> <?xml version="1.0" encoding="utf-8" ?> <manifest> <control namespace="SampleNamespace" constructor="IncrementControl" version="1.1.0" display-name-key="IncrementControl_Display_Key" description-key="IncrementControl_Desc_Key" control-type="standard"> <!-- Define supported data types --> <type-group name="numbers"> <type>Whole.None</type> <type>Currency</type> <type>FP</type> <type>Decimal</type> </type-group> <!-- Bound property definition --> <property name="value" display-name-key="value_Display_Key" description-key="value_Desc_Key" of-type-group="numbers" usage="bound" required="true" /> <!-- Resources --> <resources> <code path="index.ts" order="1" /> <css path="css/IncrementControl.css" order="2" /> <resx path="strings/IncrementControl.1033.resx" version="1.0.0" /> </resources> </control> </manifest> ``` --- ## Dataverse SDK - ServiceClient (.NET 6.0+) ### WhoAmI Request The simplest Dataverse operation - retrieve information about the authenticated user. ```csharp // dataverse/orgsvc/CSharp-NETCore/ServiceClient/WhoAmI/Program.cs using Microsoft.Crm.Sdk.Messages; using Microsoft.Extensions.Configuration; using Microsoft.PowerPlatform.Dataverse.Client; class Program { IConfiguration Configuration { get; } Program() { string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS"); if (path == null) path = "appsettings.json"; Configuration = new ConfigurationBuilder() .AddJsonFile(path, optional: false, reloadOnChange: true) .Build(); } static void Main(string[] args) { Program app = new(); // Create ServiceClient using connection string ServiceClient serviceClient = new(app.Configuration.GetConnectionString("default")); // Execute WhoAmI request WhoAmIResponse resp = (WhoAmIResponse)serviceClient.Execute(new WhoAmIRequest()); Console.WriteLine("User ID is {0}.", resp.UserId); serviceClient.Dispose(); } } // appsettings.json // { // "ConnectionStrings": { // "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;UserName=you@yourorg.onmicrosoft.com;Password=yourPassword;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;LoginPrompt=Auto" // } // } ``` ### Create, Update, Retrieve, Delete Operations Complete CRUD operations using the ServiceClient. ```csharp // dataverse/orgsvc/CSharp-NETCore/ServiceClient/CreateUpdateDelete/Program.cs using Microsoft.Extensions.Configuration; using Microsoft.PowerPlatform.Dataverse.Client; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; class Program { IConfiguration Configuration { get; } Program() { string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS"); if (path == null) path = "appsettings.json"; Configuration = new ConfigurationBuilder() .AddJsonFile(path, optional: false, reloadOnChange: true) .Build(); } static void Main(string[] args) { Program app = new(); ServiceClient serviceClient = new(app.Configuration.GetConnectionString("default")); // CREATE - Create an in-memory account entity Entity account = new("account"); account["name"] = "Nightmare Coffee"; account.Id = serviceClient.Create(account); // Returns the new record's GUID // UPDATE - Modify properties and update account["name"] = "Fourth Coffee"; account["address2_postalcode"] = "98052"; serviceClient.Update(account); // RETRIEVE - Get specific columns Entity retrievedAccount = serviceClient.Retrieve( entityName: account.LogicalName, id: account.Id, columnSet: new ColumnSet("name", "address2_postalcode") ); Console.WriteLine("Name: {0}, Postal: {1}", retrievedAccount["name"], retrievedAccount["address2_postalcode"]); // DELETE - Remove the record serviceClient.Delete(account.LogicalName, account.Id); serviceClient.Dispose(); } } ``` --- ## Dataverse Web API (.NET 6.0+) ### Basic CRUD Operations with HttpClient Low-level HTTP operations using the Web API with JSON payloads. ```csharp // dataverse/webapi/CSharp-NETx/BasicOperations/Program.cs using Newtonsoft.Json.Linq; using PowerApps.Samples; using System.Text; namespace BasicOperations { internal class Program { static async Task Main() { Config config = App.InitializeApp(); var service = new Service(config); List<EntityReference> recordsToDelete = new(); // CREATE with HttpRequestMessage var contactData = new JObject { { "firstname", "Rafel" }, { "lastname", "Shillo" } }; HttpRequestMessage createRequest = new( method: HttpMethod.Post, requestUri: new Uri("contacts", UriKind.Relative)) { Content = new StringContent( content: contactData.ToString(), encoding: Encoding.UTF8, mediaType: "application/json") }; HttpResponseMessage createResponse = await service.SendAsync(createRequest); var contactUri = new Uri(createResponse.Headers.GetValues("OData-EntityId").FirstOrDefault()); var contactRef = new EntityReference(uri: contactUri.ToString()); recordsToDelete.Add(contactRef); // UPDATE with PATCH JObject updateData = new() { { "annualincome", 80000 }, { "jobtitle", "Junior Developer" } }; HttpRequestMessage updateRequest = new(HttpMethod.Patch, contactUri) { Content = new StringContent(updateData.ToString(), Encoding.UTF8, "application/json") }; updateRequest.Headers.Add("If-Match", "*"); // Ensure update, not upsert await service.SendAsync(updateRequest); // RETRIEVE with $select and annotations HttpRequestMessage retrieveRequest = new(HttpMethod.Get, contactUri + "?$select=fullname,annualincome,jobtitle"); retrieveRequest.Headers.Add("Prefer", "odata.include-annotations=\"*\""); HttpResponseMessage retrieveResponse = await service.SendAsync(retrieveRequest); JObject retrieved = JObject.Parse(await retrieveResponse.Content.ReadAsStringAsync()); Console.WriteLine($"Name: {retrieved["fullname"]}"); Console.WriteLine($"Income: {retrieved["annualincome@OData.Community.Display.V1.FormattedValue"]}"); // CREATE with association (account with primary contact) var accountData = new JObject { { "name", "Contoso Ltd" }, { "telephone1", "555-5555" }, { "primarycontactid@odata.bind", contactRef.Path } // Associate on create }; EntityReference accountRef = await service.Create("accounts", accountData); recordsToDelete.Add(accountRef); // RETRIEVE with $expand for related records JObject accountWithContact = await service.Retrieve( entityReference: accountRef, query: "?$select=name&$expand=primarycontactid($select=fullname,jobtitle)", includeAnnotations: true); Console.WriteLine($"Account: {accountWithContact["name"]}"); Console.WriteLine($"Contact: {accountWithContact["primarycontactid"]["fullname"]}"); // Deep INSERT - Account with contact and tasks in one operation JObject deepInsertData = new() { { "name", "Fourth Coffee" }, { "primarycontactid", new JObject() { { "firstname", "Susie" }, { "lastname", "Curtis" }, { "Contact_Tasks", new JArray() { new JObject { { "subject", "Sign invoice" } }, new JObject { { "subject", "Setup display" } } } } } } }; EntityReference deepRef = await service.Create("accounts", deepInsertData); recordsToDelete.Add(deepRef); // DELETE all created records foreach (var recordRef in recordsToDelete) { HttpRequestMessage deleteRequest = new(HttpMethod.Delete, new Uri(recordRef.Path, UriKind.Relative)); await service.SendAsync(deleteRequest); } } } } ``` ### Web API Configuration ```json // dataverse/webapi/CSharp-NETx/appsettings.json { "Url": "https://yourorg.api.crm.dynamics.com", "UserPrincipalName": "you@yourorg.onmicrosoft.com", "Password": "yourPassword", "Version": "9.2", "ClientId": "51f81489-12ee-4a9e-aaae-a2591f45987d", "RedirectUri": "app://58145B91-0C36-4500-8554-080854F2AC97", "Authority": "https://login.microsoftonline.com/organizations", "TimeoutInSeconds": 120, "MaxRetries": 3 } ``` --- ## PowerShell Administration - DLP Policy Management ### Create and Manage DLP Policies Data Loss Prevention policies control which connectors can be used together. ```powershell # powershell/admin-center/Microsoft.PowerApps.Administration.PowerShell.Samples.psm1 # Install the module first: # Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Force function AllEnvironmentsPolicyTests { param( [Parameter(Mandatory = $true)] [string]$EnvironmentDisplayName, [Parameter(Mandatory = $false)] [string]$PolicyDisplayName = "Test policy for AllEnvironments" ) # Create a new DLP policy for all environments $response = New-DlpPolicy -DisplayName $PolicyDisplayName -EnvironmentType "AllEnvironments" # Add connector groups (General, Confidential, Blocked) $response.connectorGroups = CreateConnectorGroups $response = Set-DlpPolicy -PolicyName $response.name -UpdatedPolicy $response Write-Host "Policy created: $($response.displayName)" } function CreateConnectorGroups { # Blocked connectors - cannot be used $blockedConnector = [pscustomobject]@{ id = "ef8e6e0e-472a-4477-bb74-9b491ab94c46" name = "Dummy Blocked Connector" type = "/providers/dummyConnector" } $blockedGroup = [pscustomobject]@{ classification = "Blocked" connectors = @($blockedConnector) } # Confidential connectors - business data only $confidentialConnector = [pscustomobject]@{ id = "Http" name = "HttpConnector" type = "Microsoft.PowerApps/apis" } $confidentialGroup = [pscustomobject]@{ classification = "Confidential" connectors = @($confidentialConnector) } # General connectors - non-business data $generalConnector = [pscustomobject]@{ id = "0204a6cf-f911-47dc-8006-ab8a46efdf85" name = "Dummy General Connector" type = "/providers/dummyConnector" } $generalGroup = [pscustomobject]@{ classification = "General" connectors = @($generalConnector) } return @($generalGroup, $confidentialGroup, $blockedGroup) } # Move connector between groups function Add-ConnectorToBusinessDataGroupSample { param( [Parameter(Mandatory = $true)] [string]$PolicyName, [Parameter(Mandatory = $true)] [string]$ConnectorName ) $policy = Get-DlpPolicy -PolicyName $PolicyName $confidentialGroup = $policy.connectorGroups | Where-Object { $_.classification -eq 'Confidential' } # Get connector details $connector = Get-PowerAppConnector -EnvironmentName $policy.environments[0].name -ConnectorName $ConnectorName | ForEach-Object { [pscustomobject]@{ id = $_.connectorId name = ($_.connectorId -split "/apis/")[1] type = $_.internal.type } } # Add to confidential group $confidentialGroup.connectors += $connector # Remove from general group if present $generalGroup = $policy.connectorGroups | Where-Object { $_.classification -eq 'General' } $generalGroup.connectors = $generalGroup.connectors | Where-Object { $_.id -ne $connector.id } # Update policy Set-DlpPolicy -PolicyName $policy.name -UpdatedPolicy $policy } # Enable/Disable Managed Environments function EnableManagedEnvironments { param( [Parameter(Mandatory = $true)] [string]$EnvironmentId ) $environment = Get-AdminPowerAppEnvironment -EnvironmentName $EnvironmentId if ($environment -eq $null) { Write-Host "Environment not found." return } $governanceConfig = $environment.Internal.properties.governanceConfiguration $governanceConfig.protectionLevel = "Standard" # Enable Managed Environment $response = Set-AdminPowerAppEnvironmentGovernanceConfiguration ` -EnvironmentName $EnvironmentId ` -UpdatedGovernanceConfiguration $governanceConfig if ($response.Code -eq 202) { Write-Host "Managed Environment enabled successfully." } } ``` --- ## AI Builder Sample Data ### Prediction Model Training Data AI Builder uses CSV datasets for training prediction models. The repository includes sample data for common scenarios. ```csv # ai-builder/aib_onlineshopperintention.csv # Predicts whether an online shopper will make a purchase # Columns: Administrative, Administrative_Duration, Informational, ProductRelated, # BounceRates, ExitRates, PageValues, SpecialDay, Month, OperatingSystems, # Browser, Region, TrafficType, VisitorType, Weekend, Revenue # ai-builder/aib_steelplatesfault.csv # Predicts surface defects in steel plates # Features: shape indicators, reflective properties # ai-builder/order.csv, customer.csv, product.csv # Brazilian e-commerce dataset for order prediction scenarios ``` ### Using AI Builder Sample Data ```markdown ## Import Process 1. Navigate to AI Builder in Power Apps 2. Create new Prediction model 3. During data import, use the raw URL of the CSV file: https://raw.githubusercontent.com/microsoft/PowerApps-Samples/master/ai-builder/aib_onlineshopperintention.csv 4. Map columns to prediction target and features 5. Train the model ## Available Datasets | Dataset | Prediction Target | Use Case | |---------|-------------------|----------| | Online Shopper Intention | Revenue (boolean) | E-commerce conversion prediction | | Steel Plates Fault | Fault type | Manufacturing quality control | | Brazilian E-Commerce | Order completion | Order fulfillment prediction | ``` --- ## Power Apps Portals - OAuth Token Validation ### External Web API with Portal OAuth Validate Portal-issued JWT tokens in external ASP.NET Web APIs. ```csharp // portals/ExternalWebApiConsumingPortalOAuthTokenSample // Web.config - Configure allowed portal URL // <add key="Microsoft.Dynamics.AllowedPortal" value="https://yourportal.powerappsportals.com"/> // Startup.cs - Configure token validation public void Configuration(IAppBuilder app) { app.Map("/api/external", externalApp => { externalApp.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions { Provider = new DynamicsPortalBearerAuthenticationProvider(), AccessTokenFormat = new JwtFormat( new TokenValidationParameters { ValidateAudience = true, // Set false if no ClientId used ValidAudience = "your-client-id", ValidateIssuer = true, ValidIssuer = "https://yourportal.powerappsportals.com/" } ) }); }); } // Controllers/ExternalWebApiController.cs [RoutePrefix("api/external")] public class ExternalWebApiController : ApiController { [HttpGet] [Route("WhoAmI")] [Authorize] public IHttpActionResult WhoAmI() { var identity = User.Identity as ClaimsIdentity; var userId = identity?.FindFirst(ClaimTypes.NameIdentifier)?.Value; return Ok(new { UserId = userId }); } } ``` ### Testing Portal OAuth ```bash # Fetch token from Portal using JavaScript, then call external API: curl -X GET "http://localhost:60717/api/external/WhoAmI" \ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJ..." # Response: # { "UserId": "12345678-1234-1234-1234-123456789012" } ``` --- ## Build Commands Reference ### PCF Control Build ```bash # Navigate to control directory cd component-framework/IncrementControl # Install dependencies npm install # Development build with watch npm start watch # Production build npm run build # Lint code npm run lint npm run lint:fix # Create solution package mkdir SolutionFolder && cd SolutionFolder pac solution init --publisher-name sample --publisher-prefix sample pac solution add-reference --path ../ msbuild /t:rebuild /p:Configuration=Release # Output: bin/Release/*.zip ``` ### Dataverse C# Build ```bash # .NET 6.0+ samples cd dataverse/orgsvc/CSharp-NETCore/ServiceClient/WhoAmI dotnet build dotnet run # .NET Framework samples (requires Visual Studio or MSBuild) cd dataverse/orgsvc/CSharp/SampleName nuget restore packages.config msbuild ``` ### PowerShell Setup ```powershell # Install Power Platform administration module Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Force # Authenticate Add-PowerAppsAccount -Endpoint "prod" -Username "admin@tenant.onmicrosoft.com" # Import sample functions Import-Module ./Microsoft.PowerApps.Administration.PowerShell.Samples.psm1 ``` --- ## Summary This repository serves as the definitive reference for Power Platform development, providing battle-tested patterns for every major integration scenario. The PCF samples demonstrate the full spectrum of control types - from simple value controls like IncrementControl to complex data-bound grids like DataSetGrid and ModelDrivenGridControl, with React-based examples showing modern Fluent UI integration. The Dataverse samples cover both the modern ServiceClient approach (recommended for new development) and the legacy SDK patterns, with comprehensive examples for CRUD operations, file/image handling, security configuration, and bulk data operations. For production deployments, developers should focus on the `.NET 6.0+` samples in `CSharp-NETCore` and `CSharp-NETx` directories as they follow current best practices including dependency injection, ILogger integration, and modern authentication patterns. The PowerShell samples provide essential automation capabilities for tenant administration, particularly DLP policy management and Managed Environment configuration. When building custom controls, start with the appropriate template (standard for simple controls, React for complex UI, DataSet for grids) and follow the manifest-driven property binding pattern demonstrated across all samples.