Back-end implementation guide (preview)

This Microsoft Fabric developer sample repository serves as a starting point for building applications that require integration with various services, including Workload and Lakehouse. This guide helps you set up the environment and configure the necessary components to get started. This article outlines the key components and outlines their roles in the architecture.

Frontend

The frontend is where you manage the user experience (UX) and behavior. It communicates with the Fabric frontend portal via an iFrame, facilitating seamless interaction with the user.

Backend

The backend stores both data and metadata. It utilizes CRUD operations to create Workload (WL) items along with metadata, and executes jobs to populate data in storage. The communication between the frontend and backend is established through public APIs.

Azure Relay and DevGateway

Azure Relay facilitates interactions between the backend development box and the Fabric backend in local (development) mode. The DevGateway.exe utility handles the workload's side of Azure Relay channel and manages the registration of the workload local instance with Fabric in the context of a specific capacity. The utility ensures that the workload is available in all workspaces assigned to that capacity and handles the deregistration when stopped.

Dev gateway

In local mode, the workload operates on the developer's machine. Workload API calls from Fabric to the workload are channeled through Azure Relay. The workload's side of the Azure Relay channel is managed by the DevGateway command-line utility. Workload Control API calls are made directly from the workload to Fabric, and don't require the Azure Relay channel. The DevGateway utility also manages the registration of the workload's local (development) instance with Fabric within a specific capacity context, making the workload accessible in all workspaces assigned to that capacity. Terminating the DevGateway utility automatically removes the workload instance registration.

Lakehouse integration

The workload development kit architecture integrates seamlessly with Lakehouse, allowing operations such as saving, reading, and fetching data. The interaction is facilitated through Azure Relay and the Fabric SDK, ensuring secure and authenticated communication.

Authentication and security

Microsoft Entra ID is used for secure authentication, ensuring that all interactions within the architecture are authorized and secure.

Th development kit overview provides a glimpse into our architecture. For more information on project configuration, guidelines, and getting started, see the respective sections in this README.

Diagram showing how Fabric SDK integrated into Fabric.

The frontend establishes communication with the Fabric frontend portal via an iFrame. The portal, in turn, interacts with the Fabric backend by making calls to its exposed public APIs.

For interactions between the backend development box and the Fabric backend, the Azure Relay serves as a conduit. Additionally, the backend development box seamlessly integrates with Lakehouse, performing operations such as saving, reading, and fetching data from this resource. The communication is facilitated by using Azure Relay and the Fabric Software Development Kit (SDK) installed on the back end development box.

The authentication for all communication within these components is ensured through Microsoft Entra. Entra provides a secure and authenticated environment for the interactions between the frontend, backend, Azure Relay, Fabric SDK, and Lakehouse.

Prerequisites

Ensure that the NuGet Package Manager is integrated into your Visual Studio installation. This tool is required for streamlined management of external libraries and packages essential for our project.

NuGet package management

  • <NuspecFile>Packages\manifest\ManifestPackage.nuspec</NuspecFile>: This property specifies the path to the NuSpec file used for creating the NuGet package. The NuSpec file contains metadata about the package, such as its ID, version, dependencies, and other relevant information.

  • <GeneratePackageOnBuild>true</GeneratePackageOnBuild>: When set to true, this property instructs the build process to automatically generate a NuGet package during each build. This property is useful to ensure that the package is always up-to-date with the latest changes in the project.

  • <IsPackable>true</IsPackable>: When set to true, this property indicates that the project is packable, meaning it can be packaged into a NuGet package. It's an essential property for projects intended to produce NuGet packages during the build process.

The generated NuGet package is located in the src\bin\Debug directory after the build process.

Dependencies

  • The backend boilerplate depends on the following Azure SDK packages:

    • Azure.Core
    • Azure.Identity
    • Azure.Storage.Files.DataLake
  • The Microsoft Identity package

  • Our Software Development Kit (SDK) to linking the project to Fabric. The SDK resides in the repository in src/packages/fabric. To configure the NuGet Package Manager, specify the path in the 'Package Sources' section before the build process.

 <Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <BuildDependsOn>PreBuild</BuildDependsOn>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <IsPackable>true</IsPackable>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <NuspecFile>Packages\manifest\ManifestPackageRelease.nuspec</NuspecFile>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <NuspecFile>Packages\manifest\ManifestPackageDebug.nuspec</NuspecFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Azure.Core" Version="1.36.0" />
    <PackageReference Include="Azure.Identity" Version="1.10.4" />
    <PackageReference Include="Azure.Storage.Files.DataLake" Version="12.14.0" />
    <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.5" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
    <PackageReference Include="Microsoft.Identity.Client" Version="4.58.1" />
    <PackageReference Include="Microsoft.IdentityModel.Protocols" Version="6.30.1" />
    <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.30.1" />
    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.30.1" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
  </ItemGroup>

  <ItemGroup>
    <Folder Include="Properties\ServiceDependencies\" />
  </ItemGroup>

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="Packages\manifest\Fabric_Extension_BE_Boilerplate_WorkloadManifestValidator.exe Packages\manifest\WorkloadManifest.xml .\Packages\manifest\" />
    <Error Condition="Exists('.\Packages\manifest\ValidationErrors.txt')" Text="WorkloadManifest validation error" File=".\Packages\manifest\ValidationErrors.txt" />
  </Target>

</Project>

Getting Started

To set up the boilerplate sample project on your local machine, follow these steps:

  1. Clone the Boilerplate: git clone https://github.com/microsoft/Microsoft-Fabric-developer-sample.git

  2. Open the solution in Visual Studio 2022.

  3. Set up an app registration by following instructions on the Authentication guide. Ensure that both your Frontend and Backend projects have the necessary setup described in the guide. Microsoft Entra is employed for secure authentication, ensuring that all interactions within the architecture are authorized and secure.

  4. Update the One Lake DFS Base URL: Depending on your Fabric environment, you can update the OneLakeDFSBaseURL within the src\Constants folder. The default is onelake.dfs.fabric.microsoft.com but this can be updated to reflect the environment you are on. More information on the DFS paths can be found in the One Lake documentation

  5. Setup Workload Configuration

    • Copy workload-dev-mode.json from src/Config to C:\
    • In the workload-dev-mode.json file, update the following fields to match your configuration:
      • CapacityGuid: Your Capacity ID. This can be found within the Fabric Portal under the Capacity Settings of the Admin portal.
      • ManifestPackageFilePath: The location of the manifest package. When you build the solution, it saves the manifest package within src\bin\Debug. More details on the manifest package can be found in the later steps.
      • WorkloadEndpointURL: Workload Endpoint URL.
    • In the Packages/manifest/WorkloadManifest.xml file, update the following fields to match your configuration:
      • <AppId>: Client ID (Application ID) of the workload Entra application.
      • <RedirectUri>: Redirect URIs. This can be found in your app registration that you created under 'Authentication' section.
      • <ResourceId>: Audience for the incoming Entra tokens. This information can be found in your app registration that you created under 'Expose an API' section.
    • In the src/appsettings.json file, update the following fields to match your configuration:
      • PublisherTenantId: The ID of the workload publisher tenant.
      • ClientId: Client ID (AppId) of the workload Entra application.
      • ClientSecret: The secret for the workload Entra application.
      • Audience: Audience for incoming Entra tokens. This information can be found in your app registration that you created under "Expose an API" section. This is also referred to as the Application ID URI.
  6. Generate a manifest package. To generate a manifest package file, build Fabric_Extension_BE_Boilerplate. This runs a three step process to generate the manifest package file:

    1. Trigger Fabric_Extension_BE_Boilerplate_WorkloadManifestValidator.exe on workloadManifest.xml in Packages\manifest\files. You can find the code of the validation process in \workloadManifestValidator directory. if the validation fails, an error file is generated.
    2. If an error file exists, the build fails with WorkloadManifest validation error. Double select on the error in VS studio to see the error file.
    3. After successful validation, pack the WorkloadManifest.xml and FrontendManifest.json files into ManifestPackage.1.0.0.nupkg. The resulting package is in src\bin\Debug.

    Copy the ManifestPackage.1.0.0.nupkg file to the path defined in the workload-dev-mode.json configuration file.

  7. Program.cs is the entry point and startup script for your application. In this file, you can configure various services, initialize the application, and start the web host.

  8. Build to ensure your project can access the required dependencies for compilation and execution.

  9. Download the DevGateway from Microsoft's Download Center

  10. Run the Microsoft.Fabric.Workload.DevGateway.exe application located in the DevGateway folder. Sign in with a user that has capacity admin privileges to the capacity you defined in workload-dev-mode.json (CapacityGuid). Upon the initialization of the workload, an authentication prompt appears.

    Screenshot of Microsoft sign in page.

    After authentication, external workloads establish communication with the Fabric back-end through Azure Relay. This process involves relay registration and communication management, facilitated by a designated Proxy node. Furthermore, the package containing the workload manifest is uploaded and published.

    At this stage, Fabric has knowledge of the workload, encompassing its allocated capacity.

    Monitoring for potential errors can be seen in the console.

    If you don't get any errors, then the connection is established, registration is successfully executed, and the workload manifest was systematically uploaded.

    Screenshot of connection loading without any errors.

  11. Change your startup project in Visual Studio to the Boilerplate project and select Run.

    Screenshot of UI for startup project in Visual Studio.

Working with the Boilerplate

Code generation

We use the workload Boilerplate C# ASP.NET Core sample to explain how to build a workload with REST APIs. Starts with generating server stubs and contract classes based on the Workload API Swagger specification. You can generate them using any of several Swagger code generation tools. Our Boilerplate sample uses NSwag. The sample contains GenerateServerStub.cmd command line script, which wraps NSwag code generator. The script takes a single parameter, which is a full path to NSwag installation directory. It also expects to find the Swagger definition file (swagger.json) and configuration file (nswag.json) next to it.

Executing this script produces a C# file WorkloadAPI_Generated.cs. The contents of this file can be logically divided into three parts, as follows.

ASP.NET Core stub controllers

ItemLifecycleController and JobsController classes are thin implementations of ASP.NET Core controllers for two subsets of Workload API: item lifecycle management and jobs. These classes plug into ASP.NET Core HTTP pipeline, and serve as the entry points for API methods, defined in the Swagger specification. These classes forward the calls to the "real" implementation, provided by the workload.

This is an example of the CreateItem method:

/// <summary>
/// Called by Microsoft Fabric for creating a new item.
/// </summary>
/// <remarks>
/// Upon item creation Fabric performs some basic validations, creates the item with 'provisioning' state and calls this API to notify the workload. The workload is expected to perform required validations, store the item metadata, allocate required resources, and update the Fabric item metadata cache with item relations and ETag. To learn more see [Microsoft Fabric item update flow](https://updateflow).
/// <br/>
/// <br/>This API should accept [SubjectAndApp authentication](https://subjectandappauthentication).
/// <br/>
/// <br/>##Permissions
/// <br/>Permissions are checked by Microsoft Fabric.
/// </remarks>
/// <param name="workspaceId">The workspace ID.</param>
/// <param name="itemType">The item type.</param>
/// <param name="itemId">The item ID.</param>
/// <param name="createItemRequest">The item creation request.</param>
/// <returns>Successfully created.</returns>
[Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("workspaces/{workspaceId}/items/{itemType}/{itemId}")]
public System.Threading.Tasks.Task CreateItem(System.Guid workspaceId, string itemType, System.Guid itemId, [Microsoft.AspNetCore.Mvc.FromBody] CreateItemRequest createItemRequest)
{

	return _implementation.CreateItemAsync(workspaceId, itemType, itemId, createItemRequest);
}

Interfaces for workload implementation

IItemLifecycleController and IJobsController are interfaces for the previously mentioned "real" implementations. They define the same methods, which the controllers implement.

Definition of contract classes

C# contract classes used by the APIs.

Implementation

The next step after generating code is implementing the IItemLifecycleController and IJobsController interfaces. In the Boilerplate sample, ItemLifecycleControllerImpl and JobsControllerImpl implement these interfaces.

For example, this code is the implementation of CreateItem API:

/// <inheritdoc/>
public async Task CreateItemAsync(Guid workspaceId, string itemType, Guid itemId, CreateItemRequest createItemRequest)
{
	var authorizationContext = await _authenticationService.AuthenticateControlPlaneCall(_httpContextAccessor.HttpContext);
	var item = _itemFactory.CreateItem(itemType, authorizationContext);
	await item.Create(workspaceId, itemId, createItemRequest);
}

Handling item payload

Several API methods accept various types of "payload" as part of the request body, or return them as part of the response. For example, CreateItemRequest has creationPayload property.

"CreateItemRequest": {
	"description": "Create item request content.",
	"type": "object",
	"additionalProperties": false,
	"required": [ "displayName" ],
	"properties": {
	"displayName": {
		"description": "The item display name.",
		"type": "string",
		"readOnly": false
	},
	"description": {
		"description": "The item description.",
		"type": "string",
		"readOnly": false
	},
	"creationPayload": {
		"description": "Creation payload specific to the workload and item type, passed by the item editor or as Fabric Automation API parameter.",
		"$ref": "#/definitions/CreateItemPayload",
		"readOnly": false
	}
	}
}

The types for these "payload" properties are defined in the Swagger specification. There's a dedicated type for every kind of payload. These types don't define any specific properties and allow any property to be included. For example, this is CreateItemPayload type:

"CreateItemPayload": {
	"description": "Creation payload specific to the workload and item type.",
	"type": "object",
	"additionalProperties": true
}

The generated C# contract classes are defined as partial and have a dictionary with properties.

/// <summary>
/// Creation payload specific to the workload and item type.
/// </summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class CreateItemPayload
{
	private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

	[Newtonsoft.Json.JsonExtensionData]
	public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
	{
		get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
		set { _additionalProperties = value; }
	}
}

The code can use this dictionary for reading and returning properties. However, a better approach is to define specific properties with corresponding types and names. This can be easily achieved because of the 'partial' declaration on the generated classes.

For example, CreateItemPayload.cs file contains a complementary definition for CreateItemPayload class, which adds Item1Metadata property.

namespace Fabric_Extension_BE_Boilerplate.Contracts.FabricAPI.Workload
{
    /// <summary>
    /// Extend the generated class by adding item-type-specific fields.
    /// In this sample every type will have a dedicated property. Alternatively, polymorphic serialization could be used.
    /// </summary>
    public partial class CreateItemPayload
    {
        [Newtonsoft.Json.JsonProperty("item1Metadata", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public Item1Metadata Item1Metadata { get; init; }
    }
}

However, if the workload supports multiple item types, CreateItemPayload class needs to be able to handle different types of creation payload, one per item type. There are two ways to do this. The simpler way, used by the Boilerplate sample, is to define multiple optional properties, each representing the creation payload for a different item type. Every request then has just one of these properties sets, according to the item type being created. Alternatively, you could implement polymorphic serialization, but it isn't demonstrated in the sample because it doesn't provide any significant benefits.

For example, for supporting two item types this class definition would need to be extended as follows:

namespace Fabric_Extension_BE_Boilerplate.Contracts.FabricAPI.Workload
{
    public partial class CreateItemPayload
    {
        [Newtonsoft.Json.JsonProperty("item1Metadata", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public Item1Metadata Item1Metadata { get; init; }

        [Newtonsoft.Json.JsonProperty("item2Metadata", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public Item2Metadata Item2Metadata { get; init; }
    }	
}

Note

The "payload" sent to the workload is generated by the client. It could be the item editor iFrame or Fabric Automation REST API. The client is responsible for sending the correct payload and matching the item type. The workload is responsible for verification. Fabric treats this payload as an opaque object and only transfers it from the client to the workload. Similarly, for a payload returned by the workload to the client, it is workload's and client's responsibility to handle the payload correctly.

For example, this code shows how the Boilerplate sample Item1 implementation handles that:

protected override void SetDefinition(CreateItemPayload payload)
{
	if (payload == null)
	{
		Logger.LogInformation("No payload is provided for {0}, objectId={1}", ItemType, ItemObjectId);
		_metadata = Item1Metadata.Default.Clone();
		return;
	}

	if (payload.Item1Metadata == null)
	{
		throw new InvalidItemPayloadException(ItemType, ItemObjectId);
	}

	if (payload.Item1Metadata.Lakehouse == null)
	{
		throw new InvalidItemPayloadException(ItemType, ItemObjectId)
			.WithDetail(ErrorCodes.ItemPayload.MissingLakehouseReference, "Missing Lakehouse reference");
	}

	_metadata = payload.Item1Metadata.Clone();
}

Migration from code developed based on first developers drop

The Boilerplate sample was updated to work with REST APIs. Partners who start using the sample only now and those who used it before without making any customizations, can seamlessly start using the new version. Partners who customized the sample or used it as a reference for implementing their own workload need to make similar changes to their code. The following Boilerplate sample PR can be used as a reference for the required changes: Adapt the sample for working with REST API defined in Swagger without dependency on .NET SDK.

Troubleshooting and Debugging

Known Issues and Solutions

Missing Client Secret

Error:

Microsoft.Identity.Client.MsalServiceException: A configuration issue is preventing authentication. Check the error message from the server for details. You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details.

Original exception: AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'app_guid'.

Resolution: Make sure you have the correct client secret in appsettings.json.

Error:

Microsoft.Identity.Client.MsalUiRequiredException: AADSTS65001: The user or administrator didn't consent to use the application with ID '4e691b14-bffe-456c-af9f-4efdfa12ed52' named 'childofmsaapp'. Send an interactive authorization request for this user and resource.

Resolution: In the artifact editor, navigate to the bottom and select Navigate to Authentication Page. Under Scopes write .default and select Get Access token. Approve consent in the popped-up dialog.

Artifact creation fails due to capacity selection

Error: PriorityPlacement: There are no available core services for priority placement only 'name','guid','workload-name'.

Resolution: You might be using a user that only has access to Trial capacity. Make sure you're using a capacity that you have access to.

File creation failure with 404 (NotFound) error

Error:

Creating a new file failed for filePath: 'workspace-id'/'lakehouse-id'/Files/data.json. Error: Response status code doesn't indicate success: 404 (NotFound). Resolution: Ensure you're using the correct OneLake DFS URL for your environment.

Resolution: Make sure you're working with the OneLake DFS URL that fits your environment. For example, if you work with PPE environment, change EnvironmentConstants.OneLakeDFSBaseUrl in Constants.cs to the appropriate URL.

Debugging

When troubleshooting various operations, you can set breakpoints in the code to analyze and debug the behavior. Follow these steps for effective debugging:

  1. Open the code in your development environment.
  2. Navigate to the relevant operation handler function (for example, OnCreateFabricItemAsync for CRUD operations or an endpoint in a controller for Execute operations).
  3. Place breakpoints at specific lines where you want to inspect the code.
  4. Run the application in debug mode.
  5. Trigger the operation from the frontend (FE) that you want to debug.

The debugger pause execution at the specified breakpoints, enabling you to examine variables, step through code, and identify issues.

Screenshot of sample program with breakpoints for debugging.

Workspace

If you try to run the Sample to make changes on the backend be sure you are in a named workspace, and not in the default My Workspace. Otherwise, you might get this error:

Screenshot of UI for naming a sample workload item.

  1. Switch to a named workspace and leave the default My workspace:

    Screenshot of UI for creating sample workload.

  2. From the correct workspace, load the sample workload and proceed with the tests:

    Screenshot of UI for creating sample workload item.

Contribute

We welcome contributions to this project. If you find any issues or want to add new features, follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes and commit them.
  4. Push your changes to your forked repository.
  5. Create a pull request with a clear description of your changes.