Share via

How can I use local debugging for Cloudscript using Azure Functions after migrating to the Azure Functions isolated worker model?

Kim Strasser 2,556 Reputation points
2026-01-14T14:20:23.2133333+00:00

I added the class ExecuteFunction.cs to my Azure functions project in Visual Studio Code.

Local debugging for Cloudscript using Azure Functions:

https://learn.microsoft.com/en-us/gaming/playfab/live-service-management/service-gateway/automation/cloudscript-af/local-debugging-for-cloudscript-using-azure-functions

ExecuteFunction.cs:

https://github.com/PlayFab/pf-af-devfuncs/blob/main/csharp/ExecuteFunction.cs

But the problem is that ExecuteFunction.cs is no more working after migrating Azure functions from the in-process model to the isolated worker model.

I have already made a few changes in ExecuteFunction.cs but it is not working because I get the following errors:

string callerEntityToken = request.Headers["X-EntityToken"];

Cannot apply indexing with [] to an expression of type 'HttpHeadersCollection'

string body = await DecompressHttpBody(request);

Argument 1: cannot convert from 'Microsoft.Azure.Functions.Worker.Http.HttpRequestData' to 'Microsoft.AspNetCore.Http.HttpRequest'

var azureFunctionUri = ConstructLocalAzureFunctionUri(execRequest.FunctionName, request.Host);

'HttpRequestData' does not contain a definition for 'Host' and no accessible extension method 'Host' accepting a first argument of type 'HttpRequestData' could be found (are you missing a using directive or an assembly reference?)

Content = CompressResponseBody(output, request),                        

          StatusCode = HttpStatusCode.OK

Argument 2: cannot convert from 'Microsoft.Azure.Functions.Worker.Http.HttpRequestData' to 'Microsoft.AspNetCore.Http.HttpRequest'

My ExecuteFunction.cs:

// Copyright (C) Microsoft Corporation. All rights reserved.

namespace PlayFab.AzureFunctions
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using PlayFab.Internal;
    using PlayFab.Json;
    using PlayFab.ProfilesModels;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;


    public class LocalExecuteFunction
    {
        private const string DEV_SECRET_KEY = "PLAYFAB_DEV_SECRET_KEY";
        private const string TITLE_ID = "PLAYFAB_TITLE_ID";
        private const string CLOUD_NAME = "PLAYFAB_CLOUD_NAME";
        private const string _defaultRoutePrefix = "api";
        private static readonly HttpClient httpClient = new HttpClient();

        private readonly ILogger _logger;

        public LocalExecuteFunction(ILoggerFactory loggerFactory)

        {

            _logger = loggerFactory.CreateLogger<LocalExecuteFunction>();

        }

        /// <summary>
        /// A local implementation of the ExecuteFunction feature. Provides the ability to execute an Azure Function with a local URL with respect to the host
        /// of the application this function is running in.
        /// </summary>
        /// <param name="httpRequest">The HTTP request</param>
        /// <param name="log">A logger object</param>
        /// <returns>The function execution result(s)</returns>
        [Function("ExecuteFunction")]
        public async Task<HttpResponseData> ExecuteFunction([HttpTrigger(AuthorizationLevel.Function, "post", Route = "CloudScript/ExecuteFunction")] HttpRequestData request)
        {
            // Extract the caller's entity token
            string callerEntityToken = request.Headers["X-EntityToken"];

            // Extract the request body and deserialize
            string body = await DecompressHttpBody(request);
            var execRequest = PlayFabSimpleJson.DeserializeObject<ExecuteFunctionRequest>(body);
            EntityKey entityKey = null;

            if (execRequest.Entity != null)
            {
                entityKey = new EntityKey
                {
                    Id = execRequest.Entity?.Id,
                    Type = execRequest.Entity?.Type
                };
            }

            // Create a FunctionContextInternal as the payload to send to the target function
            var functionContext = new FunctionContextInternal
            {
                CallerEntityProfile = await GetEntityProfile(callerEntityToken, entityKey),
                TitleAuthenticationContext = new TitleAuthenticationContext
                {
                    Id = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process),
                    EntityToken = await GetTitleEntityToken()
                },
                FunctionArgument = execRequest.FunctionParameter
            };

            // Serialize the request to the azure function and add headers
            var functionRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(functionContext));
            functionRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var azureFunctionUri = ConstructLocalAzureFunctionUri(execRequest.FunctionName, request.Host);

            var sw = new Stopwatch();
            sw.Start();

            // Execute the local azure function
            using (var functionResponseMessage =
                await httpClient.PostAsync(azureFunctionUri, functionRequestContent))
            {
                sw.Stop();
                long executionTime = sw.ElapsedMilliseconds;

                if (!functionResponseMessage.IsSuccessStatusCode)
                {
                    throw new Exception($"An error occured while executing the target function locally: FunctionName: {execRequest.FunctionName}, HTTP Status Code: {functionResponseMessage.StatusCode}.");
                }

                // Extract the response content
                using (var functionResponseContent = functionResponseMessage.Content)
                {
                    // Prepare a response to reply back to client with and include function execution results
                    var functionResult = new ExecuteFunctionResult
                    {
                        FunctionName = execRequest.FunctionName,
                        FunctionResult = await ExtractFunctionResult(functionResponseContent),
                        ExecutionTimeMilliseconds = (int)executionTime,
                        FunctionResultTooLarge = false
                    };

                    // Reply back to client with final results
                    var output = new PlayFabJsonSuccess<ExecuteFunctionResult>
                    {
                        code = 200,
                        status = "OK",
                        data = functionResult
                    };
                    // Serialize the output and return it
                    var outputStr = PlayFabSimpleJson.SerializeObject(output);

                    return new HttpResponseMessage
                    {
                        Content = CompressResponseBody(output, request),
                        StatusCode = HttpStatusCode.OK
                    };
                }
            }
        }

        /// <summary>
        /// Fetch's an entity's profile from the PlayFab server
        /// </summary>
        /// <param name="callerEntityToken">The entity token of the entity profile being fetched</param>
        /// <returns>The entity's profile</returns>
        private static async Task<EntityProfileBody> GetEntityProfile(string callerEntityToken, EntityKey entity)
        {
            // Construct the PlayFabAPI URL for GetEntityProfile
            var getProfileUrl = GetServerApiUri("/Profile/GetProfile");

            // Create the get entity profile request
            var profileRequest = new GetEntityProfileRequest
            {
                Entity = entity
            };

            // Prepare the request headers
            var profileRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(profileRequest));
            profileRequestContent.Headers.Add("X-EntityToken", callerEntityToken);
            profileRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            PlayFabJsonSuccess<GetEntityProfileResponse> getProfileResponseSuccess = null;
            GetEntityProfileResponse getProfileResponse = null;

            // Execute the get entity profile request
            using (var profileResponseMessage =
                    await httpClient.PostAsync(getProfileUrl, profileRequestContent))
            {
                using (var profileResponseContent = profileResponseMessage.Content)
                {
                    string profileResponseString = await profileResponseContent.ReadAsStringAsync();

                    // Deserialize the http response
                    getProfileResponseSuccess =
                        PlayFabSimpleJson.DeserializeObject<PlayFabJsonSuccess<GetEntityProfileResponse>>(profileResponseString);

                    // Extract the actual get profile response from the deserialized http response
                    getProfileResponse = getProfileResponseSuccess?.data;
                }
            }

            // If response object was not filled it means there was an error
            if (getProfileResponseSuccess?.data == null || getProfileResponseSuccess?.code != 200)
            {
                throw new Exception($"Failed to get Entity Profile: code: {getProfileResponseSuccess?.code}");
            }

            return getProfileResponse.Profile;
        }

        /// <summary>
        /// Grabs the developer secret key from the environment variable (expected to be set) and uses it to
        /// ask the PlayFab server for a title entity token.
        /// </summary>
        /// <returns>The title's entity token</returns>
        private static async Task<string> GetTitleEntityToken()
        {
            var titleEntityTokenRequest = new AuthenticationModels.GetEntityTokenRequest();

            var getEntityTokenUrl = GetServerApiUri("/Authentication/GetEntityToken");

            // Grab the developer secret key from the environment variables (app settings) to use as header for GetEntityToken
            var secretKey = Environment.GetEnvironmentVariable(DEV_SECRET_KEY, EnvironmentVariableTarget.Process);

            if (string.IsNullOrEmpty(secretKey))
            {
                // Environment variable was not set on the app
                throw new Exception("Could not fetch the developer secret key from the environment. Please set \"PLAYFAB_DEV_SECRET_KEY\" in your app's local.settings.json file.");
            }

            var titleEntityTokenRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(titleEntityTokenRequest));
            titleEntityTokenRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            titleEntityTokenRequestContent.Headers.Add("X-SecretKey", secretKey);

            using (var titleEntityTokenResponseMessage =
                await httpClient.PostAsync(getEntityTokenUrl, titleEntityTokenRequestContent))
            {
                using (var titleEntityTokenResponseContent = titleEntityTokenResponseMessage.Content)
                {
                    string titleEntityTokenResponseString = await titleEntityTokenResponseContent.ReadAsStringAsync();

                    // Deserialize the http response
                    var titleEntityTokenResponseSuccess =
                        PlayFabSimpleJson.DeserializeObject<PlayFabJsonSuccess<AuthenticationModels.GetEntityTokenResponse>>(titleEntityTokenResponseString);

                    // Extract the actual get title entity token header
                    var titleEntityTokenResponse = titleEntityTokenResponseSuccess.data;

                    return titleEntityTokenResponse.EntityToken;
                }
            }
        }

        /// <summary>
        /// Constructs a function's local url given its name and the current function app's host. Assumes that the
        /// function is located in the same function app as the application host provided.
        /// </summary>
        /// <param name="functionName">The name of the function to construct a URL for</param>
        /// <param name="appHost">The function's application host</param>
        /// <returns>The function's URI</returns>
        private static string ConstructLocalAzureFunctionUri(string functionName, HostString appHost)
        {
            // Assemble the target function's path in the current App
            string routePrefix = GetHostRoutePrefix();
            string functionPath = routePrefix != null ? routePrefix + "/" + functionName
                : functionName;

            // Build URI of Azure Function based on current host
            var uriBuilder = new UriBuilder
            {
                Host = appHost.Host,
                Port = appHost.Port ?? 80,
                Path = functionPath
            };

            return uriBuilder.Uri.AbsoluteUri;
        }

        private static async Task<object> ExtractFunctionResult(HttpContent content)
        {
            string responseContent = await content.ReadAsStringAsync();

            if (!string.IsNullOrWhiteSpace(responseContent))
            {
                // JSON object or array
                if (responseContent.StartsWith("{") || responseContent.StartsWith("["))
                {
                    return PlayFabSimpleJson.DeserializeObject(responseContent);
                }
                // JSON number
                else if (float.TryParse(responseContent, out float f))
                {
                    return f;
                }
                // JSON true or false
                else if (bool.TryParse(responseContent, out bool b))
                {
                    return b;
                }
                else // JSON string
                {
                    return responseContent;
                }
            }

            return null;
        }


        private static string GetServerApiUri(string endpoint)
        {
            var sb = new StringBuilder();

            // Append the title name if applicable
            string title = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process);
            if (!string.IsNullOrEmpty(title))
            {
                sb.Append(title).Append(".");
            }
            // Append the cloud name if applicable
            string cloud = Environment.GetEnvironmentVariable(CLOUD_NAME, EnvironmentVariableTarget.Process);
            if (!string.IsNullOrEmpty(cloud))
            {
                sb.Append(cloud).Append(".");
            }
            // Append base PF API address
            sb.Append("playfabapi.com");

            var uriBuilder = new UriBuilder
            {
                Scheme = "https",
                Host = sb.ToString(),
                Path = endpoint
            };

            return uriBuilder.Uri.AbsoluteUri;
        }

        #region Utility Functions
        private static string ReadAllFileText(string filename)
        {
            var sb = new StringBuilder();

            if (!File.Exists(filename))
            {
                return string.Empty;
            }

            if (sb == null)
            {
                sb = new StringBuilder();
            }
            sb.Length = 0;

            using (var fs = new FileStream(filename, FileMode.Open))
            {
                using (var br = new BinaryReader(fs))
                {
                    while (br.BaseStream.Position != br.BaseStream.Length)
                    {
                        sb.Append(br.ReadChar());
                    }
                }
            }

            return sb.ToString();
        }

        private static string GetHostRoutePrefix()
        {
            string hostFileContent = null;
            string currDir = Directory.GetCurrentDirectory();
            string currDirHostFile = Path.Combine(currDir, "host.json");

            if (File.Exists(currDirHostFile))
            {
                hostFileContent = ReadAllFileText(currDirHostFile);
            }

            var hostModel = PlayFabSimpleJson.DeserializeObject<HostJsonModel>(hostFileContent);
            return hostModel?.extensions?.http?.routePrefix ?? _defaultRoutePrefix;
        }

        private static async Task<string> DecompressHttpBody(HttpRequest request)
        {
            string encoding = request.Headers["Content-Encoding"];

            // Compression was not present and hence attempt to simply read out the body provided
            if (string.IsNullOrWhiteSpace(encoding))
            {
                using (var reader = new StreamReader(request.Body))
                {
                    return await reader.ReadToEndAsync();
                }
            }
            else if (!encoding.ToLower().Equals("gzip"))
            {
                // Only GZIP decompression supported
                throw new Exception($"Unkown compression used on body. Content-Encoding header value: {encoding}. Expecting none or GZIP");
            }

            var responseBytes = StreamToBytes(request.Body);
            // Attempt to decompress the GZIP compressed request body
            using (Stream responseStream = new MemoryStream(responseBytes))
            {
                using (var gZipStream = new GZipStream(responseStream, CompressionMode.Decompress, false))
                {
                    byte[] buffer = new byte[4 * 1024];
                    using (var output = new MemoryStream())
                    {
                        int read;
                        while ((read = gZipStream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            output.Write(buffer, 0, read);
                        }
                        output.Seek(0, SeekOrigin.Begin);
                        return await new StreamReader(output).ReadToEndAsync();
                    }
                }
            }
        }

        private static HttpContent CompressResponseBody(object responseObject, HttpRequest request)
        {
            string responseJson = PlayFabSimpleJson.SerializeObject(responseObject);
            var responseBytes = Encoding.UTF8.GetBytes(responseJson);

            // Get all accepted encodings,
            string encodingsString = request.Headers["Accept-Encoding"];

            // If client doesn't specify accepted encodings, assume identity and respond decompressed
            if (string.IsNullOrEmpty(encodingsString))
            {
                return new ByteArrayContent(responseBytes);
            }

            List<string> encodings = encodingsString.Replace(" ", String.Empty).Split(',').ToList();
            encodings.ForEach(encoding => encoding.ToLower());

            // If client accepts identity explicitly, respond decompressed
            if (encodings.Contains("identity", StringComparer.OrdinalIgnoreCase))
            {
                return new ByteArrayContent(responseBytes);
            }

            // If client accepts gzip, compress
            if (encodings.Contains("gzip", StringComparer.OrdinalIgnoreCase))
            {
                using (var stream = new MemoryStream())
                {
                    using (var gZipStream = new GZipStream(stream, CompressionLevel.Fastest, false))
                    {
                        gZipStream.Write(responseBytes, 0, responseBytes.Length);
                    }
                    responseBytes = stream.ToArray();
                }
                var content = new ByteArrayContent(responseBytes);
                content.Headers.ContentEncoding.Add("gzip");
                return content;
            }

            // If neither identity or gzip, throw error: we support gzip only right now
            throw new Exception($"Unknown compression requested for response. The \"Accept-Encoding\" haeder values was: ${encodingsString}. Only \"Identity\" and \"GZip\" are supported right now.");
        }

        private static byte[] StreamToBytes(Stream input)
        {
            input.Seek(0, SeekOrigin.Begin);
            byte[] buffer = new byte[4 * 1024];
            using (var output = new MemoryStream())
            {
                int read;
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                {
                    output.Write(buffer, 0, read);
                }
                return output.ToArray();
            }
        }
        #endregion
    }

    #region Models
    public class TitleAuthenticationContext
    {
        public string Id;
        public string EntityToken;
    }

    public class FunctionContextInternal : FunctionContextInternal<object>
    {
    }

    public class FunctionContextInternal<TFunctionArgument>
    {
        public TitleAuthenticationContext TitleAuthenticationContext { get; set; }
        public EntityProfileBody CallerEntityProfile { get; set; }
        public TFunctionArgument FunctionArgument { get; set; }
    }

    public class ExecuteFunctionRequest : PlayFabRequestCommon
    {
        public ClientModels.EntityKey Entity { get; set; }

        public string FunctionName { get; set; }

        public object FunctionParameter { get; set; }

        public bool? GeneratePlayStreamEvent { get; set; }
    }

    public class ExecuteFunctionResult : PlayFabResultCommon
    {
        public int ExecutionTimeMilliseconds;
        public string FunctionName;
        public object FunctionResult;
        public bool? FunctionResultTooLarge;
    }

    public class HostJsonModel
    {
        public string version { get; set; }
        public HostJsonExtensionsModel extensions { get; set; }

        public class HostJsonExtensionsModel
        {
            public HostJsonHttpModel http { get; set; }
        }

        public class HostJsonHttpModel
        {
            public string routePrefix { get; set; }
        }
    }
    #endregion
}
Azure Functions
Azure Functions

An Azure service that provides an event-driven serverless compute platform.


Answer accepted by question author

  1. Pravallika KV 15,465 Reputation points Microsoft External Staff Moderator
    2026-02-06T13:14:53.41+00:00

    Hi @Kim Strasser ,

    Thanks for the offline conversation.

    I am summarizing and posting the discussion as answer.

    1. PlayFabCloudScriptAPI.ExecuteFunctionAsync calls a Cloud Script function hosted on PlayFab, not a local function in your VS solution.
    2. As PlayFab Cloud Script functions run on PlayFab’s servers, not on your local machine. If "DeleteAccountSendEmail" is a Cloud Script hosted on PlayFab, Visual Studio cannot hit breakpoints in your local solution unless you are running it locally in a debug environment that simulates PlayFab Cloud Script execution.

    It started working after applying below changes in the code.

    Installed required NuGet Packages and updated .csproj as below:

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <AzureFunctionsVersion>v4</AzureFunctionsVersion>
        <OutputType>Exe</OutputType>
        <ImplicitUsings>enable</ImplicitUsings>
    	<Nullable>annotations</Nullable>
    	<FunctionsWorkerToolPath>$(MSBuildProjectDirectory)\obj\Debug\net8.0\WorkerExtensions\bin\Release\net8.0</FunctionsWorkerToolPath>
      </PropertyGroup>
      <ItemGroup>
    	  <FrameworkReference Include="Microsoft.AspNetCore.App" />
    	  <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.23.0" />
    	  <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.51.0" />
    	  <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.50.0" />
    	  <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
    	  <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" OutputItemType="Analyzer" />
    	  <PackageReference Include="PlayFabAllSDK" Version="1.205.250718" />
      </ItemGroup>
      <ItemGroup>
        <Compile Update="Properties\Resources.Designer.cs">
          <DesignTime>True</DesignTime>
          <AutoGen>True</AutoGen>
          <DependentUpon>Resources.resx</DependentUpon>
        </Compile>
      </ItemGroup>
      <ItemGroup>
        <EmbeddedResource Update="Properties\Resources.resx">
          <Generator>ResXFileCodeGenerator</Generator>
          <LastGenOutput>Resources.Designer.cs</LastGenOutput>
        </EmbeddedResource>
      </ItemGroup>
    	<ItemGroup>
    		<None Update="host.json">
    			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    		</None>
    		<None Update="local.settings.json">
    			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    			<CopyToPublishDirectory>Never</CopyToPublishDirectory>
    		</None>
    	</ItemGroup>
    </Project>
    

    Program.cs:

    using Microsoft.Azure.Functions.Worker.Builder;
    using Microsoft.Extensions.Hosting;
    
    var builder = FunctionsApplication.CreateBuilder(args);
    builder.ConfigureFunctionsWebApplication();
    builder.Build().Run();
    

    Run below command in Powershell(Run as Admin) to enable LongPaths:

    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
    

    Open Package Manager Console in Visual Studio and run below command:

    dotnet restore
    

    Able to debug the function:

    [2026-02-06T11:31:16.064Z] Worker process started and initialized.
    
    Functions:
    
            ChangeDisplayname: [POST] http://localhost:7071/api/ChangeDisplayname
    
            ChangePasswordSendEmail: [POST] http://localhost:7071/api/ChangePasswordSendEmail
    
            ChangePasswordUpdate: [POST] http://localhost:7071/api/ChangePasswordUpdate
    
            ConfirmAccDeletion: [POST] http://localhost:7071/api/ConfirmAccDeletion
    
            DeleteAccountSendEmail: [POST] http://localhost:7071/api/DeleteAccountSendEmail
    
            ExecuteFunction: [POST] http://localhost:7071/api/CloudScript/ExecuteFunction
    
            ForgotPassword: [POST] http://localhost:7071/api/ForgotPassword
    
            GetNameFromClient: [POST] http://localhost:7071/api/GetNameFromClient
    
            LoginAccount: [POST] http://localhost:7071/api/LoginAccount
    
    For detailed output, run func with --verbose flag.
    [2026-02-06T11:31:21.876Z] Host lock lease acquired by instance ID '00000000000000000000000059F66DDD'.
    

    image

    Hope this helps!

    Was this answer helpful?

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Siddhesh Desai 7,055 Reputation points Microsoft External Staff Moderator
    2026-01-14T17:03:09.0333333+00:00

    Hi @Kim Strasser

    Thank you for reaching out to Microsoft Q&A.

    The errors are expected whenever you migrate to .NET isolated worker. In the isolated model:

    Use HttpRequestData / HttpResponseData (not ASP.NET Core’s HttpRequest / HttpResponseMessage).

    Read headers via HttpHeadersCollection (TryGetValues), not request.Headers["Name"].

    Use request.Url for host/port (instead of request.Host).

    Create responses with request.CreateResponse(HttpStatusCode.X) and write to HttpResponseData.Body.

    PlayFab’s tutorial and repo remain valid conceptually (local redirect to your function) but the sample was originally written against the in‑process HTTP types—so you need to adapt it for isolated.

    Fixes mapped to your compile errors

    Header access

    string callerEntityToken = request.Headers.TryGetValues("X-EntityToken", out var vals)
        ? vals?.FirstOrDefault()
        : null;
    

    Body reading / decompression helper

    Change your helper to accept HttpRequestData and read request.Body (a Stream). If the client sends Content-Encoding: gzip, decompress; otherwise read plain text.

    Local URL construction

    Use request.Url.Host and request.Url.Port (and http/https from request.Url.Scheme) to build the URI of the target Azure Function in the same app.

    Response creation & compression

    Create an HttpResponseData via request.CreateResponse(HttpStatusCode.OK). Optionally gzip the body if the client advertises Accept-Encoding: gzip and set Content-Encoding: gzip.

    Isolated-worker version of ExecuteFunction (minimal, working)

    This sample keeps your PlayFab flow (get entity profile, title entity token; invoke target local function; return result) but replaces ASP.NET types and response handling with isolated equivalents.

    
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    using PlayFab.Internal;
    using PlayFab.Json;
    using PlayFab.ProfilesModels;
    
    namespace PlayFab.AzureFunctions
    {
        public class LocalExecuteFunction
        {
            private const string DEV_SECRET_KEY = "PLAYFAB_DEV_SECRET_KEY";
            private const string TITLE_ID = "PLAYFAB_TITLE_ID";
            private const string CLOUD_NAME = "PLAYFAB_CLOUD_NAME";
            private const string DefaultRoutePrefix = "api";
            private static readonly HttpClient httpClient = new HttpClient();
            private readonly ILogger _logger;
            public LocalExecuteFunction(ILoggerFactory loggerFactory) =>
                _logger = loggerFactory.CreateLogger<LocalExecuteFunction>();
    
            [Function("ExecuteFunction")]
            public async Task<HttpResponseData> ExecuteFunction(
                [HttpTrigger(AuthorizationLevel.Function, "post", Route = "CloudScript/ExecuteFunction")]
                HttpRequestData request)
            {
                // 1) Caller token from headers (isolated: HttpHeadersCollection)
                string callerEntityToken = request.Headers.TryGetValues("X-EntityToken", out var vals)
                    ? vals?.FirstOrDefault()
                    : null;
    
                // 2) Read/decompress body (isolated: HttpRequestData)
                string body = await DecompressHttpBodyAsync(request);
                var execRequest = PlayFabSimpleJson.DeserializeObject<ExecuteFunctionRequest>(body);
    
                EntityKey entityKey = null;
                if (execRequest.Entity != null)
                {
                    entityKey = new EntityKey { Id = execRequest.Entity?.Id, Type = execRequest.Entity?.Type };
                }
    
                // 3) Build PlayFab function context
                var functionContext = new FunctionContextInternal
                {
                    CallerEntityProfile = await GetEntityProfile(callerEntityToken, entityKey),
                    TitleAuthenticationContext = new TitleAuthenticationContext
                    {
                        Id = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process),
                        EntityToken = await GetTitleEntityToken()
                    },
                    FunctionArgument = execRequest.FunctionParameter
                };
    
                var functionRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(functionContext));
                functionRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    
                // 4) Construct local URL using request.Url (isolated)
                string targetUrl = ConstructLocalAzureFunctionUri(execRequest.FunctionName, request.Url);
    
                var sw = Stopwatch.StartNew();
                using var targetResponse = await httpClient.PostAsync(targetUrl, functionRequestContent);
                sw.Stop();
    
                if (!targetResponse.IsSuccessStatusCode)
                    throw new Exception($"Local target failed: {execRequest.FunctionName}, status {targetResponse.StatusCode}");
    
                var result = new ExecuteFunctionResult
                {
                    FunctionName = execRequest.FunctionName,
                    FunctionResult = await ExtractFunctionResult(targetResponse.Content),
                    ExecutionTimeMilliseconds = (int)sw.ElapsedMilliseconds,
                    FunctionResultTooLarge = false
                };
    
                var output = new PlayFabJsonSuccess<ExecuteFunctionResult>
                {
                    code = 200, status = "OK", data = result
                };
    
                // 5) Create isolated response; gzip if client asks for it
                var response = request.CreateResponse(HttpStatusCode.OK);
                await WriteCompressedJsonAsync(response, output, request);
                return response;
            }
    
            // --- PlayFab calls (unchanged functional behavior) ---
            private static async Task<EntityProfileBody> GetEntityProfile(string callerEntityToken, EntityKey entity)
            {
                var url = GetServerApiUri("/Profile/GetProfile");
                var req = new StringContent(PlayFabSimpleJson.SerializeObject(new GetEntityProfileRequest { Entity = entity }));
                req.Headers.Add("X-EntityToken", callerEntityToken);
                req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    
                using var httpResp = await httpClient.PostAsync(url, req);
                string json = await httpResp.Content.ReadAsStringAsync();
                var ok = PlayFabSimpleJson.DeserializeObject<PlayFabJsonSuccess<GetEntityProfileResponse>>(json);
                if (ok?.data == null || ok?.code != 200)
                    throw new Exception($"GetEntityProfile failed: code {ok?.code}");
                return ok.data.Profile;
            }
    
            private static async Task<string> GetTitleEntityToken()
            {
                var url = GetServerApiUri("/Authentication/GetEntityToken");
                var secret = Environment.GetEnvironmentVariable(DEV_SECRET_KEY, EnvironmentVariableTarget.Process);
                if (string.IsNullOrEmpty(secret))
                    throw new Exception("Set PLAYFAB_DEV_SECRET_KEY in local.settings.json.");
    
                var content = new StringContent(PlayFabSimpleJson.SerializeObject(new AuthenticationModels.GetEntityTokenRequest()));
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                content.Headers.Add("X-SecretKey", secret);
    
                using var httpResp = await httpClient.PostAsync(url, content);
                string json = await httpResp.Content.ReadAsStringAsync();
                var ok = PlayFabSimpleJson.DeserializeObject<PlayFabJsonSuccess<AuthenticationModels.GetEntityTokenResponse>>(json);
                return ok.data.EntityToken;
            }
    
            // --- Isolated-only helpers below ---
            private static string GetServerApiUri(string endpoint)
            {
                var sb = new StringBuilder();
                var title = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process);
                if (!string.IsNullOrEmpty(title)) sb.Append(title).Append(".");
                var cloud = Environment.GetEnvironmentVariable(CLOUD_NAME, EnvironmentVariableTarget.Process);
                if (!string.IsNullOrEmpty(cloud)) sb.Append(cloud).Append(".");
                sb.Append("playfabapi.com");
                return new UriBuilder("https", sb.ToString(), -1, endpoint).Uri.AbsoluteUri;
            }
    
            private static string GetRoutePrefix()
            {
                var path = Path.Combine(Directory.GetCurrentDirectory(), "host.json");
                if (!File.Exists(path)) return DefaultRoutePrefix;
                var hostFile = File.ReadAllText(path);
                var model = PlayFabSimpleJson.DeserializeObject<HostJsonModel>(hostFile);
                return model?.extensions?.http?.routePrefix ?? DefaultRoutePrefix;
            }
    
            private static string ConstructLocalAzureFunctionUri(string functionName, Uri appUrl)
            {
                string functionPath = $"{GetRoutePrefix()}/{functionName}";
                var b = new UriBuilder
                {
                    Scheme = appUrl.Scheme,
                    Host = appUrl.Host,
                    Port = appUrl.IsDefaultPort ? (appUrl.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) ? 443 : 80) : appUrl.Port,
                    Path = functionPath
                };
                return b.Uri.AbsoluteUri;
            }
    
            private static async Task<string> DecompressHttpBodyAsync(HttpRequestData request)
            {
                string encoding = request.Headers.TryGetValues("Content-Encoding", out var v) ? v.FirstOrDefault() : null;
                if (string.IsNullOrWhiteSpace(encoding))
                {
                    using var reader = new StreamReader(request.Body);
                    return await reader.ReadToEndAsync();
                }
                if (!encoding.Equals("gzip", StringComparison.OrdinalIgnoreCase))
                    throw new Exception($"Unsupported body compression: {encoding}. Only gzip is supported.");
    
                byte[] bytes = StreamToBytes(request.Body);
                using var ms = new MemoryStream(bytes);
                using var gz = new GZipStream(ms, CompressionMode.Decompress);
                using var reader2 = new StreamReader(gz);
                return await reader2.ReadToEndAsync();
            }
    
            private static async Task WriteCompressedJsonAsync(HttpResponseData response, object obj, HttpRequestData request)
            {
                string json = PlayFabSimpleJson.SerializeObject(obj);
                byte[] payload = Encoding.UTF8.GetBytes(json);
                response.Headers.Add("Content-Type", "application/json");
    
                string accept = request.Headers.TryGetValues("Accept-Encoding", out var v) ? v.FirstOrDefault() : null;
                if (string.IsNullOrEmpty(accept) || accept.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) < 0)
                {
                    await response.Body.WriteAsync(payload, 0, payload.Length);
                    return;
                }
    
                using var outStream = new MemoryStream();
                using (var gz = new GZipStream(outStream, CompressionLevel.Fastest, leaveOpen: true))
                    gz.Write(payload, 0, payload.Length);
                var compressed = outStream.ToArray();
                response.Headers.Add("Content-Encoding", "gzip");
                await response.Body.WriteAsync(compressed, 0, compressed.Length);
            }
    
            private static byte[] StreamToBytes(Stream input)
            {
                input.Seek(0, SeekOrigin.Begin);
                using var output = new MemoryStream();
                input.CopyTo(output);
                return output.ToArray();
            }
    
            private static async Task<object> ExtractFunctionResult(HttpContent content)
            {
                string s = await content.ReadAsStringAsync();
                if (string.IsNullOrWhiteSpace(s)) return null;
                if (s.StartsWith("{") || s.StartsWith("[")) return PlayFabSimpleJson.DeserializeObject(s);
                if (float.TryParse(s, out var f)) return f;
                if (bool.TryParse(s, out var b)) return b;
                return s;
            }
        }
    
        // Models unchanged...
    }
    
    
    

    *.csproj (key bits)

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework> <!-- or net7.0 -->
        <AzureFunctionsVersion>v4</AzureFunctionsVersion>
        <OutputType>Exe</OutputType>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.*" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.*" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.*" OutputItemType="Analyzer" />
      </ItemGroup>
    </Project>
    

    Program.cs

    using Microsoft.Extensions.Hosting;
     
    var host = new HostBuilder()
        .ConfigureFunctionsWorkerDefaults()
        .Build();
    host.Run();
    

    local.settings.json

    {
      "IsEncrypted": false,
      "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "PLAYFAB_TITLE_ID": "B55D",
        "PLAYFAB_DEV_SECRET_KEY": "xxxxxxxxxx"
      
    

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.