Full F# Blog - Part 3
I promised a third part in the this FSharp.Blog series, and take the occasion of this new edition of the F# Advent Calendar to write it !
This time we'll see how to install autorenewable letsencrypt certificates on azure functions.
You probably already know letsencrypt, they provide free SSL certificates for everybody. Having a public website today without HTTPS is totally discouraged for security and privacy reasons, and most major browser issue warnings even if no form is posted. Using HTTPS on a static readonly web site is highly advise to prevent Man in the middle HTTP or script injection while on untrusted wifi.
The only requirement for letscencrypt is to prove that you own the website for which you request a certificate. For this, letsencrypt uses a challenge, a single use file that you have to serve from your web site and that will assert you have administrative rights on it.
Overview
For this, we will use the Let's encrypt extension and automate the certificate renewal with a timer trigger. On one side we'll have an azure function app with the extension and the trigger, and on the other side, your blog where you want to install the certificate.
Let's first create a function app. For this we need to install the templates first:
|
The create a directory, and create the app inside
|
The only other dependency we need is the TaskBuild.fs nuget to interop with task more easily:
|
Create a timer trigger function with the following command:
|
Now open the project in your favorite editor. Set the host.json file as "Copy if newer" using the IDE, or add the following lines to the fsproj:
|
Create a LetsEncrypt.fs file and add it as well as the Renew.fs to the project:
|
Start the file with:
|
and type inside:
module LetsEncrypt =
open System
open System.Text
open System.Net.Http
open Newtonsoft.Json
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.Azure.WebJobs.Host
open Microsoft.Extensions.Logging
type Request = {
AzureEnvironment: AzureEnvironment
AcmeConfig: AcmeConfig
CertificateSettings: CertificateSettings
AuthorizationChallengeProviderConfig: AuthorizationChallengeProviderConfig
}
and AzureEnvironment = {
WebAppName: string
ClientId: string
ClientSecret: string
ResourceGroupName: string
SubscriptionId: string
Tenant: string
}
and AcmeConfig = {
RegistrationEmail: string
Host: string
AlternateNames: string[]
RSAKeyLength: int
PFXPassword: string
UseProduction: bool
}
and CertificateSettings = {
UseIPBasedSSL: bool
}
and AuthorizationChallengeProviderConfig = {
DisableWebConfigUpdate: bool
}
type ServiceResponse = {
CertificateInfo: CertificateInfo
}
and CertificateInfo = {
PfxCertificate: string
Password: string
}
type [<Struct>] WebApp = WebApp of string
type [<Struct>] ResourceGroup = ResourceGroup of string
type [<Struct>] Host = Host of string
let env name = Environment.GetEnvironmentVariable(name)
let basic username password =
sprintf "%s:%s" username password
|> Encoding.UTF8.GetBytes
|> Convert.ToBase64String
let RenewCert (log: ILogger) (ResourceGroup resourceGroup) (WebApp webApp) (Host host) =
task {
try
let tenant = env "Tenant"
let subscriptionId = env "Subscription.Id"
let publishUrl = env "Publish.Url"
let username = env "Publish.UserName"
let password = env "Publish.Password"
let clientId = env "Client.Id"
let clientSecret = env "Client.Secret"
let registrationEmail = env "Registration.Email"
let certificatePassword = env "Cert.Password"
use client = new HttpClient();
client.DefaultRequestHeaders.TryAddWithoutValidation(
"Authorization",
"Basic " + basic username password )
|> ignore
let body = {
AzureEnvironment =
{ //AzureWebSitesDefaultDomainName = "string",
// Defaults to azurewebsites.net
//ServicePlanResourceGroupName = "string",
// Defaults to ResourceGroupName
//SiteSlotName = "string",
// Not required if site slots isn't used
WebAppName = webApp
//AuthenticationEndpoint = "string",
// Defaults to https://login.windows.net/
ClientId = clientId
ClientSecret = clientSecret
//ManagementEndpoint = "string",
// Defaults to https://management.azure.com
//Resource group of the web app
ResourceGroupName = resourceGroup
SubscriptionId = subscriptionId
Tenant = tenant //Azure AD tenant ID
//TokenAudience = "string"
// Defaults to https://management.core.windows.net/
}
AcmeConfig =
{ RegistrationEmail = registrationEmail
Host = host
AlternateNames = [||]
RSAKeyLength = 2048
//Replace with your own
PFXPassword = certificatePassword
UseProduction = true
// Replace with true if you want
// production certificate from Lets Encrypt
}
CertificateSettings =
{ UseIPBasedSSL = false }
AuthorizationChallengeProviderConfig =
{ DisableWebConfigUpdate = false }
}
log.LogInformation("Post request")
let uri = sprintf "https://%s/letsencrypt/api/certificates/challengeprovider/http/kudu/certificateinstall/azurewebapp?api-version=2017-09-01" publishUrl
let! res = client.PostAsync(
uri,
new StringContent(JsonConvert.SerializeObject(body),
Encoding.UTF8, "application/json"))
let! value = res.Content.ReadAsStringAsync()
let response = value
return Ok response
with
| ex ->
log.LogError(sprintf "Error while renewing cert: %O" ex, ex)
return Error ex
}
It's a bit long but actually really simple. It crafts a json http request on the extension and get the result.
The three parameters resourceGroup, webApp and host are the one of your blog azure function app. The host is the host name you need a certificate for. In the case of this blog, this is simply thinkbeforecoding.com.
The function also use a lot of environment variables.
Tenant and Subscription.Id are your azure account information. You can find them when you switch account/subscription. The Tenant ends with .onmicrosoft.com and the subscription id is a guid.
The Publish.Url, Publish.UserName and Publish.Password are Kudu publish profile credentials of your timer trigger functions used to call the extension.
The Client.Id and Client.Secret are the appId and secret of an Active Directory account that has Contributor access to your blog functions resource group. We'll see how to configure this later.
The Registration.Email is the email sent to Let's Encrypt. They use it only to notify you when the certificate is about to expire.
Finally Cert.Password is a password used to protect generated pfx. You may have to use it if you wan't to reuse an existing certificate.
When called with the Publish info, the extension checks that it can access your blog function app using provided Client account. Then it calls Let's encrypt to request a certificate. Let's encrypt responds with a challenge. The extension then takes the challenge and creates a file in a .well-known directory in your blog function. Let's encrypt calls your blog to check the presence of the challenge, and in case of success, creates the certificate. The extension finally installs this certificate in your blog SSL configuration.
Let's call this RenewCert function from the trigger. In the Renew.fs file, type:
open System
open Microsoft.Azure.WebJobs
open Microsoft.Extensions.Logging
open System.Threading.Tasks
open FSharp.Control.Tasks.V2.ContextSensitive
open LetsEncrypt
module Renew =
[<FunctionName("Renew")>]
let Run([<TimerTrigger("0 3 2 24 * *") >] myTimer: TimerInfo, log : ILogger) =
task {
sprintf "Timer function to renew myblog.com executed at: %O" DateTime.Now
|> log.LogInformation
let! result =
LetsEncrypt.RenewCert
log
(ResourceGroup "blog-resourcegroup")
(WebApp "blog-func")
(Host "myblog.com")
log.LogInformation "Cert for myblog.com renewed"
match result with
| Ok success -> log.LogInformation(string success)
| Error err -> log.LogError(string err)
} :> Task
The cron expresion is meant to run on each 24th of the month a 02:03. It's a good idea to not run the trigger at midnight to avoid renewing certificates at the same time as everybody else...
Deployment
Our Renew function is ready, use the az cli to deploy it.
First login and select the subscription
|
Create a resource group and a storage account. You can of course use a different region
|
We can now proceed to the functions creation:
|
Next step is to deploy our code:
|
Extension installation
On the portal, in the letsencryptadvent functions, go to the extensions tab, and click Add. Find and select the "Let's Encrypt (No web Jobs)" extensions. Accept the legal terms and OK.
Access Rights
We need to create a client user and give it appropriate access rights on the blog:
|
I've not found yet how to fully configure this account using the cli... So login to azure portal and go to the Active Directory settings. Click on the App registrations tab and select the letsencryptadvent application.
In the overview tab, the Managed application in local directory is not set, just click on the link. It will create the user. Don't forget to note the account App Id for later use.
Now go to your blog resource group, and open the Access Control (IAM) tab. Once there, add a role assignment. Select the Contributor role, and select the letencryptadvent account, and save. Alternatively, you can use the following command:
|
The account we created now has access to your blog to upload the challenge and install the certificate.
We can set the environment variables for the client:
|
Publish information
To get the publish profile information, use the azure cli:
|
get the publish url, the username and password, and set the environment variables:
|
Don't forget to set the Subscription.Id and Tenant, as well as the Registration.Email and Cert.Password with your own values.
Responding to the challenge
Let's encrypt will call your blog app to check the challenge. The extension puts the challenge in a file on the local disk, and we have to serve it.
For this we'll create a function !
In your blog functions code, add a 'challenge' http trigger:
open System
open System.IO
open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http
open Newtonsoft.Json
open Microsoft.Extensions.Logging
open FSharp.Control.Tasks.V2.ContextSensitive
open System.Net.Http
open System.Net
module challenge =
[<FunctionName("challenge")>]
let Run([<HttpTrigger(AuthorizationLevel.Anonymous, [| "get" |],Route="acme-challenge/{code}")>]req: HttpRequestMessage, code: string, log: ILogger) =
task {
try
log.LogInformation (sprintf "chalenge for: %s" code)
let content = File.ReadAllText(@"D:\home\site\wwwroot\.well-known\acme-challenge\"+code);
log.LogInformation("Challenge found")
return new HttpResponseMessage(
Content = new StringContent(content, Text.Encoding.UTF8, "text/plain") )
with
| ex ->
log.LogError(ex.Message)
return raise (AggregateException ex)
}
It just loads the challenge file from local disk and returns its content.
We also have to add a proxy rule to serve it on the expected path. For this create a proxies.json file or edit existing one:
|
Place it as the first rule to be sure the path is not overloaded by a less specific one.
You can now republish you blog with this function.
Test
To generate your first certificate, go to the letsencryptadvent function, and trigger the timer manually.
You'll see the output in the log console. You can also follow the activity with the following command:
|
Once exectued, you should be able to go to your blog with HTTPS. You can check that the certificate is correctly installed in the SSL tab.
Errors are logged, so you should be able to troubleshoot problems as they occure.
Improvements
Storing the passwords as plain text in the configuration is highly discouraged.
Hopefully, you can store them in a keyvault and use a key vault reference as a parameter value:
|
Don't forget to give read access to your function app to the key vault.
When the function runtime finds parameters like this, it loads them from keyvault, decrypts them and pass them as environment variable to the function.
Now, enjoy your auto renewed certificates every month !!
Happy xmas
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Control
--------------------
namespace Microsoft.FSharp.Control
Request.AzureEnvironment: AzureEnvironment
--------------------
type AzureEnvironment = { WebAppName: string ClientId: string ClientSecret: string ResourceGroupName: string SubscriptionId: string Tenant: string }
Request.AcmeConfig: AcmeConfig
--------------------
type AcmeConfig = { RegistrationEmail: string Host: string AlternateNames: string[] RSAKeyLength: int PFXPassword: string UseProduction: bool }
Request.CertificateSettings: CertificateSettings
--------------------
type CertificateSettings = { UseIPBasedSSL: bool }
Request.AuthorizationChallengeProviderConfig: AuthorizationChallengeProviderConfig
--------------------
type AuthorizationChallengeProviderConfig = { DisableWebConfigUpdate: bool }
val string: value: 'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>
--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
val int: value: 'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>
--------------------
[<Struct>] type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>
--------------------
type int<'Measure> = int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
<summary>An abbreviation for the CLI type <see cref="T:System.Boolean" />.</summary>
<category>Basic Types</category>
ServiceResponse.CertificateInfo: CertificateInfo
--------------------
type CertificateInfo = { PfxCertificate: string Password: string }
type StructAttribute = inherit Attribute new: unit -> StructAttribute
<summary>Adding this attribute to a type causes it to be represented using a CLI struct.</summary>
<category>Attributes</category>
--------------------
new: unit -> StructAttribute
union case WebApp.WebApp: string -> WebApp
--------------------
[<Struct>] type WebApp = | WebApp of string
union case ResourceGroup.ResourceGroup: string -> ResourceGroup
--------------------
[<Struct>] type ResourceGroup = | ResourceGroup of string
union case Host.Host: string -> Host
--------------------
[<Struct>] type Host = | Host of string
<summary>Provides information about, and means to manipulate, the current environment and platform. This class cannot be inherited.</summary>
Environment.GetEnvironmentVariable(variable: string, target: EnvironmentVariableTarget) : string
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
<summary>Represents a character encoding.</summary>
<summary>Gets an encoding for the UTF-8 format.</summary>
<returns>An encoding for the UTF-8 format.</returns>
(+0 other overloads)
Encoding.GetBytes(s: string) : byte[]
(+0 other overloads)
Encoding.GetBytes(chars: char[]) : byte[]
(+0 other overloads)
(extension) Encoding.GetBytes(chars: inref<Buffers.ReadOnlySequence<char>>, writer: Buffers.IBufferWriter<byte>) : int64
(+0 other overloads)
(extension) Encoding.GetBytes(chars: inref<Buffers.ReadOnlySequence<char>>, bytes: Span<byte>) : int
(+0 other overloads)
(extension) Encoding.GetBytes(chars: ReadOnlySpan<char>, writer: Buffers.IBufferWriter<byte>) : int64
(+0 other overloads)
Encoding.GetBytes(chars: ReadOnlySpan<char>, bytes: Span<byte>) : int
(+0 other overloads)
Encoding.GetBytes(s: string, index: int, count: int) : byte[]
(+0 other overloads)
Encoding.GetBytes(chars: char[], index: int, count: int) : byte[]
(+0 other overloads)
Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
(+0 other overloads)
<summary>Converts a base data type to another base data type.</summary>
Convert.ToBase64String(bytes: ReadOnlySpan<byte>, ?options: Base64FormattingOptions) : string
Convert.ToBase64String(inArray: byte[], options: Base64FormattingOptions) : string
Convert.ToBase64String(inArray: byte[], offset: int, length: int) : string
Convert.ToBase64String(inArray: byte[], offset: int, length: int, options: Base64FormattingOptions) : string
type ILogger = member BeginScope<'TState> : state: 'TState -> IDisposable member IsEnabled: logLevel: LogLevel -> bool member Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit
<summary>Represents a type used to perform logging.</summary>
--------------------
type ILogger<'TCategoryName> = inherit ILogger
<summary>A generic interface for logging where the category name is derived from the specified <typeparamref name="TCategoryName" /> type name. Generally used to enable activation of a named <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from dependency injection.</summary>
<typeparam name="TCategoryName">The type who's name is used for the logger category name.</typeparam>
type HttpClient = inherit HttpMessageInvoker new: unit -> unit + 2 overloads member CancelPendingRequests: unit -> unit member DeleteAsync: requestUri: string -> Task<HttpResponseMessage> + 3 overloads member GetAsync: requestUri: string -> Task<HttpResponseMessage> + 7 overloads member GetByteArrayAsync: requestUri: string -> Task<byte[]> + 3 overloads member GetStreamAsync: requestUri: string -> Task<Stream> + 3 overloads member GetStringAsync: requestUri: string -> Task<string> + 3 overloads member PatchAsync: requestUri: string * content: HttpContent -> Task<HttpResponseMessage> + 3 overloads member PostAsync: requestUri: string * content: HttpContent -> Task<HttpResponseMessage> + 3 overloads ...
<summary>Provides a class for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.</summary>
--------------------
HttpClient() : HttpClient
HttpClient(handler: HttpMessageHandler) : HttpClient
HttpClient(handler: HttpMessageHandler, disposeHandler: bool) : HttpClient
<summary>Gets the headers which should be sent with each request.</summary>
<returns>The headers which should be sent with each request.</returns>
Headers.HttpHeaders.TryAddWithoutValidation(name: string, values: Collections.Generic.IEnumerable<string>) : bool
<summary>Ignore the passed value. This is often used to throw away results of a computation.</summary>
<param name="value">The value to ignore.</param>
(extension) ILogger.LogInformation(eventId: EventId, message: string, [<ParamArray>] args: obj[]) : unit
(extension) ILogger.LogInformation(``exception`` : exn, message: string, [<ParamArray>] args: obj[]) : unit
(extension) ILogger.LogInformation(eventId: EventId, ``exception`` : exn, message: string, [<ParamArray>] args: obj[]) : unit
(+0 other overloads)
HttpClient.PostAsync(requestUri: string, content: HttpContent) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
(extension) HttpClient.PostAsync<'T>(requestUri: string, value: 'T, formatter: Formatting.MediaTypeFormatter) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
(extension) HttpClient.PostAsync<'T>(requestUri: Uri, value: 'T, formatter: Formatting.MediaTypeFormatter) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
HttpClient.PostAsync(requestUri: Uri, content: HttpContent, cancellationToken: Threading.CancellationToken) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
HttpClient.PostAsync(requestUri: string, content: HttpContent, cancellationToken: Threading.CancellationToken) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
(extension) HttpClient.PostAsync<'T>(requestUri: string, value: 'T, formatter: Formatting.MediaTypeFormatter, cancellationToken: Threading.CancellationToken) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
(extension) HttpClient.PostAsync<'T>(requestUri: string, value: 'T, formatter: Formatting.MediaTypeFormatter, mediaType: string) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
(extension) HttpClient.PostAsync<'T>(requestUri: Uri, value: 'T, formatter: Formatting.MediaTypeFormatter, cancellationToken: Threading.CancellationToken) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
(extension) HttpClient.PostAsync<'T>(requestUri: Uri, value: 'T, formatter: Formatting.MediaTypeFormatter, mediaType: string) : Threading.Tasks.Task<HttpResponseMessage>
(+0 other overloads)
type StringContent = inherit ByteArrayContent new: content: string -> unit + 2 overloads
<summary>Provides HTTP content based on a string.</summary>
--------------------
StringContent(content: string) : StringContent
StringContent(content: string, encoding: Encoding) : StringContent
StringContent(content: string, encoding: Encoding, mediaType: string) : StringContent
<summary> Provides methods for converting between .NET types and JSON types. </summary>
<example><code lang="cs" source="..\Src\Newtonsoft.Json.Tests\Documentation\SerializationTests.cs" region="SerializeObject" title="Serializing and Deserializing JSON with JsonConvert" /></example>
JsonConvert.SerializeObject(value: obj, settings: JsonSerializerSettings) : string
JsonConvert.SerializeObject(value: obj, [<ParamArray>] converters: JsonConverter[]) : string
JsonConvert.SerializeObject(value: obj, formatting: Formatting) : string
JsonConvert.SerializeObject(value: obj, formatting: Formatting, settings: JsonSerializerSettings) : string
JsonConvert.SerializeObject(value: obj, ``type`` : Type, settings: JsonSerializerSettings) : string
JsonConvert.SerializeObject(value: obj, formatting: Formatting, [<ParamArray>] converters: JsonConverter[]) : string
JsonConvert.SerializeObject(value: obj, ``type`` : Type, formatting: Formatting, settings: JsonSerializerSettings) : string
<summary>Gets or sets the content of a HTTP response message.</summary>
<returns>The content of the HTTP response message.</returns>
HttpContent.ReadAsStringAsync(cancellationToken: Threading.CancellationToken) : Threading.Tasks.Task<string>
<summary> Represents an OK or a Successful result. The code succeeded with a value of 'T. </summary>
(extension) ILogger.LogError(eventId: EventId, message: string, [<ParamArray>] args: obj[]) : unit
(extension) ILogger.LogError(``exception`` : exn, message: string, [<ParamArray>] args: obj[]) : unit
(extension) ILogger.LogError(eventId: EventId, ``exception`` : exn, message: string, [<ParamArray>] args: obj[]) : unit
<summary> Represents an Error or a Failure. The code failed with a value of 'TError representing what went wrong. </summary>
type FunctionNameAttribute = inherit Attribute new: name: string -> unit static val FunctionNameValidationRegex: Regex member Name: string
--------------------
FunctionNameAttribute(name: string) : FunctionNameAttribute
type TimerTriggerAttribute = inherit Attribute new: scheduleExpression: string -> unit + 1 overload member RunOnStartup: bool member ScheduleExpression: string member ScheduleType: Type member UseMonitor: bool
--------------------
TimerTriggerAttribute(scheduleExpression: string) : TimerTriggerAttribute
TimerTriggerAttribute(scheduleType: Type) : TimerTriggerAttribute
type TimerInfo = new: schedule: TimerSchedule * status: ScheduleStatus * ?isPastDue: bool -> unit member FormatNextOccurrences: count: int * ?now: Nullable<DateTime> -> string member IsPastDue: bool member Schedule: TimerSchedule member ScheduleStatus: ScheduleStatus
--------------------
TimerInfo(schedule: Extensions.Timers.TimerSchedule, status: Extensions.Timers.ScheduleStatus, ?isPastDue: bool) : TimerInfo
[<Struct>] type DateTime = new: year: int * month: int * day: int -> unit + 10 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMilliseconds: value: float -> DateTime member AddMinutes: value: float -> DateTime member AddMonths: months: int -> DateTime member AddSeconds: value: float -> DateTime member AddTicks: value: int64 -> DateTime member AddYears: value: int -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>
--------------------
DateTime ()
(+0 other overloads)
DateTime(ticks: int64) : DateTime
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : DateTime
(+0 other overloads)
<summary>Gets a <see cref="T:System.DateTime" /> object that is set to the current date and time on this computer, expressed as the local time.</summary>
<returns>An object whose value is the current local date and time.</returns>
union case Host.Host: string -> Host
--------------------
namespace Microsoft.Azure.WebJobs.Host
--------------------
[<Struct>] type Host = | Host of string
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 4 overloads ...
<summary>Represents an asynchronous operation.</summary>
--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: timeout: TimeSpan -> Task<'TResult> + 2 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>
--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: Threading.CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task
--------------------
Task(``function`` : Func<'TResult>) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: Threading.CancellationToken) : Task<'TResult>
Task(``function`` : Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
type HttpTriggerAttribute = inherit Attribute new: unit -> unit + 3 overloads member AuthLevel: AuthorizationLevel member Methods: string[] member Route: string
<summary> Attribute used for http triggered functions. </summary>
--------------------
HttpTriggerAttribute() : HttpTriggerAttribute
HttpTriggerAttribute([<ParamArray>] methods: string[]) : HttpTriggerAttribute
HttpTriggerAttribute(authLevel: AuthorizationLevel) : HttpTriggerAttribute
HttpTriggerAttribute(authLevel: AuthorizationLevel, [<ParamArray>] methods: string[]) : HttpTriggerAttribute
<summary> Enum used to specify the authorization level for http functions. </summary>
<summary> Allow access to anonymous requests. </summary>
type RouteAttribute = inherit Attribute interface IRouteTemplateProvider new: template: string -> unit member Name: string member Order: int member Template: string
<summary> Specifies an attribute route on a controller. </summary>
--------------------
RouteAttribute(template: string) : RouteAttribute
type HttpRequestMessage = interface IDisposable new: unit -> unit + 2 overloads member Dispose: unit -> unit member ToString: unit -> string member Content: HttpContent member Headers: HttpRequestHeaders member Method: HttpMethod member Options: HttpRequestOptions member Properties: IDictionary<string,obj> member RequestUri: Uri ...
<summary>Represents a HTTP request message.</summary>
--------------------
HttpRequestMessage() : HttpRequestMessage
HttpRequestMessage(method: HttpMethod, requestUri: string) : HttpRequestMessage
HttpRequestMessage(method: HttpMethod, requestUri: Uri) : HttpRequestMessage
type File = static member AppendAllLines: path: string * contents: IEnumerable<string> -> unit + 1 overload static member AppendAllLinesAsync: path: string * contents: IEnumerable<string> * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload static member AppendAllText: path: string * contents: string -> unit + 1 overload static member AppendAllTextAsync: path: string * contents: string * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload static member AppendText: path: string -> StreamWriter static member Copy: sourceFileName: string * destFileName: string -> unit + 1 overload static member Create: path: string -> FileStream + 2 overloads static member CreateSymbolicLink: path: string * pathToTarget: string -> FileSystemInfo static member CreateText: path: string -> StreamWriter static member Decrypt: path: string -> unit ...
<summary>Provides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of <see cref="T:System.IO.FileStream" /> objects.</summary>
--------------------
type FileAttribute = inherit Attribute new: path: string * ?access: FileAccess -> unit + 1 overload member Access: FileAccess member Mode: FileMode member Path: string
--------------------
FileAttribute(path: string, ?access: FileAccess) : FileAttribute
FileAttribute(path: string, access: FileAccess, mode: FileMode) : FileAttribute
File.ReadAllText(path: string, encoding: Text.Encoding) : string
type HttpResponseMessage = interface IDisposable new: unit -> unit + 1 overload member Dispose: unit -> unit member EnsureSuccessStatusCode: unit -> HttpResponseMessage member ToString: unit -> string member Content: HttpContent member Headers: HttpResponseHeaders member IsSuccessStatusCode: bool member ReasonPhrase: string member RequestMessage: HttpRequestMessage ...
<summary>Represents a HTTP response message including the status code and data.</summary>
--------------------
HttpResponseMessage() : HttpResponseMessage
HttpResponseMessage(statusCode: HttpStatusCode) : HttpResponseMessage
type StringContent = inherit ByteArrayContent new: content: string -> unit + 2 overloads
<summary>Provides HTTP content based on a string.</summary>
--------------------
StringContent(content: string) : StringContent
StringContent(content: string, encoding: Text.Encoding) : StringContent
StringContent(content: string, encoding: Text.Encoding, mediaType: string) : StringContent
<summary>Gets an encoding for the UTF-8 format.</summary>
<returns>An encoding for the UTF-8 format.</returns>
<summary>Raises an exception</summary>
<param name="exn">The exception to raise.</param>
<returns>The result value.</returns>
type AggregateException = inherit exn new: unit -> unit + 6 overloads member Flatten: unit -> AggregateException member GetBaseException: unit -> exn member GetObjectData: info: SerializationInfo * context: StreamingContext -> unit member Handle: predicate: Func<exn,bool> -> unit member ToString: unit -> string member InnerExceptions: ReadOnlyCollection<exn> member Message: string
<summary>Represents one or more errors that occur during application execution.</summary>
--------------------
AggregateException() : AggregateException
AggregateException(innerExceptions: Collections.Generic.IEnumerable<exn>) : AggregateException
AggregateException([<ParamArray>] innerExceptions: exn[]) : AggregateException
AggregateException(message: string) : AggregateException
AggregateException(message: string, innerExceptions: Collections.Generic.IEnumerable<exn>) : AggregateException
AggregateException(message: string, innerException: exn) : AggregateException
AggregateException(message: string, [<ParamArray>] innerExceptions: exn[]) : AggregateException