public async Task <IHttpActionResult> FulfillOrderWebHook(string partner, [FromBody] OrderFulfillmentMessage order) { // Verify that the order was sent as the request body. if (order == null) { var errorSet = new ErrorSet(ErrorCode.ValueIsRequired, nameof(order), "The order body is required."); try { var body = await this.Request.SafeReadContentAsStringAsync(); this.Log .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId()) .Information($"Response: {{Response}} { Environment.NewLine } Missing order detected for {{Controller}}::{{ActionMethod}}({{Partner}}) with Headers: [{{Headers}}] { Environment.NewLine } Body: {{RequestBody}} { Environment.NewLine } The following errors were observed: { Environment.NewLine }{{ErrorSet}}", HttpStatusCode.BadRequest, nameof(OrderSubmissionController), nameof(FulfillOrderWebHook), partner, this.Request.Headers, body, errorSet); } catch { // Do nothing; logging is a non-critical operation that should not cause // cascading failures. } return(this.BadRequest(errorSet)); } // If the emulation data was provided and the caller is not priviledged, then reject the request. var principal = this.User as ClaimsPrincipal; if ((order.Emulation != null) && ((principal == null) || (!principal.HasClaim(claim => claim.Type == CustomClaimTypes.MayAccessPriviledgedOperations)))) { try { this.Log .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId()) .Warning($"Response: {{Response}} { Environment.NewLine } Unauthorized request detected for {{Controller}}::{{ActionMethod}}({{Partner}}) with Headers: [{{Headers}}] { Environment.NewLine } The caller does not have permission to perform a priviledged operation.", HttpStatusCode.Forbidden, nameof(OrderSubmissionController), nameof(FulfillOrderWebHook), partner, this.Request.Headers); } catch { // Do nothing; logging is a non-critical operation that should not cause // cascading failures. } return(this.Content <object>(HttpStatusCode.Forbidden, null)); } var assets = order.LineItems .SelectMany(item => item.Assets) .Where(asset => asset != null) .ToDictionary(asset => asset.Name, asset => this.ParseAssetLocationToUrl(asset.Location)); // Create the command to trigger order processing and place it on the command queue var command = new ProcessOrder { OrderId = order.OrderRequestHeader.OrderId, PartnerCode = partner, Assets = assets, Emulation = order.Emulation, Id = Guid.NewGuid(), CorrelationId = this.Request.GetOrderFulfillmentCorrelationId(), OccurredTimeUtc = this.clock.GetCurrentInstant().ToDateTimeUtc(), CurrentUser = principal?.Identity?.Name, Sequence = 0 }; if (!(await this.processOrderCommandPublisher.TryPublishAsync(command))) { this.Log .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId()) .Error("Unable to publish the {CommandName} for {Partner}//{Order}", nameof(ProcessOrder), partner, command.OrderId); return(this.ServiceUnavailable(this.CaclulateRetryAfter(this.config.ServiceUnavailableeRetryAfterInSeconds))); } // This can't be fire-and-forget, as completing the handler with the outstanding request causes an exception. try { await this.eventPublisher.TryPublishAsync(command.CreateNewOrderEvent <OrderReceived>()); } catch (Exception ex) { this.Log .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId()) .Error(ex, "The event {EventName} for {Partner}//{Order} could not be published. This is non-critical for fulfillment processing.", nameof(OrderReceived), partner, command.OrderId); } return(this.Accepted(new OrderFulfillmentAccepted(command.OrderId), this.CaclulateRetryAfter(this.config.OrderAcceptedRetryAfterInSeconds))); }
/// <summary> /// Performs the tasks needed to handle a <see cref="ProcessOrder" /> command. /// </summary> /// /// <param name="processOrderMessage">The brokered message containing the command to process.</param> /// public async Task HandleProcessOrderAsync([ServiceBusTrigger(TriggerQueueNames.ProcessOrderCommandQueue)] BrokeredMessage processOrderMessage) { if (processOrderMessage == null) { this.Log.Error("The {CommandType} brokered message was null.", nameof(ProcessOrder)); throw new ArgumentNullException(nameof(processOrderMessage)); } ILogger log = null; ProcessOrder command = null; try { // Attempt to retrieve the command from the brokered message. using (var bodyStream = processOrderMessage.GetBody <Stream>()) using (var reader = new StreamReader(bodyStream)) using (var jsonReader = new JsonTextReader(reader)) { command = this.jsonSerializer.Deserialize <ProcessOrder>(jsonReader); jsonReader.Close(); reader.Close(); bodyStream.Close(); }; // If the body was not a proper ProcessOrder command, an empty command will be // returned. Verify that the Id and OrderId are not in their default states. if ((command.Id == Guid.Empty) && (command.OrderId == null)) { throw new MissingDependencyException("The command could not be extracted from the brokered message"); } // Process the order identified by the command. var correlationId = command.CorrelationId ?? processOrderMessage.CorrelationId; log = this.Log.WithCorrelationId(correlationId); log.Information("A {CommandType} command was received and is being handled. {Command}", nameof(ProcessOrder), command); var result = await this.orderProcessor.ProcessOrderAsync(command.PartnerCode, command.OrderId, (IReadOnlyDictionary <string, string>) command.Assets, command.Emulation, correlationId); // Consider the result to complete handling. If processing was successful, then the message should be completed to // ensure that it is not retried. if (result?.Outcome == Outcome.Success) { await this.submitOrderForProductionPublisher.PublishAsync(command.CreateNewOrderCommand <SubmitOrderForProduction>(cmd => cmd.Emulation = command.Emulation)); await this.CompleteMessageAsync(processOrderMessage); this.eventPublisher.TryPublishAsync(command.CreateNewOrderEvent <OrderProcessed>()).FireAndForget(); log.Information("A {CommandType} command was successfully handled. The order was staged for submission with the key {CreateOrderMessageKey}.", nameof(ProcessOrder), result?.Payload); } else { log.Warning("A {CommandType} command was successfully handled. The order procesing was not successful, however.", nameof(ProcessOrder)); // Attempt to schedule the command for a retry using a backoff policy. If scheduled, complete the current message so that it is removed // from the queue. Otherwise, throw to expose the failure to the WebJob infrastructure so that the command will be moved to the dead letter // queue after retries are exhausted. if (await this.ScheduleCommandForRetryIfEligibleAsync(command, this.retryThresholds, this.rng, this.clock, this.processOrderPublisher)) { await this.CompleteMessageAsync(processOrderMessage); } else { await this.notifyOfFatalFailurePublisher.TryPublishAsync(command.CreateNewOrderCommand <NotifyOfFatalFailure>()); throw new FailedtoHandleCommandException(); } } } catch (Exception ex) when(!(ex is FailedtoHandleCommandException)) { (log ?? this.Log).Error(ex, "An exception occurred while handling the {CommandType} comand", nameof(ProcessOrder)); var failedEvent = (command?.CreateNewOrderEvent <OrderProcessingFailed>() ?? new OrderProcessingFailed { Id = Guid.NewGuid(), CorrelationId = processOrderMessage?.CorrelationId, OccurredTimeUtc = this.clock.GetCurrentInstant().ToDateTimeUtc() }); this.eventPublisher.TryPublishAsync(failedEvent).FireAndForget(); // Attempt to schedule the command for a retry using a backoff policy. If scheduled, complete the current message so that it is removed // from the queue. Otherwise, throw to expose the failure to the WebJob infrastructure so that the command will be moved to the dead letter // queue after retries are exhausted. if (await this.ScheduleCommandForRetryIfEligibleAsync(command, this.retryThresholds, this.rng, this.clock, this.processOrderPublisher)) { await this.CompleteMessageAsync(processOrderMessage); } else { await this.notifyOfFatalFailurePublisher.TryPublishAsync(command.CreateNewOrderCommand <NotifyOfFatalFailure>()); throw; }; } }