Running Azure Functions in IIS
Tags: azure, serverless
Published: 2020-07-20 02:58:12

Have you ever wanted to run Azure Functions in IIS? No? "Why would you want to run serverless functions in IIS," you ask.

I won't try to answer *why*, but I can tell you *how*. I recently needed to do this and found a void where info should be, so this post is to fill that void.

In Azure, functions are hosted in IIS. (Weird right? Like, I assumed it was something sophisticated and new, not just some duct tapey IIS plugin thing.) I want to find out how much of a function app we can get functioning on-prem with IIS. Yeah, we could just run azure function core tools, but what would we learn from following *that* well-documented process?

## 🎪 Basic IIS setup

Let's add a plain, empty site, and bind it to a really good domain.
![Plain empty site in IIS](https://dev-to-uploads.s3.amazonaws.com/i/rwoyeikv5hjz6lafc2ut.png)

Since we used a really good domain, we'll need to update our hosts file.
![Hosts file entry for no good reason](https://dev-to-uploads.s3.amazonaws.com/i/gi2s9477fno07mwcqggj.png)

Here's quick test to prove that IIS is set up OK.
![Sweet new web page](https://dev-to-uploads.s3.amazonaws.com/i/iaq7zfm1zaw7a4qcesnr.PNG)

*Cool* so we have a site set up in IIS, and we can deploy a function app to it. Next we'll make a function app, give it a bunch of trigger types, just to see which triggers work in this janky setup. 

## 🧪 A Guinea Pig Function App

Here's a link to the source code.
{% github gjcampbell/iis-azfunc-test %}

Pretty basic, there's are 4 triggers, and each one just logs. There's a timer, HTTP, queue, and blob trigger. It's logging to a file, so that when it's silently running in IIS, I can see the function app is doing stuff.

```csharp
[FunctionName(nameof(TestHttp))]
public IActionResult TestHttp(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req)
{
    logger.LogInformation("HttpTrigger ran");
    return new OkObjectResult("Perfect! 🙃");
}

[FunctionName(nameof(TestQueue))]
public void TestQueue([QueueTrigger("iis-q-test")]string item)
    => logger.LogInformation($"QueueTrigger ran value: {item}");

[FunctionName(nameof(TestStorage))]
public void TestStorage([BlobTrigger("iis-blob-test/{name}")]Stream blob, string name)
    => logger.LogInformation($"BlobTrigger ran file name: {name}, size: {blob.Length}");

[FunctionName(nameof(TestTimer))]
public void TestTimer([TimerTrigger("*/20 * * * * *")]TimerInfo timer)
    => logger.LogInformation($"TimerTrigger ran"); 
```

I have confirmed that, running from Visual Studio, the function app works as expected. All triggers work and log as configured.

Next from Visual Studio, I'll publish locally to the web site folder. 💥

![func app site looking broke](https://dev-to-uploads.s3.amazonaws.com/i/gqk7rhfi436c8lrvlt6x.PNG)

Yeah, of course that doesn't work. I haven't configured IIS to know what to do with a function app.

## 🤫 Azure Function IIS Secret Sauce

Check out [this cool github page](https://github.com/Azure/azure-functions-host). Azure Functions Host has releases like [this one](https://github.com/Azure/azure-functions-host/releases/tag/v3.0.14191), 3.0.BLAH which we can download and use with IIS.

Download and unzip the Functions.3.0.BLAH.zip somewhere. Next make a web.config file where your function app is published. In the web.config, configure IIS to run aspNetCore module for all paths and verbs, and set the processPath for aspNetCore to the path of Function Host's Microsoft.Azure.WebJobs.Script.WebHost.exe file.

Like this:
```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <remove name="aspNetCore" />
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="C:\hosting\Functions.3.0.14191\3.0.14191\64bit\Microsoft.Azure.WebJobs.Script.WebHost.exe" hostingModel="InProcess">
	  <environmentVariables>
	    <environmentVariable name="AzureWebJobsStorage" value="UseDevelopmentStorage=true" />
	  </environmentVariables>
	</aspNetCore>
  </system.webServer>
</configuration>
```

Now what do we get?
![For real tho?](https://dev-to-uploads.s3.amazonaws.com/i/wx3b4c0nu6nkhwpj2zc5.PNG)

Weird, right? It's that default page that you get in azure. What about when we hit the HttpTrigger route?
![Yeah](https://dev-to-uploads.s3.amazonaws.com/i/cmsbzhosn03kog1uvf1n.PNG)

So HTTP works of course. What about the other triggers? The log says
```
2020-07-19 21:28:00.0158|INFO|IIS.FuncTest.Tests|TimerTrigger ran
2020-07-19 21:28:20.0162|INFO|IIS.FuncTest.Tests|TimerTrigger ran
2020-07-19 21:28:36.0969|INFO|IIS.FuncTest.Tests|QueueTrigger ran value: adsf
2020-07-19 21:28:40.0181|INFO|IIS.FuncTest.Tests|TimerTrigger ran
2020-07-19 21:28:43.7516|INFO|IIS.FuncTest.Tests|HttpTrigger ran
2020-07-19 21:28:44.1831|INFO|IIS.FuncTest.Tests|BlobTrigger ran file name: 2019_TaxReturn.pdf, size: 637435
2020-07-19 21:29:00.0530|INFO|IIS.FuncTest.Tests|TimerTrigger ran
2020-07-19 21:29:20.0069|INFO|IIS.FuncTest.Tests|TimerTrigger ran

```

## 🌻 Summary

Yeah, you can run azure functions in IIS and the vanilla triggers work just fine, and there's really just 2 steps to get it working. 
1. Download the functions runtime
1. Point to it from your web.config

I'd love to see someone get durable functions working.