Skip to content

Commit 71d3078

Browse files
WilliamBerryiiiSummer
authored andcommitted
Add F# support to template. (#7)
* add .vs folder to ignore list * remove unused usings in csharp template * add F# module template * increment version, update readme with F# and lang details * add example to readme for lang def.
1 parent 5611684 commit 71d3078

File tree

10 files changed

+305
-5
lines changed

10 files changed

+305
-5
lines changed

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Templates Short Name Langua
2020
---------------------------------------------------------------------------------------------------------------
2121
Console Application console [C#], F#, VB Common/Console
2222
Class library classlib [C#], F#, VB Common/Library
23-
Azure IoT Edge Module aziotedgemodule [C#] Console
23+
Azure IoT Edge Module aziotedgemodule [C#], F# Console
2424
Contoso Sample 06 sample06 [C#], F# Console
2525
Unit Test Project mstest [C#], F#, VB Test/MSTest
2626
xUnit Test Project xunit [C#], F#, VB Test/xUnit
@@ -76,6 +76,10 @@ Options:
7676
bool - Optional
7777
Default: false
7878
79+
-lang|--language
80+
string - Optional
81+
Default: C#
82+
7983
```
8084

8185
Parameter `-t` means you if want all Azure IoT Edge module files or just a deployment.json file.
@@ -88,4 +92,10 @@ Now create the Azure IoT Edge module by the template with name:
8892

8993
```
9094
dotnet new aziotedgemodule -n <your_module_name>
91-
```
95+
```
96+
97+
Optionally, to create an F# module use the `-lang` or `--language` flag as follows:
98+
99+
```
100+
dotnet new aziotedgemodule -lang F# -n <your_module_name>
101+
```

content/dotnet-template-azure-iot-edge-module/CSharp/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ artifacts/
2828
*.pidb
2929
*.svclog
3030
*.scc
31+
.vs

content/dotnet-template-azure-iot-edge-module/CSharp/Program.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ namespace SampleModule
22
{
33
using System;
44
using System.IO;
5-
using System.Collections.Generic;
65
using System.Runtime.InteropServices;
76
using System.Runtime.Loader;
87
using System.Security.Cryptography.X509Certificates;
@@ -11,8 +10,6 @@ namespace SampleModule
1110
using System.Threading.Tasks;
1211
using Microsoft.Azure.Devices.Client;
1312
using Microsoft.Azure.Devices.Client.Transport.Mqtt;
14-
using Microsoft.Azure.Devices.Shared;
15-
using Newtonsoft.Json;
1613

1714
class Program
1815
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# .NET Core
2+
project.lock.json
3+
project.fragment.lock.json
4+
artifacts/
5+
**/Properties/launchSettings.json
6+
7+
*_i.c
8+
*_p.c
9+
*_i.h
10+
*.ilk
11+
*.meta
12+
*.obj
13+
*.pch
14+
*.pdb
15+
*.pgc
16+
*.pgd
17+
*.rsp
18+
*.sbr
19+
*.tlb
20+
*.tli
21+
*.tlh
22+
*.tmp
23+
*.tmp_proj
24+
*.log
25+
*.vspscc
26+
*.vssscc
27+
.builds
28+
*.pidb
29+
*.svclog
30+
*.scc
31+
.vs
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"author": "William Berry",
3+
"classifications": [
4+
"Console"
5+
],
6+
"name": "Azure IoT Edge Module",
7+
"identity": "Azure.IoT.Edge.Module.FSharp",
8+
"groupIdentity": "Azure.IoT.Edge.Module",
9+
"shortName": "aziotedgemodule",
10+
"tags": {
11+
"language": "F#",
12+
"type": "project"
13+
},
14+
"sourceName": "SampleModule",
15+
"preferNameDirectory": "true",
16+
"primaryOutputs": [
17+
{
18+
"path": ""
19+
}
20+
],
21+
"symbols": {
22+
"target": {
23+
"type": "parameter",
24+
"datatype": "choice",
25+
"defaultValue": "all",
26+
"choices": [
27+
{
28+
"choice": "all"
29+
},
30+
{
31+
"choice": "deploy"
32+
}
33+
]
34+
},
35+
"linux-x64": {
36+
"type": "parameter",
37+
"datatype": "bool",
38+
"defaultValue": "true"
39+
},
40+
"windows-nano": {
41+
"type": "parameter",
42+
"datatype": "bool",
43+
"defaultValue": "true"
44+
},
45+
"skipRestore": {
46+
"type": "parameter",
47+
"datatype": "bool",
48+
"defaultValue": "false"
49+
}
50+
},
51+
"sources": [
52+
{
53+
"exclude": [
54+
".template.config/*",
55+
"Docker/**/*",
56+
"bin/**/*",
57+
"obj/**/*"
58+
],
59+
"modifiers": [
60+
{
61+
"condition": "(windows-nano && target != 'deploy')",
62+
"include": [
63+
"Docker/windows-nano/*"
64+
]
65+
},
66+
{
67+
"condition": "(linux-x64 && target != 'deploy')",
68+
"include": [
69+
"Docker/linux-x64/*"
70+
]
71+
},
72+
{
73+
"condition": "(target == 'deploy')",
74+
"exclude": [
75+
"*.cs", "*.csproj", ".gitignore"
76+
]
77+
}
78+
]
79+
}
80+
],
81+
"postActions": [
82+
{
83+
"condition": "(!skipRestore && target != 'deploy')",
84+
"description": "Restore NuGet packages required by this project.",
85+
"manualInstructions": [
86+
{
87+
"text": "Run 'dotnet restore'"
88+
}
89+
],
90+
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
91+
"continueOnError": true
92+
}
93+
]
94+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM microsoft/dotnet:2.0.0-runtime
2+
3+
ARG EXE_DIR=.
4+
5+
WORKDIR /app
6+
7+
COPY $EXE_DIR/ ./
8+
9+
CMD ["dotnet", "SampleModule.dll"]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM microsoft/dotnet:2.0.0-runtime
2+
3+
ARG EXE_DIR=.
4+
5+
WORKDIR /app
6+
7+
COPY $EXE_DIR/ ./
8+
9+
RUN apt-get update
10+
11+
RUN apt-get install -y unzip procps
12+
13+
RUN curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg
14+
15+
CMD ["dotnet", "SampleModule.dll"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM microsoft/dotnet:2.0.0-runtime-nanoserver-1709
2+
3+
ARG EXE_DIR=.
4+
5+
WORKDIR /app
6+
7+
COPY $EXE_DIR/ ./
8+
9+
CMD ["dotnet", "SampleModule.dll"]
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
namespace SampleModule
2+
3+
open System
4+
open System.IO
5+
open System.Runtime.InteropServices
6+
open System.Runtime.Loader
7+
open System.Security.Cryptography.X509Certificates
8+
open System.Text
9+
open System.Threading
10+
open System.Threading.Tasks
11+
open Microsoft.Azure.Devices.Client
12+
open Microsoft.Azure.Devices.Client.Transport.Mqtt
13+
14+
module SampleModule =
15+
16+
let counter = ref 0
17+
18+
let awaitTask (t: Task) = t.ContinueWith (ignore) |> Async.AwaitTask
19+
20+
let PipeMessage (message:Message) (userContext:obj) =
21+
let counterValue = Interlocked.Increment(counter)
22+
23+
let deviceClient = userContext :?> DeviceClient
24+
if (isNull(deviceClient)) then
25+
raise (InvalidOperationException("UserContext doesn't contain " + "expected values"))
26+
27+
let messageBytes = message.GetBytes()
28+
let messageString = Encoding.UTF8.GetString(messageBytes)
29+
printfn "Received message: %i, Body: [%s]" counterValue messageString
30+
31+
if (not (String.IsNullOrEmpty(messageString))) then
32+
let pipeMessage = new Message(messageBytes)
33+
34+
message.Properties
35+
|> Seq.iter (fun prop -> pipeMessage.Properties.Add(prop.Key, prop.Value))
36+
37+
deviceClient.SendEventAsync("output1", pipeMessage)
38+
|> Async.AwaitTask
39+
|> Async.Start
40+
41+
Console.WriteLine("Received message sent");
42+
43+
Task.FromResult (MessageResponse.Completed)
44+
45+
let Init (connectionString:string) (bypassCertVerification:bool) =
46+
printfn "Connection String %s" connectionString
47+
48+
let mqttSetting = MqttTransportSettings(TransportType.Mqtt_Tcp_Only)
49+
// During dev you might want to bypass the cert verification.
50+
// It is highly recommended to verify certs systematically in production
51+
if bypassCertVerification then
52+
mqttSetting.RemoteCertificateValidationCallback <- (fun _ _ _ _ -> true)
53+
let transportSettings = mqttSetting :> ITransportSettings
54+
let settings = [|transportSettings|]
55+
56+
// Open a connection to the Edge runtime
57+
let ioTHubModuleClient =
58+
DeviceClient.CreateFromConnectionString(connectionString, settings)
59+
60+
ioTHubModuleClient.OpenAsync()
61+
|> Async.AwaitTask
62+
|> Async.Start
63+
64+
printfn "IoT Hub module client initialized."
65+
66+
// Register callback to be called when a message is received by the module
67+
let pipeMessageHandler = new MessageHandler(PipeMessage)
68+
69+
ioTHubModuleClient.SetInputMessageHandlerAsync("input1", pipeMessageHandler, ioTHubModuleClient)
70+
|> Async.AwaitTask
71+
|> Async.Start
72+
73+
74+
let InstallCert () =
75+
let certPath = Environment.GetEnvironmentVariable("EdgeModuleCACertificateFile");
76+
77+
if (String.IsNullOrWhiteSpace(certPath)) then
78+
// We cannot proceed further without a proper cert file
79+
printfn "Missing path to certificate collection file: %s" certPath
80+
raise (InvalidOperationException("Missing path to certificate file."))
81+
elif (not (File.Exists(certPath))) then
82+
// We cannot proceed further without a proper cert file
83+
printfn "Missing path to certificate collection file: %s" certPath
84+
raise (InvalidOperationException("Missing certificate file."))
85+
else
86+
let store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)
87+
store.Open(OpenFlags.ReadWrite)
88+
store.Add(new X509Certificate2(X509Certificate2.CreateFromCertFile(certPath)))
89+
Console.WriteLine("Added Cert: " + certPath)
90+
store.Close()
91+
92+
93+
let WhenCancelled (cancellationToken:CancellationToken) : Task<bool> =
94+
let tcs = new TaskCompletionSource<bool>()
95+
let setTcsResult =
96+
Action<obj>(fun s -> (s :?> TaskCompletionSource<bool>).SetResult(true))
97+
cancellationToken.Register(setTcsResult, tcs) |> ignore
98+
tcs.Task
99+
100+
101+
[<EntryPoint>]
102+
let main _ =
103+
let connectionString = Environment.GetEnvironmentVariable("EdgeHubConnectionString")
104+
105+
// Cert verification is not yet fully functional when using Windows OS for the container
106+
let bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
107+
if (not bypassCertVerification) then InstallCert ()
108+
Init connectionString bypassCertVerification
109+
110+
// Wait until the app unloads or is cancelled
111+
let cts = new CancellationTokenSource()
112+
AssemblyLoadContext.Default.add_Unloading(fun _ -> cts.Cancel())
113+
Console.CancelKeyPress.Add(fun _ -> cts.Cancel())
114+
WhenCancelled(cts.Token).Wait()
115+
0 // return an integer exit code
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="Program.fs" />
8+
</ItemGroup>
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.6.0-preview-001" />
11+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0-preview2-final" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0-preview2-final" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0-preview2-final" />
14+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0-preview2-final" />
15+
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0-preview2-final" />
16+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0-preview2-final" />
17+
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
18+
</ItemGroup>
19+
</Project>

0 commit comments

Comments
 (0)