private Dictionary <HttpServiceActionTypeTransferObject, string> GetActionTypes(IAspDotNetOptions options)
        {
            Dictionary <HttpServiceActionTypeTransferObject, string> dictionary = new();

            if (options.HttpGet)
            {
                dictionary.Add(HttpServiceActionTypeTransferObject.Get, options.HttpGetRoute);
            }
            if (options.HttpPost)
            {
                dictionary.Add(HttpServiceActionTypeTransferObject.Post, options.HttpPostRoute);
            }
            if (options.HttpPatch)
            {
                dictionary.Add(HttpServiceActionTypeTransferObject.Patch, options.HttpPatchRoute);
            }
            if (options.HttpPut)
            {
                dictionary.Add(HttpServiceActionTypeTransferObject.Put, options.HttpPutRoute);
            }
            if (options.HttpDelete)
            {
                dictionary.Add(HttpServiceActionTypeTransferObject.Delete, options.HttpDeleteRoute);
            }
            return(dictionary);
        }
        public virtual void Read(AspDotNetReadConfiguration configuration)
        {
            configuration.Controller.AssertIsNotNull($"ASP: {nameof(configuration.Controller)}");
            configuration.Controller.Name.AssertIsNotNull($"ASP: {nameof(configuration.Controller)}.{nameof(configuration.Controller.Name)}");
            configuration.Controller.Namespace.AssertIsNotNull($"ASP: {nameof(configuration.Controller)}.{nameof(configuration.Controller.Namespace)}");
            Logger.Trace($"Read ASP.NET controller {configuration.Controller.Namespace}.{configuration.Controller.Name}...");
            Type type = GeneratorTypeLoader.Get(configuration.Controller.Assembly, configuration.Controller.Namespace, configuration.Controller.Name);

            if (type == null)
            {
                return;
            }

            HttpServiceTransferObject controller = new();

            controller.Name     = type.Name;
            controller.Language = ReflectionLanguage.Instance;

            IOptions typeOptions = this.options.Get(type);

            this.options.Set(controller, typeOptions);
            IAspDotNetOptions typeAspOptions = this.aspOptions.Get(type);

            this.aspOptions.Set(controller, typeAspOptions);
            controller.Route   = typeAspOptions.Route;
            controller.Version = typeAspOptions.ApiVersion?.LastOrDefault();

            List <MethodInfo> methods = new();
            Type currentTyp           = type;

            while (currentTyp?.Namespace != null && !currentTyp.Namespace.StartsWith("Microsoft") && !currentTyp.Namespace.StartsWith("System"))
            {
                IOptions currentOptions = this.options.Get(currentTyp);
                if (currentOptions.Ignore)
                {
                    break;
                }
                methods.AddRange(currentTyp.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
                                 // Remove all overwritten methods
                                 .Where(method => methods.All(m => m.GetBaseDefinition() != method))
                                 );
                currentTyp = currentTyp.BaseType;
            }
            foreach (MethodInfo method in methods)
            {
                IOptions          methodOptions    = this.options.Get(method);
                IAspDotNetOptions methodAspOptions = this.aspOptions.Get(method);
                if (methodOptions.Ignore || methodAspOptions.IsNonAction)
                {
                    continue;
                }

                Dictionary <HttpServiceActionTypeTransferObject, string> actionTypes = this.GetActionTypes(methodAspOptions);
                if (actionTypes.Count == 0)
                {
                    Logger.Error($"{type.FullName}.{method.Name} has to be decorated with at least one of [HttpGet], [HttpPost], [HttpPut], [HttpPatch], [HttpDelete], [NonAction] or with [GenerateIgnore].");
                    continue;
                }
                Type returnType = (methodAspOptions.Produces ?? method.ReturnType)
                                  .IgnoreGeneric("System.Threading.Tasks", "Task")
                                  .IgnoreGeneric("Microsoft.AspNetCore.Mvc", "IActionResult")
                                  .IgnoreGeneric("Microsoft.AspNetCore.Mvc", "ActionResult")
                                  .IgnoreGeneric("System.Web.Mvc", "ActionResult")
                                  .IgnoreGeneric("System.Web.Mvc", "ContentResult")
                                  .IgnoreGeneric("System.Web.Mvc", "EmptyResult")
                                  .IgnoreGeneric("System.Web.Mvc", "FileContentResult")
                                  .IgnoreGeneric("System.Web.Mvc", "FilePathResult")
                                  .IgnoreGeneric("System.Web.Mvc", "FileResult")
                                  .IgnoreGeneric("System.Web.Mvc", "FileStreamResult")
                                  .IgnoreGeneric("System.Web.Mvc", "JsonResult")
                                  .IgnoreGeneric(typeAspOptions.IgnoreGenerics)
                                  .IgnoreGeneric(methodAspOptions.IgnoreGenerics);

                Type returnEntryType = returnType.IgnoreGeneric(typeof(IEnumerable <>)).IgnoreGeneric(typeof(List <>)).IgnoreGeneric(typeof(IList <>));
                IAspDotNetOptions returnEntryTypeOptions = this.aspOptions.Get(returnEntryType, methodAspOptions);
                foreach (KeyValuePair <HttpServiceActionTypeTransferObject, string> actionType in actionTypes)
                {
                    HttpServiceActionTransferObject action = new();
                    action.Name       = actionTypes.Count == 1 ? method.Name : $"{actionType.Key}{method.Name.FirstCharToUpper()}";
                    action.ReturnType = this.modelReader.Read(returnType, methodOptions);
                    action.Route      = actionType.Value ?? methodAspOptions.Route;
                    if (action.Route?.Contains(":") ?? false)
                    {
                        action.Route = Regex.Replace(action.Route, "({[^:]*)((:apiVersion)|:[^}?]+)(\\??})", "$1$3$4");
                    }
                    action.Type    = actionType.Key;
                    action.Version = methodAspOptions.ApiVersion?.OrderByDescending(x => x).FirstOrDefault();
                    action.FixCasingWithMapping = returnEntryTypeOptions.FixCasingWithMapping || methodAspOptions.FixCasingWithMapping;
                    action.RequireBodyParameter = action.Type.IsBodyParameterRequired();
                    List <ParameterInfo> parameters = method.GetParameters().Where(
                        parameter => !this.aspOptions.Get(parameter, methodAspOptions).IsFromHeader &&
                        !this.aspOptions.Get(parameter, methodAspOptions).IsFromServices &&
                        parameter.ParameterType.FullName != "System.Threading.CancellationToken"
                        ).ToList();
                    foreach (ParameterInfo parameter in parameters)
                    {
                        IAspDotNetOptions parameterOptions = this.aspOptions.Get(parameter, methodAspOptions);
                        string            fullRoute        = $"{controller.Route}/{action.Route}";
                        HttpServiceActionParameterTransferObject actionParameter = new();
                        actionParameter.Name        = parameter.Name;
                        actionParameter.Type        = this.modelReader.Read(parameter.ParameterType, methodOptions);
                        actionParameter.FromBody    = parameterOptions.IsFromBody || action.Type != HttpServiceActionTypeTransferObject.Get && !parameter.ParameterType.IsValueType && parameter.ParameterType != typeof(string);
                        actionParameter.FromQuery   = parameterOptions.IsFromQuery;
                        actionParameter.Inline      = fullRoute.Contains($"{{{parameter.Name}}}");
                        actionParameter.InlineIndex = actionParameter.Inline && action.Route != null?action.Route.IndexOf($"{{{parameter.Name}}}") : 0;

                        actionParameter.IsOptional = parameter.IsOptional;
                        if (fullRoute.Contains($"{{{parameter.Name}?}}"))
                        {
                            actionParameter.Inline      = true;
                            actionParameter.IsOptional  = true;
                            actionParameter.InlineIndex = fullRoute.IndexOf($"{{{parameter.Name}?}}");
                            action.Route = action.Route.Replace($"{{{parameter.Name}?}}", $"{{{parameter.Name}}}");
                        }
                        action.Parameters.Add(actionParameter);
                        if (action.Type == HttpServiceActionTypeTransferObject.Get && actionParameter.Type.Name == "List" && !actionParameter.FromQuery)
                        {
                            Logger.Error($"HttpGet methods with list parameter '{parameter.Name}' of {type.FullName}.{method.Name} has to be decorated with [FromQuery]");
                        }
                    }
                    if (action.RequireBodyParameter)
                    {
                        if (action.Parameters.Count == 0)
                        {
                            throw new InvalidOperationException($"Can not write {method.Name}. {action.Type} requires at least one parameter, but no parameter found. Use [GenerateIgnore] to skip generation for that method");
                        }
                        if (action.Parameters.Count == 1)
                        {
                            action.Parameters.Single().FromBody = true;
                        }
                        else if (action.Parameters.All(x => !x.FromBody))
                        {
                            throw new InvalidOperationException($"Can not write {method.Name}. {action.Type} requires at least one parameter marked with [FromBody] or can have only one parameter");
                        }
                    }
                    if (action.Parameters.Count(x => x.FromBody) > 1)
                    {
                        throw new InvalidOperationException($"Can not write {method.Name}. {action.Type} can have only one parameter marked with [FromBody] or only one reference type");
                    }
                    int tries = 0;
                    while (controller.Actions.Any(a => a.Name == action.Name))
                    {
                        if (tries > 10)
                        {
                            throw new InvalidOperationException($"Can not find a suitable name for {action.Name}");
                        }
                        if (parameters.Count > 0 && tries == 0)
                        {
                            action.Name += "By" + parameters.First().Name.FirstCharToUpper();
                        }
                        else if (parameters.Count > tries)
                        {
                            action.Name += "And" + parameters.Skip(tries).First().Name.FirstCharToUpper();
                        }
                        else
                        {
                            action.Name += tries - parameters.Count + 1;
                        }
                        tries++;
                    }
                    controller.Actions.Add(action);
                }
            }
            transferObjects.Add(controller);
        }
Exemple #3
0
        public void Read(AspDotNetReadConfiguration configuration)
        {
            configuration.Hub.AssertIsNotNull($"SignalR: {nameof(configuration.Hub)}");
            configuration.Hub.Name.AssertIsNotNull($"SignalR: {nameof(configuration.Hub)}.{nameof(configuration.Hub.Name)}");
            configuration.Hub.Namespace.AssertIsNotNull($"SignalR: {nameof(configuration.Hub)}.{nameof(configuration.Hub.Namespace)}");
            Logger.Trace($"Read SignalR hub {configuration.Hub.Namespace}.{configuration.Hub.Name}...");
            Type type = GeneratorTypeLoader.Get(configuration.Hub.Assembly, configuration.Hub.Namespace, configuration.Hub.Name);

            if (type == null || type.BaseType == null || type.BaseType.Name != "Hub`1")
            {
                return;
            }
            if (!type.BaseType.IsGenericType)
            {
                Logger.Error("Implement generic Hub<T> instead of non-generic Hub type");
            }

            SignalRHubTransferObject hub = new();

            hub.Name     = type.Name;
            hub.Language = ReflectionLanguage.Instance;

            IAspDotNetOptions typeOptions = this.aspOptions.Get(type);

            this.aspOptions.Set(hub, typeOptions);

            MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
            foreach (MethodInfo method in methods)
            {
                if (method.Name == "OnConnectedAsync" || method.Name == "OnDisconnectedAsync")
                {
                    continue;
                }
                IOptions methodOptions = this.options.Get(method);
                HttpServiceActionTransferObject action = new();
                action.Name = method.Name;
                if (method.ReturnType.Name != typeof(void).Name && method.ReturnType.Name != nameof(Task))
                {
                    Logger.Error($"Return type of method {method.Name} in {hub.Name} has to be void or Task");
                }
                foreach (ParameterInfo parameter in method.GetParameters())
                {
                    action.Parameters.Add(new HttpServiceActionParameterTransferObject
                    {
                        Name = parameter.Name,
                        Type = this.modelReader.Read(parameter.ParameterType, methodOptions)
                    });
                }
                hub.Actions.Add(action);
            }

            Type notificationType = type.BaseType.GetGenericArguments().Single();

            MethodInfo[] notificationMethods = notificationType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
            foreach (MethodInfo method in notificationMethods)
            {
                IOptions methodOptions = this.options.Get(method);
                HttpServiceActionTransferObject action = new();
                action.Name = method.Name;
                foreach (ParameterInfo parameter in method.GetParameters())
                {
                    action.Parameters.Add(new HttpServiceActionParameterTransferObject
                    {
                        Name = parameter.Name,
                        Type = this.modelReader.Read(parameter.ParameterType, methodOptions)
                    });
                }
                hub.Events.Add(action);
            }
            transferObjects.Add(hub);
        }