Skip to content

Commit

Permalink
Azure function support (dotnet#572)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkotalik authored Jul 15, 2020
1 parent e53b59a commit 379ff07
Show file tree
Hide file tree
Showing 150 changed files with 6,696 additions and 15 deletions.
3 changes: 3 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<Import Project="$(RepoRoot)test\Test.Infrastructure\Microsoft.AspNetCore.Testing.props" Condition=" '$(IsTestProject)' == 'true' " />


</Project>
77 changes: 77 additions & 0 deletions docs/recipes/azure_functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Using Tye and Azure Functions

[Azure Functions](https://azure.microsoft.com/en-us/services/functions/) is a popular serverless compute platform from Azure. Tye supports running Azure functions locally.

## Getting Started: Create an Azure Function

Starting from the [sample here](https://github.com/dotnet/tye/tree/master/samples/frontend-backend), we are going to transform the backend from a web application to an azure function app.

To start, create an Azure Function project in a folder called `backend-function`. You can do this via:
- [Visual Studio Code](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code?pivots=programming-language-csharp)
- [Visual Studio](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio)
- [Commandline](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function-azure-cli?tabs=bash%2Cbrowser&pivots=programming-language-csharp)

Next, create an HttpTrigger called `MyHttpTrigger` in your functions project. Change the contents of MyHttpTrigger to the following:

```c#
[FunctionName("MyHttpTrigger")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");

var backendInfo = new BackendInfo()
{
IP = req.HttpContext.Connection.LocalIpAddress.ToString(),
Hostname = System.Net.Dns.GetHostName(),
};

return new OkObjectResult(backendInfo);
}

class BackendInfo
{
public string IP { get; set; } = default!;

public string Hostname { get; set; } = default!;
}
```

Finally, change line in the frontend's `Startup.cs` to call the right endpoint (line 63), changing "/" to "api/MyHttpTrigger".

```c#
endpoints.MapGet("/", async context =>
{
var bytes = await httpClient.GetByteArrayAsync("/api/MyHttpTrigger");
var backendInfo = JsonSerializer.Deserialize<BackendInfo>(bytes, options);
...
}
```

## Adding your Azure Function in tye.yaml

Now that we have a backend function added, you can simply modify your tye.yaml to point to the azure function instead:

```yaml
# tye application configuration file
# read all about it at https://github.com/dotnet/tye
name: frontend-backend
services:
- name: backend
azureFunction: backend-function/ # folder path to the azure function.
- name: frontend
project: frontend/frontend.csproj
```

## Running locally

You can now run the application locally by doing `tye run`.

On first run of an app that requires functions, tye will install any tools necessary to run functions apps in the future. This may take a while on first run, but will be saved afterwards.

Navigate to the tye dashboard to see both the frontend and backend running. Navigate to the frontend to see that the app still has the same behavior as before.

## Deployment

Deployment of azure functions is currently not supported.
7 changes: 7 additions & 0 deletions docs/reference/azure_function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Azure function support details in Tye

- supports v2 and v3 functions
- Specify v2 or v3 in tye.yaml
- Specify x64 or x86 in tye.yaml (defaults to x64)
- Specify another path to func.exe

199 changes: 199 additions & 0 deletions samples/azure-functions/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
root = true

[*]
indent_style = space

[*.{props,targets,csproj}]
indent_style = space
indent_size = 2

[*.{js,json}]
indent_style = space
indent_size = 2

[*.{yml,yaml}]
indent_style = space
indent_size = 2

[*.{cs,csx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom

# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom

# Powershell files
[*.ps1]
indent_size = 2

# Shell script files
[*.sh]
end_of_line = lf
indent_size = 2

# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:refactoring
dotnet_style_qualification_for_property = false:refactoring
dotnet_style_qualification_for_method = false:refactoring
dotnet_style_qualification_for_event = false:refactoring

# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion

# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion

# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style

dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static

dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case

# Non-private readonly fields are PascalCase
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style

dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly

dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case

# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style

dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const

dotnet_naming_style.constant_style.capitalization = pascal_case

# Static fields are camelCase
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style

dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static

dotnet_naming_style.static_field_style.capitalization = camel_case

# Instance fields are camelCase
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style

dotnet_naming_symbols.instance_fields.applicable_kinds = field

dotnet_naming_style.instance_field_style.capitalization = camel_case

# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style

dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local

dotnet_naming_style.camel_case_style.capitalization = camel_case

# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style

dotnet_naming_symbols.local_functions.applicable_kinds = local_function

dotnet_naming_style.local_function_style.capitalization = pascal_case

# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style

dotnet_naming_symbols.all_members.applicable_kinds = *

dotnet_naming_style.pascal_case_style.capitalization = pascal_case

[*.cs]
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left

# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion

# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none

# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none

# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
12 changes: 12 additions & 0 deletions samples/azure-functions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# VotingSample
Voting sample app inspired by https://github.com/dockersamples/example-voting-app with a few different implementation choices. This voting app uses [Azure Functions](https://azure.microsoft.com/en-us/services/functions/) with a Queue and Http function.

## For running

To run, first make sure the azure storage emulator is running. You can use [Azurite](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite) cross platform or use the [Azure Storage Emulator](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator?toc=/azure/storage/blobs/toc.json) on Windows.

Next, all you need to do is execute `tye run` and navigate to the dashboard.

## For deployment

Deployment is currently not supported for Azure Functions.
21 changes: 21 additions & 0 deletions samples/azure-functions/ingress.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-basic
namespace: default
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- http:
paths:
- backend:
serviceName: vote
servicePort: 80
path: /vote(/|$)(.*)
- backend:
serviceName: results
servicePort: 80
path: /results(/|$)(.*)
10 changes: 10 additions & 0 deletions samples/azure-functions/results/App.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
13 changes: 13 additions & 0 deletions samples/azure-functions/results/Data/VotingResults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Results
{
public class VotingResult
{
public string Vote { get; set; }
public int Count { get; set; }
}
}
16 changes: 16 additions & 0 deletions samples/azure-functions/results/Pages/Error.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@page "/error"


<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Loading

0 comments on commit 379ff07

Please sign in to comment.