HTTP 405 using ApiFormAsync in Blazor wasm

I am trying to upload FormData in my Blazor WASM client, but I get a HTTP 405 “Method not allowed” error. Any idea what I am doing wrong?

I am running ServiceStack 8.6.1 with .NET8 on Windows

The stacktrace is

   at ServiceStack.JsonApiClient.ThrowWebServiceException[ErrorResponse](HttpResponseMessage httpRes, Object request, String requestUri, Object response) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/JsonApiClient.cs:line 941
   at ServiceStack.JsonApiClient.ThrowResponseTypeException[String](HttpResponseMessage httpRes, Object request, String requestUri, Object response) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/JsonApiClient.cs:line 866
   at ServiceStack.JsonApiClient.ThrowIfError[String](Func`1 fn, HttpResponseMessage httpRes, Object request, String requestUri) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/JsonApiClient.cs:line 828
   at ServiceStack.JsonApiClient.ConvertToResponse[MultiCrudResponse`1](HttpResponseMessage httpRes, String httpMethod, String absoluteUrl, Object request) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/JsonApiClient.cs:line 775
   at ServiceStack.JsonApiClient.<SendFormAsync>d__282`1[[Framework.ServiceModel.TypesNs.CoreNs.MultiCrudResponse`1[[Framework.ServiceModel.TypesNs.AttachmentNs.FileStore, Framework.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Framework.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/JsonApiClient.cs:line 1626
   at ServiceStack.ServiceGatewayAsyncWrappers.<ApiFormAsync>d__17`1[[Framework.ServiceModel.TypesNs.CoreNs.MultiCrudResponse`1[[Framework.ServiceModel.TypesNs.AttachmentNs.FileStore, Framework.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Framework.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/ServiceGatewayExtensions.cs:line 300

Blazor WASM Client code

//TableFileStoreSummary.razor.cs

namespace Framework.Client.Components.Table;

public partial class TableFileStoreSummary : Page.TableComponent<FileStoreSummary>
{
	// FileSelectFileInfo is a Telerik class that provides file data from an upload dialog
	private async Task<string?> UploadFile(FileSelectFileInfo fileInfo)
	{
		var request = new CreateAttachedFile
		{
			AttachedId = AttachedId,
			AttachedSource = AttachedSource,
			AppFile = new FileStore
			{
				FileName = fileInfo.Name,
				FileSize = fileInfo.Size,
				Closed = false,
				AttachedSource = AttachedSource,
				AttachedId = AttachedId
			}
		};

		string mimeType = FileExtensionMimeMapping
			.FirstOrDefault(item => item.Item1.ToLower().Contains(fileInfo.Extension.ToLower()))
			.Item2 ?? string.Empty;

		try
		{
			byte[] byteArray = new byte[fileInfo.Size];
			await using MemoryStream ms = new(byteArray);
			await fileInfo.Stream.CopyToAsync(ms);
			ms.Position = 0;

			using var content = new MultipartFormDataContent()
				.AddParams(request)
				.AddFile(nameof(CreateAttachedFile.AppFile), fileInfo.Name, ms, mimeType);

			Console.WriteLine("Uploading file length: " + ms.Length);
			
			var uploadUrl = request.ToPostUrl();
			
			// **** HTTP 405 on next line ***
			var api = await ApiFormAsync<FileStore>(uploadUrl, content); 
			if (!api.Succeeded)
			{ 
				api.Response.PrintDump();
			}
	
			return "uploaded";
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex);
			return ex.Message;
		}
	}
	
}

ServiceInterface
Works correctly using API Explorer to upload a file, but never gets called my client because of 405 error.

namespace Framework.ServiceInterface.AttachmentNs;

public class FileStoreService(IAutoQueryDb autoQuery) : Service
{
	
	// Does not get called, because of HTTP 405 error
    public FileStore Post(CreateAttachedFile request)
    {
        _ = request ?? throw new ArgumentNullException(nameof(request));

		// code omitted, but work correctly using API Explorer to upload file
	}
	
}

ServiceModel

namespace Framework.ServiceModel.AttachmentNs;

[Route("/api/attachment/filestore/file", "POST")]
[AutoApply(Behavior.AuditCreate)]
public class CreateAttachedFile : IReturn<MultiCrudResponse<FileStore>>, IPost
{
    public Guid AttachedId { get; init; }
    public string AttachedSource { get; init; } = null!;

	[Input(Type = "file")]
	public FileStore AppFile { get; init; } = null!;
}

ServiceModel Type:
namespace Framework.ServiceModel.TypesNs.AttachmentNs;

// DB table to hold file metadata
[Schema("Attachment")]
[DataContract]
public record FileStore 
{
    [AutoId]
    [DataMember, Required]
    public Guid Id { get; init; }

    [DataMember, Required]
    public Guid AttachedId { get; init; }

    [DataMember, Required]
    public string AttachedSource { get; init; } = null!;

    [DataMember, Required]
    public string FileName { get; init; } = null!;

    [DataMember, Required]
    public long FileSize { get; init; }

    [DataMember, Required]
    public DateTime AttachedTstp { get; init; }

    [DataMember, Required]
    public Guid AttachedBy { get; init; }

    [IgnoreOnInsert, IgnoreOnUpdate]
    [DataMember, Required]
    public bool Closed { get; init; }
}

Can you provide the Request and Response HTTP Headers for the failed HTTP File Upload Request?

Also if you’ve not disabled the /api pre-defined route your custom user-defined routes should not be using an /api prefix. Try again after removing the /api prefix or [Route] attribute entirely.

It’s because ApiFormAsync requires a Request DTO not a string, should be:

var api = await ApiFormAsync<FileStore>(request, content); 

Same 405 with the suggested change

What’s the HTTP Headers now?

But the request URL should be “api/attachment/filestore/file” as annotated on the model’s route.

Your custom user-defined routes shouldn’t have an /api prefix.

But this request should work, from the Allow Response Header it seems POST requests are not allowed? It doesn’t look like this request is reaching ServiceStack, you must have some middleware preventing it.

I still use /api prefix in my routes and other Post requests work.

Using API Explorer the file upload works, using a Post request too.

So, I do not understand why this Post request is not allowed.

What’s generating the Allow: GET, HEAD Response Header?

What’s the HTTP Request Headers for the API Explorer Request?

The above error is when running locally on my PC. I will deploy to my dev server (IIS) and see if the POST works there.

Same 405 on IIS.

Also try disabling your CORS Configuration to verify if that’s rejecting the requests.

Disabling CORS did not help.

Am I correct in thinking that the API Explorer does not use the BlazorUtils? If so, could the problem be in the BlazorUtils function ManagedApiFormAsync?

No built-in UIs use Blazor, the issue is the server is rejecting the request with a 405 Response Error.

What are the HTTP headers for the API Explorer request?

API Explorer correctly uploaded a file:

Then you might have already disabled the /api pre-defined route in which case your clients will need to be pre-configured to use the legacy pre-defined routes.

If you’re not calling any other endpoints it would be easiest to set it globally at Startup:

JsonApiClient.DefaultBasePath = "/json/reply/";

Otherwise use client.UseBasePath = "/json/reply/" to configure a client instance.

I will check you advice later, when I have more time.
Thank you for you attention.

1 Like

I managed to get it working by changing the route to match the one used by API Explorer:

//[Route("/api/attachment/filestore/file", "POST")]
[Route("/api/CreateAttachedFile", "POST")]

The POST service is now called, so I am a step further forward, although I would prefer to have it working with my original route.

You’re using a custom User Defined Route to register a fake pre-defined route for the new /api pre-defined routes which your App has presumably disabled.

Why not configure the Service Client to use the legacy pre-defined routes that your App actually uses?