private ConfigSectionNode describe(Type tController, object instance, ApiDocGenerator.ControllerContext apictx, ConfigSectionNode dataRoot, NodeOverrideRules overrideRules)
        {
            var cdata          = dataRoot.AddChildNode("scope");
            var cattr          = apictx.ApiDocAttr;
            var docContent     = tController.GetText(cattr.DocFile ?? "{0}.md".Args(tController.Name));
            var ctlTitle       = MarkdownUtils.GetTitle(docContent);
            var ctlDescription = MarkdownUtils.GetTitleDescription(docContent);

            (var drequest, var dresponse) = writeCommon(ctlTitle, ctlDescription, tController, apictx.Generator, cattr, cdata);
            cdata.AddAttributeNode("uri-base", cattr.BaseUri);
            cdata.AddAttributeNode("auth", cattr.Authentication);

            cdata.AddAttributeNode("doc-content", docContent);

            var allMethodContexts = apictx.Generator.GetApiMethods(tController, apictx.ApiDocAttr);

            foreach (var mctx in allMethodContexts)
            {
                var edata = cdata.AddChildNode("endpoint");
                (var mrequest, var mresponse) = writeCommon(null, null, mctx.Method, apictx.Generator, mctx.ApiEndpointDocAttr, edata);

                var epuri = mctx.ApiEndpointDocAttr.Uri.AsString().Trim();
                if (epuri.IsNullOrWhiteSpace())
                {
                    // infer from action attribute
                    var action = mctx.Method.GetCustomAttributes <ActionBaseAttribute>().FirstOrDefault();
                    if (action != null)
                    {
                        epuri = action.Name;
                    }
                    if (epuri.IsNullOrWhiteSpace())
                    {
                        epuri = mctx.Method.Name;
                    }
                }


                if (!epuri.StartsWith("/"))
                {
                    var bu = cattr.BaseUri.Trim();
                    if (!bu.EndsWith("/"))
                    {
                        bu += "/";
                    }
                    epuri = bu + epuri;
                }

                edata.AddAttributeNode("uri", epuri);
                writeCollection(mctx.ApiEndpointDocAttr.Methods, "method", mrequest, ':');

                //docAnchor
                var docAnchor = mctx.ApiEndpointDocAttr.DocAnchor.Default("### " + epuri);
                edata.AddAttributeNode("doc-content", MarkdownUtils.GetSectionContent(docContent, docAnchor));

                //Get all method attributes except ApiDoc
                var epattrs = mctx.Method
                              .GetCustomAttributes(true)
                              .Where(a => !(a is ApiDocAttribute) && !(a is ActionBaseAttribute));

                writeInstanceCollection(epattrs.Where(a => !(a is IInstanceCustomMetadataProvider) ||
                                                      (a is IInstanceCustomMetadataProvider cip &&
                                                       cip.ShouldProvideInstanceMetadata(apictx.Generator, edata))).ToArray(), TYPE_REF, edata, apictx.Generator);

                writeTypeCollection(epattrs.Select(a => a.GetType())
                                    .Where(t => !apictx.Generator.IsWellKnownType(t))
                                    .Distinct()
                                    .ToArray(),
                                    TYPE_REF, edata, apictx.Generator);//distinct attr types

                //todo Get app parameters look for Docs and register them and also permissions
                var epargs = mctx.Method.GetParameters()
                             .Where(pi => !pi.IsOut && !pi.ParameterType.IsByRef && !apictx.Generator.IsWellKnownType(pi.ParameterType))
                             .Select(pi => pi.ParameterType).ToArray();
                writeTypeCollection(epargs, TYPE_REF, edata, apictx.Generator);
            }

            return(cdata);
        }
        private ConfigSectionNode describe(Type tController, object instance, ApiDocGenerator.ControllerContext apictx, ConfigSectionNode dataRoot, NodeOverrideRules overrideRules)
        {
            var cdata          = dataRoot.AddChildNode("scope");
            var cattr          = apictx.ApiDocAttr;
            var docContent     = tController.GetText(cattr.DocFile ?? "{0}.md".Args(tController.Name));
            var ctlTitle       = MarkdownUtils.GetTitle(docContent);
            var ctlDescription = MarkdownUtils.GetTitleDescription(docContent);

            (var drequest, var dresponse) = writeCommon(ctlTitle, ctlDescription, tController, apictx.Generator, cattr, cdata);
            cdata.AddAttributeNode("uri-base", cattr.BaseUri);
            cdata.AddAttributeNode("auth", cattr.Authentication);

            cdata.AddAttributeNode("doc-content-tpl", docContent);

            var allMethodContexts = apictx.Generator.GetApiMethods(tController, apictx.ApiDocAttr);

            foreach (var mctx in allMethodContexts)
            {
                var edata = cdata.AddChildNode("endpoint");
                (var mrequest, var mresponse) = writeCommon(null, null, mctx.Method, apictx.Generator, mctx.ApiEndpointDocAttr, edata);

                var epuri = mctx.ApiEndpointDocAttr.Uri.AsString().Trim();
                if (epuri.IsNullOrWhiteSpace())
                {
                    // infer from action attribute
                    var action = mctx.Method.GetCustomAttributes <ActionBaseAttribute>().FirstOrDefault();
                    if (action != null)
                    {
                        epuri = action.Name;
                    }
                    if (epuri.IsNullOrWhiteSpace())
                    {
                        epuri = mctx.Method.Name;
                    }
                }


                if (!epuri.StartsWith("/"))
                {
                    var bu = cattr.BaseUri.Trim();
                    if (!bu.EndsWith("/"))
                    {
                        bu += "/";
                    }
                    epuri = bu + epuri;
                }

                edata.AddAttributeNode("uri", epuri);
                writeCollection(mctx.ApiEndpointDocAttr.Methods, "method", mrequest, ':');

                //Get all method attributes except ApiDoc
                var epattrs = mctx.Method
                              .GetCustomAttributes(true)
                              .Where(a => !(a is ApiDocAttribute) &&
                                     !(a is ActionBaseAttribute) &&
                                     !apictx.Generator.IgnoreTypePatterns.Any(ignore => a.GetType().FullName.MatchPattern(ignore))
                                     );

                writeInstanceCollection(epattrs.Where(a => !(a is IInstanceCustomMetadataProvider) ||
                                                      (a is IInstanceCustomMetadataProvider cip &&
                                                       cip.ShouldProvideInstanceMetadata(apictx.Generator, edata))).ToArray(), TYPE_REF, edata, apictx.Generator);

                writeTypeCollection(epattrs.Select(a => a.GetType())
                                    .Where(t => !apictx.Generator.IsWellKnownType(t))
                                    .Distinct()
                                    .ToArray(),
                                    TYPE_REF, edata, apictx.Generator);//distinct attr types

                //get method parameters
                var epargs = mctx.Method.GetParameters()
                             .Where(pi => !pi.IsOut &&
                                    !pi.ParameterType.IsByRef &&
                                    !apictx.Generator.IsWellKnownType(pi.ParameterType) &&
                                    !apictx.Generator.IgnoreTypePatterns.Any(ignore => pi.ParameterType.FullName.MatchPattern(ignore))
                                    )
                             .Select(pi => pi.ParameterType).ToArray();
                writeTypeCollection(epargs, TYPE_REF, edata, apictx.Generator);

                //docAnchor
                var docAnchor    = mctx.ApiEndpointDocAttr.DocAnchor.Default("### " + epuri);
                var epDocContent = MarkdownUtils.GetSectionContent(docContent, docAnchor);

                edata.AddAttributeNode("doc-content-tpl", epDocContent);

                //finally regenerate doc content expanding all variables
                epDocContent = MarkdownUtils.EvaluateVariables(epDocContent, (v) =>
                {
                    if (v.IsNullOrWhiteSpace())
                    {
                        return(v);
                    }
                    //Escape: ``{{a}}`` -> `{a}`
                    if (v.StartsWith("{") && v.EndsWith("}"))
                    {
                        return(v.Substring(1, v.Length - 2));
                    }
                    if (v.StartsWith("@"))
                    {
                        return($"`{{{v}}}`");        //do not expand TYPE spec here
                    }
                    //else navigate config path
                    return(edata.Navigate(v).Value);
                });

                edata.AddAttributeNode("doc-content", epDocContent);
            }//all endpoints

            //finally regenerate doc content expanding all variables for the controller
            docContent = MarkdownUtils.EvaluateVariables(docContent, (v) =>
            {
                if (v.IsNullOrWhiteSpace())
                {
                    return(v);
                }
                //Escape: ``{{a}}`` -> `{a}`
                if (v.StartsWith("{") && v.EndsWith("}"))
                {
                    return(v.Substring(1, v.Length - 2));
                }
                if (v.StartsWith("@"))
                {
                    return($"`{{{v}}}`");          //do not expand TYPE spec here
                }
                //else navigate config path
                return(cdata.Navigate(v).Value);
            });

            cdata.AddAttributeNode("doc-content", docContent);

            return(cdata);
        }