.NET (C# / VBA.NET): arupcompute-connect-dotnet
There is no project charging for use of ArupCompute, however project usage data is critical to justifying continued funding of ArupCompute - please provide a job number!
To contribute and / or submit bug reports go to the arupcompute-connect-dotnet github.
You will require access to the Arup GitHub account to see the above link. If you are having trouble reach out on the ArupCompute teams.
You can join the teams using the code b53lq6l
Installation
You can reference the arupcompute-connect-dotnet library using NuGet, dotnet, or Visual Studio.
Dotnet
As arupcompute-connect-dotnet is hosted on a private feed you will need to install the Azure Artifacts Credential Provider.
Also ensure that you visit this webpage to ensure that you have access to the repository where the reference libraries are stored (enter Arup login details if prompted).
Project setup
Add a nuget.config file to your project, in the same folder as your .csproj or .sln file
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="arupcomputedevops@Release" value="https://pkgs.dev.azure.com/arupcomputedevops/_packaging/arupcomputedevops/nuget/v3/index.json" />
</packageSources>
</configuration>
Add references
Use the dotnet add package --interactive command (using the interactive flag, which allows the credential manager to prompt you for credentials):
dotnet add [<PROJECT>] package --interactive arupcompute-connect-dotnet
The three packages you will need to add to your solution are:
- arupcompute-connect-dotnet
- Arup.Compute.Models
- Arup.Compute.DotNetSdk
Restore packages
If prompted to restore packages you may need to use the --interactive flag:
dotnet restore --interactive
Simple example
using Arup.Compute.Connect;
using Arup.Compute.Models;
using Arup.Compute.DotNetSdk;
using System.Threading.Tasks;
Connection connection = new Connection("00000-00"); // The job number will _not_ be charged
int calcId = 11416564; // Sample Library - Basic calculation
Dictionary<string, object> inputs = new Dictionary<string, object>()
{
{ "a", 1 },
{ "b", 2}
};
Task<CalcRecord> resultTask = Call.ExecuteCalculationAsync(connection, calcId, false, inputs);
CalcRecord calcRecord = resultTask.Result;
If you are interacting with a library that utilises the expanded features of ArupCompute (e.g. report writing, errors, warnings, remarks etc.) such as DesignCheck2 you will need the additional step below to interpret the .Result
:
Task<CalcRecord> resultTask = Call.ExecuteCalculationAsync(connection, calcId, false, inputs, resultType: Arup.Compute.Connect.Call.ResultType.Simple); // Simple provides reports, errors etc. default is mini which just returns results
CalcRecord result = resultTask.Result;
ArupComputeResult acr = result.ParseOutput<Arup.Compute.DotNetSdk.ArupComputeResult>();
// For example if you want access to the plain-text report
string report = acr.ArupComputeReport;
If you are utilising the batch execution feature of ArupCompute and want access to reports etc. you will need to do the following:
///Call.ExecuteBatchCalculationAsync(...)
var lacr = result.ParseOutput<List<Arup.Compute.DotNetSdk.ArupComputeResult>>();
Connection object
The Connection
class is one of the most important parts of the Dotnet ArupCompute client. It controls all authentication and most settings related to the client. It is a required input in most other functions related to ArupCompute.
This object also controls when to use AC Local.
The Connection
object also replaces the old authentication methods. If you are working on an older Dotnet project that has not yet updated to use the Connection
object then you should perform this upgrade as older authentication methods may stop working.
Constructor properties
Name | Description | Default |
---|---|---|
string jobNumber | The Job Number this call is associated with. This is for usage tracking purposes only and will not result in any charges to the job | N/A |
Connect.ConfigMode Mode | The "Mode" that Connect will run in. This primarily affects how/why/when Connect will call AC Local or remote. Change to Connect.ConfigMode.PROXY to always use remote, recommended for instances where Connect is being used on a server | ConfigMode.DEFAULT |
Task<string> tokenTask | The task object that contains the authentication method. In most cases this should be left to Connection to handle. Should only be used in scenarios where the auth to Arup systems are being handled outside of Connect | null |
Connection modes
The Connection
object can run in multiple different "modes". These modes control when/how Connect will try to connect to AC Local and when it will instead send requests directly to the remote server. This mode is changed through changing the Connect.ConfigMode Mode
argument in the constructor.
Most users should not change the mode that Connect is using. If you are using Connect inside a Compute function then you should not change the mode. The correct mode will be set automatically by ArupCompute.
Name | Description | When to use |
---|---|---|
DEFAULT | Connect will prefer AC Local over using the remote server. It will only use remote if AC Local cannot be found | This is the default mode that should be used in the vast majority of programs. Specifically it should be used on any program that is running on user's machines |
PROXY | Connect will always use the remote server, it will not look for whether AC Local is running | This should be used when a dotnet program is running on a server where AC Local will not be present |
FUNC_SERVICE | Connect will always use AC Local and will throw an error if AC Local cannot be found. It makes calls to AC Local through Unix Domain Sockets rather than HTTP | This mode should not be set directly by users. It is primarily used when Connect is being used inside an ArupCompute calc running on AC Local |
Example use
string jobNumber = "00000000"
Connection connection = new Connection(jobNumber); // The job number will not be charged
Non-primitive inputs / outputs - JSON
The recommended library for working with JSON in C# is Newtonsoft.JSON. However, the arupcompute-connect-dotnet
library uses this in the background and can deal with serialisation / deserialisation for you.
Parallel Mode
When making calls to AC Local you can use "parallel mode" to run a batch of calcs. This will make AC Local run the calculations in the batch in parallel, meaning that the batch will run faster. There is additional setup time required on AC Local when running calcs in parallel, so for small batches it is better to run the batch not in parallel mode. From testing we have found that if the batch is larger than 10 calcs then running in parallel will be faster than in regular batch mode.
To run a calc in parallel, set the parallel
param in Call.ExecuteCalculationAsync
to true
.
Connection connection = new Connection("00000-00"); // The job number will _not_ be charged
int calcId = 11416564; // Sample Library - Basic calculation
Dictionary<string, object> inputs = new Dictionary<string, object>()
{
{ "a", new List<int>() { 1, 2, 3, 4, 5 } },
{ "b", new List<int>() { 2, 3, 4, 5, 6 } }
};
Task<CalcRecord> resultTask = Call.ExecuteCalculationAsync(connection, calcId, batch: true, inputs, parallel: true); // Note: parallel and batch are set to true
CalcRecord calcRecord = resultTask.Result;
useFanOut
parallel
should not be set to True
at the same time as useFanOut
. Doing this would mean that parallel
would have no effect
Optional Inputs
When you want to use the default value for an optional input you can either set the value of the input to be null
, or leave out the input entirely from your inputs dictionary:
// for single invocation
Dictionary<string, object> inputs = new Dictionary<string, object>()
{
{ "a", 1 },
{ "b", 2 },
{ "optional-input", null } // or leave out this line
};
Task<CalcRecord> resultTask = Call.ExecuteCalculationAsync(connection, calcId, batch: false, inputs);
CalcRecord calcRecord = resultTask.Result;
// for batch call
Dictionary<string, object> inputs = new Dictionary<string, object>()
{
{ "a", new List<int>() { 1, 2, 3, 4, 5 } },
{ "b", new List<int>() { 2, 3, 4, 5, 6 } },
{ "optional-input", new List<object>() { null, 3, 4, 1 } } // NOTE: This list is not as long as the above two lists
};
Task<CalcRecord> resultTask = Call.ExecuteCalculationAsync(connection, calcId, batch: true, inputs);
CalcRecord calcRecord = resultTask.Result;
File Inputs/Outputs
File Inputs
There are two ways to send a file as a file input using Connect:
- Send the file path as a string - for when you have the file stored in your file system
- Send an object that implements the
IBaseComputeFile
interface - for when you want to send through a file stream without saving the file to your file system
Example of sending a file input using the file path:
Calc calc = await Call.GetCalcAsync(connection, calcId, true);
Dictionary<string, object> dicBody = new()
{
{ "file input", @"path\to\file\file1.txt" }
};
CalcRecord calcResult = await Call.ExecuteCalculationAsync(
connection,
calc,
false,
dicBody,
resultType: resultType
);
Example of sending a file input using a class that implements IBaseComputeFile
, in this example we are using the ComputeFile
class which is part of the Arup.Compute.DotNetSdk
package:
ComputeFile computeFile = new(fileStream, "file1.txt");
Dictionary<string, object> inputs = new()
{
{
"file input",
computeFile
}
};
CalcRecord calcResult = await Call.ExecuteCalculationAsync(
connection,
calcId,
false,
inputs
);
For more information on the IBaseComputeFile
interface see File Inputs - Advanced.
File Outputs
When a calculation returns a file it will either return just the file as a download link, or it will return the file as a part of an ArupComputeResult
object. There are built in methods to Connect to help with downloading both of these types of file returns.
When a calculation returns just a single file output:
Calc calc = await Call.GetCalcAsync(connection, calculationId, true); // calculation that adds the provided lines to a file and returns that file
Dictionary<string, object> dicBody = new()
{
{ "Lines", "first line,second line" }
};
CalcRecord calcResult = await Call.ExecuteCalculationAsync(
connection,
calc,
false,
dicBody,
resultType: resultType
);
byte[] outputFile = await FileIO.GetFileFromBlobAsync(calcResult.Output);
// now we have the byte[] object we can do whatever we want with it, including saving it to the file system
File.WriteAllBytes(@"path\to\file\save\location\output-file.txt", outputFile);
When a calculation returns an ArupComputeResult
object it may contain files in the ArupComputeResultFileItems
field. This field contains a list of ArupComputeResultFileItem
objects. This object has the following interface:
Field Name | Description |
---|---|
FileName | The name of the file |
FileData | The file data in an IBaseComputeFile format. When the calculation is first returned this field will be empty |
FileLink | The link to download the file data. This will be used by Compute to fill the FileData field when requested |
Description | The description of the file |
To fill the FileData
field you should call the ParseOutputAsync
method on the CalcRecord
object that is returned when calling a calculation. For example, below is an example of calling a calculation that returns three files:
Calc calc = await Call.GetCalcAsync(connection, calcId, true);
Dictionary<string, object> dicBody = new()
{
{ "file input", @"path\to\file\input\file1.txt" }
};
CalcRecord calcResult = await Call.ExecuteCalculationAsync(
connection,
calc,
false,
dicBody,
resultType: resultType
);
ArupComputeResult acr = await calcResult.ParseOutputAsync(true); // the "true" indicates that the files should be downloaded
Console.WriteLine(
string.Join(
",",
new ComputeFile(acr.ArupComputeResultFileItems[0].FileData.GetStream(), "out.txt")
.GetLines()
.ToList()
)
);
Console.WriteLine(
string.Join(
",",
new ComputeFile(acr.ArupComputeResultFileItems[1].FileData.GetStream(), "out 1.txt")
.GetLines()
.ToList()
)
);
Console.WriteLine(
string.Join(
",",
new ComputeFile(acr.ArupComputeResultFileItems[2].FileData.GetStream(), "out 2.txt")
.GetLines()
.ToList()
)
);
If you have made a batch request then you should instead call the ParseBatchOutputAsync
. It works in the same way as the ParseOutputAsync
method, but returns a list of ArupComputeResult
objects instead of just one.