0

I have a Blazor WASM app deployed to an Azure Static Web app with a managed .NET 8 isolated Azure Function API. When calling the API directly from Postman using the auto-generated web app URL + the API route I receive the expected response and can see a log in Application Insights. However when I call the same endpoint from within the deployed WASM application I receive an net::ERR_CONNECTION_REFUSED response and a request isn't recorded in Application Insights. I have confirmed that this is functioning as expected locally.

The successful postman request: Successful postman request

The Log in application insights: Successful request in Azure Application Insights

My appsettings.json file that is packaged with the Blazor WASM app:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiBase": "http://localhost:7071"
}

The launchSettings.json of the Azure Function App API:

{
  "profiles": {
    "Project.Api": {
      "commandName": "Project",
      "commandLineArgs": "--port 7071",
      "launchBrowser": false
    }
  }
}

The Azure Function app is deployed to the Azure Static Web App and the SendContactEmailFunction function is registered in Azure: Deployed azure function app with list of functions

The error I receive in the console from the deployed Blazor WASM app: Blazor WASM app console error

How the HttpClient is registered:

var apiBase = builder.Configuration["ApiBase"]
              ?? throw new ArgumentException("API base address not found in config.");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBase) });

The code that is calling the API function:

var response = await HttpClient.PostAsJsonAsync("/api/send-contact-email", sendEmailRequest);

The Azure function code:

public class SendContactEmailFunction(ILoggerFactory loggerFactory, ISender sender)
{
    private readonly ILogger logger = loggerFactory.CreateLogger<SendContactEmailFunction>();

    [Function(nameof(SendContactEmailFunction))]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "send-contact-email")] HttpRequestData req,
        FunctionContext executionContext, CancellationToken cancellationToken)
    {
        logger.LogInformation($"{nameof(SendContactEmailFunction)} function received a request.");

        var requestBody = await new StreamReader(req.Body).ReadToEndAsync(cancellationToken);
        var request = JsonConvert.DeserializeObject<SendContactEmailRequest>(requestBody);

        HttpResponseData response;

        try
        {
            if (request == null)
            {
                throw new ApplicationException("Unable to deserialize response.");
            }

            var emailCommand = new SendContactEmailCommand(
                request.Name,
                request.FromEmail,
                request.Message,
                request.RecaptchaResponse);

            var sendEmailResult = await sender.Send(emailCommand, cancellationToken);

            if (sendEmailResult.IsFailure)
            {
                logger.LogError("Send email command failed. Error: {Error}", sendEmailResult.Error.Message);

                response = req.CreateResponse(HttpStatusCode.BadRequest);

                var error = new ErrorResponse
                {
                    Code = (int)HttpStatusCode.BadRequest,
                    Message = sendEmailResult.Error.Message
                };

                await response.WriteAsJsonAsync(error, cancellationToken);
            }
            else
            {
                response = req.CreateResponse(HttpStatusCode.OK);

                await response.WriteAsJsonAsync("", cancellationToken: cancellationToken);
            }
        }
        catch (Exception e)
        {
            logger.LogError(e, "Exception occured. Error: '{Message}'", e.Message);

            response = req.CreateResponse(HttpStatusCode.InternalServerError);
            
            await response.WriteAsJsonAsync(ErrorResponse.Generic, cancellationToken);
        }

        return response;
    }
}
5
  • If it is possible can you share your repository here. Commented Mar 11, 2024 at 6:47
  • Unfortunately not @AsleshaKantamsetti, it's a private repo. Commented Mar 11, 2024 at 7:49
  • Is it possible to share SendContactEmailFunction Commented Mar 11, 2024 at 7:53
  • @AsleshaKantamsetti I've updated the question with the function code but as I said, it's being executed fine when called directly - just not from the Blazor app. Commented Mar 11, 2024 at 22:14
  • Are you looking for this Commented Mar 12, 2024 at 4:44

2 Answers 2

0

I'm just learning how to program with C#/Blazor so take this as you will. Why would the app be using the ApiBase of http://localhost:7071 when running as the Azure SWA? Shouldn't that be relative such that the static web app url is substituted?

Sign up to request clarification or add additional context in comments.

1 Comment

Generally you'd be right in a more traditional deployment, but Azure Static Web App managed function APIs operate on the localhost of the SWA and are accessible via the /api prefix only on the generated SWA domain. CORs should be automatically configured to block any requests from outside that domain.
0

I was able to get this working but I'm not quite sure what solved this in the end out of the two things I tried:

  1. I deleted and recreated the Azure Static Web App.

  2. I added a conditional check for hosting environment in the Program.cs file for the API base address. If in development - user the Function App URI from the appsettings.json. If not use the hosting environment base address:

    string apiBase; if (builder.HostEnvironment.IsDevelopment()) { apiBase = builder.Configuration["ApiBase"] ?? throw new ArgumentException("API base address not found in config."); } else { apiBase = builder.HostEnvironment.BaseAddress; }

    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBase) });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.