/// <summary> /// Registers an activity implementation with Temporal. /// </summary> /// <typeparam name="TActivity">The <see cref="ActivityBase"/> derived class implementing the activity.</typeparam> /// <param name="disableDuplicateCheck">Disable checks for duplicate activity registrations.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException"> /// Thrown if the worker has already been started. You must register workflow /// and activity implementations before starting a worker. /// </exception> /// <exception cref="RegistrationException">Thrown when there's a problem with the registration.</exception> /// <remarks> /// <note> /// Be sure to register all services you will be injecting into activities via /// <see cref="NeonHelper.ServiceContainer"/> before you call this as well as /// registering of your activity implementations before starting a worker. /// </note> /// </remarks> public async Task RegisterActivityAsync <TActivity>(bool disableDuplicateCheck = false) where TActivity : ActivityBase { await SyncContext.Clear; TemporalHelper.ValidateActivityImplementation(typeof(TActivity)); EnsureNotDisposed(); EnsureCanRegister(); lock (registeredActivityTypes) { var activityType = typeof(TActivity); if (registeredActivityTypes.Contains(activityType)) { if (disableDuplicateCheck) { return; } else { throw new RegistrationException($"Activity implementation [{typeof(TActivity).FullName}] has already been registered."); } } registeredActivityTypes.Add(activityType); } }
/// <summary> /// Called internally to initialize the activity. /// </summary> /// <param name="worker">The worker hosting the activity.</param> /// <param name="activityType">Specifies the target activity type.</param> /// <param name="activityMethod">Specifies the target activity method.</param> /// <param name="dataConverter">Specifies the data converter to be used for parameter and result serilization.</param> /// <param name="contextId">The activity's context ID.</param> internal void Initialize(Worker worker, Type activityType, MethodInfo activityMethod, IDataConverter dataConverter, long contextId) { Covenant.Requires <ArgumentNullException>(worker != null, nameof(worker)); Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType)); Covenant.Requires <ArgumentNullException>(activityMethod != null, nameof(activityMethod)); Covenant.Requires <ArgumentNullException>(dataConverter != null, nameof(dataConverter)); TemporalHelper.ValidateActivityImplementation(activityType); this.worker = worker; this.Client = worker.Client; this.Activity = new Activity(this); this.activityType = activityType; this.activityMethod = activityMethod; this.dataConverter = dataConverter; this.ContextId = contextId; this.CancellationTokenSource = new CancellationTokenSource(); this.CancellationToken = CancellationTokenSource.Token; this.logger = LogManager.Default.GetLogger(module: activityType.FullName); }
/// <summary> /// Registers an activity implementation with the temporal-proxy. /// </summary> /// <param name="activityType">The activity implementation type.</param> /// <exception cref="RegistrationException">Thrown when there's a problem with the registration.</exception> private async Task RegisterActivityImplementationAsync(Type activityType) { await SyncContext.Clear; TemporalHelper.ValidateActivityImplementation(activityType); var interfaceType = TemporalHelper.GetActivityInterface(activityType); TemporalHelper.ValidateActivityInterface(interfaceType); // We need to register each activity method that implements an activity interface method // with the same signature that that was tagged by [ActivityMethod]. // // First, we'll create a dictionary that maps method signatures from any inherited // interfaces that are tagged by [ActivityMethod] to the attribute. var methodSignatureToAttribute = new Dictionary <string, ActivityMethodAttribute>(); foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { var activityMethodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>(); if (activityMethodAttribute == null) { continue; } var signature = method.ToString(); if (methodSignatureToAttribute.ContainsKey(signature)) { throw new NotSupportedException($"Activity type [{activityType.FullName}] cannot implement the [{signature}] method from two different interfaces."); } methodSignatureToAttribute.Add(signature, activityMethodAttribute); } // Next, we need to register the activity methods that implement the // activity interface. foreach (var method in activityType.GetMethods()) { if (!methodSignatureToAttribute.TryGetValue(method.ToString(), out var activityMethodAttribute)) { continue; } var activityTarget = TemporalHelper.GetActivityTarget(interfaceType, activityMethodAttribute.Name); lock (nameToActivityRegistration) { if (nameToActivityRegistration.TryGetValue(activityTarget.ActivityTypeName, out var registration)) { if (!object.ReferenceEquals(registration.ActivityType, registration.ActivityType)) { throw new InvalidOperationException($"Conflicting activity type registration: Activity type [{activityType.FullName}] is already registered for activity type name [{activityTarget.ActivityTypeName}]."); } } else { nameToActivityRegistration[activityTarget.ActivityTypeName] = new ActivityRegistration() { ActivityType = activityType, ActivityMethod = method, ActivityMethodParameterTypes = method.GetParameterTypes() }; } } var reply = (ActivityRegisterReply)await Client.CallProxyAsync( new ActivityRegisterRequest() { WorkerId = WorkerId, Name = activityTarget.ActivityTypeName, }); reply.ThrowOnError(); } }