init commit

This commit is contained in:
iFlip721
2025-04-16 09:55:57 -04:00
commit a03209dc9e
935 changed files with 177492 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
{
"env": {
"browser": true,
"es2020": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:promise/recommended"
],
"globals": {
"ComponentFramework": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": [
"@microsoft/power-apps",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-unused-vars": "off"
}
}

23
bin/pac/tools/templates/pcf/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# generated directory
**/generated
# output directory
/out
# msbuild output directories
/bin
/obj
# MSBuild Binary and Structured Log
*.binlog
# Visual Studio cache/options directory
/.vs
# macos
.DS_Store

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="$namespaceplaceholder$" constructor="$controlnameplaceholder$" version="0.0.1" display-name-key="$controlnameplaceholder$" description-key="$controlnameplaceholder$ description" control-type="standard" design-mapping-file="design-maps/map.Fluent.xml">
<!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
Example1:
<external-service-usage enabled="true">
<domain>www.Microsoft.com</domain>
</external-service-usage>
Example2:
<external-service-usage enabled="false">
</external-service-usage>
-->
<external-service-usage enabled="false">
<!--UNCOMMENT TO ADD EXTERNAL DOMAINS
<domain></domain>
<domain></domain>
-->
</external-service-usage>
<!-- dataset node represents a set of entity records on CDS; allow more than one datasets -->
<data-set name="sampleDataSet" display-name-key="Dataset_Display_Key">
<!-- 'property-set' node represents a unique, configurable property that each record in the dataset must provide. -->
<!-- UNCOMMENT TO ADD PROPERTY-SET NODE
<property-set name="samplePropertySet" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
-->
</data-set>
<resources>
<code path="index.ts" order="1"/>
<!-- UNCOMMENT TO ADD MORE RESOURCES
<css path="css/$controlnameplaceholder$.css" order="1" />
<resx path="strings/$controlnameplaceholder$.1033.resx" version="1.0.0" />
-->
</resources>
<!-- UNCOMMENT TO ENABLE THE SPECIFIED API
<feature-usage>
<uses-feature name="Device.captureAudio" required="true" />
<uses-feature name="Device.captureImage" required="true" />
<uses-feature name="Device.captureVideo" required="true" />
<uses-feature name="Device.getBarcodeValue" required="true" />
<uses-feature name="Device.getCurrentPosition" required="true" />
<uses-feature name="Device.pickFile" required="true" />
<uses-feature name="Utility" required="true" />
<uses-feature name="WebAPI" required="true" />
</feature-usage>
-->
</control>
</manifest>

View File

@@ -0,0 +1,55 @@
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
export class $controlnameplaceholder$ implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor()
{
}
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @param container If a control is marked control-type='standard', it will receive an empty div element within which it can render its content.
*/
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement): void
{
// Add control initialization code
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
*/
public updateView(context: ComponentFramework.Context<IInputs>): void
{
// Add code to update control view
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output"
*/
public getOutputs(): IOutputs
{
return {};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void
{
// Add code to cleanup control if necessary
}
}

View File

@@ -0,0 +1,51 @@
/* eslint-disable no-undef */
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { IDesign, DesignContext, DesignComponent } from "./generated/ManifestDesignTypes";
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
export class $controlnameplaceholder$ implements DesignComponent<IInputs, IOutputs, IDesign> {
/**
* Empty constructor.
*/
constructor() {
}
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @param container If a control is marked control-type='standard', it will receive an empty div element within which it can render its content.
*/
public init(context: DesignContext<IInputs, IDesign>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {
// Add control initialization code
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
*/
public updateView(context: DesignContext<IInputs, IDesign>): void {
// Add code to update control view
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
*/
public getOutputs(): IOutputs {
return {};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void {
// Add code to cleanup control if necessary
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<design-map language="Fluent">
<ColorsPalettePrimary01>Colors.Palette.Primary01</ColorsPalettePrimary01>
<ColorsPalettePrimary02>Colors.Palette.Primary02</ColorsPalettePrimary02>
<ColorsPalettePrimary03>Colors.Palette.Primary03</ColorsPalettePrimary03>
<ColorsPalettePrimary04>Colors.Palette.Primary04</ColorsPalettePrimary04>
<ColorsPalettePrimary05>Colors.Palette.Primary05</ColorsPalettePrimary05>
<ColorsPalettePrimary06>Colors.Palette.Primary06</ColorsPalettePrimary06>
</design-map>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="$namespaceplaceholder$" constructor="$controlnameplaceholder$" version="0.0.1" display-name-key="$controlnameplaceholder$" description-key="$controlnameplaceholder$ description" control-type="standard" design-mapping-file="design-maps/map.Fluent.xml">
<!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
Example1:
<external-service-usage enabled="true">
<domain>www.Microsoft.com</domain>
</external-service-usage>
Example2:
<external-service-usage enabled="false">
</external-service-usage>
-->
<external-service-usage enabled="false">
<!--UNCOMMENT TO ADD EXTERNAL DOMAINS
<domain></domain>
<domain></domain>
-->
</external-service-usage>
<!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
<!--
Property node's of-type attribute can be of-type-group attribute.
Example:
<type-group name="numbers">
<type>Whole.None</type>
<type>Currency</type>
<type>FP</type>
<type>Decimal</type>
</type-group>
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
-->
<resources>
<code path="index.ts" order="1"/>
<!-- UNCOMMENT TO ADD MORE RESOURCES
<css path="css/$controlnameplaceholder$.css" order="1" />
<resx path="strings/$controlnameplaceholder$.1033.resx" version="1.0.0" />
-->
</resources>
<!-- UNCOMMENT TO ENABLE THE SPECIFIED API
<feature-usage>
<uses-feature name="Device.captureAudio" required="true" />
<uses-feature name="Device.captureImage" required="true" />
<uses-feature name="Device.captureVideo" required="true" />
<uses-feature name="Device.getBarcodeValue" required="true" />
<uses-feature name="Device.getCurrentPosition" required="true" />
<uses-feature name="Device.pickFile" required="true" />
<uses-feature name="Utility" required="true" />
<uses-feature name="WebAPI" required="true" />
</feature-usage>
-->
</control>
</manifest>

View File

@@ -0,0 +1,53 @@
import { IInputs, IOutputs } from "./generated/ManifestTypes";
export class $controlnameplaceholder$ implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor()
{
}
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @param container If a control is marked control-type='standard', it will receive an empty div element within which it can render its content.
*/
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement): void
{
// Add control initialization code
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
*/
public updateView(context: ComponentFramework.Context<IInputs>): void
{
// Add code to update control view
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output"
*/
public getOutputs(): IOutputs
{
return {};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void
{
// Add code to cleanup control if necessary
}
}

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
import { Label } from '@fluentui/react-components';
export interface IHelloWorldProps {
name?: string;
}
export class HelloWorld extends React.Component<IHelloWorldProps> {
public render(): React.ReactNode {
return (
<Label>
Hello {this.props.name}!
</Label>
)
}
}

View File

@@ -0,0 +1,32 @@
{
"env": {
"browser": true,
"es2020": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:promise/recommended"
],
"globals": {
"ComponentFramework": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": [
"@microsoft/power-apps",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-unused-vars": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@@ -0,0 +1,33 @@
{
"name": "pcf-project",
"version": "1.0.0",
"description": "Project containing your PowerApps Component Framework (PCF) control.",
"scripts": {
"build": "pcf-scripts build",
"clean": "pcf-scripts clean",
"lint": "pcf-scripts lint",
"lint:fix": "pcf-scripts lint fix",
"rebuild": "pcf-scripts rebuild",
"start": "pcf-scripts start",
"start:watch": "pcf-scripts start watch",
"refreshTypes": "pcf-scripts refreshTypes"
},
"dependencies": {
"react": "^16.14.0"
},
"devDependencies": {
"@microsoft/eslint-plugin-power-apps": "^0.2.51",
"@types/powerapps-component-framework": "^1.3.15",
"@types/react": "^16.14.60",
"@types/react-dom": "^16.9.24",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-react": "^7.37.1",
"pcf-scripts": "^1",
"pcf-start": "^1",
"react": "^16.14.0",
"typescript": "^4.9.5"
}
}

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="$namespaceplaceholder$" constructor="$controlnameplaceholder$" version="0.0.1" display-name-key="$controlnameplaceholder$" description-key="$controlnameplaceholder$ description" control-type="virtual" design-mapping-file="design-maps/map.Fluent.xml">
<!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
Example1:
<external-service-usage enabled="true">
<domain>www.Microsoft.com</domain>
</external-service-usage>
Example2:
<external-service-usage enabled="false">
</external-service-usage>
-->
<external-service-usage enabled="false">
<!--UNCOMMENT TO ADD EXTERNAL DOMAINS
<domain></domain>
<domain></domain>
-->
</external-service-usage>
<!-- dataset node represents a set of entity records on CDS; allow more than one datasets -->
<data-set name="sampleDataSet" display-name-key="Dataset_Display_Key">
<!-- 'property-set' node represents a unique, configurable property that each record in the dataset must provide. -->
<!-- UNCOMMENT TO ADD PROPERTY-SET NODE
<property-set name="samplePropertySet" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
-->
</data-set>
<resources>
<code path="index.ts" order="1"/>
<platform-library name="React" version="$pcfReactVersion$" />
<platform-library name="Fluent" version="$pcfFluentVersion$" />
<!-- UNCOMMENT TO ADD MORE RESOURCES
<css path="css/$controlnameplaceholder$.css" order="1" />
<resx path="strings/$controlnameplaceholder$.1033.resx" version="1.0.0" />
-->
</resources>
<!-- UNCOMMENT TO ENABLE THE SPECIFIED API
<feature-usage>
<uses-feature name="Device.captureAudio" required="true" />
<uses-feature name="Device.captureImage" required="true" />
<uses-feature name="Device.captureVideo" required="true" />
<uses-feature name="Device.getBarcodeValue" required="true" />
<uses-feature name="Device.getCurrentPosition" required="true" />
<uses-feature name="Device.pickFile" required="true" />
<uses-feature name="Utility" required="true" />
<uses-feature name="WebAPI" required="true" />
</feature-usage>
-->
</control>
</manifest>

View File

@@ -0,0 +1,58 @@
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { HelloWorld, IHelloWorldProps } from "./HelloWorld";
import * as React from "react";
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
export class $controlnameplaceholder$ implements ComponentFramework.ReactControl<IInputs, IOutputs> {
private theComponent: ComponentFramework.ReactControl<IInputs, IOutputs>;
private notifyOutputChanged: () => void;
/**
* Empty constructor.
*/
constructor() { }
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
*/
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary
): void {
this.notifyOutputChanged = notifyOutputChanged;
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
* @returns ReactElement root react element for the control
*/
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
const props: IHelloWorldProps = { name: 'Power Apps' };
return React.createElement(
HelloWorld, props
);
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output"
*/
public getOutputs(): IOutputs {
return {};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void {
// Add code to cleanup control if necessary
}
}

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
import { Label } from '@fluentui/react-components';
export interface IHelloWorldProps {
name?: string;
}
export class HelloWorld extends React.Component<IHelloWorldProps> {
public render(): React.ReactNode {
return (
<Label>
Hello {this.props.name}!
</Label>
)
}
}

View File

@@ -0,0 +1,32 @@
{
"env": {
"browser": true,
"es2020": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:promise/recommended"
],
"globals": {
"ComponentFramework": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": [
"@microsoft/power-apps",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-unused-vars": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@@ -0,0 +1,33 @@
{
"name": "pcf-project",
"version": "1.0.0",
"description": "Project containing your PowerApps Component Framework (PCF) control.",
"scripts": {
"build": "pcf-scripts build",
"clean": "pcf-scripts clean",
"lint": "pcf-scripts lint",
"lint:fix": "pcf-scripts lint fix",
"rebuild": "pcf-scripts rebuild",
"start": "pcf-scripts start",
"start:watch": "pcf-scripts start watch",
"refreshTypes": "pcf-scripts refreshTypes"
},
"dependencies": {
"react": "^16.14.0"
},
"devDependencies": {
"@microsoft/eslint-plugin-power-apps": "^0.2.51",
"@types/powerapps-component-framework": "^1.3.15",
"@types/react": "^16.14.60",
"@types/react-dom": "^16.9.24",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-react": "^7.37.1",
"pcf-scripts": "^1",
"pcf-start": "^1",
"react": "^16.14.0",
"typescript": "^4.9.5"
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="$namespaceplaceholder$" constructor="$controlnameplaceholder$" version="0.0.1" display-name-key="$controlnameplaceholder$" description-key="$controlnameplaceholder$ description" control-type="virtual" design-mapping-file="design-maps/map.Fluent.xml">
<!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
Example1:
<external-service-usage enabled="true">
<domain>www.Microsoft.com</domain>
</external-service-usage>
Example2:
<external-service-usage enabled="false">
</external-service-usage>
-->
<external-service-usage enabled="false">
<!--UNCOMMENT TO ADD EXTERNAL DOMAINS
<domain></domain>
<domain></domain>
-->
</external-service-usage>
<!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
<!--
Property node's of-type attribute can be of-type-group attribute.
Example:
<type-group name="numbers">
<type>Whole.None</type>
<type>Currency</type>
<type>FP</type>
<type>Decimal</type>
</type-group>
<property name="sampleProperty" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
-->
<resources>
<code path="index.ts" order="1"/>
<platform-library name="React" version="$pcfReactVersion$" />
<platform-library name="Fluent" version="$pcfFluentVersion$" />
<!-- UNCOMMENT TO ADD MORE RESOURCES
<css path="css/$controlnameplaceholder$.css" order="1" />
<resx path="strings/$controlnameplaceholder$.1033.resx" version="1.0.0" />
-->
</resources>
<!-- UNCOMMENT TO ENABLE THE SPECIFIED API
<feature-usage>
<uses-feature name="Device.captureAudio" required="true" />
<uses-feature name="Device.captureImage" required="true" />
<uses-feature name="Device.captureVideo" required="true" />
<uses-feature name="Device.getBarcodeValue" required="true" />
<uses-feature name="Device.getCurrentPosition" required="true" />
<uses-feature name="Device.pickFile" required="true" />
<uses-feature name="Utility" required="true" />
<uses-feature name="WebAPI" required="true" />
</feature-usage>
-->
</control>
</manifest>

View File

@@ -0,0 +1,56 @@
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { HelloWorld, IHelloWorldProps } from "./HelloWorld";
import * as React from "react";
export class $controlnameplaceholder$ implements ComponentFramework.ReactControl<IInputs, IOutputs> {
private theComponent: ComponentFramework.ReactControl<IInputs, IOutputs>;
private notifyOutputChanged: () => void;
/**
* Empty constructor.
*/
constructor() { }
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
*/
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary
): void {
this.notifyOutputChanged = notifyOutputChanged;
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
* @returns ReactElement root react element for the control
*/
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
const props: IHelloWorldProps = { name: 'Power Apps' };
return React.createElement(
HelloWorld, props
);
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output"
*/
public getOutputs(): IOutputs {
return { };
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void {
// Add code to cleanup control if necessary
}
}

View File

@@ -0,0 +1,29 @@
{
"name": "pcf-project",
"version": "1.0.0",
"description": "Project containing your PowerApps Component Framework (PCF) control.",
"scripts": {
"build": "pcf-scripts build",
"clean": "pcf-scripts clean",
"lint": "pcf-scripts lint",
"lint:fix": "pcf-scripts lint fix",
"rebuild": "pcf-scripts rebuild",
"start": "pcf-scripts start",
"start:watch": "pcf-scripts start watch",
"refreshTypes": "pcf-scripts refreshTypes"
},
"dependencies": {
},
"devDependencies": {
"@microsoft/eslint-plugin-power-apps": "^0.2.51",
"@types/node": "^18.19.54",
"@types/powerapps-component-framework": "^1.3.15",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
"eslint-plugin-promise": "^7.1.0",
"pcf-scripts": "^1",
"pcf-start": "^1",
"typescript": "^4.9.5"
}
}

View File

@@ -0,0 +1,3 @@
{
"outDir": "./out/controls"
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props')" />
<PropertyGroup>
<Name>$pcfProjectName$</Name>
<ProjectGuid>$pcfProjectGuid$</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<!--Remove TargetFramework when this is available in 16.1-->
<TargetFramework>net462</TargetFramework>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.PowerApps.MSBuild.Pcf" Version="1.*" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.gitignore" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\bin\**" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\obj\**" />
<ExcludeDirectories Include="$(OutputPath)\**" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj.user" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.sln" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\node_modules\**" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\**" Exclude="@(ExcludeDirectories)" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets')" />
</Project>

View File

@@ -0,0 +1,6 @@
{
"extends": "./node_modules/pcf-scripts/tsconfig_base.json",
"compilerOptions": {
"typeRoots": ["node_modules/@types"]
}
}

View File

@@ -0,0 +1,11 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# msbuild output directories
/bin
/obj
# vs intermediate directory
/.vs
# MSBuild Binary and Structured Log
*.binlog

View File

@@ -0,0 +1,30 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/$safeprojectname$.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/$safeprojectname$.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -0,0 +1,44 @@
using Microsoft.Xrm.Sdk;
using System;
namespace $safeprojectname$
{
/*
* Plugin development guide: https://docs.microsoft.com/powerapps/developer/common-data-service/plug-ins
* Best practices and guidance: https://docs.microsoft.com/powerapps/developer/common-data-service/best-practices/business-logic/
*/
public class Plugin1 : PluginBase
{
public Plugin1(string unsecureConfiguration, string secureConfiguration)
: base(typeof(Plugin1))
{
// TODO: Implement your custom configuration handling
// https://docs.microsoft.com/powerapps/developer/common-data-service/register-plug-in#set-configuration-data
}
// Entry point for custom business logic execution
protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
if (localPluginContext == null)
{
throw new ArgumentNullException(nameof(localPluginContext));
}
var context = localPluginContext.PluginExecutionContext;
// TODO: Implement your custom business logic
// Check for the entity on which the plugin would be registered
//if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
//{
// var entity = (Entity)context.InputParameters["Target"];
// // Check for entity name on which this plugin would be registered
// if (entity.LogicalName == "account")
// {
// }
//}
}
}
}

View File

@@ -0,0 +1,282 @@
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.PluginTelemetry;
using System;
using System.ServiceModel;
namespace $safeprojectname$
{
/// <summary>
/// Base class for all plug-in classes.
/// Plugin development guide: https://docs.microsoft.com/powerapps/developer/common-data-service/plug-ins
/// Best practices and guidance: https://docs.microsoft.com/powerapps/developer/common-data-service/best-practices/business-logic/
/// </summary>
public abstract class PluginBase : IPlugin
{
protected string PluginClassName { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PluginBase"/> class.
/// </summary>
/// <param name="pluginClassName">The <see cref="Type"/> of the plugin class.</param>
internal PluginBase(Type pluginClassName)
{
PluginClassName = pluginClassName.ToString();
}
/// <summary>
/// Main entry point for he business logic that the plug-in is to execute.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <remarks>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Execute")]
public void Execute(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new InvalidPluginExecutionException("serviceProvider");
}
// Construct the local plug-in context.
var localPluginContext = new LocalPluginContext(serviceProvider);
localPluginContext.Trace($"Entered {PluginClassName}.Execute() " +
$"Correlation Id: {localPluginContext.PluginExecutionContext.CorrelationId}, " +
$"Initiating User: {localPluginContext.PluginExecutionContext.InitiatingUserId}");
try
{
// Invoke the custom implementation
ExecuteDataversePlugin(localPluginContext);
// Now exit - if the derived plugin has incorrectly registered overlapping event registrations, guard against multiple executions.
return;
}
catch (FaultException<OrganizationServiceFault> orgServiceFault)
{
localPluginContext.Trace($"Exception: {orgServiceFault.ToString()}");
throw new InvalidPluginExecutionException($"OrganizationServiceFault: {orgServiceFault.Message}", orgServiceFault);
}
finally
{
localPluginContext.Trace($"Exiting {PluginClassName}.Execute()");
}
}
/// <summary>
/// Placeholder for a custom plug-in implementation.
/// </summary>
/// <param name="localPluginContext">Context for the current plug-in.</param>
protected virtual void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
// Do nothing.
}
}
/// <summary>
/// This interface provides an abstraction on top of IServiceProvider for commonly used PowerPlatform Dataverse Plugin development constructs
/// </summary>
public interface ILocalPluginContext
{
/// <summary>
/// The PowerPlatform Dataverse organization service for the Current Executing user.
/// </summary>
IOrganizationService InitiatingUserService { get; }
/// <summary>
/// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService.
/// </summary>
IOrganizationService PluginUserService { get; }
/// <summary>
/// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information.
/// </summary>
IPluginExecutionContext PluginExecutionContext { get; }
/// <summary>
/// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus. <br/>
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus.
/// </summary>
IServiceEndpointNotificationService NotificationService { get; }
/// <summary>
/// Provides logging run-time trace information for plug-ins.
/// </summary>
ITracingService TracingService { get; }
/// <summary>
/// General Service Provider for things not accounted for in the base class.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary>
/// OrganizationService Factory for creating connection for other then current user and system.
/// </summary>
IOrganizationServiceFactory OrgSvcFactory { get; }
/// <summary>
/// ILogger for this plugin.
/// </summary>
ILogger Logger { get; }
/// <summary>
/// Writes a trace message to the CRM trace log.
/// </summary>
/// <param name="message">Message name to trace.</param>
void Trace(string message, [CallerMemberName] string method = null);
}
/// <summary>
/// Plug-in context object.
/// </summary>
public class LocalPluginContext : ILocalPluginContext
{
/// <summary>
/// The PowerPlatform Dataverse organization service for current user account.
/// </summary>
public IOrganizationService InitiatingUserService { get; }
/// <summary>
/// The PowerPlatform Dataverse organization service for system user account.
/// </summary>
public IOrganizationService PluginUserService { get; }
/// <summary>
/// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information.
/// </summary>
public IPluginExecutionContext PluginExecutionContext { get; }
/// <summary>
/// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus. <br/>
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus.
/// </summary>
public IServiceEndpointNotificationService NotificationService { get; }
/// <summary>
/// Provides logging run-time trace information for plug-ins.
/// </summary>
public ITracingService TracingService { get; }
/// <summary>
/// General Service Provider for things not accounted for in the base class.
/// </summary>
public IServiceProvider ServiceProvider { get; }
/// <summary>
/// OrganizationService Factory for creating connection for other then current user and system.
/// </summary>
public IOrganizationServiceFactory OrgSvcFactory { get; }
/// <summary>
/// ILogger for this plugin.
/// </summary>
public ILogger Logger { get; }
/// <summary>
/// Helper object that stores the services available in this plug-in.
/// </summary>
/// <param name="serviceProvider"></param>
public LocalPluginContext(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new InvalidPluginExecutionException("serviceProvider");
}
ServiceProvider = serviceProvider;
Logger = serviceProvider.Get<ILogger>();
PluginExecutionContext = serviceProvider.Get<IPluginExecutionContext>();
TracingService = new LocalTracingService(serviceProvider);
NotificationService = serviceProvider.Get<IServiceEndpointNotificationService>();
OrgSvcFactory = serviceProvider.Get<IOrganizationServiceFactory>();
PluginUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.UserId); // User that the plugin is registered to run as, Could be same as current user.
InitiatingUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.InitiatingUserId); //User who's action called the plugin.
}
/// <summary>
/// Writes a trace message to the CRM trace log.
/// </summary>
/// <param name="message">Message name to trace.</param>
public void Trace(string message)
{
if (string.IsNullOrWhiteSpace(message) || TracingService == null)
{
return;
}
if (PluginExecutionContext == null)
{
TracingService.Trace(message);
}
else
{
TracingService.Trace(string.format(
"{0}, Correlation Id: {1}, Initiating User: {2}",
message,
PluginExecutionContext.CorrelationId,
PluginExecutionContext.InitiatingUserId));
}
}
}
/// <summary>
/// Specialized ITracingService implementation that prefixes all traced messages with a time delta for Plugin performance diagnostics
/// </summary>
public class LocalTracingService : ITracingService
{
private readonly ITracingService _tracingService;
private DateTime _previousTraceTime;
public LocalTracingService(IServiceProvider serviceProvider)
{
DateTime utcNow = DateTime.UtcNow;
var context = (IExecutionContext)serviceProvider.GetService(typeof(IExecutionContext));
DateTime initialTimestamp = context.OperationCreatedOn;
if (initialTimestamp > utcNow)
{
initialTimestamp = utcNow;
}
_tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
_previousTraceTime = initialTimestamp;
}
public void Trace(string message, params object[] args)
{
var utcNow = DateTime.UtcNow;
// The duration since the last trace.
var deltaMilliseconds = utcNow.Subtract(_previousTraceTime).TotalMilliseconds;
try
{
if (args == null || args.Length == 0)
_tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {message}");
else
_tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {string.Format(message, args)}");
}
catch (FormatException ex)
{
throw new InvalidPluginExecutionException($"Failed to write trace message due to error {ex.Message}", ex);
}
_previousTraceTime = utcNow;
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>$SignAssemblyBlock$
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<ProjectTypeGuids>{4C25E9B5-9FA6-436c-8E19-B395D2A65FAF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.props')" />
<ItemGroup>
<PackageReference Include="Microsoft.CrmSdk.CoreAssemblies" Version="9.0.2.*" />
<PackageReference Include="Microsoft.PowerApps.MSBuild.Plugin" Version="1.*" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.targets')" />
</Project>

View File

@@ -0,0 +1,11 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# msbuild output directories
/bin
/obj
# vs intermediate directory
/.vs
# MSBuild Binary and Structured Log
*.binlog

View File

@@ -0,0 +1,30 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/$safeprojectname$.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/$safeprojectname$.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -0,0 +1,44 @@
using Microsoft.Xrm.Sdk;
using System;
namespace $safeprojectname$
{
/// <summary>
/// Plugin development guide: https://docs.microsoft.com/powerapps/developer/common-data-service/plug-ins
/// Best practices and guidance: https://docs.microsoft.com/powerapps/developer/common-data-service/best-practices/business-logic/
/// </summary>
public class Plugin1 : PluginBase
{
public Plugin1(string unsecureConfiguration, string secureConfiguration)
: base(typeof(Plugin1))
{
// TODO: Implement your custom configuration handling
// https://docs.microsoft.com/powerapps/developer/common-data-service/register-plug-in#set-configuration-data
}
// Entry point for custom business logic execution
protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
if (localPluginContext == null)
{
throw new ArgumentNullException(nameof(localPluginContext));
}
var context = localPluginContext.PluginExecutionContext;
// TODO: Implement your custom business logic
// Check for the entity on which the plugin would be registered
//if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
//{
// var entity = (Entity)context.InputParameters["Target"];
// // Check for entity name on which this plugin would be registered
// if (entity.LogicalName == "account")
// {
// }
//}
}
}
}

View File

@@ -0,0 +1,274 @@
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.PluginTelemetry;
using System;
using System.Runtime.CompilerServices;
using System.ServiceModel;
namespace $safeprojectname$
{
/// <summary>
/// Base class for all plug-in classes.
/// Plugin development guide: https://docs.microsoft.com/powerapps/developer/common-data-service/plug-ins
/// Best practices and guidance: https://docs.microsoft.com/powerapps/developer/common-data-service/best-practices/business-logic/
/// </summary>
public abstract class PluginBase : IPlugin
{
protected string PluginClassName { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PluginBase"/> class.
/// </summary>
/// <param name="pluginClassName">The <see cref="Type"/> of the plugin class.</param>
internal PluginBase(Type pluginClassName)
{
PluginClassName = pluginClassName.ToString();
}
/// <summary>
/// Main entry point for he business logic that the plug-in is to execute.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <remarks>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Execute")]
public void Execute(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new InvalidPluginExecutionException(nameof(serviceProvider));
}
// Construct the local plug-in context.
var localPluginContext = new LocalPluginContext(serviceProvider);
localPluginContext.Trace($"Entered {PluginClassName}.Execute() " +
$"Correlation Id: {localPluginContext.PluginExecutionContext.CorrelationId}, " +
$"Initiating User: {localPluginContext.PluginExecutionContext.InitiatingUserId}");
try
{
// Invoke the custom implementation
ExecuteDataversePlugin(localPluginContext);
// Now exit - if the derived plugin has incorrectly registered overlapping event registrations, guard against multiple executions.
return;
}
catch (FaultException<OrganizationServiceFault> orgServiceFault)
{
localPluginContext.Trace($"Exception: {orgServiceFault.ToString()}");
throw new InvalidPluginExecutionException($"OrganizationServiceFault: {orgServiceFault.Message}", orgServiceFault);
}
finally
{
localPluginContext.Trace($"Exiting {PluginClassName}.Execute()");
}
}
/// <summary>
/// Placeholder for a custom plug-in implementation.
/// </summary>
/// <param name="localPluginContext">Context for the current plug-in.</param>
protected virtual void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
// Do nothing.
}
}
/// <summary>
/// This interface provides an abstraction on top of IServiceProvider for commonly used PowerPlatform Dataverse Plugin development constructs
/// </summary>
public interface ILocalPluginContext
{
/// <summary>
/// The PowerPlatform Dataverse organization service for the Current Executing user.
/// </summary>
IOrganizationService InitiatingUserService { get; }
/// <summary>
/// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService.
/// </summary>
IOrganizationService PluginUserService { get; }
/// <summary>
/// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information.
/// </summary>
IPluginExecutionContext PluginExecutionContext { get; }
/// <summary>
/// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus. <br/>
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus.
/// </summary>
IServiceEndpointNotificationService NotificationService { get; }
/// <summary>
/// Provides logging run-time trace information for plug-ins.
/// </summary>
ITracingService TracingService { get; }
/// <summary>
/// General Service Provide for things not accounted for in the base class.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary>
/// OrganizationService Factory for creating connection for other then current user and system.
/// </summary>
IOrganizationServiceFactory OrgSvcFactory { get; }
/// <summary>
/// ILogger for this plugin.
/// </summary>
ILogger Logger { get; }
/// <summary>
/// Writes a trace message to the trace log.
/// </summary>
/// <param name="message">Message name to trace.</param>
void Trace(string message, [CallerMemberName] string method = null);
}
/// <summary>
/// Plug-in context object.
/// </summary>
public class LocalPluginContext : ILocalPluginContext
{
/// <summary>
/// The PowerPlatform Dataverse organization service for the Current Executing user.
/// </summary>
public IOrganizationService InitiatingUserService { get; }
/// <summary>
/// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService.
/// </summary>
public IOrganizationService PluginUserService { get; }
/// <summary>
/// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information.
/// </summary>
public IPluginExecutionContext PluginExecutionContext { get; }
/// <summary>
/// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus. <br/>
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus.
/// </summary>
public IServiceEndpointNotificationService NotificationService { get; }
/// <summary>
/// Provides logging run-time trace information for plug-ins.
/// </summary>
public ITracingService TracingService { get; }
/// <summary>
/// General Service Provider for things not accounted for in the base class.
/// </summary>
public IServiceProvider ServiceProvider { get; }
/// <summary>
/// OrganizationService Factory for creating connection for other then current user and system.
/// </summary>
public IOrganizationServiceFactory OrgSvcFactory { get; }
/// <summary>
/// ILogger for this plugin.
/// </summary>
public ILogger Logger { get; }
/// <summary>
/// Helper object that stores the services available in this plug-in.
/// </summary>
/// <param name="serviceProvider"></param>
public LocalPluginContext(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new InvalidPluginExecutionException(nameof(serviceProvider));
}
ServiceProvider = serviceProvider;
Logger = serviceProvider.Get<ILogger>();
PluginExecutionContext = serviceProvider.Get<IPluginExecutionContext>();
TracingService = new LocalTracingService(serviceProvider);
NotificationService = serviceProvider.Get<IServiceEndpointNotificationService>();
OrgSvcFactory = serviceProvider.Get<IOrganizationServiceFactory>();
PluginUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.UserId); // User that the plugin is registered to run as, Could be same as current user.
InitiatingUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.InitiatingUserId); //User who's action called the plugin.
}
/// <summary>
/// Writes a trace message to the trace log.
/// </summary>
/// <param name="message">Message name to trace.</param>
public void Trace(string message, [CallerMemberName] string method = null)
{
if (string.IsNullOrWhiteSpace(message) || TracingService == null)
{
return;
}
if (method != null)
TracingService.Trace($"[{method}] - {message}");
else
TracingService.Trace($"{message}");
}
}
/// <summary>
/// Specialized ITracingService implementation that prefixes all traced messages with a time delta for Plugin performance diagnostics
/// </summary>
public class LocalTracingService : ITracingService
{
private readonly ITracingService _tracingService;
private DateTime _previousTraceTime;
public LocalTracingService(IServiceProvider serviceProvider)
{
DateTime utcNow = DateTime.UtcNow;
var context = (IExecutionContext)serviceProvider.GetService(typeof(IExecutionContext));
DateTime initialTimestamp = context.OperationCreatedOn;
if (initialTimestamp > utcNow)
{
initialTimestamp = utcNow;
}
_tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
_previousTraceTime = initialTimestamp;
}
public void Trace(string message, params object[] args)
{
var utcNow = DateTime.UtcNow;
// The duration since the last trace.
var deltaMilliseconds = utcNow.Subtract(_previousTraceTime).TotalMilliseconds;
try
{
if (args == null || args.Length == 0)
_tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {message}");
else
_tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {string.Format(message, args)}");
}
catch (FormatException ex)
{
throw new InvalidPluginExecutionException($"Failed to write trace message due to error {ex.Message}", ex);
}
_previousTraceTime = utcNow;
}
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>$SignAssemblyBlock$
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<ProjectTypeGuids>{4C25E9B5-9FA6-436c-8E19-B395D2A65FAF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.props')" />
<!--
NuGet pack and restore as MSBuild targets reference:
https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets
-->
<PropertyGroup>
<PackageId>$safeprojectname$</PackageId>
<Version>$(FileVersion)</Version>
<Authors>$authors$</Authors>
<Company>MyCompany</Company>
<Description>This is a sample nuget package which contains a Dataverse plugin and its runtime dependencies like Newtonsoft.Json</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CrmSdk.CoreAssemblies" Version="9.0.2.*" PrivateAssets="All" />
<PackageReference Include="Microsoft.PowerApps.MSBuild.Plugin" Version="1.*" PrivateAssets="All" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.*" PrivateAssets="All" />
</ItemGroup>
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.targets')" />
</Project>

View File

@@ -0,0 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# msbuild output directories
/bin
/obj
# MSBuild Binary and Structured Log
*.binlog

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<ImportExportXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Entities />
<Roles />
<Workflows />
<FieldSecurityProfiles />
<Templates />
<EntityMaps />
<EntityRelationships />
<OrganizationSettings />
<optionsets />
<CustomControls />
<SolutionPluginAssemblies />
<EntityDataProviders />
<Languages>
<Language>1033</Language>
</Languages>
</ImportExportXml>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<EntityRelationships xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<ImportExportXml version="9.1.0.643" SolutionPackageVersion="9.1" languagecode="1033" generatedBy="CrmLive" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SolutionManifest>
<!-- Unique Name of Cds Solution-->
<UniqueName>$solutionName$</UniqueName>
<LocalizedNames>
<!-- Localized Solution Name in language code -->
<LocalizedName description="$solutionName$" languagecode="1033" />
</LocalizedNames>
<Descriptions />
<Version>1.0</Version>
<!-- Solution Package Type: Unmanaged(0)/Managed(1)/Both(2)-->
<Managed>2</Managed>
<Publisher>
<!-- Unique Publisher Name of Cds Solution -->
<UniqueName>$publisherName$</UniqueName>
<LocalizedNames>
<!-- Localized Cds Publisher Name in language code-->
<LocalizedName description="$publisherName$" languagecode="1033" />
</LocalizedNames>
<Descriptions>
<!-- Description of Cds Publisher in language code -->
<Description description="$publisherName$" languagecode="1033" />
</Descriptions>
<EMailAddress xsi:nil="true"></EMailAddress>
<SupportingWebsiteUrl xsi:nil="true"></SupportingWebsiteUrl>
<!-- Customization Prefix for the Cds Publisher-->
<CustomizationPrefix>$customizationPrefix$</CustomizationPrefix>
<!-- Derived Option Value Prefix for the Customization Prefix of Cds Publisher -->
<CustomizationOptionValuePrefix>$customizationOptionValuePrefix$</CustomizationOptionValuePrefix>
<Addresses>
<!-- Address of the Publisher-->
<Address>
<AddressNumber>1</AddressNumber>
<AddressTypeCode>1</AddressTypeCode>
<City xsi:nil="true"></City>
<County xsi:nil="true"></County>
<Country xsi:nil="true"></Country>
<Fax xsi:nil="true"></Fax>
<FreightTermsCode xsi:nil="true"></FreightTermsCode>
<ImportSequenceNumber xsi:nil="true"></ImportSequenceNumber>
<Latitude xsi:nil="true"></Latitude>
<Line1 xsi:nil="true"></Line1>
<Line2 xsi:nil="true"></Line2>
<Line3 xsi:nil="true"></Line3>
<Longitude xsi:nil="true"></Longitude>
<Name xsi:nil="true"></Name>
<PostalCode xsi:nil="true"></PostalCode>
<PostOfficeBox xsi:nil="true"></PostOfficeBox>
<PrimaryContactName xsi:nil="true"></PrimaryContactName>
<ShippingMethodCode>1</ShippingMethodCode>
<StateOrProvince xsi:nil="true"></StateOrProvince>
<Telephone1 xsi:nil="true"></Telephone1>
<Telephone2 xsi:nil="true"></Telephone2>
<Telephone3 xsi:nil="true"></Telephone3>
<TimeZoneRuleVersionNumber xsi:nil="true"></TimeZoneRuleVersionNumber>
<UPSZone xsi:nil="true"></UPSZone>
<UTCOffset xsi:nil="true"></UTCOffset>
<UTCConversionTimeZoneCode xsi:nil="true"></UTCConversionTimeZoneCode>
</Address>
<Address>
<AddressNumber>2</AddressNumber>
<AddressTypeCode>1</AddressTypeCode>
<City xsi:nil="true"></City>
<County xsi:nil="true"></County>
<Country xsi:nil="true"></Country>
<Fax xsi:nil="true"></Fax>
<FreightTermsCode xsi:nil="true"></FreightTermsCode>
<ImportSequenceNumber xsi:nil="true"></ImportSequenceNumber>
<Latitude xsi:nil="true"></Latitude>
<Line1 xsi:nil="true"></Line1>
<Line2 xsi:nil="true"></Line2>
<Line3 xsi:nil="true"></Line3>
<Longitude xsi:nil="true"></Longitude>
<Name xsi:nil="true"></Name>
<PostalCode xsi:nil="true"></PostalCode>
<PostOfficeBox xsi:nil="true"></PostOfficeBox>
<PrimaryContactName xsi:nil="true"></PrimaryContactName>
<ShippingMethodCode>1</ShippingMethodCode>
<StateOrProvince xsi:nil="true"></StateOrProvince>
<Telephone1 xsi:nil="true"></Telephone1>
<Telephone2 xsi:nil="true"></Telephone2>
<Telephone3 xsi:nil="true"></Telephone3>
<TimeZoneRuleVersionNumber xsi:nil="true"></TimeZoneRuleVersionNumber>
<UPSZone xsi:nil="true"></UPSZone>
<UTCOffset xsi:nil="true"></UTCOffset>
<UTCConversionTimeZoneCode xsi:nil="true"></UTCConversionTimeZoneCode>
</Address>
</Addresses>
</Publisher>
<RootComponents />
<MissingDependencies />
</SolutionManifest>
</ImportExportXml>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.props')" />
<PropertyGroup>
<ProjectGuid>$cdsProjectGuid$</ProjectGuid>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<!--Remove TargetFramework when this is available in 16.1-->
<TargetFramework>net462</TargetFramework>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<SolutionRootPath>$solutionRootPath$</SolutionRootPath>
</PropertyGroup>
<!--
Solution Packager overrides, un-comment to use: SolutionPackagerType (Managed, Unmanaged, Both)
Solution Localization Control, if you want to enabled localization of your solution, un-comment SolutionPackageEnableLocalization and set the value to true. - Requires use of -loc flag on Solution Clone or Sync
-->
<!--
<PropertyGroup>
<SolutionPackageType>Managed</SolutionPackageType>
<SolutionPackageEnableLocalization>false</SolutionPackageEnableLocalization>
</PropertyGroup>
-->
<ItemGroup>
<PackageReference Include="Microsoft.PowerApps.MSBuild.Solution" Version="1.*" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.gitignore" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\bin\**" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\obj\**" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.cdsproj" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.cdsproj.user" />
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.sln" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\**" Exclude="@(ExcludeDirectories)" />
<Content Include="$(SolutionPackageZipFilePath)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.targets')" />
</Project>