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.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Monday, 26 August 2013 08:26:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!