Tutorials > Solution Designer > Workflow > Workflow Customization in Therefore⢠Online > Azure Functions Hosted by the User Developing a New Azure Function |
Scroll |
How do I develop a new Azure function?
Although there are many languages and tools to choose from, this guide only discusses how to create Azure functions using C# in Visual Studio. This is the recommended way as Azure functions can be tested and debugged locally without the need for an Azure subscription just by pressing F5. It is also possible to deploy it to Azure from within Visual Studio.
Make sure you have installed Visual Studio with the âAzure developmentâ workload.
In Visual Studio, click on the âToolsâ menu and then âGet Tools and FeaturesâŚâ to open the installer.
Open the âWorkloadsâ tab and look for âAzure developmentâ. Activate the checkbox on the top-right and click âModifyâ to apply the changes.
|
---|
How do I create a new Azure function?
If the âAzure developmentâ workload was successfully installed, âAzure Functionsâ will be one of the options when creating a new project in Visual Studio. It is however recommended to start from the âEmptyFunctionAppâ sample, instead of starting development from scratch. Make sure to select âAzure Functions v2â (as âv1â will be discontinued at some point) and âHttp triggerâ, as Therefore⢠currently only supports calling Azure functions using HTTP(S) requests. Access rights will only be applied when the function is published, not for local development. When choosing âAnonymousâ, no key will be required. âFunctionâ will require the function key, while âAdminâ will require the Function App key. The master key could be used, as well. |
---|
How do I use REST for Web API calls?
Add the âThereforeâ folder from the âEmptyFunctionAppâ sample to your project.
The files are not required, but make development easier and faster. If changes in the Web API require the WebAPIContract.cs to be updated, follow these steps:
1.Download and install Silverlight SDK 5.0 or later 2.Make sure the XML Service is running 3.Create a temporary folder e.g. âC:\WebAPIâ 4.Open a CMD line and go to âC:\Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Toolsâ 5.Execute the following command: SlSvcUtil.exe http://servername:8000/theservice/v0001?singlewsdl /out:C:\WebAPI\WebAPIContract.cs /config:C:\WebAPI\output /namespace:http://schemas.therefore.net/webservices/interop/v0001/types,Therefore.WebAPI 6. Go to âC:\WebAPIâ and copy the WebAPIContract.cs to the âTherefore\WebAPIâ folder, replacing the old one.
Like described in the sample project, the SendRequest function of the Therefore.WebAPI.WebApiClient class, can be used to send a request. For a list of all available commands, please see the Web API documentation.
Using the WebAPIContract.cs will require NuGet package âSystem.ServiceModel.Httpâ. Please see Dependencies for additional information.
|
---|
How do I use SOAP for Web API Calls?
Add the âThereforeâ folder from the âEmptyFunctionAppâ sample to your project and then delete the âWebAPIâ sub folder as it is only required for REST calls. As using SOAP is not the recommended way, the value for âUseRESTâ in TheConfig class needs to be set to âfalseâ, otherwise the WebAPIBaseUrl property will be wrong.
Add Therefore⢠Web API as a Connected Service:
As Connected Service, choose âMicrosoft WCF Web Service Reference Providerâ. Insert the URL where the Web Service is running and press âGoâ. If the service is running, it should be listed under Services as âThereforeServiceâ.
Provide a meaningful Namespace like âTherefore.WebAPIâ and press Finish.
The Therefore.WebAPI.ThereforeServiceClient class can be used to make Web API calls.
Please see Dependencies for additional information about NuGet package âSystem.ServiceModel.Httpâ.
|
---|
To add the NuGet package âSystem.ServiceModel.Httpâ, right click on your project and select âManage NuGet PackagesâŚâ
Switch to the Browse-tab and search for âSystem.ServiceModel.Httpâ, select it and click Install.
If you get the following error when building, a bug is not yet resolved:
System.IO.FileNotFoundException: Could not load file or assembly 'System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
As a workaround, edit the .csproj file and add the following config as child of the âProjectâ root-node:
<!--this is temporary workaround for issue https://github.com/dotnet/wcf/issues/2824 also requires nuget package--> <Target Name="CopySPSM" BeforeTargets="Build"> <Copy SourceFiles="$(USERPROFILE)\.nuget\packages\system.private.servicemodel\ </Target> <ItemGroup> <None Include="$(USERPROFILE)\.nuget\packages\system.private.servicemodel\ </ItemGroup> <!--end workaround-->
Please note that version numbers and line breaks might need to be adjusted. |
---|
The EmptyFunctionApp sample is a small Azure function that retrieves the passed parameters from Thereforeâ˘, configures the WebApiClient object to be ready to use, and returns the required parameters when finished. It was designed to be used as a starting point for further development.
public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/Function1")] HttpRequest req, ILogger log) { string errMsg = ""; // error message that will be returned to Therefore for logging Exception logEx = null; // exception details for logging bool routingDone = false; // false: WF instance will be routed to the next task. // true: WF instance will not be routed because routing was done by WebAPI call tryâŚ
if (!String.IsNullOrWhiteSpace(errMsg) || logEx != null) log.LogError(logEx, errMsg); // log the error so it can be viewed in the Azure Portal
TheRespParams respParams = new TheRespParams(errMsg, routingDone); return new OkObjectResult(respParams.Serialize());
The âRunâ function is the main function that will be called on an HTTP request.
The âAuthorizationLevelâ defined in the HttpTrigger will only be applied when the function is published, not for local development. When choosing âAnonymousâ no key will be required, âFunctionâ will require the function key and âAdminâ will require the Function App key. The master key could be used, as well.
The âRouteâ defined in the HttpTrigger as âv1/Function1â, provides a way of versioning. Suppose a new workflow version would require a new version of the function, but some instances need to remain on the old one. The function could be copied and the route changed from âv1â to âv2â in the code and in the task configuration of new tasks to fulfill this need. A Function App can hold multiple functions.
At the end of the sample, any errors that might have occurred are logged to Azure and also passed to an object of class âTheRespParamsâ. The âTheRespParamsâ class takes care of converting the mandatory return parameters for Therefore⢠as a JSON object.
There is always an âOkObjectResultâ created, which returns the JSON object with HTTP status code 200. Any error message in âerrMsgâ will be returned to Therefore⢠for logging in the WF history. From the Therefore⢠side, the function call was successful if no error occurred (errMsg is empty). |
---|
try { // read parameters passed by Therefore string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); TheReqParams reqParams = TheReqParams.Deserialize(requestBody); if (reqParams == null || reqParams.TheInstanceNo <= 0 || reqParams.TheTokenNo <= 0) return new BadRequestObjectResult("Mandatory parameter not set"); // function was not called by Therefore, return HTTP error 400 reqParams.LogWFInfo(log); // log workflow information now, in case the function crashes later
// configuration TheConfig config = TheConfig.Instance; config.WebAPIBaseUrl = reqParams.TheWebAPIUrl; config.Tenant = reqParams.TheTenant;
// Web API connection WebApiClient client; // specify a user name and password if the permissions from the accessToken would not be sufficient if (!String.IsNullOrEmpty(config.UserName) && !String.IsNullOrEmpty(config.Password)) // connection with user name and password/token: client = new WebApiClient(config.UserName, config.Password, config.IsToken, config.WebAPIUrl, config.Tenant, config.Language, config.RequestTimeout, config.ClientTimezoneIANA); else // connection with JWT Bearer token 'accessToken' client = new WebApiClient(reqParams.TheAccessToken, config.WebAPIUrl, config.Tenant, config.Language, config.RequestTimeout, config.ClientTimezoneIANA);
// Add your code here }
The âTheReqParamsâ takes care of reading all request parameters passed in from Thereforeâ˘.
The âTheConfigâ class has various properties, required for the WebAPI call, like language, timezone, request timeout and many more. Those can be configured to your needs, or left at their defaults.
There are two properties without useful default values: âWebAPIBaseUrlâ and âTenantâ. The values are passed in from the Therefore⢠server and just need to be assigned to the âTheConfigâ object.
TheConfig class will read out UserName and Password properties from âlocal.settings.jsonâ when developing locally, or from the Application Settings when running in Azure.
The WebApiClient object can either be instantiated using user credentials or the JWT-token.
This is decided by checking whether they are empty or not. If no credentials are configured, the JWT-token is used.
The SendRequest function of the WebApiClient class can be used to send a request. For a list of all available commands, please see the Web API documentation.
At the âAdd your code hereâ comment, you can make Web API requests and implement your business logic.
On failure or on success, the error or an empty string will be returned, so Therefore⢠knows if the function was working as designed (outside the try/catch-statement). |
---|
This documentation only explains how to send requests using the Web API in general, but does not describe each available function. Please see the Web API documentation for details on how to use the various functions.
Web API functions can be called using the Therefore.WebAPI.WebApiClient class.
Creating a new instance requires a few parameters. As described in the EmptyFunctionApp sample, the âTheConfigâ object can help with those. When configuring a requestTimeout, please be aware that the function itself is called by a HTTP-request and will time out after a maximum of 230 seconds. Credentials: WebApiClient client = new WebApiClient(username, password, isToken, webApiUrl, tenant, language, requestTimeout, clientTimezoneIANA);
JWT bearer token: WebApiClient client = new WebApiClient(bearerToken, webApiUrl, tenant, language, requestTimeout, clientTimezoneIANA);
When the WebApiClient object is created, requests can be sent using the âSendRequestâ function. Task<TResp> SendRequest<TReq, TResp>(TReq requestData)
This single function can be used to cover all Web API functionality. The request is determined by the type of request- and response-parameter. Request and response need to match. For example, GetDocumentParams with GetDocumentResponse can be used to make a âGetDocumentâ request.
Example: Retrieving a document (with files):
Documents can be retrieved using GetDocumentParams with GetDocumentResponse in the SendRequest function.
In the GetDocumentParams request-parameter, the document number needs to be specified.
If the files of the document need to be retrieved as well, the âIsStreamsInfoAndDataNeededâ property needs to be set to true. If just the stream information, without the actual data is enough, âIsStreamsInfoNeededâ can be set to true.
// Create request parameters GetDocumentParams parameters = new GetDocumentParams(); parameters.DocNo = docNo; parameters.IsStreamsInfoAndDataNeeded = true; parameters.IsIndexDataValuesNeeded = true;
// Send the request GetDocumentResponse docResp = client.SendRequest<GetDocumentParams, GetDocumentResponse>(parameters).Result;
// Extract all file streams to the specified directory string extractDir = Path.GetTempPath(); foreach (var streamInfo in docResp.StreamsInfo) { string extractFileName = Path.Combine(extractDir, streamInfo.FileName); File.WriteAllBytes(extractFileName, streamInfo.StreamData); } |
---|
Response:
Every function called by the new âCall Functionâ workflow task needs to return a few mandatory return parameters in JSON-format in the HTTP response: •TheError: Mandatory string value. If the string is not empty, it will be displayed in the Workflow history. The instance will be marked as faulty, unless a retry at a later time was requested. •TheRetryAfterMin: Optional integer value. Can be used to request a retry from Thereforeâ˘. If set to a value greater zero, the âCall Functionâ task will behave like a âWaitâ task and send a new request after the specified amount of minutes has passed (approximately). If âTheErrorâ is set, it will be treated as a warning and will not cause the workflow instance to be marked as faulty. •TheRoutingDone: Mandatory bool value, unless âTheRetryAfterMinâ is specified. Can be set to âtrueâ, to indicate that routing the workflow instance was already done by the function using the âFinishCurrentWorkflowTaskâ Web API function. If set to âfalseâ, Therefore⢠will route the workflow instance to the next task. If âTheRetryAfterMinâ is specified, the routing will not be done yet, so in that case itâs optional. To simplify returning those parameters, âTheRespParamsâ class was created. It holds all those parameters as properties and provides the âSerializeâ function for creating a JSON object. There are two ways to initialize an object of this class: •Using âTheErrorâ and âTheRoutingDoneâ TheRespParams respParams = new TheRespParams(errMsg, routingDone); This should be used as default case, or if an error occurred. Both values are optional.
By default the errMsg is empty and routingDone is false.
•Using âTheErrorâ and âTheRetryAfterMinâ TheRespParams respParams = new TheRespParams(retryAfterMin, errMsg);
This will tell Therefore⢠to retry the function call at a later point in time. Integer value retryAfterMin needs to be greater than zero; errMsg is optional and will be logged as additional information (not as error) to the workflow history.
Request:
With every function call, Therefore⢠sends the following parameters to the function as JSON with the HTTPS request:
•TheWebAPIUrl: Holds the âWeb API URLâ server setting, which can be configured in the âXML Web Serviceâ tab or in the advanced settings. This setting will be already configured correctly when using Therefore⢠Online, but can be empty if not configured. •TheTenant: Name of the current tenant. Will be empty if the system does not use multi-tenancy. •TheAccessToken: A JWT-token that can be used to connect to Therefore⢠using the Web API.The token is configured to give the â$TheWFSystemâ user, access to the document or case associated with the current WF instance, as well as access to the current WF instance and token. With the granted permissions, it is possible to make changes to the associated document, case, or documents associated to the case. The granted permissions should also allow routing the workflow instance to a different task. It is not allowed to create new documents or cases, or to run queries. It is also not allowed to delete the main case or document associated to the workflow instance, or the workflow instance itself. •TheInstanceNo: Instance number of the current workflow instance. •TheTokenNo: Token number of the current workflow instance. •TheCaseNo: Case number of the case associated to the workflow instance. This parameter is only available for case-workflows. •TheDocNo: Document number of the main document associated to the workflow instance. This parameter is only available for document-workflows. •TheSettings: JSON object (passed as string) of all configured âCustom function settingsâ.
This can be empty or used to pass additional parameters to the called function. Name and value of each setting can be chosen freely, but names need to be unique according to the JSON specification. All values are treated as string values and need to be converted to another type inside the function if required.
To simplify reading those parameters from the JSON string, âTheReqParamsâ class was created. It holds all those parameters as properties and provides the âDeserializeâ function for creating an object from a JSON string.
The code of âTheReqParamsâ class can be used as an example for your custom settings.
Suppose you configured âTaskNoâ with some integer value in the workflow task configuration as âcustom function settingâ, you could use the following class to read it.
class CustomSettings { public string taskNo; public int TaskNo { get { return int.Parse(taskNo); } set { taskNo = value.ToString(); } } public static CustomSettings Deserialize(string json) { return JsonConvert.DeserializeObject<CustomSettings>(json); } }
|
---|
How do I specify a User name and password for Web API connection?
The code in the sample project will primarily check if user name and password have been specified, and only use the provided access token as a fallback. This way the default will be the token unless you specify user name and password. Those credentials can be needed if more permissions are required than the token provides.
TheConfig.cs uses the ConfigurationBuilder to access both the âlocal.settings.jsonâ and the environment variables to look for the configuration.
When running the Azure function locally, the setting will be read from âlocal.settings.jsonâ. To add the setting, insert the settings âWebAPIUserâ and âWebAPIPasswordâ to the âValuesâ JSON-object like this: { "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "WebAPIUser": "Domain\\TestUser1", "WebAPIPassword": "secretpassword" } }
When the function has been deployed to Azure, the settings will be read from the App Settings.
To add user name and password, select your Function App in the Azure Portal and click âApplication settingsâ. New settings can be added by clicking âNew application settingâ. Click âSaveâ when done.
|
---|
Azure functions can be deployed to the Microsoft Azure platform directly within Visual Studio. Right click the project and click âPublishâ to publish your Azure function. Select âAzure Function Appâ to upload it to Azure, directly from within Visual Studio.
Choosing âFolderâ would require you to upload it manually using FTP. The Function App name needs to be globally unique, as a small website âhttps://FunctionAppName.azurewebsites.netâ is created that is publicly available, regardless if the function is HTTP triggered or not (the HTTP trigger will use the same URL). Every Azure Function App needs to have a Storage Account where the functions as well as logs can be stored. There are two different options to host a Function App:
•Consumption Plan
•App Service Plan •In the dedicated App Service plan, your function apps run on dedicated VMs on Basic, Standard, Premium, and Isolated SKUs, which is the same as other App Service apps. Dedicated VMs are allocated to your function app, which means the functions host can be always running. •A VM decouples cost from number of executions, execution time, and memory used. An App Service Plan can be more cost-effective if your function apps run continuously, or nearly continuously.
After a successful deployment, calling the function will result in a 401 âUnauthorizedâ error.
By default, a function key is used for authorization that needs to be provided with every call.
This key can be obtained from the Azure Portal. After opening the Function App, click on âManageâ and copy the âdefaultâ key.
A call from Therefore⢠will include they key as âx-functions-keyâ in the request header if configured.
Testing the successful deployment without Therefore⢠can be done by using cURL or a similar program.
curl âd ââ âi âX POST âhttps://AppName.azurwebsites.net/api/Route?code=FunctionKeyâ
If no route is defined, the function name is used instead. |
---|
How do I develop a new Azure function using Visual Studio Code?
Visual Studio Code can be used as an alternative to Visual Studio. It supports creating a new Azure function from scratch, or using one of our samples as well as local debugging without the need for an Azure subscription. For more details please visit the guide from Microsoft. •Microsoft .Net Core SDK. oFor Azure functions using version 2, the .NET Core 2.1 SDK is needed. oFor Azure functions using version 3, the .NET Core 3.1 SDK is needed. •Microsoft SQL Server Express (for Azure Storage Emulator) A restart is required, run the setup again if it does not start automatically. If a non-Express version of SQL Server is already installed, this step can be skipped. •C# Extension for Visual Studio Code •Azure Function extension for Visual Studio Code
Setting up the Azure Storage Emulator
When creating a new Azure function in VS Code, no storage is used by default. It can be enabled by adding the following lines to the local.settings.json: Please be aware that VS Code does not support trailing commas.
In contrary to Visual Studio, VS Code does not start the Storage Emulator automatically, so it needs to be started manually before debugging / running your function.
Start development based on our SDK samples
Start VS Code, click on âFile -> Open FolderâŚâ and specifying the folder containing one of our SDK samples, e.g. EmptyFunctionApp folder that contains the EmptyFunctionApp.sln. Please note that the .sln files will be hidden in this dialog.
After opening a sample in VS Code, there might be some notifications. If there are any unresolved dependencies, click on âRestoreâ to resolve those.
If âDetected an Azure Function Project⌠that may have been created outside of VS Code.â is show, click âYesâ and wait for VS Code to finish configuring environment. |
---|