Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions core/dotnet2.2/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@

# Quick .NET Core 2.2 Action

A .NET Core action is a .NET Core class library with a method called `Main` that has the exact signature as follows:
A .NET Core action is a .NET Core class library with a method called `Main` or `MainAsync` that has the exact signature as follows:
Copy link
Copy Markdown

@mattwelke mattwelke Apr 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this just a .NET convention, that async methods should end in "Async"? (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model)

I'd argue that that should be up to the OpenWhisk user. In the end, I imagine the usefulness of this convention is that people writing code and using this function will see right away that it returns Task or Task<T>. In this case, they're coding a function that the runtime will use, so they don't get any value out of following that convention.

Right now, the runtime detects whether it needs to await the method, and if so, it does. This means the quickstarts before this PR can be used whether the user function is async or sync, and they're simpler that way because the user doesn't need to worry about following different paths depending on if they're writing an async or sync Main method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are just provided as examples of how you can create it -- it isn't gospel -- the user still has the option of defining the functions full name.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the user has the option to define the function name any way they like, I think the quickstarts should be about getting started as quick as possible with as few decisions to make as possible. Someone new to OpenWhisk is likely to be more interested in getting up and running easily than tweaking a function. They wouldn't even have a function yet.


Synchronous:

```csharp
public Newtonsoft.Json.Linq.JObject Main(Newtonsoft.Json.Linq.JObject);
```

Asynchronous:

```csharp
public async System.Threading.Tasks.Task<Newtonsoft.Json.Linq.JObject> MainAsync(Newtonsoft.Json.Linq.JObject);
```

In order to compile, test and archive .NET Core projects, you must have the [.NET Core SDK](https://www.microsoft.com/net/download) installed locally and the environment variable `DOTNET_HOME` set to the location where the `dotnet` executable can be found.

For example, create a C# project called `Apache.OpenWhisk.Example.Dotnet`:
Expand All @@ -37,11 +45,13 @@ cd Apache.OpenWhisk.Example.Dotnet
Install the [Newtonsoft.Json](https://www.newtonsoft.com/json) NuGet package as follows:

```bash
dotnet add package Newtonsoft.Json -v 12.0.2
dotnet add package Newtonsoft.Json -v 13.0.1
```

Now create a file called `Hello.cs` with the following content:

Synchronous example:

```csharp
using System;
using Newtonsoft.Json.Linq;
Expand All @@ -64,6 +74,32 @@ namespace Apache.OpenWhisk.Example.Dotnet
}
```

Asynchronous example:

```csharp
using System;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;

namespace Apache.OpenWhisk.Example.Dotnet
{
public class Hello
{
public async Task<JObject> MainAsync(JObject args)
{
await Task.Delay(10); // Just do a delay to have an async/await process occur.
string name = "stranger";
if (args.ContainsKey("name")) {
name = args["name"].ToString();
}
JObject message = new JObject();
message.Add("greeting", new JValue($"Hello, {name}!"));
return (message);
}
}
}
```

Publish the project as follows:

```bash
Expand All @@ -79,17 +115,27 @@ zip -r -0 helloDotNet.zip *

You need to specify the name of the function handler using `--main` argument.
The value for `main` needs to be in the following format:
`{Assembly}::{Class Full Name}::{Method}`, e.q.,
`Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main`
`{Assembly}::{Class Full Name}::{Method}`, e.q.:

+ Synchronous: `Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main`
+ Asynchronous: `Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::MainAsync`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example of the user having to make a decision where they technically don't need to, adding friction.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could simplify the docs - to show the sync case with Main first because i think it's simpler.
Then describe the async case. The function can be called Main in both since the name isn't material.
I think you're right in that as shown it could lead one to think the name/Async suffix is semantically meaningful when it's not.

Copy link
Copy Markdown

@mattwelke mattwelke Apr 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to hear you agree. After looking again at @shawnallen85 's changes though, I agree there is one spot it's important to show both. That's at the very top where it says "that has the exact signature as follows", because in the async case, the signature is materially different.

I think to keep it simple, we should just use Main for the function name everywhere in the quickstart, and everywhere after that first part about the signature, just show one example regardless of sync/async, because the user would deploy the sync/async versions the exact same way with the exact same wsk commands.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to take a shot at editing the quick start along these lines?
We do need to show the async case for sure, I've worked with enterprise devs who have asked how to write async examples.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, this PR is already merged in, so I'll do a new PR when I have time (most of my OpenWhisking is done on the weekends right now) with suggested doc changes and ask for a review. I think for this PR, we're all set.


## Create the .NET Core Action

To use on a deployment of OpenWhisk that contains the runtime as a kind:

Synchronous:

```bash
wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main --kind dotnet:2.2
```

Asynchronous:

```bash
wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::MainAsync --kind dotnet:2.2
```

## Invoke the .NET Core Action

Action invocation is the same for .NET Core actions as it is for Swift and JavaScript actions:
Expand Down Expand Up @@ -150,7 +196,7 @@ Install dependencies from the root directory on $OPENWHISK_HOME repository
```bash
pushd $OPENWHISK_HOME
./gradlew install
podd $OPENWHISK_HOME
popd
```

Using gradle to run all tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<Version>2.2.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>12.0.2</Version>
<Version>13.0.1</Version>
Comment thread
shawnallen85 marked this conversation as resolved.
</PackageReference>
</ItemGroup>

Expand Down
17 changes: 9 additions & 8 deletions core/dotnet2.2/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -88,23 +89,23 @@ public async Task<Run> HandleRequest(HttpContext httpContext)
return (null);
}

string base64Zip = message["code"].ToString();
string tempPath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString());
string tempFile = Path.GetTempFileName();
await File.WriteAllBytesAsync(tempFile, Convert.FromBase64String(base64Zip));
string base64Zip = message["code"].ToString();
try
{
System.IO.Compression.ZipFile.ExtractToDirectory(tempFile, tempPath);
using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(base64Zip)))
{
using (ZipArchive archive = new ZipArchive(stream))
{
archive.ExtractToDirectory(tempPath);
}
}
}
catch (Exception)
{
await httpContext.Response.WriteError("Unable to decompress package.");
return (null);
}
finally
{
File.Delete(tempFile);
}

Environment.CurrentDirectory = tempPath;

Expand Down
56 changes: 51 additions & 5 deletions core/dotnet3.1/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@

# Quick .NET Core 3.1 Action

A .NET Core action is a .NET Core class library with a method called `Main` that has the exact signature as follows:
A .NET Core action is a .NET Core class library with a method called `Main` or `MainAsync` that has the exact signature as follows:

Synchronous:

```csharp
public Newtonsoft.Json.Linq.JObject Main(Newtonsoft.Json.Linq.JObject);
```

Asynchronous:

```csharp
public async System.Threading.Tasks.Task<Newtonsoft.Json.Linq.JObject> MainAsync(Newtonsoft.Json.Linq.JObject);
```

In order to compile, test and archive .NET Core projects, you must have the [.NET Core SDK](https://www.microsoft.com/net/download) installed locally and the environment variable `DOTNET_HOME` set to the location where the `dotnet` executable can be found.

For example, create a C# project called `Apache.OpenWhisk.Example.Dotnet`:
Expand All @@ -37,11 +45,13 @@ cd Apache.OpenWhisk.Example.Dotnet
Install the [Newtonsoft.Json](https://www.newtonsoft.com/json) NuGet package as follows:

```bash
dotnet add package Newtonsoft.Json -v 12.0.2
dotnet add package Newtonsoft.Json -v 13.0.1
```

Now create a file called `Hello.cs` with the following content:

Synchronous example:

```csharp
using System;
using Newtonsoft.Json.Linq;
Expand All @@ -64,6 +74,32 @@ namespace Apache.OpenWhisk.Example.Dotnet
}
```

Asynchronous example:

```csharp
using System;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;

namespace Apache.OpenWhisk.Example.Dotnet
{
public class Hello
{
public async Task<JObject> MainAsync(JObject args)
{
await Task.Delay(10); // Just do a delay to have an async/await process occur.
string name = "stranger";
if (args.ContainsKey("name")) {
name = args["name"].ToString();
}
JObject message = new JObject();
message.Add("greeting", new JValue($"Hello, {name}!"));
return (message);
}
}
}
```

Publish the project as follows:

```bash
Expand All @@ -79,17 +115,27 @@ zip -r -0 helloDotNet.zip *

You need to specify the name of the function handler using `--main` argument.
The value for `main` needs to be in the following format:
`{Assembly}::{Class Full Name}::{Method}`, e.q.,
`Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main`
`{Assembly}::{Class Full Name}::{Method}`, e.q.:

+ Synchronous: `Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main`
+ Asynchronous: `Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::MainAsync`

## Create the .NET Core Action

To use on a deployment of OpenWhisk that contains the runtime as a kind:

Synchronous:

```bash
wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main --kind dotnet:3.1
```

Asynchronous:

```bash
wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::MainAsync --kind dotnet:3.1
```

## Invoke the .NET Core Action

Action invocation is the same for .NET Core actions as it is for Swift and JavaScript actions:
Expand Down Expand Up @@ -150,7 +196,7 @@ Install dependencies from the root directory on $OPENWHISK_HOME repository
```bash
pushd $OPENWHISK_HOME
./gradlew install
podd $OPENWHISK_HOME
popd
```

Using gradle to run all tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

<ItemGroup>
<PackageReference Include="Newtonsoft.Json">
<Version>12.0.3</Version>
<Version>13.0.1</Version>
</PackageReference>
</ItemGroup>

Expand Down
17 changes: 9 additions & 8 deletions core/dotnet3.1/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -89,23 +90,23 @@ public async Task<Run> HandleRequest(HttpContext httpContext)
return (null);
}

string base64Zip = message["code"].ToString();
string tempPath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString());
string tempFile = Path.GetTempFileName();
await File.WriteAllBytesAsync(tempFile, Convert.FromBase64String(base64Zip));
string base64Zip = message["code"].ToString();
try
{
System.IO.Compression.ZipFile.ExtractToDirectory(tempFile, tempPath);
using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(base64Zip)))
{
using (ZipArchive archive = new ZipArchive(stream))
{
archive.ExtractToDirectory(tempPath);
}
}
}
catch (Exception)
{
await httpContext.Response.WriteError("Unable to decompress package.");
return (null);
}
finally
{
File.Delete(tempFile);
}

Environment.CurrentDirectory = tempPath;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ class DotNet3_1ActionContainerTests_2_2 extends BasicActionRunnerTests with WskA
TestConfig(functionb64, main = "Apache.OpenWhisk.Tests.Dotnet::Apache.OpenWhisk.Tests.Dotnet.Environment::Main")
}

override val testEnvParameters = {
TestConfig(functionb64, main = "Apache.OpenWhisk.Tests.Dotnet::Apache.OpenWhisk.Tests.Dotnet.Init::Main")
}

override val testEcho = {
TestConfig(functionb64, main = "Apache.OpenWhisk.Tests.Dotnet::Apache.OpenWhisk.Tests.Dotnet.AltEcho::Main")
}

override val testEnvParameters = {
TestConfig(functionb64, main = "Apache.OpenWhisk.Tests.Dotnet::Apache.OpenWhisk.Tests.Dotnet.Init::Main")
}

val testEchoNoWrite = {
TestConfig(functionb64, main = "Apache.OpenWhisk.Tests.Dotnet::Apache.OpenWhisk.Tests.Dotnet.Echo::MainAsync")
}
Expand Down