Beispiel #1
0
        /// <summary>
        /// 移除相关的订阅方法。
        /// </summary>
        /// <param name="subjectType">主题的类型。</param>
        public void RemoveSubscriber(Type subjectType)
        {
            var client      = GetConnection();
            var channelName = TopicHelper.GetTopicName(subjectType);

            RemoveSubscriber(channelName);
        }
Beispiel #2
0
        /// <summary>
        /// 在 Redis 服务器中添加一个订阅方法。
        /// </summary>
        /// <param name="subjectType">主题的类型。</param>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddSubscriber(Type subjectType, Delegate subscriber)
        {
            var client = GetConnection();
            var name   = TopicHelper.GetTopicName(subjectType);

#if NETSTANDARD
            channels.GetOrAdd(name, () => new List <CSRedisClient.SubscribeObject>())
            .Add(client.Subscribe((name, msg =>
            {
                var subject = Deserialize(subjectType, msg.Body);
                if (subject != null)
                {
                    subscriber.DynamicInvoke(subject);
                }
            }
                                   )));
#else
            client.GetSubscriber().Subscribe(name, (channel, value) =>
            {
                var subject = Deserialize(subjectType, Encoding.UTF8.GetString(value));
                if (subject != null)
                {
                    subscriber.DynamicInvoke(subject);
                }
            });
#endif
        }
Beispiel #3
0
        /// <summary>
        /// 在 Redis 服务器中添加一个订阅方法。
        /// </summary>
        /// <param name="subjectType">主题的类型。</param>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddSubscriber(Type subjectType, Delegate subscriber)
        {
            var client = GetConnection();
            var name   = TopicHelper.GetTopicName(subjectType);

            _channels.GetOrAdd(name, () => new List <CSRedisClient.SubscribeObject>())
            .Add(client.Subscribe((name, msg =>
            {
                StoredSubject subject = null;
                try
                {
                    try
                    {
                        subject = Deserialize <StoredSubject>(msg.Body);
                        subscriber.DynamicInvoke(Deserialize(subjectType, subject.Body));
                    }
                    catch (SerializationException)
                    {
                        subscriber.DynamicInvoke(Deserialize(subjectType, msg.Body));
                    }
                }
                catch (Exception exp)
                {
                    Tracer.Error($"Throw exception when consume message of '{name}':\n{exp.Output()}");

                    RetryPublishData(subject, exp);
                }
            }
                                   )));
        }
Beispiel #4
0
        /// <summary>
        /// 向 Rabbit 服务器发送消息主题。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subject">主题内容。</param>
        public void Publish <TSubject>(TSubject subject) where TSubject : class
        {
            var name = TopicHelper.GetTopicName(typeof(TSubject));
            var data = Serialize(subject);

            Publish(name, data);
        }
Beispiel #5
0
        public void Login(string username, string password)
        {
            if (!IsLogin)
            {
                if (!Logining)
                {
                    Logining       = true;
                    ClientUserName = username;
                    //消息发送处理频道
                    RunExchangeConsumeChannel(MyServiceMessageChannelName, TopicHelper.UserTopic(MyServiceName, ClientUserName), ExchangeType.topic,
                                              MyServiceMessageExchangeName, TopicHelper.UserTopic(MyServiceName, ClientUserName), MessageChannelCallBack);

                    rabbitMqProvider.Send(MyServiceLoginChannelName, new RemoteMessage()
                    {
                        JsonContent      = username + " " + password,
                        Sender           = ClientUserName,
                        EnablePersistent = true,
                        MessageType      = MessageType.Unknown,
                    }, queueName: MyServiceLoginQueueName);
                }
                else
                {
                    Console.WriteLine("正在登录中");
                }
            }
            else
            {
                Console.WriteLine("已经登录");
            }
        }
Beispiel #6
0
        /// <summary>
        /// 在 Redis 服务器中添加一个订阅方法。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddAsyncSubscriber <TSubject>(Func <TSubject, Task> subscriber) where TSubject : class
        {
            var client = GetConnection();
            var name   = TopicHelper.GetTopicName(typeof(TSubject));

            AddAsyncSubscriber <TSubject>(name, subscriber);
        }
Beispiel #7
0
        private bool MessageChannelCallBack(RemoteMessage remoteMessage, BasicDeliverEventArgs args)
        {
            switch (remoteMessage.MessageType)
            {
            case MessageType.LoginCallBack:
                if (remoteMessage.JsonContent == "true")
                {
                    Console.WriteLine("登录成功");
                    IsLogin  = true;
                    Logining = false;
                }
                else
                {
                    Console.WriteLine("登录失败");
                    Logining = false;
                    rabbitMqProvider.DeleteQueue(MyServiceMessageChannelName, TopicHelper.UserTopic(MyServiceName, ClientUserName));
                }
                break;

            case MessageType.SystemMessage:
                break;

            case MessageType.GroupMessage:
                break;

            case MessageType.UserMessage:
                Console.WriteLine(remoteMessage.JsonContent);
                break;
            }

            return(true);
        }
Beispiel #8
0
        /// <summary>
        /// 在 Rabbit 服务器中添加一个异步的订阅方法。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddAsyncSubscriber <TSubject>(Func <TSubject, Task> subscriber) where TSubject : class
        {
            Guard.ArgumentNull(subscriber, nameof(subscriber));

            var name = TopicHelper.GetTopicName(typeof(TSubject));

            AddAsyncSubscriber <TSubject>(name, subscriber);
        }
Beispiel #9
0
        /// <summary>
        /// 移除相关的订阅方法。
        /// </summary>
        /// <param name="subjectType">主题的类型。</param>
        public void RemoveSubscriber(Type subjectType)
        {
            Guard.ArgumentNull(subjectType, nameof(subjectType));

            var channelName = TopicHelper.GetTopicName(subjectType);

            RemoveSubscriber(channelName);
        }
Beispiel #10
0
        /// <summary>
        /// 向 Redis 服务器发送消息主题。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subject">主题内容。</param>
        public void Publish <TSubject>(TSubject subject) where TSubject : class
        {
            var client = GetConnection(null);
            var name   = TopicHelper.GetTopicName(typeof(TSubject));
            var body   = SerializeToBytes(subject);

            Publish(client, name, body);
        }
Beispiel #11
0
        /// <summary>
        /// 在 Rabbit 服务器中添加一个订阅方法。
        /// </summary>
        /// <param name="subjectType">主题的类型。</param>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddSubscriber(Type subjectType, Delegate subscriber)
        {
            Guard.ArgumentNull(subjectType, nameof(subjectType));
            Guard.ArgumentNull(subscriber, nameof(subscriber));

            var name = TopicHelper.GetTopicName(subjectType);
            var list = _subscribers.GetOrAdd(name, () => new RabbitChannelCollection());

            list.Add(new RabbitChannel(new SyncSubscribeDelegate(subjectType, subscriber), CreateAliveModel(name)));
        }
Beispiel #12
0
        /// <summary>
        /// 在 Rabbit 服务器中添加一个订阅方法。
        /// </summary>
        /// <param name="subjectType">主题的类型。</param>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddSubscriber(Type subjectType, Delegate subscriber)
        {
            Guard.ArgumentNull(subjectType, nameof(subjectType));
            Guard.ArgumentNull(subscriber, nameof(subscriber));

            var name = TopicHelper.GetTopicName(subjectType);
            var list = subscribers.GetOrAdd(name, () => new RabbitChannelCollection());

            list.Add(new RabbitChannel(subscriber, StartQueue(name)));
        }
Beispiel #13
0
        /// <summary>
        /// 异步的,向 Redis 服务器发送消息主题。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subject">主题内容。</param>
        /// <param name="cancellationToken">取消操作的通知。</param>
        public async Task PublishAsync <TSubject>(TSubject subject, CancellationToken cancellationToken = default) where TSubject : class
        {
            cancellationToken.ThrowIfCancellationRequested();

            var client = GetConnection(null);
            var name   = TopicHelper.GetTopicName(typeof(TSubject));
            var body   = SerializeToBytes(subject);

            await PublishAsync(client, name, body);
        }
Beispiel #14
0
        /// <summary>
        /// 异步的,向 Redis 服务器发送消息主题。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subject">主题内容。</param>
        /// <param name="cancellationToken">取消操作的通知。</param>
        public async Task PublishAsync <TSubject>(TSubject subject, CancellationToken cancellationToken = default) where TSubject : class
        {
#if NETSTANDARD
            var client = GetConnection();
            var name   = TopicHelper.GetTopicName(typeof(TSubject));
            await client.PublishAsync(name, Serialize(subject));
#else
            var data = Encoding.UTF8.GetBytes(Serialize(subject));
            var name = TopicHelper.GetTopicName(typeof(TSubject));
            Publish(name, data);
#endif
        }
Beispiel #15
0
        /// <summary>
        /// 向 Redis 服务器发送消息主题。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subject">主题内容。</param>
        public void Publish <TSubject>(TSubject subject) where TSubject : class
        {
#if NETSTANDARD
            var client = GetConnection();
            var name   = TopicHelper.GetTopicName(typeof(TSubject));
            client.Publish(name, Serialize(subject));
#else
            var data = Encoding.UTF8.GetBytes(Serialize(subject));
            var name = TopicHelper.GetTopicName(typeof(TSubject));
            Publish(name, data);
#endif
        }
Beispiel #16
0
 public MyService(TimeSpan requestHeartBeat, TimeSpan networkRecoveryInterval)
 {
     rabbitMqProvider = new RabbitMqProvider();
     if (rabbitMqProvider.ConstructMqConsumerConn(requestHeartBeat, networkRecoveryInterval))
     {
         //登录请求处理频道
         RunSimpleConsumeChannel(MyServiceLoginChannelName, MyServiceLoginQueueName, LoginChannelCallBack);
         //消息接收处理频道
         RunSimpleConsumeChannel(MyServiceReceiveChannelName, MyServiceReceiveQueueName, ReceiveChannelCallBack);
         //消息发送处理频道
         RunExchangeChannel(MyServiceMessageChannelName, MyServiceMessageQueueName, ExchangeType.topic,
                            MyServiceMessageExchangeName, TopicHelper.UserTopic(MyServiceName, MyServiceName), MessageChannelCallBack);
     }
 }
 private static string GetRealLink(IEnumerable <TableOfContentsItem> topics, string name)
 {
     foreach (var topic in topics)
     {
         if (TopicHelper.LinkMatchesTopic(name, topic))
         {
             return(topic.Link);
         }
         var childLink = GetRealLink(topic.Topics, name);
         if (!string.IsNullOrEmpty(childLink))
         {
             return(childLink);
         }
     }
     return(string.Empty);
 }
Beispiel #18
0
        /// <summary>
        /// 在 Redis 服务器中添加一个订阅方法。
        /// </summary>
        /// <typeparam name="TSubject"></typeparam>
        /// <param name="subscriber">读取主题的方法。</param>
        public void AddSubscriber <TSubject>(Action <TSubject> subscriber) where TSubject : class
        {
            var client = GetConnection();
            var name   = TopicHelper.GetTopicName(typeof(TSubject));

#if NETSTANDARD
            channels.GetOrAdd(name, () => new List <CSRedisClient.SubscribeObject>())
            .Add(client.Subscribe((name, msg =>
            {
                var subject = Deserialize <TSubject>(msg.Body);
                subscriber(subject);
            }
                                   )));
#else
            client.GetSubscriber().Subscribe(name, (channel, value) =>
            {
                var subject = Deserialize <TSubject>(Encoding.UTF8.GetString(value));
                subscriber(subject);
            });
#endif
        }
Beispiel #19
0
        private bool ReceiveChannelCallBack(RemoteMessage remoteMessage, BasicDeliverEventArgs args)
        {
            switch (remoteMessage.MessageType)
            {
            case MessageType.UserMessage:
                rabbitMqProvider.Send(MyServiceMessageChannelName, new RemoteMessage()
                {
                    JsonContent      = remoteMessage.JsonContent,
                    Sender           = remoteMessage.Sender,
                    TopicRoute       = TopicHelper.UserTopic(MyServiceName, remoteMessage.TopicRoute),
                    MessageType      = remoteMessage.MessageType,
                    EnablePersistent = true
                });
                break;

            case MessageType.GroupMessage:
                break;
            }

            return(true);
        }
        private string AutoGenerateTitle(string intermediateHtml)
        {
            var value = SettingsHelper.GetSetting <TrueFalseAuto>(SettingsEnum.RenderTitleInTopic, TocSettings, CurrentTopicSettings, CurrentRequestRootSettings);

            // If it is false, we never do it, no matter what
            if (value == TrueFalseAuto.False)
            {
                return(intermediateHtml);
            }

            // If the setting is auto, we only render the title if the topic doesn't start with an h1
            if (value == TrueFalseAuto.Auto && intermediateHtml.Trim().StartsWith("<h1"))
            {
                return(intermediateHtml);
            }

            // We need to auto-generate a title
            var link = TopicHelper.GetNormalizedName(SelectedTopic.Title);

            intermediateHtml = "<h1 id=\"" + link + "\">" + SelectedTopic.Title + "</h1>" + intermediateHtml;

            return(intermediateHtml);
        }
Beispiel #21
0
        public void Check()
        {
            StatusReportList = new List <Report>();
            var clusters              = _setting.General.Kafka.Clusters;
            var connectionTimeoutSec  = _setting.General.Kafka.ConnectionTimeoutSec;
            var topicNameFromSettings = _setting.General.Kafka.TopicName;
            var certificateLocation   = _setting.General.Kafka.SslCertificateLocation;
            var certificateSubject    = _setting.General.Kafka.SslCertificateSubject;
            var date = DateTime.Now.ToString("dd.MM.yyyy.HH.m");


            if (File.Exists(certificateLocation))
            {
                // delete an old certificate
                File.Delete(certificateLocation);
            }

            var counter = 0;

            foreach (var cluster in clusters)
            {
                counter++;
                string topicName;
                var    kafkaStatus   = ReportStatus.Undefined;
                var    mongoDbStatus = ReportStatus.Undefined;
                var    statusReport  = new Report();
                statusReport.Number  = counter;
                statusReport.EnvName = cluster.Name;
                _logger.Info($" [{counter}] from [{clusters.Count}]. " +
                             $"Work with the '{cluster.Name}' cluster");

                // Check Mongo
                _logger.Info($"Checking Mongo DB:");
                var mongoDbHelper        = new MongoDbHelper();
                var mongoDbConnectionStr = cluster.MongoDb;
                mongoDbStatus = mongoDbHelper.Ping(mongoDbConnectionStr, connectionTimeoutSec);

                // Check Kafka
                _logger.Info("Checking Kafka:");
                var bootStrapServers = string.Join(",", cluster.BootstrapServers);
                _logger.Info($" bootstrap servers: {bootStrapServers}");
                var clientConfig = new ClientConfig
                {
                    BootstrapServers = bootStrapServers,
                    SocketTimeoutMs  = connectionTimeoutSec * 1000,
                };
                topicName = $"{topicNameFromSettings}.{date}";
                if (cluster.SslEnabled)
                {
                    _logger.Info("SSL connection is enabled for this cluster");
                    if (!File.Exists(certificateLocation))
                    {
                        try
                        {
                            var certificate = CertificateHelper.GetCertificate(certificateSubject);
                            CertificateHelper.ExportToPEMFile(certificate, certificateLocation);
                        }
                        catch (CertificateException ce)
                        {
                            _logger.Error(ce.Message);
                            kafkaStatus = ReportStatus.CertificateError;
                            _logger.Warn($" Kafka status - [{kafkaStatus}]");
                            WriteClusterStatus(cluster, statusReport, mongoDbStatus, kafkaStatus);
                            StatusReportList.Add(statusReport);
                            continue;
                        }
                    }
                    clientConfig.SslCaLocation    = certificateLocation;
                    clientConfig.SecurityProtocol = SecurityProtocol.Ssl;
                    clientConfig.Debug            = "security";
                    topicName = $"{topicNameFromSettings}.ssl.{date}";
                }
                var bootstrapServersCount = cluster.BootstrapServers.Count;
                var topicHelper           = new TopicHelper();
                var producerConsumer      = new ProducerConsumer();

                var connectionIsOk = topicHelper
                                     .CheckConnectivity(clientConfig,
                                                        bootstrapServersCount);

                if (connectionIsOk)
                {
                    var topicWasCreated = topicHelper
                                          .CreateTopic(clientConfig, bootstrapServersCount, topicName);
                    if (topicWasCreated)
                    {
                        _logger.Info(string.Empty);
                        var producedMessageCount = producerConsumer.Produce(clientConfig, topicName);
                        var consumedMessageCount = producerConsumer.Consume(clientConfig, topicName);

                        if (producedMessageCount == consumedMessageCount)
                        {
                            _logger.Info($" * Produced messages == consumed messages: '{consumedMessageCount}' - [ok]");
                            kafkaStatus = ReportStatus.Ok;
                        }
                        else
                        {
                            _logger.Error($" * Produced messages != consumed messages: '{consumedMessageCount}' - [error]");
                            kafkaStatus = ReportStatus.Error;
                        }
                    }
                }
                else
                {
                    kafkaStatus = ReportStatus.Error;
                }
                _logger.Info($" Kafka status - [{kafkaStatus}]");
                WriteClusterStatus(cluster, statusReport, mongoDbStatus, kafkaStatus);
                StatusReportList.Add(statusReport);
                _logger.Info(string.Empty);
            }
            new HtmlReportHelper().PopulateTemplate(StatusReportList);
        }
Beispiel #22
0
        public void Publish <TSubject>(TSubject subject) where TSubject : class
        {
            var name = TopicHelper.GetTopicName(typeof(TSubject));

            Publish(name, subject);
        }
Beispiel #23
0
        public async Task PublishAsync <TSubject>(TSubject subject, CancellationToken cancellationToken = default) where TSubject : class
        {
            var name = TopicHelper.GetTopicName(typeof(TSubject));

            await PublishAsync(name, subject, cancellationToken);
        }
Beispiel #24
0
        private bool LoginChannelCallBack(RemoteMessage remoteMessage, BasicDeliverEventArgs args)
        {
            var    param         = remoteMessage.JsonContent.Split(" ");
            bool   ret           = false;
            string returnMessage = "true";

            if (param.Length != 2)
            {
                LogHelper.LogError($"用户{remoteMessage.Sender}请求登录参数异常");
            }
            else
            {
                var username = param[0];
                var password = param[1];
                LogHelper.Log($"{username} 请求登录。UID:{remoteMessage.Sender};请求时间:{remoteMessage.Timestamp};");
                try
                {
                    //using (var db = new ChegevalaContext())
                    //{
                    //var user = db.Set<User>().Where(n => n.UserName == username).FirstOrDefault();
                    //if (user == null)
                    //{
                    //    //用户未注册,为其注册账号
                    //    db.Add(new User()
                    //    {
                    //        UserName = username,
                    //        PassWord = password
                    //    });
                    //    db.SaveChanges();
                    //    ret = true;
                    //    returnMessage = "登录成功,已经成功创建账号";
                    //}
                    //else
                    //{
                    //    //用户已注册,判断密码是否正确
                    //    if (user.PassWord == password)
                    //    {
                    //        ret = true;
                    //        returnMessage = "登录成功";
                    //    }
                    //    else
                    //    {
                    //        ret = false;
                    //        returnMessage = "登录失败,密码错误";
                    //    }
                    //}
                    rabbitMqProvider.Send(MyServiceMessageChannelName, new RemoteMessage()
                    {
                        JsonContent      = returnMessage,
                        TopicRoute       = TopicHelper.UserTopic(MyServiceName, remoteMessage.Sender),
                        Sender           = MyServiceName,
                        MessageType      = MessageType.LoginCallBack,
                        Timestamp        = DateTime.Now,
                        EnablePersistent = false
                    });
                    if (ret)
                    {
                        LogHelper.Log($"用户{username}登录成功");
                    }
                    else
                    {
                        LogHelper.Log($"用户{username}登录失败");
                    }
                    //}
                }
                catch (Exception e)
                {
                    LogHelper.LogError(e.Message);
                }
            }

            return(true);
        }
        private async Task GetHtmlContent()
        {
            var rawTopic = new TopicInformation {
                OriginalName = SelectedTopic.Title, Type = SelectedTopic.Type
            };

            ImageRootUrl = string.Empty;

            var normalizedLink = SelectedTopic.LinkPure.ToLowerInvariant();

            if (normalizedLink.StartsWith("https://") || normalizedLink.StartsWith("http://"))
            {
                // This is an absolute link, so we can just try to load it
                rawTopic.OriginalContent = await WebClientEx.GetStringAsync(SelectedTopic.Link);

                ImageRootUrl = StringHelper.JustPath(SelectedTopic.Link) + "/";
            }
            else if (!string.IsNullOrEmpty(normalizedLink))
            {
                var repositoryType = RepositoryTypeHelper.GetTypeFromTypeName(GetSetting <string>(SettingsEnum.RepositoryType));

                // Even if the overall repository type is something else, we will switch to different repository access for specific node types,
                // as they may point to other repositories or require different APIs even within the same repository
                if (TopicTypeHelper.IsVstsWorkItemType(rawTopic?.Type))
                {
                    repositoryType = RepositoryTypes.VstsWorkItemTracking;
                }

                switch (repositoryType)
                {
                case RepositoryTypes.GitHubRaw:
                    var fullGitHubRawUrl = GitHubMasterUrlRaw + SelectedTopic.Link;
                    if (string.IsNullOrEmpty(rawTopic.Type))
                    {
                        rawTopic.Type = TopicTypeHelper.GetTopicTypeFromLink(fullGitHubRawUrl);
                    }
                    if (TopicTypeHelper.IsMatch(rawTopic.Type, TopicBodyFormats.Markdown) || TopicTypeHelper.IsMatch(rawTopic.Type, TopicBodyFormats.Html))
                    {
                        rawTopic.OriginalContent = await WebClientEx.GetStringAsync(fullGitHubRawUrl);
                    }
                    else if (TopicTypeHelper.IsMatch(rawTopic.Type, TopicBodyFormats.ImageUrl))
                    {
                        rawTopic.OriginalContent = fullGitHubRawUrl;
                    }
                    ImageRootUrl = StringHelper.JustPath(fullGitHubRawUrl);
                    if (!string.IsNullOrEmpty(ImageRootUrl) && !ImageRootUrl.EndsWith("/"))
                    {
                        ImageRootUrl += "/";
                    }
                    break;

                case RepositoryTypes.GitHubApi:
                    if (TopicTypeHelper.IsMatch(rawTopic.Type, TopicBodyFormats.Markdown) || TopicTypeHelper.IsMatch(rawTopic.Type, TopicBodyFormats.Html))
                    {
                        var gitHubClient  = new GithubRepositoryParser(GitHubOwner, GitHubRepository, GitHubPat);
                        var gitHubContent = await gitHubClient.GetItemContent(SelectedTopic.Link);

                        rawTopic.OriginalContent = gitHubContent.Text;
                    }
                    // TODO: else if (TopicTypeHelper.IsMatch(rawTopic.Type, TopicBodyFormats.ImageUrl))
                    //    rawTopic.OriginalContent = fullGitHubRawUrl;
                    //ImageRootUrl = StringHelper.JustPath(fullGitHubRawUrl);
                    //if (!string.IsNullOrEmpty(ImageRootUrl) && !ImageRootUrl.EndsWith("/")) ImageRootUrl += "/";
                    break;

                case RepositoryTypes.VstsGit:
                    if (!string.IsNullOrEmpty(SelectedTopic.LinkPure))
                    {
                        rawTopic.OriginalContent = await VstsHelper.GetFileContents(SelectedTopic.LinkPure, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsProjectName), GetSetting <string>(SettingsEnum.VstsDocsFolder), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));
                    }
                    ImageRootUrl = "/___FileProxy___?mode=" + RepositoryTypeNames.VstsGit + "&path=";
                    if (SelectedTopic.LinkPure.Contains("/"))
                    {
                        ImageRootUrl += StringHelper.JustPath(SelectedTopic.LinkPure) + "/";
                    }
                    break;

                case RepositoryTypes.VstsWorkItemTracking:
                    if ((TopicTypeHelper.IsMatch(rawTopic?.Type, TopicBodyFormats.VstsWorkItemQuery) || TopicTypeHelper.IsMatch(rawTopic?.Type, TopicBodyFormats.VstsWorkItemQueries)) && HttpContext.Request.Query.ContainsKey("workitemnumber"))
                    {
                        // The current node is a work item query, but we use it as a context to get the actual work item
                        var itemNumber = int.Parse(HttpContext.Request.Query["workitemnumber"]);
                        rawTopic.OriginalContent = await VstsHelper.GetWorkItemJson(itemNumber, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));

                        rawTopic.Type = TopicBodyFormats.VstsWorkItem;
                    }
                    else if (TopicTypeHelper.IsMatch(rawTopic?.Type, TopicBodyFormats.VstsWorkItemQueries) && HttpContext.Request.Query.ContainsKey("queryid"))
                    {
                        // The current node is a list of work item queries, but we use it as a context to run the actual query
                        var queryId       = HttpContext.Request.Query["queryid"];
                        var queryInfoJson = await VstsHelper.GetWorkItemQueriesJson(queryId, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsProjectName), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));

                        dynamic queryInfo = JObject.Parse(queryInfoJson);
                        if (queryInfo != null)
                        {
                            Title = "Query: " + queryInfo.name;
                        }
                        rawTopic.OriginalContent = await VstsHelper.RunWorkItemQueryJson(queryId, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsProjectName), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));

                        if (rawTopic.OriginalContent.StartsWith("{"))
                        {
                            rawTopic.Type = TopicBodyFormats.VstsWorkItemQuery;
                        }
                        else
                        {
                            rawTopic.Type = TopicBodyFormats.Markdown;     // Something went wrong, but one way or another, we didn't end up with JSON
                        }
                    }
                    else if (TopicTypeHelper.IsMatch(rawTopic?.Type, TopicBodyFormats.VstsWorkItem))
                    {
                        // Plain work item node
                        var itemNumber = int.Parse(SelectedTopic.Link);
                        rawTopic.OriginalContent = await VstsHelper.GetWorkItemJson(itemNumber, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));
                    }
                    else if (TopicTypeHelper.IsMatch(rawTopic?.Type, TopicBodyFormats.VstsWorkItemQueries))
                    {
                        // Plain work item queries
                        rawTopic.OriginalContent = await VstsHelper.GetWorkItemQueriesJson(SelectedTopic.Link, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsProjectName), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));

                        Title = SelectedTopic.Title;
                    }
                    else if (TopicTypeHelper.IsMatch(rawTopic?.Type, TopicBodyFormats.VstsWorkItemQuery))
                    {
                        // Plain work item query
                        rawTopic.OriginalContent = await VstsHelper.RunWorkItemQueryJson(SelectedTopic.Link, GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsProjectName), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));

                        Title = SelectedTopic.Title;
                    }

                    Vsts.ImageLink = "/___FileProxy___?mode=" + RepositoryTypeNames.VstsWorkItemTracking + "&topic=" + CurrentSlug + "&path=";
                    break;
                }
            }

            var renderer = TopicRendererFactory.GetTopicRenderer(rawTopic);

            var intermediateHtml = renderer.RenderToHtml(rawTopic, ImageRootUrl, this);

            if (!string.IsNullOrEmpty(intermediateHtml))
            {
                intermediateHtml = await ProcessKavaTopic(intermediateHtml);

                intermediateHtml = AutoGenerateTitle(intermediateHtml);
                intermediateHtml = ProcessBrokenImageLinks(intermediateHtml, ImageRootUrl);
            }

            Html = intermediateHtml;

            Json         = renderer.RenderToJson(rawTopic, ImageRootUrl, this);
            TemplateName = renderer.GetTemplateName(rawTopic, TemplateName, this);

            if (string.IsNullOrEmpty(Html) && SelectedTopic != null)
            {
                var sb = new StringBuilder();
                sb.Append("<h1>" + SelectedTopic.Title + "</h1>");

                if (SelectedTopic.Topics.Count > 0)
                {
                    sb.Append("<ul>");
                    foreach (var topic in SelectedTopic.Topics)
                    {
                        sb.Append("<li class=\"kava-auto-link\">");
                        sb.Append("<a href=\"" + TopicHelper.GetNormalizedName(topic.Title) + "\">");
                        sb.Append(topic.Title);
                        sb.Append("</a>");
                        sb.Append("</li>");
                    }
                    sb.Append("</ul>");
                }

                Html = sb.ToString();
            }
        }
        private async Task BuildToc()
        {
            string tocJson = null;

            var repositoryType = RepositoryTypeHelper.GetTypeFromTypeName(GetSetting <string>(SettingsEnum.RepositoryType));

            var logoUrl           = GetSetting <string>(SettingsEnum.LogoPath);
            var logoUrlLower      = logoUrl.ToLowerInvariant();
            var logoUrlIsAbsolute = true;

            if (!logoUrl.StartsWith("http://") && !logoUrl.StartsWith("https://"))
            {
                logoUrlIsAbsolute = false;
            }
            LogoUrl = logoUrl;

            if (UseSqlServer) // SQL server *may* provide a local tabe of contents that would override all others
            {
                tocJson = await SqlDataAccess.GetRepositoryLocalTableOfContents(CurrentPrefix);
            }

            if (string.IsNullOrEmpty(tocJson))
            {
                switch (repositoryType)
                {
                case RepositoryTypes.GitHubRaw:
                    tocJson = await TableOfContentsHelper.GetTocJsonFromGitHubRaw(GitHubMasterUrlRaw);

                    if (!logoUrlIsAbsolute)
                    {
                        LogoUrl = GitHubMasterUrlRaw + logoUrl;
                    }
                    break;

                case RepositoryTypes.GitHubApi:
                    tocJson = await TableOfContentsHelper.GetTocJsonFromGitHubApi(GitHubOwner, GitHubRepository, GitHubPat);

                    // TODO: if (!logoUrlIsAbsolute)
                    //    LogoUrl = GitHubMasterUrlRaw + logoUrl;
                    break;

                case RepositoryTypes.VstsGit:
                    tocJson = await VstsHelper.GetTocJson(GetSetting <string>(SettingsEnum.VstsInstance), GetSetting <string>(SettingsEnum.VstsProjectName), GetSetting <string>(SettingsEnum.VstsDocsFolder), GetSetting <string>(SettingsEnum.VstsPat), GetSetting <string>(SettingsEnum.VstsApiVersion));

                    if (!logoUrlIsAbsolute)
                    {
                        LogoUrl = $"/___FileProxy___?mode=vstsgit&path={logoUrl}";
                    }
                    break;
                }
            }
            if (string.IsNullOrEmpty(tocJson))
            {
                return;
            }

            var dynamicToc = TableOfContentsHelper.GetDynamicTocFromJson(tocJson);

            if (dynamicToc.title != null)
            {
                RepositoryTitle = dynamicToc.title;
            }
            if (dynamicToc.owner != null)
            {
                Owner = dynamicToc.owner;
            }

            Topics     = TableOfContentsHelper.BuildTocFromDynamicToc(dynamicToc, this, CurrentSlug, out List <TableOfContentsItem> flatTopicList);
            FlatTopics = flatTopicList;
            MainMenu   = TableOfContentsHelper.BuildMainMenuStructureFromDynamicToc(dynamicToc);

            var matchingTopic = FlatTopics.FirstOrDefault(t => TopicHelper.SlugMatchesTopic(CurrentSlug, t));

            if (matchingTopic == null)
            {
                matchingTopic = FlatTopics.FirstOrDefault(t => TopicHelper.SlugMatchesTopic(CurrentSlug, t, true));
            }
            if (matchingTopic == null)
            {
                matchingTopic = FlatTopics.FirstOrDefault(t => TopicHelper.LinkMatchesTopic(CurrentSlug, t));
            }
            if (matchingTopic == null)
            {
                matchingTopic = Topics.FirstOrDefault();
            }

            SelectedTopic = matchingTopic;
            TableOfContentsHelper.EnsureExpanded(SelectedTopic);

            TocSettings          = dynamicToc.settings;
            CurrentTopicSettings = SelectedTopic?.SettingsDynamic;
        }