Ontwikkeling

Webresources bouwen met React in Dynamics 365

15 juni 2025
Sabhat Qureshi
11 min. leestijd

Waarom React voor Dynamics 365-webresources?

Met webresources in Dynamics 365 sluit u eigen HTML-, JavaScript- en CSS-inhoud in model-driven apps in. Klassiek JavaScript volstaat voor eenvoudige scenario's, maar complexe gebruikersinterfaces hebben veel baat bij de componentgebaseerde architectuur, het state management en het ecosysteem van React. Webresources in Dynamics 365.

Voordelen van React

  • Herbruikbare componenten: Eén keer bouwen, gebruiken in meerdere formulieren en apps
  • State Management: Voorspelbare dataflow met hooks en context
  • Rijk ecosysteem: Duizenden kant-en-klare componenten en libraries
  • Ontwikkelaarservaring: Hot reload, ondersteuning voor TypeScript, uitstekende tooling
  • Prestaties: De virtuele DOM werkt efficiënt alleen de gewijzigde elementen bij

Het project opzetten

Het React-project aanmaken

Zet een nieuw React-project op dat geconfigureerd is voor uitrol naar Dynamics 365:

# Create React app with TypeScript
npx create-react-app d365-webresource --template typescript

cd d365-webresource

# Install additional dependencies
npm install @types/xrm axios

# For UI components (optional)
npm install @fluentui/react

TypeScript-configuratie voor Xrm

Voeg de Xrm-typedefinities toe voor toegang tot de Dynamics 365-API:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "types": ["xrm"]
  },
  "include": ["src"]
}

Buildconfiguratie voor één uitvoerbestand

Configureer de build zodat die één JavaScript-bestand oplevert dat geschikt is om als webresource uit te rollen:

// For Create React App, use react-app-rewired
npm install react-app-rewired

// config-overrides.js
module.exports = {
  webpack: function(config, env) {
    // Disable code splitting
    config.optimization.splitChunks = {
      cacheGroups: {
        default: false
      }
    };
    config.optimization.runtimeChunk = false;

    // Single output file
    config.output.filename = 'static/js/bundle.js';

    return config;
  }
};

// Update package.json scripts
"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test"
}

De Dynamics 365-context gebruiken

De bovenliggende context ophalen

Webresources draaien in een iframe, dus u moet de Xrm-context van het bovenliggende venster aanspreken:

// src/utils/XrmContext.ts
declare global {
  interface Window {
    parent: Window & { Xrm?: typeof Xrm };
  }
}

export function getXrm(): typeof Xrm | undefined {
  // Try parent window first (when embedded in form)
  if (window.parent && window.parent.Xrm) {
    return window.parent.Xrm;
  }
  // Fall back to current window (for standalone testing)
  if ((window as any).Xrm) {
    return (window as any).Xrm;
  }
  return undefined;
}

export function getFormContext(): Xrm.FormContext | undefined {
  const xrm = getXrm();
  if (xrm && xrm.Page) {
    return xrm.Page as unknown as Xrm.FormContext;
  }
  return undefined;
}

React-contextprovider voor Xrm

Maak een React-context om Xrm beschikbaar te maken in uw volledige componentenboom:

// src/context/D365Context.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import { getXrm, getFormContext } from '../utils/XrmContext';

interface D365ContextType {
  xrm: typeof Xrm | undefined;
  formContext: Xrm.FormContext | undefined;
  recordId: string | undefined;
  entityName: string | undefined;
  isLoading: boolean;
}

const D365Context = createContext<D365ContextType>({
  xrm: undefined,
  formContext: undefined,
  recordId: undefined,
  entityName: undefined,
  isLoading: true
});

export const D365Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [state, setState] = useState<D365ContextType>({
    xrm: undefined,
    formContext: undefined,
    recordId: undefined,
    entityName: undefined,
    isLoading: true
  });

  useEffect(() => {
    const initContext = () => {
      const xrm = getXrm();
      const formContext = getFormContext();

      if (xrm) {
        setState({
          xrm,
          formContext,
          recordId: formContext?.data?.entity?.getId()?.replace(/[{}]/g, ''),
          entityName: formContext?.data?.entity?.getEntityName(),
          isLoading: false
        });
      } else {
        setTimeout(initContext, 100);
      }
    };

    initContext();
  }, []);

  return (
    <D365Context.Provider value={state}>
      {children}
    </D365Context.Provider>
  );
};

export const useD365 = () => useContext(D365Context);

Integratie met de Web API

Eigen hook voor Web API-aanroepen

Maak een eigen hook om Web API-aanroepen met de juiste authenticatie uit te voeren:

// src/hooks/useWebApi.ts
import { useCallback } from 'react';
import { useD365 } from '../context/D365Context';

export function useWebApi() {
  const { xrm } = useD365();

  const retrieveRecord = useCallback(async (
    entityName: string,
    recordId: string,
    options?: string
  ) => {
    if (!xrm) throw new Error('Xrm not available');
    return await xrm.WebApi.retrieveRecord(entityName, recordId, options);
  }, [xrm]);

  const retrieveMultiple = useCallback(async (
    entityName: string,
    options?: string
  ) => {
    if (!xrm) throw new Error('Xrm not available');
    return await xrm.WebApi.retrieveMultipleRecords(entityName, options);
  }, [xrm]);

  const createRecord = useCallback(async (
    entityName: string,
    data: object
  ) => {
    if (!xrm) throw new Error('Xrm not available');
    return await xrm.WebApi.createRecord(entityName, data);
  }, [xrm]);

  const updateRecord = useCallback(async (
    entityName: string,
    recordId: string,
    data: object
  ) => {
    if (!xrm) throw new Error('Xrm not available');
    return await xrm.WebApi.updateRecord(entityName, recordId, data);
  }, [xrm]);

  const deleteRecord = useCallback(async (
    entityName: string,
    recordId: string
  ) => {
    if (!xrm) throw new Error('Xrm not available');
    return await xrm.WebApi.deleteRecord(entityName, recordId);
  }, [xrm]);

  return { retrieveRecord, retrieveMultiple, createRecord, updateRecord, deleteRecord };
}

Componenten bouwen

Voorbeeld: component voor gerelateerde records

Bouw een component die gerelateerde records toont en laat bewerken:

// src/components/RelatedContacts.tsx
import React, { useEffect, useState } from 'react';
import { useD365 } from '../context/D365Context';
import { useWebApi } from '../hooks/useWebApi';

interface Contact {
  contactid: string;
  fullname: string;
  emailaddress1: string;
  telephone1: string;
}

export const RelatedContacts: React.FC = () => {
  const { recordId, entityName, isLoading: contextLoading } = useD365();
  const { retrieveMultiple } = useWebApi();
  const [contacts, setContacts] = useState<Contact[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchContacts = async () => {
      if (!recordId || entityName !== 'account') {
        setLoading(false);
        return;
      }

      try {
        const result = await retrieveMultiple(
          'contacts',
          `?$select=contactid,fullname,emailaddress1,telephone1&$filter=_parentcustomerid_value eq ${recordId}`
        );
        setContacts(result.entities as Contact[]);
      } finally {
        setLoading(false);
      }
    };

    if (!contextLoading) fetchContacts();
  }, [recordId, entityName, contextLoading, retrieveMultiple]);

  if (contextLoading || loading) return <div>Loading...</div>;

  return (
    <div className="contacts-grid">
      <h3>Related Contacts ({contacts.length})</h3>
      <table>
        <thead>
          <tr><th>Name</th><th>Email</th><th>Phone</th></tr>
        </thead>
        <tbody>
          {contacts.map(contact => (
            <tr key={contact.contactid}>
              <td>{contact.fullname}</td>
              <td>{contact.emailaddress1}</td>
              <td>{contact.telephone1}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

Fluent UI-componenten gebruiken

Fluent UI biedt componenten die aansluiten bij de look-and-feel van Dynamics 365:

// Using Fluent UI for consistent D365 styling
import { Card, CardHeader, Text, Badge, makeStyles } from '@fluentui/react-components';
import { PersonRegular, MailRegular } from '@fluentui/react-icons';

const useStyles = makeStyles({
  card: {
    marginBottom: '8px',
    cursor: 'pointer',
    '&:hover': { boxShadow: '0 2px 8px rgba(0,0,0,0.15)' }
  }
});

export const ContactCard: React.FC<ContactCardProps> = ({ contactId, fullname, email, isPrimary }) => {
  const styles = useStyles();
  const { xrm } = useD365();

  const openContact = () => {
    xrm?.Navigation.openForm({ entityName: 'contact', entityId: contactId });
  };

  return (
    <Card className={styles.card} onClick={openContact}>
      <CardHeader
        header={
          <div>
            <PersonRegular />
            <Text weight="semibold">{fullname}</Text>
            {isPrimary && <Badge>Primary</Badge>}
          </div>
        }
      />
    </Card>
  );
};

Communicatie met het bovenliggende formulier

Formuliergegevens lezen en schrijven

Werk met formuliervelden vanuit uw React-webresource:

// src/hooks/useFormAttribute.ts
import { useState, useEffect, useCallback } from 'react';
import { useD365 } from '../context/D365Context';

export function useFormAttribute<T>(attributeName: string) {
  const { formContext } = useD365();
  const [value, setValue] = useState<T | null>(null);

  useEffect(() => {
    if (!formContext) return;

    const attribute = formContext.getAttribute(attributeName);
    if (attribute) {
      setValue(attribute.getValue() as T);
      attribute.addOnChange(() => setValue(attribute.getValue() as T));
    }
  }, [formContext, attributeName]);

  const updateValue = useCallback((newValue: T) => {
    if (!formContext) return;
    const attribute = formContext.getAttribute(attributeName);
    if (attribute) {
      attribute.setValue(newValue);
      setValue(newValue);
    }
  }, [formContext, attributeName]);

  return [value, updateValue] as const;
}

// Usage
const [accountName, setAccountName] = useFormAttribute<string>('name');
const [revenue, setRevenue] = useFormAttribute<number>('revenue');

Uitrol

Bouwen voor productie

# Build the production bundle
npm run build

# Output structure:
# build/
#   index.html        → Upload as HTML WebResource
#   static/js/bundle.js   → Upload as JS WebResource
#   static/css/main.css   → Upload as CSS WebResource

HTML-sjabloon voor de webresource

Maak een HTML-bestand dat uw React-bundel laadt:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React WebResource</title>
  <link href="new_/css/main.css" rel="stylesheet">
  <style>
    body { margin: 0; padding: 8px; font-family: 'Segoe UI', sans-serif; }
    #root { min-height: 100vh; }
  </style>
</head>
<body>
  <div id="root"></div>
  <script src="new_/js/bundle.js"></script>
</body>
</html>

Toevoegen aan een formulier

  • Open de formuliereditor in make.powerapps.com
  • Voeg een nieuwe sectie of tab toe voor de webresource
  • Voeg een HTML-webresourcecomponent in
  • Selecteer het HTML-bestand dat u hebt geüpload
  • Stel het aantal rijen en kolommen in naar behoefte
  • Vink indien nodig "Pass record object-type code and unique identifier as parameters" aan (objecttypecode en unieke id van de record als parameters doorgeven)

Best practices

Prestaties

  • Houd de bundel klein; gebruik waar mogelijk code splitting
  • Toon laadstatussen bij asynchrone bewerkingen
  • Gebruik debouncing bij updates van formuliervelden om overbodige API-aanroepen te vermijden
  • Gebruik React.memo voor componenten die zwaar zijn om te renderen

Foutafhandeling

  • Gebruik error boundaries om fouten netjes op te vangen en te tonen
  • Vang situaties op waarin de Xrm-context niet beschikbaar is (bijvoorbeeld bij testen)
  • Log fouten naar Application Insights of naar uw eigen logging

Ontwikkelworkflow

  • Gebruik testgegevens (mock data) tijdens de lokale ontwikkeling
  • Stel omgevingsvariabelen in voor de verschillende D365-omgevingen
  • Automatiseer de uitrol van webresources met CLI-tools
  • Neem webresources op in uw solution voor ALM

Conclusie

Webresources bouwen met React brengt moderne webontwikkeling naar het maatwerk in Dynamics 365. Het componentmodel van React, de typeveiligheid van TypeScript en de uitbreidbaarheid van Dynamics 365 maken samen krachtige ervaringen op maat mogelijk die de productiviteit van gebruikers verhogen. Begin met eenvoudige componenten en bouw geleidelijk complexere oplossingen naarmate u vertrouwd raakt met de integratiepatronen.

Hulp nodig bij je Dynamics 365-project?

We helpen je graag om deze werkwijzen toe te passen in je eigen Dynamics 365-omgeving.