Running a pure F# Web API on Azure Web Sites by Mark Seemann
This post explains how to make a pure F# implementation of an ASP.NET Web API run in an Azure Web Site.
In my previous post, I explained how to create an ASP.NET Web API project entirely in F#, without relying on a C# or VB host project. At the end of that article, I had a service which can be launched from Visual Studio 2012 and run in IIS Express, but it didn't run in Azure Web Sites. In this post, I'll explain how to make this possible.
Deploying to Azure #
The first problem to solve is how to even deploy the Web Project to Azure in the first place. Because the project is a 'real' Visual Studio Web Project, it's possible to right-click on the project and select "Publish..." This brings up the dialog for publishing a Web Project to Azure, so that seems promising.
However, actually attempting to do so soon produces this error message:
Exception in executing publishing : The method or operation is not implemented.Apparently, someone in Microsoft chose to violate the Liskov Substitution Principle...
Another, and, as it turns out, ultimately more productive, deployment options is to deploy via Git. Fortunately, I already kept a Git repository for the code, in order to make it easier for me to back out, if my experiments took me in wrong directions (which did happen a couple of times). Thus, I created the Web Site on the Azure portal, and configured it with a Git repository to which I can push.
This should enable me to simply go
$ git push azure master
in order to deploy to my new Azure Web Site. Unfortunately, it didn't quite work:
$ git push azure master Counting objects: 113, done. Delta compression using up to 4 threads. Compressing objects: 100% (101/101), done. Writing objects: 100% (113/113), 1.41 MiB | 22.00 KiB/s, done. Total 113 (delta 34), reused 0 (delta 0) remote: Updating branch 'master'. remote: Updating submodules. remote: Preparing deployment for commit id 'dd501baeaa'. remote: Generating deployment script. remote: . remote: info: Executing command site deploymentscript remote: info: Project file path: .\FebApi\FebApi.fsproj remote: info: Solution file path: .\FebApi.sln remote: info: Generating deployment script for .NET Web Application remote: info: Generated deployment script files remote: info: site deploymentscript command OK remote: Running deployment command... remote: Handling .NET Web Application deployment. remote: ..... remote: FebApi -> C:\DWASFiles\Sites\FebApi\VirtualDirectory0\site\repository\FebApi\bin\Release\FebApi.dll remote: C:\DWASFiles\Sites\FebApi\VirtualDirectory0\site\repository\FebApi\FebApi.fsproj : error MSB4057: The target "pipelinePreDeployCopyAllFilesToOneFolder" does not exist in the project. remote: An error has occurred during web site deployment. remote: remote: Error - Changes committed to remote repository but your website not updated. To https://******@febapi.scm.azurewebsites.net:443/FebApi.git * [new branch] master -> master
More work apparently remained.
Import MSBuild projects #
As you can tell from the error message, the "target "pipelinePreDeployCopyAllFilesToOneFolder" does not exist in the project." Knowing that Visual Studio project files are actually MSBuild files with another extension, this sounds like an MSBuild issue. To figure out what to do, I opened a C# Web Project and began looking for various Import
elements.
After copying a couple of Import
elements from a C# Web Project, and a bit of experimentation, I ended up with this in my .fsproj file:
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets" Condition="true" />
My .fsproj file already had an existing Import
element, so I added these two just below the existing element. I haven't experimented with removing one of these elements, so it may be possible to simplify this, or somehow make it more robust. What mattered to me was that this enabled me to move on:
$ git push azure master Counting objects: 7, done. Delta compression using up to 4 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (4/4), 404 bytes | 0 bytes/s, done. Total 4 (delta 3), reused 0 (delta 0) remote: Updating branch 'master'. remote: Updating submodules. remote: Preparing deployment for commit id 'd3625cfef0'. remote: Generating deployment script. remote: Running deployment command... remote: Handling .NET Web Application deployment. remote: .... remote: FebApi -> C:\DWASFiles\Sites\FebApi\VirtualDirectory0\site\repository\FebApi\bin\Release\FebApi.dll remote: Copying all files to temporary location below for package/publish: remote: C:\DWASFiles\Sites\FebApi\Temp\7a1a548e-d5fe-48d6-94ec-99146f20676a. remote: KuduSync.NET from: 'C:\DWASFiles\Sites\FebApi\Temp\7a1a548e-d5fe-48d6-94ec-99146f20676a' to: 'C:\DWASFiles\Sites\FebApi\VirtualDirectory0\site\wwwroot' remote: Deleting file: 'hostingstart.html' remote: Copying file: 'bin\FebApi.dll' remote: Copying file: 'bin\FebApi.XML' remote: Copying file: 'bin\FSharp.Core.dll' remote: Copying file: 'bin\Microsoft.Web.Infrastructure.dll' remote: Copying file: 'bin\Newtonsoft.Json.dll' remote: Copying file: 'bin\System.Net.Http.Formatting.dll' remote: Copying file: 'bin\System.Web.Http.dll' remote: Copying file: 'bin\System.Web.Http.WebHost.dll' remote: Finished successfully. remote: Deployment successful. To https://ploeh@febapi.scm.azurewebsites.net:443/FebApi.git 658e859..d3625cf master -> master
Alas, while deployment succeeded, I wasn't out of the woods yet.
Build actions #
Browsing to the (successfully deployed) site gave me this (rather disappointing) message:
You do not have permission to view this directory or page.
After digging around for a while, I discovered that neither Global.asax nor web.config were deployed to the actual site. The way to resolve that is to change the Build Action for these files to Content.
This was the last hurdle. Pushing those changes to the remote Git repository updated the API, which now works! For the time being, you can see it running here, although I will not promise to keep it around forever.