/// <inheritdoc /> public Variable Build(MiddlewareBuilderContext context, ExecutorReturnType executorReturnType) { // We rely on the compiler infrastructure to make the correct calls, to the correct type (i.e. the actual // operation), and to fill in the parameters of that method as required. var handlerInvokeCall = new MethodCall(context.Descriptor.OperationType, this._method) { IgnoreReturnVariable = executorReturnType == ExecutorReturnType.NoReturn, }; // Note that although we know the handler type at compile time, we still specify it as a // parameter to logging so that it is output as a structured value (as it changes between // invocations) context.AppendFrames( LogFrame.Debug( _apiOperationExecutorLogEvent, "Executing API operation {OperationType} with inline handler", ReflectionUtilities.PrettyTypeName(context.Descriptor.OperationType)), handlerInvokeCall); // We have a void, or a Task (i.e. async with no return) so we will convert to a 'NoResult' if (handlerInvokeCall.ReturnVariable == null || handlerInvokeCall.ReturnVariable.VariableType == typeof(Task)) { var emptyResultCreation = new VariableCreationFrame( typeof(NoResultOperationResult), $"{typeof(NoResultOperationResult).FullNameInCode()}.{nameof(NoResultOperationResult.Instance)};"); context.AppendFrames(emptyResultCreation); return(emptyResultCreation.CreatedVariable); } return(handlerInvokeCall.ReturnVariable); }
/// <inheritdoc /> public void Build( IReadOnlyCollection <OwnedPropertyDescriptor> ownedProperties, IReadOnlyCollection <OwnedPropertyDescriptor> ownedBySource, MiddlewareBuilderContext context) { // Never apply if we are not in a HTTP-supported operation if (context.Descriptor.TryGetFeatureData <HttpOperationFeatureData>(out var _) == false) { return; } if (this._applies?.Invoke(context) == false) { return; } var operationVariable = context.FindVariable(context.Descriptor.OperationType); var httpContextVariable = context.FindVariable(typeof(HttpContext)); var sourceVariable = this._sourceCodeExpression(httpContextVariable); foreach (var prop in this._isCatchAll ? context.Descriptor.Properties : ownedBySource.Select(s => s.Property)) { if (prop.CanWrite == false) { continue; } // If this is a catch-all instance we DO NOT want to generate any code for a property // that is owned by a different source. if (this._isCatchAll && ownedProperties.Any(p => p.Property == prop)) { continue; } var partKey = this.GetPartKey(prop); var operationProperty = operationVariable.GetProperty(prop.Name); // When this property is not in ALL routes we use TryGetValue from the RouteData dictionary and pass // the var output from that to `GetConversionExpression` as part of ternary. If the route data // does not exist then we fallback to the value already on the operation var outVariableName = $"{this._variablePrefix}{prop.Name}"; var conversionExpression = GetConversionExpression(prop, outVariableName, this._supportsMultiValues, this._variablePrefix); var indexProperty = sourceVariable.VariableType.GetProperties().SingleOrDefault(p => p.GetIndexParameters().Length == 1); if (indexProperty == null) { throw new InvalidOperationException($"Source variable {sourceVariable} has no index property specified. Cannot use"); } var isStringValues = indexProperty.PropertyType == typeof(StringValues); var emptyCheckRhs = isStringValues ? $"{typeof(StringValues).FullNameInCode()}.Empty" : "null"; var source = $"{sourceVariable}[\"{partKey}\"]"; // We want to support the key naming scheme of key[] to indicate an array, therefore // we add a null-coalesce to partKey[] if (IsArrayLike(prop.PropertyType, out _)) { source = $"{source} == {emptyCheckRhs} ? {sourceVariable}[\"{partKey}[]\"] : {source}"; } var sourceVariableGetter = new VariableCreationFrame(typeof(object), outVariableName, source); // [Source] == null ? [conversion of valuePropertyName] : [operationProperty]; var tryConversionExpression = $"{sourceVariableGetter.CreatedVariable} != {emptyCheckRhs} ? " + $"{conversionExpression} : " + $"{operationProperty}"; context.AppendFrames( sourceVariableGetter, new VariableSetterFrame( operationProperty, tryConversionExpression), new BlankLineFrame()); } }