Example #1
0
        protected virtual ParsedApiDesc ParseAction(ApiControllerDesc controller, ApiActionDesc action)
        {
            var    actionRegex     = @"(?:\[action\]|\{action\})";
            var    controllerRegex = @"(?:\[controller\]|\{controller\})";
            string template        = _options.DefaultRouteTemplate;

            if (controller.RouteTemplate != null || action.RouteTemplate != null)
            {
                if (controller.RouteTemplate != null)
                {
                    template = controller.RouteTemplate;
                }
                if (action.RouteTemplate != null)
                {
                    var art      = action.RouteTemplate.TrimStart('~');
                    var absolute = art.StartsWith("/");
                    template = absolute ? art : JoinUrls(template, art);
                }
                template = template.Trim('/');
            }

            template = Regex.Replace(template, actionRegex, action.ActionName, RegexOptions.IgnoreCase);
            template = Regex.Replace(template, controllerRegex, controller.ControllerName, RegexOptions.IgnoreCase);

            ApiParamDesc postParameter  = null;
            ApiParamDesc modelParameter = null;

            ApiParamDesc[] getParameters = ValidateParameters(action.Parameters, action.Method, template, out postParameter, out modelParameter);

            string GetParamString(IEnumerable <ApiParamDesc> aa) => String.Join(", ", aa.Select(p => $"{p.ParameterName}{(p.IsOptional ? "?" : "")}: {_converter.GetTypeScriptName(p.ParameterType)}"));

            var path = BuildUrlString(action, template, getParameters, modelParameter);

            return(new ParsedApiDesc
            {
                EndpointParamString = GetParamString(modelParameter != null ? new[] { modelParameter } : getParameters),
                ParamString = GetParamString(action.Parameters),
                PathString = path,
                PostParameter = postParameter,
                ModelParameter = modelParameter,
                GetParameters = getParameters,
                NameString = _options.FnActionName(action),
                Action = action,
                Template = template,
            });
        }
Example #2
0
        protected virtual string BuildUrlString(ApiActionDesc action, string template, ApiParamDesc[] getParameters, ApiParamDesc modelParameter)
        {
            List <string>       routeJs         = new List <string>();
            List <string>       queryJs         = new List <string>();
            List <ApiParamDesc> routeParameters = new List <ApiParamDesc>();

            bool seenOptionalRoute = false;

            string GetParamExecString(ApiParamDesc p)
            {
                var name = modelParameter == null ? p.ParameterName : $"{modelParameter.ParameterName}.{p.ParameterName}";
                var imm  = CreateTypeInitializerMethod(p.ParameterType);

                if (imm != null)
                {
                    return($"from_{imm}({name})");
                }
                return(name);
            }

            var parts = template.Split('/');

            for (int i = 0; i < parts.Length; i++)
            {
                var part = parts[i];
                if (!part.StartsWith("{"))
                {
                    routeJs.Add("\"" + part + "\"");
                    continue;
                }

                part = part.Substring(1, part.Length - 2);

                var typeIndex = part.IndexOf(":");
                if (typeIndex > 0)
                {
                    part = part.Substring(0, typeIndex);
                }

                if (part.StartsWith("*"))
                {
                    part = part.Substring(1);
                }

                var templateOptional = part.EndsWith("?");
                part = part.TrimEnd('?');

                if (templateOptional)
                {
                    seenOptionalRoute = true;
                }
                else if (seenOptionalRoute == true)
                {
                    throw new Exception($"In action '{action.ActionName}', required route parameter must not come after an optional route parameter in template: '{template}'.");
                }

                var get = getParameters.FirstOrDefault(g => g.ParameterName.Equals(part));
                if (get == null)
                {
                    throw new Exception($"In action '{action.ActionName}', route parameter `{part}` does not match any available method parameters. " +
                                        $"Please check your route template: '{template}'." +
                                        $"Parameters: [{String.Join(", ", action.Parameters.Select(s => s.ParameterName))}]");
                }

                if (templateOptional != get.IsOptional)
                {
                    bool canBeNull = !get.ParameterType.GetDnxCompatible().IsValueType || (Nullable.GetUnderlyingType(get.ParameterType) != null);
                    if (!canBeNull)
                    {
                        throw new Exception($"In action '{action.ActionName}', route parameter `{part}` is marked optional={get.IsOptional}, but the route " +
                                            $"template '{template}' is marked optional={templateOptional} and the type is non-nullable so is required.");
                    }
                }

                var typeCode = Type.GetTypeCode(get.ParameterType);
                switch (typeCode)
                {
                case TypeCode.DateTime:
                case TypeCode.Object:
                    throw new Exception($"In action '{action.ActionName}' parameter type '{get.ParameterType.Name}' is not suitable " +
                                        $"as a route parameter for template '{template}'. (Type code: {typeCode})");
                }

                routeParameters.Add(get);
                routeJs.Add($"[{GetParamExecString(get)}, \"{get.ParameterName}\"]");
            }

            foreach (var q in getParameters.Except(routeParameters))
            {
                queryJs.Add($"[{GetParamExecString(q)}, \"{q.ParameterName}\", {q.IsOptional.ToString().ToLower()}]");
            }

            return($"_build_url(this._basePath, [{String.Join(", ", routeJs)}], [{String.Join(", ", queryJs)}])");
        }
Example #3
0
        protected virtual ApiParamDesc[] ValidateParameters(List <ApiParamDesc> parameters, ApiMethod httpMethod, string routeTemplate, out ApiParamDesc postParam, out ApiParamDesc modelParam)
        {
            var canHavePost = new[] { ApiMethod.Patch, ApiMethod.Post, ApiMethod.Put }.Contains(httpMethod);

            var postPossibilities = parameters
                                    .Where(p => !IsRouteParameter(p.ParameterName, routeTemplate))
                                    .Where(p => p.Mode != ApiParameterMode.FromUri)
                                    .Where(p => p.Mode == ApiParameterMode.FromBody || TypeConverter.IsComplexType(p.ParameterType))
                                    .ToArray();

            if (postPossibilities.Length > 1)
            {
                throw new InvalidOperationException($"Invalid action, can't have more than one candidate for post parameters. Try using [FromBody] or [FromUri] to provide additional context. (at {routeTemplate}");
            }

            var post = postPossibilities.FirstOrDefault();

            if (!canHavePost && post != null)
            {
                // if there's only a single parameter in a get method, mvc will bind query parameters into it.
                if (post.Mode != ApiParameterMode.FromBody && parameters.Count == 1 && !IsRouteParameter(post.ParameterName, routeTemplate))
                {
                    var m = GetMembersAsParams(post.ParameterType).Select(kvp => new ApiParamDesc
                    {
                        ParameterName = kvp.Key,
                        ParameterType = kvp.Value,
                        IsOptional    = true,
                        Mode          = ApiParameterMode.FromUri,
                    }).ToList();

                    var inner = ValidateParameters(m, httpMethod, routeTemplate, out postParam, out modelParam);
                    modelParam = post;
                    postParam  = null;
                    return(inner);
                }

                throw new InvalidOperationException($"Invalid action, unable to map complex parameter '{post.ParameterName}' to {httpMethod} request as a message body is not allowed. (at {routeTemplate})");
            }

            postParam  = post;
            modelParam = null;
            return(parameters.Except(new[] { post }).ToArray());
        }