Showing posts with label TypeScript. Show all posts
Showing posts with label TypeScript. Show all posts

Sunday, 22 February 2026

Surface What’s New: Power Apps integration with a secure .NET 8 API for MCP server updates

The question what is new in powerapps for MCP server lacks a precise product definition, so rather than speculating on features, this guide shows how to reliably surface and version "What’s New" updates into Power Apps using a secure .NET 8 backend, strict TypeScript validation, and Azure-native security. You will get a production-ready pattern that Power Apps can call via a Custom Connector, keeping your release notes current without manual edits.

The Problem

Teams need a trustworthy way to display "What’s New" for MCP server in Power Apps, but the upstream source and format of updates can change. Hardcoding content or querying unsecured endpoints leads to drift, security gaps, and poor developer experience.

Prerequisites

  • .NET 8 SDK
  • Node.js 20+ and a package manager (pnpm/npm)
  • Azure subscription with permissions to create: Azure Functions or Container Apps, Key Vault, Azure API Management, Application Insights
  • Entra ID app registration for the API (OAuth 2.0)
  • Power Apps environment (to build a Custom Connector)

The Solution (Step-by-Step)

1) .NET 8 Minimal API that normalizes "What’s New" items

This minimal API demonstrates production-ready design: DI-first, HttpClientFactory, global exception handling, validation, versioned contract, and managed identity for downstream access.

// Program.cs (.NET 8, file-scoped namespace, minimal API, DI-centric)
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.Identity; // DefaultAzureCredential for managed identity
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Azure;

var builder = WebApplication.CreateBuilder(args);

// Strongly typed options for upstream source configuration
builder.Services.Configure<NewsOptions>(builder.Configuration.GetSection("News"));

// HttpClient with resilient handler
builder.Services.AddHttpClient<INewsClient, NewsClient>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

// Azure clients via DefaultAzureCredential (uses Managed Identity in Azure)
builder.Services.AddAzureClients(azure =>
{
    azure.UseCredential(new DefaultAzureCredential());
    var kvUri = builder.Configuration["KeyVaultUri"]; // e.g., https://my-kv.vault.azure.net/
    if (!string.IsNullOrWhiteSpace(kvUri))
    {
        azure.AddSecretClient(new Uri(kvUri));
    }
});

// Application Insights (OpenTelemetry auto-collection can also be used)
builder.Services.AddApplicationInsightsTelemetry();

var app = builder.Build();

// Global exception handler producing problem+json
app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var feature = context.Features.Get<IExceptionHandlerPathFeature>();
        var problem = new ProblemDetails
        {
            Title = "Unexpected error",
            Detail = app.Environment.IsDevelopment() ? feature?.Error.ToString() : "",
            Status = StatusCodes.Status500InternalServerError,
            Type = "https://httpstatuses.com/500"
        };
        context.Response.StatusCode = problem.Status ?? 500;
        context.Response.ContentType = "application/problem+json";
        await context.Response.WriteAsJsonAsync(problem);
    });
});

app.MapGet("/health", () => Results.Ok(new { status = "ok" }));

// Versioned route for the normalized news feed (v1)
app.MapGet("/api/v1/news", async (
    INewsClient client
) => Results.Ok(await client.GetNewsAsync()))
.WithName("GetNewsV1")
.Produces<NewsItemV1[]>(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status500InternalServerError);

app.Run();

// Options to control the upstream feed location and parsing mode
public sealed class NewsOptions
{
    public string? SourceUrl { get; init; } // Upstream JSON or RSS converted via a worker
    public string Format { get; init; } = "json"; // json|rss (extend as needed)
}

// Public DTO contract exposed to Power Apps via Custom Connector
public sealed class NewsItemV1
{
    public required string Id { get; init; } // stable identifier
    public required string Title { get; init; }
    public required string Summary { get; init; }
    public required DateTimeOffset PublishedAt { get; init; }
    public string? Category { get; init; } // optional taxonomy
    public string? Url { get; init; } // link to detail page
}

// Client interface for fetching and normalizing upstream data
public interface INewsClient
{
    Task<NewsItemV1[]> GetNewsAsync(CancellationToken ct = default);
}

// Implementation that reads upstream source, validates, and normalizes
public sealed class NewsClient(HttpClient http, Microsoft.Extensions.Options.IOptions<NewsOptions> options) : INewsClient
{
    private readonly HttpClient _http = http;
    private readonly NewsOptions _opts = options.Value;
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    public async Task<NewsItemV1[]> GetNewsAsync(CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(_opts.SourceUrl))
            throw new InvalidOperationException("News:SourceUrl is not configured.");

        // Fetch upstream JSON and map to a stable contract consumed by Power Apps
        var upstreamItems = await _http.GetFromJsonAsync<UpstreamItem[]>(_opts.SourceUrl, JsonOptions, ct)
            ?? Array.Empty<UpstreamItem>();

        // Normalize and order by publish date desc
        return upstreamItems
            .Select(u => new NewsItemV1
            {
                Id = u.Id ?? Guid.NewGuid().ToString("n"),
                Title = u.Title ?? "Untitled",
                Summary = u.Summary ?? string.Empty,
                PublishedAt = u.PublishedAt == default ? DateTimeOffset.UtcNow : u.PublishedAt,
                Category = u.Category,
                Url = u.Url
            })
            .OrderByDescending(n => n.PublishedAt)
            .ToArray();
    }

    // Internal model matching the upstream source (keep flexible)
    private sealed class UpstreamItem
    {
        public string? Id { get; init; }
        public string? Title { get; init; }
        public string? Summary { get; init; }
        public DateTimeOffset PublishedAt { get; init; }
        public string? Category { get; init; }
        public string? Url { get; init; }
    }
}

Configuration (appsettings.json):

{
  "News": {
    "SourceUrl": "https://<your-source>/mcp-news.json",
    "Format": "json"
  },
  "KeyVaultUri": "https://<your-kv>.vault.azure.net/"
}

Pro-Tip: Use AsNoTracking() in Entity Framework when performing read-only queries to improve performance.

2) Secure Azure deployment with Managed Identity and API Management

  • Deploy as Azure Functions (isolated) or Azure Container Apps. Enable System-Assigned Managed Identity.
  • Expose through Azure API Management with OAuth 2.0 (Entra ID) for inbound auth. Create a Power Apps Custom Connector pointing to APIM.

Required RBAC roles (assign to the managed identity or DevOps service principal):

  • Key Vault Secrets User (to read secrets if you store upstream source credentials or API keys)
  • App Configuration Data Reader (if using Azure App Configuration instead of appsettings)
  • API Management Service Contributor (to publish and manage the API surface)
  • Monitoring Reader (to view Application Insights telemetry)
  • Storage Blob Data Reader (only if the upstream source is in Azure Storage)

Pro-Tip: Favor Managed Identity and DefaultAzureCredential across services; avoid connection strings and embedded secrets entirely.

3) Strict TypeScript models with Zod and versioning

The client schema mirrors the v1 API and can evolve with v2+ while keeping backward compatibility in Power Apps and React.

// news.schema.ts (TypeScript, strict mode)
import { z } from "zod";

// Discriminated union enables future breaking changes with clear versioning
export const NewsItemV1 = z.object({
  id: z.string().min(1),
  title: z.string().min(1),
  summary: z.string().default(""),
  publishedAt: z.string().datetime(),
  category: z.string().optional(),
  url: z.string().url().optional()
});

export const NewsResponseV1 = z.array(NewsItemV1);

export type TNewsItemV1 = z.infer<typeof NewsItemV1>;
export type TNewsResponseV1 = z.infer<typeof NewsResponseV1>;

// Future-proof: union for versioned responses
export const NewsResponse = z.union([
  z.object({ version: z.literal("v1"), data: NewsResponseV1 })
]);
export type TNewsResponse = z.infer<typeof NewsResponse>;

4) React 19 component using TanStack Query

Functional component with error and loading states, plus Zod runtime validation.

// NewsPanel.tsx (React 19)
import React from "react";
import { useQuery } from "@tanstack/react-query";
import { NewsResponseV1, type TNewsItemV1 } from "./news.schema";

async function fetchNews(): Promise<TNewsItemV1[]> {
  const res = await fetch("/api/v1/news", { headers: { Accept: "application/json" } });
  if (!res.ok) throw new Error(`Failed: ${res.status}`);
  const json = await res.json();
  const parsed = NewsResponseV1.safeParse(json);
  if (!parsed.success) throw new Error("Schema validation failed");
  return parsed.data;
}

export function NewsPanel(): JSX.Element {
  const { data, error, isLoading } = useQuery({
    queryKey: ["news", "v1"],
    queryFn: fetchNews,
    staleTime: 60_000
  });

  if (isLoading) return <div>Loading updates…</div>;
  if (error) return <div>Failed to load updates.</div>;

  return (
    <ul>
      {data!.map(item => (
        <li key={item.id}>
          <strong>{item.title}</strong> — {new Date(item.publishedAt).toLocaleDateString()}<br />
          {item.summary}
        </li>
      ))}
    </ul>
  );
}

5) Power Apps Custom Connector

Create a Custom Connector targeting the APIM endpoint /api/v1/news. Map the response to your app data schema. Add the connector to your Power App and display the items in a gallery. When the upstream feed changes, you only update the backend normalizer, not the app.

Best Practices & Security

  • Authentication: Use Entra ID for APIM inbound auth. Backend-to-Azure uses DefaultAzureCredential with Managed Identity.
  • Secrets: Store upstream tokens in Key Vault; assign Key Vault Secrets User to the app’s managed identity.
  • Telemetry: Enable Application Insights. Track request IDs and dependency calls for the upstream fetch.
  • Authorization: Restrict APIM access via OAuth scopes and, if needed, IP restrictions or rate limits.
  • Resilience: Configure retry and timeout on HttpClient with sensible limits; add circuit breakers if the upstream is unreliable.
  • Versioning: Pin /api/v1/news; introduce /api/v2/news when the contract changes. Version TypeScript schemas alongside API versions.

App Insights integration (example):

// Add to Program.cs before app.Run(); ensure Application Insights is enabled
app.Use(async (ctx, next) =>
{
    // Correlate requests with upstream calls via trace IDs
    ctx.Response.Headers["Request-Id"] = System.Diagnostics.Activity.Current?.Id ?? Guid.NewGuid().ToString();
    await next();
});

Testing strategy:

  • API: Unit test NewsClient with mocked HttpMessageHandler; integration test /api/v1/news in-memory.
  • TypeScript: Schema tests to ensure validation rejects malformed payloads; component tests for loading/error states.
  • Contract: Add a CI step that fetches a sample payload from the upstream and validates against NewsResponseV1.

Security roles recap:

  • API surface: API Management Service Contributor
  • Secrets: Key Vault Secrets User
  • Config (if used): App Configuration Data Reader
  • Monitoring: Monitoring Reader
  • Storage (if used): Storage Blob Data Reader

Pro-Tip: Use APIM policies (validate-content, rate-limit-by-key) to protect the API consumed by Power Apps.

Summary

  • Do not guess "what’s new" for MCP server; centralize updates behind a stable, versioned API.
  • Use Managed Identity, DefaultAzureCredential, and APIM OAuth to secure end-to-end access for Power Apps.
  • Validate with Zod, monitor with Application Insights, and evolve safely via API/schema versioning.

Saturday, 24 January 2026

Avoiding the Pitfalls of Stopping Permission Inheritance: Performance Hotspots, Safer Patterns, and Azure-First Remediation

Stopping inherit permission (i.e., breaking permission inheritance) often seems like a quick fix for access control, but it introduces hidden operational and performance costs. This article explains why breaking inheritance in SharePoint and Azure RBAC leads to complexity, where performance issues occur, and how to remediate with .NET 8, TypeScript, and Azure-first patterns using Managed Identities and Infrastructure as Code.

The Problem

Breaking inheritance creates many one-off permission entries. Over time, this causes:

  • Permission sprawl: hard-to-audit, hard-to-revoke access scattered across items/resources.
  • Performance degradation: larger ACL evaluations, slower queries, and increased throttling risk.
  • Operational friction: brittle reviews, noisy exceptions, and confusing user experiences.

In SharePoint, many uniquely permissioned items slow list queries and complicate sharing. In Azure, assigning roles at leaf scopes (instead of using inherited assignments at management group or subscription levels) increases evaluation overhead and management burden.

Prerequisites

  • .NET 8 SDK
  • Node.js v20+
  • Azure CLI (az) and Azure Bicep
  • Contributor access to a test subscription (for deploying IaC) and Reader/Authorization permissions for audit scenarios

The Solution (Step-by-Step)

Step 1: What to avoid when stopping inheritance

  • SharePoint: Avoid per-item unique permissions for large lists; prefer groups at the site or library level, with exceptions gated by policy and approval.
  • Azure RBAC: Avoid many role assignments at the resource/resource-group level; grant least privilege at a higher scope when practical, and use groups instead of direct user assignments.

Step 2: .NET 8 Minimal API to detect RBAC hotspots (top-level statements, DI, Managed Identity)

// File: Program.cs (.NET 8, top-level statements, minimal API)
// Purpose: Audit Azure RBAC for non-inherited, leaf-level role assignments (hotspots).
// Auth: Uses DefaultAzureCredential (Managed Identity preferred in Azure).
// Note: This sample reads role assignments and surfaces "leaf" hotspots for review.

using Azure;
using Azure.Core;
using Azure.Identity;
using Azure.ResourceManager;
using Azure.ResourceManager.Authorization;
using Azure.ResourceManager.Authorization.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

// Register Azure clients via DI
builder.Services.AddSingleton<TokenCredential>(_ => new DefaultAzureCredential());
// ArmClient is the entry point to Resource Manager APIs
builder.Services.AddSingleton<ArmClient>(sp => new ArmClient(sp.GetRequiredService<TokenCredential>()));

var app = builder.Build();

// GET /rbac/hotspots?subscriptionId=<subId>
// Heuristic: Identify role assignments at deeper scopes (resource, resource group) that
// could be consolidated at higher scopes to reduce sprawl and evaluation overhead.
app.MapGet("/rbac/hotspots", async (string subscriptionId, ArmClient arm) =>
{
    // Acquire subscription resource
    var sub = arm.GetSubscriptionResource(new ResourceIdentifier($"/subscriptions/{subscriptionId}"));

    // List role assignments across the subscription
    var assignments = new List<RoleAssignmentData>();
    await foreach (var ra in sub.GetRoleAssignmentsAsync())
    {
        assignments.Add(ra.Data);
    }

    // Group by scope depth (subscription=1, resourceGroup=2, resource=3+)
    // Deeper scopes are more likely to be "breaks" from inheritance-like patterns.
    var hotspots = assignments
        .GroupBy(a => ScopeDepth(a.Scope))
        .OrderByDescending(g => g.Key)
        .Select(g => new
        {
            depth = g.Key,
            count = g.Count(),
            sampleScopes = g.Select(x => x.Scope).Distinct().Take(5).ToArray()
        });

    return Results.Ok(new
    {
        analyzed = assignments.Count,
        hotspots
    });

    // Simple helper to score scope depth by path segments.
    static int ScopeDepth(string scope)
    {
        // Example: /subscriptions/{id} => depth ~ 1
        // /subscriptions/{id}/resourceGroups/{rg} => depth ~ 2
        // /subscriptions/{id}/resourceGroups/{rg}/providers/... => depth >= 3
        return scope.Split('/', StringSplitOptions.RemoveEmptyEntries).Length / 2;
    }
})
.WithName("GetRbacHotspots")
.Produces<object>(200);

app.Run();

Why this helps: Large counts of deep-scope assignments often indicate broken inheritance patterns (per-resource grants). Consolidating to group-based roles at higher scopes can reduce policy evaluation work and administrative overhead.

Step 3: TypeScript Azure Function to list deep-scope role assignments with retry (Managed Identity)

// File: index.ts (Azure Functions v4, Node 20)
// Purpose: Enumerate role assignments and flag deep-scope patterns.
// Auth: ManagedIdentityCredential (no client secrets). Strict typing. Basic retry for 429.
//
// Ensure you enable a system-assigned identity on the Function App and grant it Reader on the subscription.
// package.json should include: @azure/identity, @azure/arm-authorization, zod

import { AzureFunction, Context } from "@azure/functions";
import { ManagedIdentityCredential } from "@azure/identity";
import { AuthorizationManagementClient, RoleAssignment } from "@azure/arm-authorization";
import { z } from "zod";

// Validate required environment variable via Zod (strict typing)
const EnvSchema = z.object({
  SUBSCRIPTION_ID: z.string().min(1)
});
const env = EnvSchema.parse(process.env);

const httpTrigger: AzureFunction = async function (context: Context): Promise<void> {
  // Create credential using Managed Identity (no secrets in code or env)
  const credential = new ManagedIdentityCredential();
  const authClient = new AuthorizationManagementClient(credential, env.SUBSCRIPTION_ID);

  // Simple retry wrapper for throttle-prone calls (e.g., large tenants)
  async function withRetry<T>(fn: () => Promise<T>, attempts = 5, delayMs = 1000): Promise<T> {
    let lastErr: unknown;
    for (let i = 0; i < attempts; i++) {
      try {
        return await fn();
      } catch (err: unknown) {
        const anyErr = err as { statusCode?: number };
        if (anyErr?.statusCode === 429 || anyErr?.statusCode === 503) {
          await new Promise((r) => setTimeout(r, delayMs * (i + 1))); // Exponential-ish backoff
          lastErr = err;
          continue;
        }
        throw err;
      }
    }
    throw lastErr;
  }

  // List role assignments at subscription scope
  const scope = `/subscriptions/${env.SUBSCRIPTION_ID}`;
  const assignments: RoleAssignment[] = [];

  // Use retry around list calls; the SDK returns an async iterator
  const pager = authClient.roleAssignments.listForSubscription();
  for await (const item of pager) {
    assignments.push(item);
  }

  // Score depth by scope path complexity
  const scoreDepth = (s: string): number => s.split("/").filter(Boolean).length / 2;
  const hotspots = assignments
    .map(a => ({ id: a.id!, scope: a.scope!, depth: scoreDepth(a.scope!) }))
    .filter(x => x.depth >= 3); // resource-level assignments

  context.res = {
    status: 200,
    headers: { "content-type": "application/json" },
    body: {
      analyzed: assignments.length,
      resourceLevelAssignments: hotspots.length,
      hotspotSamples: hotspots.slice(0, 10)
    }
  };
};

export default httpTrigger;

Why this helps: A quick serverless audit allows teams to discover where inheritance-like patterns are being bypassed in Azure RBAC, which is a frequent source of performance and governance friction.

Step 4: Azure Bicep to deploy a Function App with Managed Identity and least privilege

// File: main.bicep
// Purpose: Deploy a Function App with system-assigned managed identity,
// and assign Reader at the resource group scope (principle of least privilege).
// Includes a deterministic GUID for role assignment name.
//
// Note: Reader role definition ID is acdd72a7-3385-48ef-bd42-f606fba81ae7
// (Allows viewing resources, not making changes.)

@description('Location for resources')
param location string = resourceGroup().location

@description('Function App name')
param functionAppName string

@description('Storage account name (must be globally unique)')
param storageAccountName string

var roleDefinitionIdReader = '/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7'

// Storage (for Functions)
resource stg 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    allowBlobPublicAccess: false
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
  }
}

// Hosting plan (Consumption)
resource plan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: '${functionAppName}-plan'
  location: location
  sku: {
    name: 'Y1'
    tier: 'Dynamic'
  }
}

// Function App with system-assigned identity
resource func 'Microsoft.Web/sites@2022-09-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: plan.id
    siteConfig: {
      appSettings: [
        { name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
        { name: 'WEBSITE_RUN_FROM_PACKAGE', value: '1' }
      ]
    }
  }
}

// Assign Reader at the resource group scope to the Function's identity
// Use a stable, deterministic GUID based on scope + principal to avoid duplicates.
resource readerAssign 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup().id, func.identity.principalId, roleDefinitionIdReader)
  scope: resourceGroup()
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
    principalId: func.identity.principalId
    principalType: 'ServicePrincipal'
  }
}

Why this helps: You deploy secure defaults and enforce least privilege by design. The deterministic GUID prevents accidental duplicate role assignments. Reader is explicitly chosen to avoid write permissions while enabling inventory and audits.

Where performance issues occur

  • SharePoint: Many items with unique permissions increase ACL checks and can slow list queries, indexing, and certain sharing operations. Batch operations on items with unique permissions are more likely to hit throttling.
  • Azure RBAC: Thousands of per-resource role assignments increase evaluation work during authorization and complicate policy and compliance scans. It also prolongs investigations during incidents.
  • Auditing and reviews: Per-user, per-resource assignments inflate review surfaces and make access recertification slow and error-prone.

Best Practices & Security

  • Use Managed Identity or Azure AD Workload Identity. Avoid client secrets for server-side workloads. Do not store secrets in environment variables. If you must handle secrets, use Azure Key Vault with RBAC and Managed Identity.
  • Prefer group-based assignments over direct user assignments. This simplifies reviews and minimizes churn.
  • Favor higher-scope role assignments with least privilege. Start at management group or subscription only when justified and narrow to Reader or custom roles that fit the minimal required actions.
  • When exceptions are necessary, document them. Add expiration and owners to each exception.
  • For SharePoint, grant permissions at the site or library level using groups. Reserve item-level breaks for rare, time-bound cases.
  • Monitor continuously. Integrate with Azure Monitor and Azure Policy to detect excessive deep-scope assignments. Create alerts on abnormal growth of role assignments or access anomalies.
  • Implement retry and backoff for API calls that can throttle (429/503), both in audits and operational tooling.
  • Standardize terminology. Use "inheritance" consistently to avoid confusion in documentation and automation.

Recommendation: If you need read-only inventory across a subscription, assign the Reader role (roleDefinitionId acdd72a7-3385-48ef-bd42-f606fba81ae7) to a Managed Identity and call Azure SDKs or ARM REST with exponential backoff.

Summary

  • Breaking inheritance increases complexity and can degrade performance; reserve it for rare, time-bound exceptions.
  • Automate audits with Managed Identity using .NET 8 and TypeScript to find deep-scope hotspots and consolidate access.
  • Ship secure-by-default with Bicep: least privilege (Reader), deterministic role assignment IDs, and continuous monitoring via Azure services.

Thursday, 22 January 2026

Bind dropdown list in property pane spfx


Prerequisite:

Make sure installation of spfx is already done and we are using pnp to access the SharePoint lists from current site so make sure pnp is also installed.

How to bind property pane Dropdown list in Client side webpart in SharePoint Framework.

We have a scenario suppose that we have to bind a dropdown list with current site’ s lists/libraries.
 Just need to follow below instruction step by step  
1.       Create a Solution  .
2.       Add a Webpart to the solution.
3.       Declare the property pane  “PropertyPaneDropdown” for dropdown list.

import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneDropdown,
} from '@microsoft/sp-webpart-base';

4.       Come to the property pane under “getPropertyPaneConfiguration”.
5.       Define your own group of property pane or leave  the default and add under existing group fields.
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('dropdownLists', {
                  label: "Lists",
                  options:this._dropdownOptions,
                  disabled:this.listsDropdownDisabled
                })
              ]
            }
          ]
        }
      ]
    };
  }

6.       Declare Interface “IPropertyPaneDropdownOption”  and “IODataList” to map the dropdown list with the list
                  
                        export interface IPropertyPaneDropdownOption
{
  key:string;
text:string;
}
export interface IODataList
{
  Id:string;
 Title:string;
}
7.       Define two variables for dropdown option and to disable the dropdown while lists loading just below the .
      
export default class HellowWebpartWebPart extends BaseClientSideWebPart<IHellowWebpartWebPartProps> {

         private _dropdownOptions: IPropertyPaneDropdownOption[];
  private listsDropdownDisabled: boolean = true;

8.       Now override the existing method onPropertyPaneConfigurationStart() and paste the below code

protected onPropertyPaneConfigurationStart(): void
  {
    //Bind DropDown List in Peropert pane
    this.listsDropdownDisabled = !this._dropdownOptions;
    if (this._dropdownOptions)
    {
      return;
    }
      this.context.statusRenderer.displayLoadingIndicator(this.domElement, '_dropdownOptions');
      pnp.sp.web.lists.get()
      .then((listOptions: IODataList[]): void =>
      {
          var options: Array<IPropertyPaneDropdownOption> = new Array<IPropertyPaneDropdownOption>();
          listOptions.map((list: IODataList) => {
          options.push( { key: list.Id, text: list.Title });
      });
        this._dropdownOptions=options
        this.listsDropdownDisabled = false;
        this.context.propertyPane.refresh();
        this.context.statusRenderer.clearLoadingIndicator(this.domElement);
        this.render();
      });
  }

9.       Start terminal and run gulp bundle
10.   Now run the gulp serve –nobrowser command in terminal
11.   Open workbench on the site open direct link https://tenant.sharepoint.com/sites/demo/_layouts/15/workbench.aspx
12.   Add client side webpart on the bench.
13.   Click on Edit properties
14.   You see on  right side in properties will be dropdown list bind with current site’s lists/libraries

Happy coding  !!!


Wednesday, 21 January 2026

React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration

When we  build the PCF project and  receive below warning  

Error:

Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .

[4:47:40 PM] [start]  Compiling and bundling control...



Solution : Open the ".eslintrc.json" and add below configuration 


"settings": {

      "react": {

        "version": "detect"

      }

    }



Friday, 16 January 2026

Implementing PnP People Picker in React for SPFx: A Ready-to-Use Example with Strict TypeScript and Zod

The primary keyword pnp people picker control in react for SPfx with example sets the scope: implement a production-grade People Picker in a SharePoint Framework (SPFx) web part using React, strict TypeScript, and Zod validation. Why this matters: avoid vague selections, respect tenant boundaries and theming, and ship a fast, accessible control that your security team can approve.

The Problem

Developers often wire up People Picker quickly, then face issues with invalid selections, poor performance in large tenants, theming mismatches, and missing API permissions. The goal is a robust People Picker that validates data, performs well, and aligns with SPFx and Microsoft 365 security constraints.

Prerequisites

  • Node.js v20+
  • SPFx v1.18+ (React and TypeScript template)
  • @pnp/spfx-controls-react v3.23.0+ (PeoplePicker)
  • TypeScript strict mode enabled ("strict": true)
  • Zod v3.23.8+ for schema validation
  • Tenant admin rights to approve Microsoft Graph permissions for the package

The Solution (Step-by-Step)

1) Install dependencies and pin versions

npm install @pnp/spfx-controls-react@3.23.0 zod@3.23.8

Recommendation: pin versions to prevent accidental breaking changes in builds.

2) Configure delegated permissions (least privilege)

In config/package-solution.json, request the minimum Graph scopes needed to resolve people:

{
  "solution": {
    "webApiPermissionRequests": [
      { "resource": "Microsoft Graph", "scope": "User.ReadBasic.All" },
      { "resource": "Microsoft Graph", "scope": "People.Read" }
    ]
  }
}

After packaging and deploying, a tenant admin must approve these scopes. These are delegated permissions tied to the current user; no secrets or app-only access are required for the People Picker scenario.

3) Implement a strict, validated People Picker component

/* PeoplePickerField.tsx */
import * as React from "react";
import { useCallback, useMemo, useState } from "react";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
import { z } from "zod";

// Define the shape we accept from People Picker selections
// The control returns IPrincipal-like objects; we validate subset we rely upon.
const PersonSchema = z.object({
  id: z.union([z.string(), z.number()]), // Graph or SP ID can be number or string
  secondaryText: z.string().nullable().optional(), // usually email or subtitle
  text: z.string().min(1), // display name
});

const SelectedPeopleSchema = z.array(PersonSchema).max(5); // enforce cardinality

export type ValidPerson = z.infer<typeof PersonSchema>;

export interface PeoplePickerFieldProps {
  context: WebPartContext; // SPFx context to ensure tenant and theme alignment
  label?: string;
  required?: boolean;
  maxPeople?: number; // override default of 5
  onChange: (people: ValidPerson[]) => void; // emits validated data only
}

// Memoized to avoid unnecessary re-renders in large forms
const PeoplePickerField: React.FC<PeoplePickerFieldProps> = ({
  context,
  label = "Assign to",
  required = false,
  maxPeople = 5,
  onChange,
}) => {
  // Internal state to show validation feedback
  const [error, setError] = useState<string | null>(null);

  // Enforce hard cap
  const personSelectionLimit = useMemo(() => Math.min(Math.max(1, maxPeople), 25), [maxPeople]);

  // Convert PeoplePicker selections through Zod
  const handleChange = useCallback((items: unknown[]) => {
    // PeoplePicker sends unknown shape; validate strictly before emitting
    const parsed = SelectedPeopleSchema.safeParse(items);
    if (!parsed.success) {
      setError("Invalid selection. Please choose valid users only.");
      onChange([]);
      return;
    }

    // Optional business rule: ensure each user has an email-like secondaryText
    const withEmail = parsed.data.filter(p => (p.secondaryText ?? "").includes("@"));
    if (withEmail.length !== parsed.data.length) {
      setError("Some selections are missing a valid email.");
      onChange([]);
      return;
    }

    setError(null);
    onChange(parsed.data);
  }, [onChange]);

  return (
    <div>
      <label>{label}{required ? " *" : ""}</label>
      {/**
       * PeoplePicker respects SPFx theme through provided context.
       * Use PrincipalType to limit search to users only, avoiding groups for clarity.
       */}
      <PeoplePicker
        context={context}
        titleText={label}
        personSelectionLimit={personSelectionLimit}
        ensureUser={true} // resolves users to the site collection to avoid auth issues
        showHiddenInUI={false}
        principalTypes={[PrincipalType.User]}
        resolveDelay={300} // debounce for performance in large tenants
        onChange={handleChange}
        required={required}
      />

      {/** Live region for accessibility */}
      <div aria-live="polite">{error ? error : ""}</div>
    </div>
  );
};

export default React.memo(PeoplePickerField);

Notes: resolveDelay reduces repeated queries while typing. principalTypes avoids unnecessary group matches unless you require them.

4) Use the field in a web part with validated submit

/* MyWebPartComponent.tsx */
import * as React from "react";
import { useCallback, useState } from "react";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import PeoplePickerField, { ValidPerson } from "./PeoplePickerField";

interface MyWebPartProps { context: WebPartContext; }

const MyWebPartComponent: React.FC<MyWebPartProps> = ({ context }) => {
  const [assignees, setAssignees] = useState<ValidPerson[]>([]);
  const [submitStatus, setSubmitStatus] = useState<"idle" | "saving" | "success" | "error">("idle");

  const handlePeopleChange = useCallback((people: ValidPerson[]) => setAssignees(people), []);

  const handleSubmit = useCallback(async () => {
    try {
      setSubmitStatus("saving");
      // Example: persist only the IDs or emails to a list/Graph to avoid storing PII redundantly
      const payload = assignees.map(p => ({ id: String(p.id), email: p.secondaryText }));
      // TODO: call a secure API (e.g., SPHttpClient to SharePoint list) using current user's context
      await new Promise(r => setTimeout(r, 600)); // simulate network
      setSubmitStatus("success");
    } catch {
      setSubmitStatus("error");
    }
  }, [assignees]);

  return (
    <div>
      <h3>Create Task</h3>
      <PeoplePickerField
        context={context}
        label="Assignees"
        required={true}
        maxPeople={3}
        onChange={handlePeopleChange}
      />
      <button onClick={handleSubmit} disabled={assignees.length === 0 || submitStatus === "saving"}>
        Save
      </button>
      <div aria-live="polite">{submitStatus === "saving" ? "Saving..." : ""}</div>
      <div aria-live="polite">{submitStatus === "success" ? "Saved" : ""}</div>
      <div aria-live="polite">{submitStatus === "error" ? "Save failed" : ""}</div>
    </div>
  );
};

export default MyWebPartComponent;

5) Authentication in SPFx context

SPFx provides delegated authentication via the current user. For Microsoft Graph calls, use MSGraphClientFactory; for SharePoint calls, use SPHttpClient. You do not need to store tokens; SPFx handles tokens and consent. Avoid manual token acquisition unless implementing advanced scenarios.

6) Minimal test to validate the component contract

// PeoplePickerField.test.tsx
import React from "react";
import { render } from "@testing-library/react";
import PeoplePickerField from "./PeoplePickerField";

// Mock SPFx WebPartContext minimally for the control; provide shape your test runner needs
const mockContext = {} as unknown as any; // In real tests, provide a proper mock of the context APIs used by the control

test("renders label and enforces required", () => {
  const { getByText } = render(
    <PeoplePickerField context={mockContext} label="Assignees" required onChange={() => {}} />
  );
  expect(getByText(/Assignees/)).toBeTruthy();
});

Note: In integration tests, mount within an SPFx test harness or mock the PeoplePicker dependency. For unit tests, focus on validation logic paths invoked by onChange.

Best Practices & Security

  • Least privilege permissions. Request only User.ReadBasic.All and People.Read for resolving users. Do not request write scopes unless necessary.
  • Azure RBAC and Microsoft 365 roles. This scenario uses delegated permissions within Microsoft 365; no Azure subscription RBAC role is required. Users need a valid SharePoint license and access to the site. Tenant admin must approve Graph scopes. For directory-read scenarios beyond basics, Directory Readers role may be required by policy.
  • PII hygiene. Persist only identifiers (e.g., user IDs or emails) rather than full profiles. Avoid logging personal data. Mask PII in telemetry.
  • Performance. Use resolveDelay to debounce search. Limit personSelectionLimit to a realistic value (e.g., 3–5). Memoize the field (React.memo) and callbacks (useCallback) to reduce re-renders in complex forms.
  • Accessibility. Provide aria-live regions for validation and submit status. Ensure color contrast via SPFx theming; the PeoplePicker uses SPFx theme tokens when context is provided.
  • Theming. Always pass the SPFx context to ensure the control inherits the current site theme.
  • Error resilience. Wrap parent forms with an error boundary to display a fallback UI if a child component throws.
  • Versioning. Pin dependency versions in package.json to avoid unexpected changes. Regularly update to the latest stable to receive security fixes.
  • No server-side tech references here. Entity Framework patterns such as AsNoTracking are not applicable in SPFx client-side code.

Example package.json pins

{
  "dependencies": {
    "@pnp/spfx-controls-react": "3.23.0",
    "zod": "3.23.8"
  }
}

Optional: Error boundary pattern

import React from "react";

class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
  constructor(props: {}) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() { return { hasError: true }; }
  render() { return this.state.hasError ? <div>Something went wrong.</div> : this.props.children; }
}

export default ErrorBoundary;

Wrap your form: ErrorBoundary around MyWebPartComponent to ensure a graceful fallback.

Summary

  • Implemented a strict, validated People Picker for SPFx with React, Zod, and tenant-aware theming via context.
  • Applied least privilege delegated permissions with admin consent, clear performance tuning, and accessibility patterns.
  • Hardened production readiness through validation-first design, memoization, testing hooks, and pinned dependencies.

Wednesday, 14 January 2026

What’s New in PnP for SPFx: PnPjs v3+, React Controls, and Secure Patterns

PnP for SPFx has evolved with practical updates that reduce bundle size, improve performance, and harden security. The problem: teams migrating or maintaining SPFx solutions are unsure which PnP changes truly matter and how to adopt them safely. The solution: adopt PnPjs v3+ modular imports, leverage updated PnP SPFx React Controls where it makes sense, and implement concrete RBAC permissions with least privilege. The value: smaller bundles, faster pages, and auditable access aligned to enterprise security.

The Problem

Developers building SPFx web parts and extensions need a clear, production-grade path to modern PnP usage. Without guidance, projects risk bloated bundles, brittle permissions, and fragile data access patterns.

Prerequisites

  • Node.js v20+
  • SPFx v1.18+ (Yo @microsoft/sharepoint generator)
  • TypeScript 5+ with strict mode enabled
  • Office 365 tenant with App Catalog and permission to deploy apps
  • PnPjs v3+ and @pnp/spfx-controls-react
  • Optional: PnP PowerShell (latest), Azure CLI if integrating with Azure services

The Solution (Step-by-Step)

1) Adopt PnPjs v3+ with strict typing, ESM, and SPFx behavior

Use modular imports and the SPFx behavior to bind to the current context. Validate runtime data with Zod for resilient web parts.

/* Strict TypeScript example for SPFx with PnPjs v3+ */
import { spfi, SPFI } from "@pnp/sp"; // Core PnPjs factory and interface
import { SPFx } from "@pnp/sp/behaviors/spfx"; // Binds SPFx context as a behavior
import "@pnp/sp/items"; // Bring in list items API surface
import "@pnp/sp/lists"; // Bring in lists API surface
import { z } from "zod"; // Runtime schema validation

// Minimal shape for data we expect from SharePoint
const TaskSchema = z.object({
  Id: z.number(),
  Title: z.string(),
  Status: z.string().optional(),
});

type Task = z.infer<typeof TaskSchema>;

// SPFx helper to create a bound SP instance. This avoids global state and is testable.
export function getSP(context: unknown): SPFI {
  // context should be the WebPartContext or Extension context
  return spfi().using(SPFx(context as object));
}

// Fetch list items with strong typing and runtime validation
export async function fetchTasks(sp: SPFI, listTitle: string): Promise<readonly Task[]> {
  // Select only the fields needed for minimal payloads
  const raw = await sp.web.lists.getByTitle(listTitle).items.select("Id", "Title", "Status")();
  // Validate at runtime to catch unexpected shapes
  const parsed = z.array(TaskSchema).parse(raw);
  return parsed;
}

Why this matters: smaller imports improve tree shaking, and behaviors keep your data layer clean, testable, and context-aware.

2) Use batching and caching behaviors for fewer round-trips

Batch multiple reads to reduce network overhead, and apply caching for read-heavy views.

import { spfi, SPFI } from "@pnp/sp";
import { SPFx } from "@pnp/sp/behaviors/spfx";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import { Caching } from "@pnp/queryable"; // Behavior for query caching

export function getCachedSP(context: unknown): SPFI {
  return spfi().using(SPFx(context as object)).using(
    Caching({
      store: "local", // Use localStorage for simplicity; consider session for sensitive data
      defaultTimeout: 30000, // 30s cache duration; tune to your UX needs
    })
  );
}

export async function batchedRead(sp: SPFI, listTitle: string): Promise<{ count: number; first: string }> {
  // Create a batched instance
  const [batchedSP, execute] = sp.batched();

  // Queue multiple operations
  const itemsPromise = batchedSP.web.lists.getByTitle(listTitle).items.select("Id", "Title")();
  const topItemPromise = batchedSP.web.lists.getByTitle(listTitle).items.top(1).select("Title")();

  // Execute the batch
  await execute();

  const items = await itemsPromise;
  const top = await topItemPromise;

  return { count: items.length, first: (top[0]?.Title ?? "") };
}

Pro-Tip: Combine select, filter, and top to minimize payloads and speed up rendering.

3) Use PnP SPFx React Controls when they save time

Prefer controls that encapsulate complex, well-tested UX patterns. Examples:

  • PeoplePicker for directory-aware selection
  • FilePicker for consistent file selection
  • ListView for performant tabular data
import * as React from "react";
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";

// Strongly typed shape for selected people
export type Person = {
  id: string;
  text: string;
  secondaryText?: string;
};

type Props = {
  onChange: (people: readonly Person[]) => void;
};

export function PeopleSelector(props: Props): JSX.Element {
  return (
    <div>
      <PeoplePicker
        context={(window as unknown as { spfxContext: unknown }).spfxContext}
        titleText="Select people"
        personSelectionLimit={3}
        principalTypes={[PrincipalType.User]}
        showtooltip
        required={false}
        onChange={(items) => {
          const mapped: readonly Person[] = items.map((i) => ({
            id: String(i.id),
            text: i.text,
            secondaryText: i.secondaryText,
          }));
          props.onChange(mapped);
        }}
      />
    </div>
  );
}

Pro-Tip: Keep these controls behind thin adapters so you can swap or mock them in tests without touching business logic.

4) Streamline deployment with PnP PowerShell

Automate packaging and deployment to ensure consistent, auditable releases.

# Install: https://pnp.github.io/powershell/
# Deploy an SPFx package to the tenant app catalog and install to a site
Connect-PnPOnline -Url https://contoso-admin.sharepoint.com -Interactive

# Publish/overwrite SPPKG into the tenant catalog
Add-PnPApp -Path .\sharepoint\solution\my-solution.sppkg -Scope Tenant -Publish -Overwrite

# Install the app to a specific site
Connect-PnPOnline -Url https://contoso.sharepoint.com/sites/ProjectX -Interactive
$pkg = Get-PnPApp | Where-Object { $_.Title -eq "My Solution" }
Install-PnPApp -Identity $pkg.Id -Scope Site -Overwrite

Pro-Tip: Run these commands from CI using OIDC to Azure AD (no stored secrets) and conditional approvals for production sites.

5) Security and RBAC: explicit, least-privilege permissions

Be explicit about the minimal roles required:

  • SharePoint site and list permissions: Read (for read-only web parts), Edit or Contribute (only when creating/updating items). Prefer item- or list-scoped permissions over site-wide.
  • Graph delegated permissions in SPFx: User.Read, User.ReadBasic.All, Sites.Read.All (only if cross-site reads are required). Request via API access in the package solution. Avoid .All scopes unless necessary.
  • Azure service calls via backend API: If your SPFx calls an Azure Function or App Service, secure the backend with Entra ID and assign a Managed Identity to the backend. Grant that identity minimal roles such as Storage Blob Data Reader or Storage Blob Data Contributor on specific storage accounts or containers only.

Pro-Tip: Prefer resource-specific consent to SharePoint or Graph endpoints and scope consents to the smallest set of sites or resources.

6) Add an error boundary for resilient UI

SPFx runs inside complex pages; isolate failures so one component does not break the whole canvas.

import * as React from "react";

type BoundaryState = { hasError: boolean };

export class ErrorBoundary extends React.Component<React.PropsWithChildren<unknown>, BoundaryState> {
  state: BoundaryState = { hasError: false };

  static getDerivedStateFromError(): BoundaryState {
    return { hasError: true };
  }

  componentDidCatch(error: unknown): void {
    // Log to a centralized telemetry sink (e.g., Application Insights)
    // Avoid PII; sanitize messages before sending
    console.error("ErrorBoundary caught:", error);
  }

  render(): React.ReactNode {
    if (this.state.hasError) {
      return <div role="alert">Something went wrong. Please refresh or try again later.</div>;
    }
    return this.props.children;
  }
}

Wrap your data-heavy components with ErrorBoundary and fail gracefully.

7) Modernize imports for tree shaking and smaller bundles

Only import what you use. Avoid star imports.

// Good: minimal surface
import { spfi } from "@pnp/sp";
import "@pnp/sp/items";
import "@pnp/sp/lists";

// Avoid: broad or legacy preset imports that include APIs you don't need
// import "@pnp/sp/presets/all";

Pro-Tip: Run webpack-bundle-analyzer to confirm reductions as you trim imports.

Best Practices & Security

  • Principle of Least Privilege: grant Read before Edit or Contribute; avoid tenant-wide Sites.Read.All unless essential.
  • Runtime validation: use Zod to guard against content type or field drift.
  • Behavior-driven PnPjs: keep SPFx context in a factory; never in globals.
  • Resiliency: add retries/backoff for throttling with PnPjs behaviors; display non-blocking toasts for transient failures.
  • No secrets in client code: if integrating with Azure, call a backend secured with Entra ID; use Managed Identities on the backend instead of keys.
  • Accessibility: ensure controls include aria labels and keyboard navigation.
  • Observability: log warnings and errors with correlation IDs to diagnose issues across pages.

Pro-Tip: For heavy reads, combine batching with narrow select filters and increase cache duration carefully; always provide a user-initiated refresh.

Summary

  • PnPjs v3+ with behaviors, batching, and caching delivers smaller, faster, and cleaner SPFx data access.
  • PnP SPFx React Controls accelerate complex UX while remaining testable behind adapters.
  • Explicit RBAC and runtime validation raise your security bar without slowing delivery.

Sunday, 11 January 2026

Binding Pre-Search on Lookup Fields in Model-Driven Apps Using TypeScript

The Problem

Developers often need to filter lookup fields dynamically in Microsoft Power Apps model-driven forms. The challenge is binding a pre-search event to a lookup control using TypeScript with strong typing, ensuring maintainability and avoiding the use of 'any'.

Prerequisites

  • Node.js v20+
  • TypeScript 5+
  • @types/xrm installed for strong typing
  • Power Apps environment with a model-driven app
  • Appropriate security roles: System Customizer or equivalent with form customization privileges

The Solution (Step-by-Step)

1. Add TypeScript Reference

// Add Xrm type definitions at the top of your file
/// 

2. Implement the Pre-Search Binding

// Function to bind pre-search event on a lookup field
function addPreSearchToLookup(executionContext: Xrm.Events.EventContext): void {
    const formContext = executionContext.getFormContext();

    // Get the lookup control by name
    const lookupControl = formContext.getControl("new_lookupfield");

    if (lookupControl) {
        // Add pre-search event handler
        lookupControl.addPreSearch(() => {
            // Define custom filter XML
            const filterXml = "";

            // Apply the filter to the lookup
            lookupControl.addCustomFilter(filterXml, "entitylogicalname");
        });
    }
}

// Register this function on the form's OnLoad event in the form editor

Explanation:

  • We use addPreSearch to inject a custom filter before the lookup search executes.
  • Strong typing is enforced using Xrm.Controls.LookupControl.
  • No 'any' type is used, ensuring type safety.

Best Practices & Security

  • Type Safety: Always use @types/xrm for strong typing and avoid 'any'.
  • Security Roles: Ensure users have roles like System Customizer or App Maker to customize forms.
  • Principle of Least Privilege: Assign only necessary roles to users who manage form scripts.
  • Deployment Automation: Use Power Platform CLI or Azure DevOps pipelines for ALM. For IaC, consider Azure Bicep to manage environment configurations.
  • Testing: Test the pre-search logic in a sandbox environment before deploying to production.

Pro-Tip: Use lookupControl.addCustomFilter() with specific entity names to avoid applying filters globally, which can cause unexpected behavior.

Summary

  • Bind pre-search events using addPreSearch for dynamic lookup filtering.
  • Enforce strong typing with @types/xrm and avoid 'any'.
  • Secure and automate deployments using Power Platform CLI and Azure IaC tools.