-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPluginBase.cs
264 lines (214 loc) · 10.4 KB
/
PluginBase.cs
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
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.PluginTelemetry;
using System;
using System.Runtime.CompilerServices;
using System.ServiceModel;
namespace DependentAssemblyExample
{
/// <summary>
/// Base class for all plug-in classes.
/// Plugin development guide: https://docs.microsoft.com/powerapps/developer/common-data-service/plug-ins
/// Best practices and guidance: https://docs.microsoft.com/powerapps/developer/common-data-service/best-practices/business-logic/
/// </summary>
public abstract class PluginBase : IPlugin
{
protected string PluginClassName { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PluginBase"/> class.
/// </summary>
/// <param name="pluginClassName">The <see cref=" cred="Type"/> of the plugin class.</param>
internal PluginBase(Type pluginClassName)
{
PluginClassName = pluginClassName.ToString();
}
/// <summary>
/// Main entry point for he business logic that the plug-in is to execute.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <remarks>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Execute")]
public void Execute(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new InvalidPluginExecutionException(nameof(serviceProvider));
}
// Construct the local plug-in context.
var localPluginContext = new LocalPluginContext(serviceProvider);
localPluginContext.Trace($"Entered {PluginClassName}.Execute() " +
$"Correlation Id: {localPluginContext.PluginExecutionContext.CorrelationId}, " +
$"Initiating User: {localPluginContext.PluginExecutionContext.InitiatingUserId}");
try
{
// Invoke the custom implementation
ExecuteCdsPlugin(localPluginContext);
// Now exit - if the derived plugin has incorrectly registered overlapping event registrations, guard against multiple executions.
return;
}
catch (FaultException<OrganizationServiceFault> orgServiceFault)
{
localPluginContext.Trace($"Exception: {orgServiceFault.ToString()}");
throw new InvalidPluginExecutionException($"OrganizationServiceFault: {orgServiceFault.Message}", orgServiceFault);
}
finally
{
localPluginContext.Trace($"Exiting {PluginClassName}.Execute()");
}
}
/// <summary>
/// Placeholder for a custom plug-in implementation.
/// </summary>
/// <param name="localPluginContext">Context for the current plug-in.</param>
protected virtual void ExecuteCdsPlugin(ILocalPluginContext localPluginContext)
{
// Do nothing.
}
}
/// <summary>
/// This interface provides an abstraction on top of IServiceProvider for commonly used PowerPlatform Dataverse Plugin development constructs
/// </summary>
public interface ILocalPluginContext
{
/// <summary>
/// The PowerPlatform Dataverse organization service for the Current Executing user.
/// </summary>
IOrganizationService InitiatingUserService { get; }
/// <summary>
/// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService.
/// </summary>
IOrganizationService PluginUserService { get; }
/// <summary>
/// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information.
/// </summary>
IPluginExecutionContext PluginExecutionContext { get; }
/// <summary>
/// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus. <br/>
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus.
/// </summary>
IServiceEndpointNotificationService NotificationService { get; }
/// <summary>
/// Provides logging run-time trace information for plug-ins.
/// </summary>
ITracingService TracingService { get; }
/// <summary>
/// General Service Provide for things not accounted for in the base class.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary>
/// OrganizationService Factory for creating connection for other then current user and system.
/// </summary>
IOrganizationServiceFactory OrgSvcFactory { get; }
/// <summary>
/// ILogger for this plugin.
/// </summary>
ILogger Logger { get; }
/// <summary>
/// Writes a trace message to the trace log.
/// </summary>
/// <param name="message">Message name to trace.</param>
void Trace(string message, [CallerMemberName] string method = null);
}
/// <summary>
/// Plug-in context object.
/// </summary>
public class LocalPluginContext : ILocalPluginContext
{
/// <summary>
/// The PowerPlatform Dataverse organization service for the Current Executing user.
/// </summary>
public IOrganizationService InitiatingUserService { get; }
/// <summary>
/// The PowerPlatform Dataverse organization service for the Account that was registered to run this plugin, This could be the same user as InitiatingUserService.
/// </summary>
public IOrganizationService PluginUserService { get; }
/// <summary>
/// IPluginExecutionContext contains information that describes the run-time environment in which the plug-in executes, information related to the execution pipeline, and entity business information.
/// </summary>
public IPluginExecutionContext PluginExecutionContext { get; }
/// <summary>
/// Synchronous registered plug-ins can post the execution context to the Microsoft Azure Service Bus. <br/>
/// It is through this notification service that synchronous plug-ins can send brokered messages to the Microsoft Azure Service Bus.
/// </summary>
public IServiceEndpointNotificationService NotificationService { get; }
/// <summary>
/// Provides logging run-time trace information for plug-ins.
/// </summary>
public ITracingService TracingService { get; }
/// <summary>
/// General Service Provider for things not accounted for in the base class.
/// </summary>
public IServiceProvider ServiceProvider { get; }
/// <summary>
/// OrganizationService Factory for creating connection for other then current user and system.
/// </summary>
public IOrganizationServiceFactory OrgSvcFactory { get; }
/// <summary>
/// ILogger for this plugin.
/// </summary>
public ILogger Logger { get; }
/// <summary>
/// Helper object that stores the services available in this plug-in.
/// </summary>
/// <param name="serviceProvider"></param>
public LocalPluginContext(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new InvalidPluginExecutionException(nameof(serviceProvider));
}
ServiceProvider = serviceProvider;
Logger = serviceProvider.Get<ILogger>();
PluginExecutionContext = serviceProvider.Get<IPluginExecutionContext>();
TracingService = new LocalTracingService(serviceProvider);
NotificationService = serviceProvider.Get<IServiceEndpointNotificationService>();
IOrganizationServiceFactory factory = serviceProvider.Get<IOrganizationServiceFactory>();
PluginUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.UserId); // User that the plugin is registered to run as, Could be same as current user.
InitiatingUserService = serviceProvider.GetOrganizationService(PluginExecutionContext.InitiatingUserId); //User who's action called the plugin.
}
/// <summary>
/// Writes a trace message to the trace log.
/// </summary>
/// <param name="message">Message name to trace.</param>
public void Trace(string message, [CallerMemberName] string method = null)
{
if (string.IsNullOrWhiteSpace(message) || TracingService == null)
{
return;
}
if (method != null)
TracingService.Trace($"[{method}] - {message}");
else
TracingService.Trace($"{message}");
}
}
/// <summary>
/// Specialized ITracingService implementation that prefixes all traced messages with a time delta for Plugin performance diagnostics
/// </summary>
public class LocalTracingService : ITracingService
{
private readonly ITracingService _tracingService;
private DateTime _previousTraceTime;
public LocalTracingService(IServiceProvider serviceProvider)
{
DateTime utcNow = DateTime.UtcNow;
var context = (IExecutionContext)serviceProvider.GetService(typeof(IExecutionContext));
DateTime initialTimestamp = context.OperationCreatedOn;
if (initialTimestamp > utcNow)
{
initialTimestamp = utcNow;
}
_tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
_previousTraceTime = initialTimestamp;
}
public void Trace(string message, params object[] args)
{
var utcNow = DateTime.UtcNow;
// The duration since the last trace.
var deltaMilliseconds = utcNow.Subtract(_previousTraceTime).TotalMilliseconds;
_tracingService.Trace($"[+{deltaMilliseconds:N0}ms] - {string.Format(message, args)}");
_previousTraceTime = utcNow;
}
}
}