Prerequisites

Before proceeding with the tutorial, please install the following software:

  • Microsoft Visual Studio 2015
  • Oracle VM VirtualBox
  • HashiCorp Vagrant
  • Avid Platform Virtual Machine

For instructions on setting up VirtualBox, Vagrant, and the Avid Platform Virtual Machine, see the README included in the Avid Platform Virtual Machine download file (avid-platform-vagrant-vm-YYYY-MM-DD-XXXX.zip).

Creating the Project Structure

  1. Create a new Windows Console Application project by selecting File->New->Project....

  2. In the New Project dialog, navigate to Templates->Visual C#->Windows and select the Console Application template.

  3. Set the project name to NetService and click OK to create the project.

  4. In the Solution Explorer pane, rename the Program.cs file to NetServiceMain.cs. Click Yes, when asked to perform a rename in the project of all references to the code element Program.

  5. Add the following content to NetServiceMain.cs.

  6. Add the Avid-supplied libs folder as a NuGet repository as follows.

    1. Select Tools->NuGet Package Manager->Package Manager Settings.
    2. In the Options dialog that appears, choose Package Sources. In the Available Package Sources area of the dialog, click the plus button.
    3. Name it Avid .NET, and for the Source value navigate to the libs folder (extracted from the avid-connector-api-net-XXXX.zip).
    4. Click OK to perform the operation and close the Options window.
  7. Add the avid-acs-proxybal-net package to solution as follows.

    1. Select Project->Manage NuGet Packages....
    2. Check the Include prerelease checkbox.
    3. Change the Package source to Avid .NET
    4. Select the Browse option and search for avid-acs-proxybal-net.
    5. When the avid-acs-proxybal-net package appears in the results list, select the latest version (v2.2.1-build-XXXX at time of writing).
    6. Click the Install button.
    7. In the Preview dialog that appears, click OK to accept the changes.
    8. In the License Acceptance dialog that appears, click the I Accept button.

    Note: If it becomes necessary to update a package, it is good practise to eliminate dependency issues by uninstalling all packages associated with a project prior to the update. To uninstall a package associated with a project, right-click the project in the Solution Explorer and select Manage Nuget Packages for Solution... from the popup menu.

  8. Close the package manager pane.

  9. Select Project->Add Reference.... In the Reference Manager dialog that appears, browse the Assemblies Framework group and add a reference to System.Runtime.Serialization assembly. Click the OK button to save your changes.

  10. Build the project using the "Rebuild Solution" option by selecting Build->Rebuild Solution. In the Output pane you should see output similar to the following:

    1
    2
    3
    1>------ Rebuild All started: Project: net-service, Configuration: Debug Any CPU ------
    1> net-service -> c:\users\name\documents\visual studio 2015\Projects\net-service\net-service\bin\Debug\net-service.exe
    ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
  11. Although it will currently not connect to the Avid Platform, you should build your project by clicking the Start button within Visual Studio.

Connecting to the Platform

  1. Create a NetServiceBase.cs file by right-clicking on the NetService project in the Visual Studio Solution Explorer pane. From the pop-up menu, select Add->New Item.... In the Add New Item dialog, select the Visual C# Class solution item, give it the name NetServiceBase.cs, and click Add.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using Avid.Platform.Bus;
using Avid.Platform.Bus.Exceptions;

namespace NetService
{
public partial class NetServiceBase : IConnectionSubscriber
{
private BusAccess bus;

public NetServiceBase()
{

}

public void OnConnect()
{
Console.WriteLine("Connected to the Avid Platform");
}

public void OnDisconnect()
{
Console.WriteLine("Disconnected from the Avid Platform");
}

public void Start()
{
try
{
using (bus = new BusAccess(new IpAuthenticationProvider(), this))
{

}
}
catch (BusAccessException ex)
{
Console.WriteLine("Failed to connect to the platform and register service: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught while trying to connect to the platform and register service:" + ex.Message);
}
}
}
}
  1. Update the file NetServiceMain.cs to have the following content.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;

namespace NetService {

static class NetServiceMain {
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main() {
// If you are connecting to the avid platform from visual studio please uncomment the following lines
// ACS_GATEWAY_HOST should be the hostname of the machine you are connecting to.
//Environment.SetEnvironmentVariable("ACS_GATEWAY_HOST", "avid-platform-zone1");
//Environment.SetEnvironmentVariable("ACS_GATEWAY_PORT", "9900");

NetServiceBase service = new NetServiceBase();

service.Start();
}
}
}
  1. Build the project using the "Rebuild Solution" option by selecting Build->Rebuild Solution. In the Output pane you should see output similar to the following:

    1
    2
    3
    1>------ Rebuild All started: Project: net-service, Configuration: Debug Any CPU ------
    1> net-service -> c:\users\name\documents\visual studio 2015\Projects\net-service\net-service\bin\Debug\net-service.exe
    ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
  2. Start the service by selecting Debug->Start Debugging.

Congratulations! You have successfully connected to Avid Platform as a client.

Creating a Service

  1. Create a NetService.cs file by right-clicking on the net-service project in the Visual Studio Solution Explorer pane. From the pop-up menu, select Add->New Item.... In the Add New Item dialog, select the Visual C# Class solution item, give it the name NetService.cs, and click Add.

  2. Add following content to the NetService.cs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using Avid.Platform.Bus.Messaging;
using Avid.Platform.Bus.Service;
using System.Collections.Generic;
using System.Threading;

namespace NetService
{
[Service("avid.tutorial.net.service", 1, "Tutorial service based on Avid Connector API for C#", new int[] { })]
[Examples("NetService.Resources.Examples")]
[Error("INVALID_JOB_ID", 500, ErrorSeverity.Error, "Invalid jobId: %{jobId}")]
[Error("INVALID_EXECUTION_TIME", 500, ErrorSeverity.Error, "Invalid execution time: %{jobId}")]
public class NetService : ServiceBase
{
private AutoResetEvent _stopEvent = new AutoResetEvent(false);

[Operation("submitJob", "Submit job for execution. Returns response back with information whether job submitted successfully or not")]
[RestRequest("jobs/{jobId}", RequestMethod.POST, new string[] { "execTime" })]
public void SubmitJob(int jobId, int execTime, IOperationContext<string> response)
{
if (jobId < 1)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("jobId", jobId.ToString());
response.Error(new Error("INVALID_JOB_ID", parameters, "Invalid jobId was provided. Must be integer > 0."));
}
else if (execTime < 100)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("execTime", execTime.ToString());
response.Error(new Error("INVALID_JOB_ID", parameters, "Invalid execution time provided. Must be integer >= 100."));
}
else
{
response.Respond("submitted");
}
}

public void WaitForStop()
{
_stopEvent.WaitOne();
}
}
}
  1. Modify the NetServiceBase.cs file as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using Avid.Platform.Bus;
using Avid.Platform.Bus.Exceptions;
using Avid.Platform.Bus.Messaging;
using System;

namespace NetService
{
public partial class NetServiceBase : IConnectionSubscriber
{
private BusAccess bus;

public NetServiceBase()
{

}

public void OnConnect()
{
Console.WriteLine("Connected to the Avid Platform");
}

public void OnDisconnect()
{
Console.WriteLine("Disconnected from the Avid Platform");
}

public void Start()
{
try
{
using (bus = new BusAccess(new IpAuthenticationProvider(), this))
{
NetService service = new NetService();
bus.RegisterService(service,
new AnonAsyncOpResult<Avid.Platform.Bus.Service.ServiceBase>()
{
onSuccess = (Avid.Platform.Bus.Service.ServiceBase result) => {
Console.WriteLine("Registered service: " + result + ".");
},
onError = (OperationError error) => {
Console.WriteLine("Failed to register service: " + service + ", error: " + error.Message + ".");
}
});
service.WaitForStop();
}
}
catch (BusAccessException ex)
{
Console.WriteLine("Failed to connect to the platform and register service: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught while trying to connect to the platform and register service:" + ex.Message);
}
}
}
}
  1. Add a folder named Resources to your project.

  2. Right-click on the Resources folder and select Add->New Item... from the pop-up menu. Select Resources File from the Add New Item dialog, give it the name Examples.resx, and click the Add button.

  3. Open Examples.resx.

  4. Change the Access Modifier for the resources file to No code generation.

  5. Add following string resources to this file: (If when entering the example resouce you get an invalid identifier error, right-click on Examples.resx->Properties and clear the value for the field Custom Tool)

    Name Value
    submitJob.Submit_invalid_execTime { "jobId": 2, "execTime": 10 }
    submitJob.Submit_invalid_jobId { "jobId": -1, "execTime": 1000 }
    submitJob.Submit_valid_job { "jobId": 1, "execTime": 1000 }
  6. Build the project using the "Rebuild Solution" option by selecting Build->Rebuild Solution.

  7. Start the service by selecting Debug->Start Debugging.

  8. Test the service to verify it is working:

    • Open the ACS Monitor, and filter the services list by providing net\.service regular expression in the box. You should now see now only one service in the list: avid.tutorial.net.service
    • Expand operations list, the operations list, using the small leftmost icon under the service name. You should see a submitJob operation there.
    • Click on Submit valid job link. You should see a message generated on the right-hand side, in the Request field.
    • Click Query button. You should get a response back from your service in the Response field.
  9. Stop the NetService.

Congratulations! You have successfully registered your service on the Platform.

Sending Requests to Another Service

  1. Create the file NetServiceResponse.cs with the following contents:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Runtime.Serialization;

namespace NetService
{
[DataContract]
public sealed class NetServiceResponse
{
internal NetServiceResponse(string jobStatus, Nullable<int> numberOfSubmittedJobs)
{
this.JobStatus = jobStatus;
this.NumberOfSubmittedJobs = numberOfSubmittedJobs;
}

[DataMember(Name = "jobStatus")]
public string JobStatus { get; internal set; }

[DataMember(Name = "numberOfSubmittedJobs")]
public Nullable<int> NumberOfSubmittedJobs { get; internal set; }
}
}
  1. In this step you modify the SubmitJob method in the NetService.cs file. You add a constructor and two new methods, SetNumberOfSubmittedJobs and GetNumberOfSubmittedJobs, to update and request the number of submitted jobs. In addition, you add methods and interfaces to handle asynchronous requests:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
using Avid.Platform.Bus;
using Avid.Platform.Bus.Exceptions;
using Avid.Platform.Bus.Messaging;
using Avid.Platform.Bus.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace NetService
{
[Service("avid.tutorial.net.service", 1, "Tutorial service based on Avid Connector API for C#", new int[] { })]
[Examples("NetService.Resources.Examples")]
[Error("INVALID_JOB_ID", 500, ErrorSeverity.Error, "Invalid jobId: %{jobId}")]
[Error("INVALID_EXECUTION_TIME", 500, ErrorSeverity.Error, "Invalid execution time: %{jobId}")]
public class NetService : ServiceBase
{
private BusAccess Access;
private AutoResetEvent _stopEvent = new AutoResetEvent(false);

public NetService(BusAccess bus)
{
this.Access = bus;
}
public void WaitForStop()
{
_stopEvent.WaitOne();
}

[Operation("submitJob", "Submit job for execution. Returns response back with information whether job submitted successfully or not")]
[RestRequest("jobs/{jobId}", RequestMethod.POST, new string[] { "execTime" })]
public void SubmitJob(int jobId, int execTime, IOperationContext<NetServiceResponse> response)
{
if (jobId < 1)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("jobId", jobId.ToString());
response.Error(new Error("INVALID_JOB_ID", parameters, "Invalid jobId was provided. Must be integer > 0."));
}
else if (execTime < 100)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("execTime", execTime.ToString());
response.Error(new Error("INVALID_JOB_ID", parameters, "Invalid execution time provided. Must be integer >= 100."));
}
else
{
SetNumberOfSubmittedJobsCallback setNumberOfSubmittedJobsCallback = new SetNumberOfSubmittedJobsCallback(response);
GetNumberOfSubmittedJobsCallback getNumberOfSubmittedJobsCallback = new GetNumberOfSubmittedJobsCallback(this, setNumberOfSubmittedJobsCallback, response);
GetNumberOfSubmittedJobs(getNumberOfSubmittedJobsCallback);
}
}

void GetNumberOfSubmittedJobs(GetNumberOfSubmittedJobsCallback callback)
{
Message message = new Message("avid.acs.attributes", "global", 3, "fetch");
message.Parameters.Add("name", "net.service.store");

MessageOptions messageOptions = new MessageOptions()
{
Timeout = 2000
};

try
{
Access.Query(message, new GetNumberOfSubmittedJobsResponseHandler(callback), messageOptions);
}
catch (BusAccessException e)
{
callback.Handle(true, null, null);
}
}

void SetNumberOfSubmittedJobs(int? number, int? newNumber, SetNumberOfSubmittedJobsCallback callback)
{
Message message = new Message("avid.acs.attributes", "global", 3, "store");
message.Parameters.Add("name", "net.service.store");
IDictionary<string, int> map = new Dictionary<string, int>();
map.Add("numberOfSubmittedJobs", newNumber.Value);
message.Parameters.Add("value", map);

MessageOptions messageOptions = new MessageOptions()
{
Timeout = 2000
};

try
{
Access.Query(message, new SetNumberOfSubmittedJobsResponseHandler(callback, number, newNumber), messageOptions);
}
catch (BusAccessException e)
{
callback.Handle(true, null, number, newNumber);
}
}

private class GetNumberOfSubmittedJobsResponseHandler : IAsyncQueryResult
{
private GetNumberOfSubmittedJobsCallback Callback;

public GetNumberOfSubmittedJobsResponseHandler(GetNumberOfSubmittedJobsCallback callback)
{
this.Callback = callback;
}

public void OnError(OperationError error)
{
Callback.Handle(true, null, null);
}

public void OnSuccess(object obj)
{
Message response = obj as Message;
IDictionary<string, int?> value = response.Results.TryGetItem<IDictionary<string, int?>>("value");
int? number = null;

if (value != null)
{
value.TryGetValue("numberOfSubmittedJobs", out number);
}

Callback.Handle(false, response.Errors, number);
}

public void OnTimeout()
{
Callback.Handle(true, null, null);
}
}

private class SetNumberOfSubmittedJobsResponseHandler : IAsyncQueryResult
{
private SetNumberOfSubmittedJobsCallback Callback;
private int? NewNumber;
private int? Number;

public SetNumberOfSubmittedJobsResponseHandler(SetNumberOfSubmittedJobsCallback callback, int? number, int? newNumber)
{
this.Callback = callback;
this.Number = number;
this.NewNumber = newNumber;
}

public void OnError(OperationError error)
{
Callback.Handle(true, null, Number, NewNumber);
}

public void OnSuccess(object result)
{
Callback.Handle(false, (result as Message).Errors, Number, NewNumber);
}

public void OnTimeout()
{
Callback.Handle(true, null, Number, NewNumber);
}
}


private class GetNumberOfSubmittedJobsCallback
{
private SetNumberOfSubmittedJobsCallback Callback;
private IOperationContext<NetServiceResponse> Responder;
private NetService Service;

public GetNumberOfSubmittedJobsCallback(NetService service, SetNumberOfSubmittedJobsCallback callback, IOperationContext<NetServiceResponse> responder)
{
this.Responder = responder;
this.Callback = callback;
this.Service = service;
}

public void Handle(Boolean responseMissing, IErrorSet errors, int? number)
{
int newNumber;
if (!responseMissing && (errors == null || errors.Count == 0))
{
if (number == null)
{
newNumber = 1;
}
else
{
newNumber = number.Value + 1;
}
Service.SetNumberOfSubmittedJobs(number, new int?(newNumber), Callback);
}
else if (errors != null && errors.Count > 0 && "avid.acs.attributes/NOT_FOUND".Equals(errors.First().Code))
{
newNumber = 1;
Service.SetNumberOfSubmittedJobs(number, newNumber, Callback);
}
else
{
NetServiceResponse response = new NetServiceResponse("submitted", null);
Responder.Respond(response);
}
}
}

private class SetNumberOfSubmittedJobsCallback
{
private IOperationContext<NetServiceResponse> Responder;

public SetNumberOfSubmittedJobsCallback(IOperationContext<NetServiceResponse> responder)
{
this.Responder = responder;
}

public void Handle(Boolean responseMissing, IErrorSet errors, int? oldNumber, int? newNumber)
{
NetServiceResponse response;
if (responseMissing || errors.Count > 0)
{
response = new NetServiceResponse("submitted", oldNumber);
}
else
{
response = new NetServiceResponse("submitted", newNumber);
}

Responder.Respond(response);
}
}
}
}
  1. Next, modify the contents of the NetServiceBase.cs file, to pass a BusAccess to constructor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using Avid.Platform.Bus;
using Avid.Platform.Bus.Exceptions;
using Avid.Platform.Bus.Messaging;
using System;

namespace NetService
{
public partial class NetServiceBase : IConnectionSubscriber
{
private BusAccess bus;

public NetServiceBase()
{

}

public void OnConnect()
{
Console.WriteLine("Connected to the Avid Platform");
}

public void OnDisconnect()
{
Console.WriteLine("Disconnected from the Avid Platform");
}

public void Start()
{
try
{
using (bus = new BusAccess(new IpAuthenticationProvider(), this))
{
NetService service = new NetService(bus);
bus.RegisterService(service,
new AnonAsyncOpResult<Avid.Platform.Bus.Service.ServiceBase>()
{
onSuccess = (Avid.Platform.Bus.Service.ServiceBase result) => {
Console.WriteLine("Registered service: " + result + ".");
},
onError = (OperationError error) => {
Console.WriteLine("Failed to register service: " + service + ", error: " + error.Message + ".");
}
});

service.WaitForStop();
}
}
catch (BusAccessException ex)
{
Console.WriteLine("Failed to connect to the platform and register service: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught while trying to connect to the platform and register service:" + ex.Message);
}
}
}
}
  1. Build the project using the "Rebuild Solution" option by selecting Build->Rebuild Solution.

  2. Start the NetService service by clicking Debug->Start.

  3. Test the service in the ACS Monitor. Send several submitJob requests to your service. With each request you should see the number of submitted jobs increment in the response:

1
2
3
4
5
6
{
"resultSet": {
"jobStatus": "submitted",
"numberOfSubmittedJobs": 2
}
}
  1. Stop the NetService service.

Congratulations! You have successfully sent a request to another service, and received a response from it.

Posting Events When the Job is Submitted and Completed

  1. Edit the NetService.cs file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
using Avid.Platform.Bus;
using Avid.Platform.Bus.Exceptions;
using Avid.Platform.Bus.Messaging;
using Avid.Platform.Bus.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace NetService
{
[Service("avid.tutorial.net.service", 1, "Tutorial service based on Avid Connector API for C#", new int[] { })]
[Examples("NetService.Resources.Examples")]
[Error("INVALID_JOB_ID", 500, ErrorSeverity.Error, "Invalid jobId: %{jobId}")]
[Error("INVALID_EXECUTION_TIME", 500, ErrorSeverity.Error, "Invalid execution time: %{jobId}")]
public class NetService : ServiceBase
{
private BusAccess Access;
private AutoResetEvent _stopEvent = new AutoResetEvent(false);

public NetService(BusAccess bus)
{
this.Access = bus;
}
public void WaitForStop()
{
_stopEvent.WaitOne();
}

[Operation("submitJob", "Submit job for execution. Returns response back with information whether job submitted successfully or not")]
[RestRequest("jobs/{jobId}", RequestMethod.POST, new string[] { "execTime" })]
public void SubmitJob(int jobId, int execTime, IOperationContext<NetServiceResponse> response)
{
if (jobId < 1)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("jobId", jobId.ToString());
response.Error(new Error("INVALID_JOB_ID", parameters, "Invalid jobId was provided. Must be integer > 0."));
}
else if (execTime < 100)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("execTime", execTime.ToString());
response.Error(new Error("INVALID_JOB_ID", parameters, "Invalid execution time provided. Must be integer >= 100."));
}
else
{
PostChannelMessages(jobId, execTime);
SetNumberOfSubmittedJobsCallback setNumberOfSubmittedJobsCallback = new SetNumberOfSubmittedJobsCallback(response);
GetNumberOfSubmittedJobsCallback getNumberOfSubmittedJobsCallback = new GetNumberOfSubmittedJobsCallback(this, setNumberOfSubmittedJobsCallback, response);
GetNumberOfSubmittedJobs(getNumberOfSubmittedJobsCallback);
}
}

private void PostChannelMessages(int jobId, int execTime)
{
ChannelMessage message = new ChannelMessage("net.service.jobs", "submit");
message.Data.Add("jobId", jobId);

try
{
Access.PostToChannel(message, new AnonAsyncOpResult()
{
onError = (OperationError error) => {
Console.WriteLine("Post to channel 'net.service.jobs':'submit' failed: " + error.Message);
}
});
}
catch (BusAccessException e)
{
Console.WriteLine("Caught BusAccessException : " + e.Message);
}

System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += (sender, e) => { MessageProcessed(jobId); timer.Close(); };
timer.Interval = execTime;
timer.Enabled = true;
}

void MessageProcessed(int jobId)
{
ChannelMessage message = new ChannelMessage("net.service.jobs", "complete");
message.Data.Add("jobId", jobId);

try
{
Access.PostToChannel(message, new AnonAsyncOpResult()
{
onError = (OperationError error) => {
Console.WriteLine("Post to channel 'net.service.jobs':'complete' failed: " + error.Message);
}
});
}
catch (BusAccessException e)
{
}
}
void GetNumberOfSubmittedJobs(GetNumberOfSubmittedJobsCallback callback)
{
Message message = new Message("avid.acs.attributes", "global", 3, "fetch");
message.Parameters.Add("name", "net.service.store");

MessageOptions messageOptions = new MessageOptions()
{
Timeout = 2000
};

try
{
Access.Query(message, new GetNumberOfSubmittedJobsResponseHandler(callback), messageOptions);
}
catch (BusAccessException e)
{
callback.Handle(true, null, null);
}
}

void SetNumberOfSubmittedJobs(int? number, int? newNumber, SetNumberOfSubmittedJobsCallback callback)
{
Message message = new Message("avid.acs.attributes", "global", 3, "store");
message.Parameters.Add("name", "net.service.store");
IDictionary<string, int> map = new Dictionary<string, int>();
map.Add("numberOfSubmittedJobs", newNumber.Value);
message.Parameters.Add("value", map);

MessageOptions messageOptions = new MessageOptions()
{
Timeout = 2000
};

try
{
Access.Query(message, new SetNumberOfSubmittedJobsResponseHandler(callback, number, newNumber), messageOptions);
}
catch (BusAccessException e)
{
callback.Handle(true, null, number, newNumber);
}
}

private class GetNumberOfSubmittedJobsResponseHandler : IAsyncQueryResult
{
private GetNumberOfSubmittedJobsCallback Callback;

public GetNumberOfSubmittedJobsResponseHandler(GetNumberOfSubmittedJobsCallback callback)
{
this.Callback = callback;
}

public void OnError(OperationError error)
{
Callback.Handle(true, null, null);
}

public void OnSuccess(object obj)
{
Message response = obj as Message;
IDictionary<string, int?> value = response.Results.TryGetItem<IDictionary<string, int?>>("value");
int? number = null;

if (value != null)
{
value.TryGetValue("numberOfSubmittedJobs", out number);
}

Callback.Handle(false, response.Errors, number);
}

public void OnTimeout()
{
Callback.Handle(true, null, null);
}
}

private class SetNumberOfSubmittedJobsResponseHandler : IAsyncQueryResult
{
private SetNumberOfSubmittedJobsCallback Callback;
private int? NewNumber;
private int? Number;

public SetNumberOfSubmittedJobsResponseHandler(SetNumberOfSubmittedJobsCallback callback, int? number, int? newNumber)
{
this.Callback = callback;
this.Number = number;
this.NewNumber = newNumber;
}

public void OnError(OperationError error)
{
Callback.Handle(true, null, Number, NewNumber);
}

public void OnSuccess(object result)
{
Callback.Handle(false, (result as Message).Errors, Number, NewNumber);
}

public void OnTimeout()
{
Callback.Handle(true, null, Number, NewNumber);
}
}


private class GetNumberOfSubmittedJobsCallback
{
private SetNumberOfSubmittedJobsCallback Callback;
private IOperationContext<NetServiceResponse> Responder;
private NetService Service;

public GetNumberOfSubmittedJobsCallback(NetService service, SetNumberOfSubmittedJobsCallback callback, IOperationContext<NetServiceResponse> responder)
{
this.Responder = responder;
this.Callback = callback;
this.Service = service;
}

public void Handle(Boolean responseMissing, IErrorSet errors, int? number)
{
int newNumber;
if (!responseMissing && (errors == null || errors.Count == 0))
{
if (number == null)
{
newNumber = 1;
}
else
{
newNumber = number.Value + 1;
}
Service.SetNumberOfSubmittedJobs(number, new int?(newNumber), Callback);
}
else if (errors != null && errors.Count > 0 && "avid.acs.attributes/NOT_FOUND".Equals(errors.First().Code))
{
newNumber = 1;
Service.SetNumberOfSubmittedJobs(number, newNumber, Callback);
}
else
{
NetServiceResponse response = new NetServiceResponse("submitted", null);
Responder.Respond(response);
}
}
}

private class SetNumberOfSubmittedJobsCallback
{
private IOperationContext<NetServiceResponse> Responder;

public SetNumberOfSubmittedJobsCallback(IOperationContext<NetServiceResponse> responder)
{
this.Responder = responder;
}

public void Handle(Boolean responseMissing, IErrorSet errors, int? oldNumber, int? newNumber)
{
NetServiceResponse response;
if (responseMissing || errors.Count > 0)
{
response = new NetServiceResponse("submitted", oldNumber);
}
else
{
response = new NetServiceResponse("submitted", newNumber);
}

Responder.Respond(response);
}
}
}
}
  1. Build the project using the "Rebuild Solution" option by selecting Build->Rebuild Solution.

  2. Start the NetService service by clicking Debug->Start.

  3. Test the service in the ACS Monitor.

  4. Stop the NetService service.

Congratulations! You have successfully published events to the Platform.

At this point the service behavior in the ACS Monitor is unchanged. Next, we create a consumer of our service events.

Subscribing to Service Events

Other services and clients may wish to listen on the channel to which our service posts, so they can receive event information. We demonstrate this functionality by creating a new client that listens on the channel.

  1. Create a new Console Application project by right-clicking on the solution and selecting Add->New Project... from the pop-up menu. Select C#->Windows->Classic Desktop->Console Application, giving it the name NetClient. Click the OK button to save your changes.

  2. Add the avid-acs-proxybal-net package to the NetClient project as follows.

    1. Select the NetClient project in the Solution Explorer.
    2. Select Project->Manage NuGet Packages....
    3. Check the Include Prerelease checkbox (if not already done)
    4. Select the Browse option and search for avid-acs-proxybal-net.
    5. When the avid-acs-proxybal-net package appears in the results list, select the latest version (v2.2.1-build-XXXX at time of writing).
    6. Click the Install button.
    7. In the Preview dialog that appears, click OK to accept the changes.
    8. In the License Acceptance dialog that appears, click the I Accept button.
  3. Rename Program.cstoNetClient.cs` and add the following content to the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using Avid.Platform.Bus;
using Avid.Platform.Bus.Messaging;
using System;

namespace NetClient
{
class NetClient
{
static void Main(string[] args)
{
Environment.SetEnvironmentVariable("ACS_GATEWAY_HOST", "avid-platform-zone1");

CancellationTokenSource cancel = new CancellationTokenSource();
cancel.CancelAfter(Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds));

using (BusAccess bus = new BusAccess(new IpAuthenticationProvider(), false)) {
try {
bus.Connect(cancel.Token);
} catch (OperationCanceledException) {
Console.WriteLine("Failed to connect.");
return;
}
bus.SubscribeToChannel("net.service.jobs", new string[] { "submit" }, new SubmitSubscriber(),
new AnonAsyncOpResult() {
onSuccess = () => {
Console.WriteLine("Subscription to 'net.service.jobs':'submit' succeeded.");
},
onError = (OperationError error) => {
Console.WriteLine("Subscription to 'net.service.jobs':'submit' failed: " + error.Message);
}
});
bus.SubscribeToChannel("net.service.jobs", new string[] { "complete" }, new CompleteSubscriber(),
new AnonAsyncOpResult() {
onSuccess = () => {
Console.WriteLine("Subscription to 'net.service.jobs':'complete' succeeded.");
},
onError = (OperationError error) => {
Console.WriteLine("Subscription to 'net.service.jobs':'complete' failed: " + error.Message);
}
});

Console.WriteLine("Connected, and waiting for events");
Console.WriteLine("Press ESC to stop");
do {
while (!Console.KeyAvailable) {
}
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
}
}

private class SubmitSubscriber : IChannelMessageSubscriber
{
public void OnChannelMessage(IChannelContext context)
{
Console.WriteLine(String.Format("Job {0} is submitted", context.ChannelMessage.Data.TryGetItem<int>("jobId")));
}
}

private class CompleteSubscriber : IChannelMessageSubscriber {
public void OnChannelMessage(IChannelContext context)
{
Console.WriteLine(String.Format("Job {0} is completed", context.ChannelMessage.Data.TryGetItem<int>("jobId")));
}
}
}
}
  1. Build the project using the "Rebuild Solution" option by selecting Build->Rebuild Solution.

  2. Setup the solution to start both service and client at the same time by right clicking on Solution->Properties->Startup Project->Multiple Startup Projects. Ensure that the value of both NetClient and NetService are Start.

  3. Run both project with Debug->Start.

  4. Test the service to verify it is working:

    • Open the ACS Monitor, and filter the services list by providing net\.service regular expression in the box. You should now see now only one service in the list: avid.tutorial.net.service
    • Send multiple submitJob messages to your service with different values. Examine the logs of your client. The logs ought to contain each "job submit" and "job complete" event.

Congratulations! You have created a client that subscribes to the events being posted by your service.

Sending REST Requests to Your Service

  1. Log in to avid-platform-zone1 with ssh (username: root; password: vagrant):
1
ssh root@avid-platform-zone1
  1. Send POST requests to your service using the curl command, use the avidAccessToken generated previously:
1
2
3
curl -H "Content-Type: application/json" --data '{"execTime":15000}' "http://avid-platform-zone1:8000/apis/avid.tutorial.net.service;version=1/jobs/10?_avidAccessToken=ZWEwZDhlOTItYmJhZS00YTQ5LWFiN2QtNTAyYWMwZjhjZjJj"

#NOTE: request should be routed via NGINX and use HTTPS protocol: https://avid-platform-zone1/apis/...
Given HTTP request will send following message to your service:
1
2
3
4
5
6
7
8
9
10
{
"serviceType": "avid.tutorial.net.service",
"serviceRealm": "global",
"serviceVersion": 1,
"op": "submitJob",
"paramSet": {
"jobId": 10,
"execTime": 15000
}
}
  1. Stop the service and press ESC in the shell to terminate the client.

Congratulations! You have completed this tutorial.

Additional Resources

For more details on the .NET API please refer to the Microsoft documentation: