private bool RunTemplate(WebData data) { IDictionary <string, WebTemplate> templates = this.templates; if (templates == null) { return(false); } string key = Uri.UnescapeDataString(data.Request.DecodedUrl).TrimStart('/'); if (key == string.Empty) { key = "index"; } if (!templates.TryGetValue(key, out WebTemplate caveWebTemplate)) { string str = FileSystem.Combine(data.Server.StaticFilesPath, key + ".cwt"); if (!FileSystem.IsRelative(str, data.Server.StaticFilesPath)) { throw new WebServerException(WebError.NotFound, 0, string.Format("{0} not found!", key)); } if (!File.Exists(str)) { return(false); } templates[key] = caveWebTemplate = new WebTemplate(data.Server, str); } return(caveWebTemplate.Render(data)); }
/// <summary>Entry point for explain.</summary> /// <param name="data">The data.</param> /// <exception cref="WebServerException">Error during explain.</exception> public void Explain(WebData data) { string name = null, type = null; try { if (data.Request.Parameters.Count > 0) { if (data.Request.Parameters.TryGetValue("function", out name)) { type = "function"; ExplainFunction(data, name); return; } if (data.Request.Parameters.TryGetValue("type", out name)) { type = "type"; ExplainType(data, name); return; } } ExplainFunctionList(data); } catch (Exception ex) { if (ex is WebServerException) { throw; } Trace.TraceError("Error during explain <cyan>{0} <magenta>{1}.", type, name); throw new WebServerException(WebError.InternalServerError, 0, "Error during explain {0} {1}.", type, name); } }
void ExplainStruct(WebData data, Type t) { var html = new HtmlPageBuilder(data.Request); html.Breadcrump.Add(new WebLink() { Text = t.FullName }); Bootstrap4 content = html.Content; var layout = RowLayout.CreateTyped(t); content.CardOpen($"<h2>Struct {t.Name}<h2><h4>Table {layout.Name}</h4>{layout.FieldCount} Fields, {t.AssemblyQualifiedName}"); DocumentHtml(content, documentation.GetType(t), t.ToString()); content.ListGroupOpen(); int i = 0; foreach (FieldProperties field in layout.Fields) { XNetDocItem doc = documentation.GetField(t, field.Name.ToString()); FieldHtml(content, i++, field, doc); } content.ListGroupClose(); content.CardClose(); content.AddHtml(" "); var message = WebMessage.Create("Explain " + t.Name, string.Format("Explain struct {0}", t.Name)); data.Answer = html.ToAnswer(message); }
void ExplainEnum(WebData data, Type t) { var html = new HtmlPageBuilder(data.Request); html.Breadcrump.Add(new WebLink() { Text = t.FullName }); Bootstrap4 content = html.Content; content.CardOpenText($"Enum {t.Name}"); DocumentHtml(content, documentation.GetEnum(t), t.ToString()); content.ListGroupOpen(); int i = 0; foreach (object value in Enum.GetValues(t)) { XNetDocItem doc = documentation.GetField(t, value.ToString()); FieldHtml(content, i++, value, doc); } content.ListGroupClose(); content.CardClose(); content.AddHtml(" "); var message = WebMessage.Create("Explain " + t.Name, string.Format("Explain enum {0}", t.Name)); data.Answer = html.ToAnswer(message); }
void SslHandshake() { if (WebServer.Certificate == null) { Reader = new DataReader(Stream, newLineMode: NewLineMode.CRLF); Writer = new DataWriter(Stream, newLineMode: NewLineMode.CRLF); return; } try { var sslStream = new SslStream(Stream); sslStream.AuthenticateAsServer(WebServer.Certificate); Reader = new DataReader(sslStream, newLineMode: NewLineMode.CRLF); Writer = new DataWriter(sslStream, newLineMode: NewLineMode.CRLF); if (WebServer.PerformanceChecks) { Trace.TraceInformation("SslHandshake completed. Elapsed {0}.", StopWatch.Elapsed.FormatTime()); } } catch (Exception ex) { if (WebServer.PerformanceChecks) { Trace.TraceError("SslHandshake <red>error<default> {1}. Elapsed {0}.", StopWatch.Elapsed.FormatTime(), ex); } var data = new WebData(WebServer, StopWatch); data.Result.AddMessage("SslHandshake", WebError.ClientError, $"Http connections are not supported!"); data.Result.Type = WebResultType.Html; data.Result.CloseAfterAnswer = true; SendAnswer(data); } }
void ExplainFunction(WebData data, string name) { if (!data.Server.RegisteredPaths.TryGetValue(name, out WebServerMethod function)) { throw new WebServerException(WebError.InvalidParameters, 0, "Unknown function or function not registered!"); } ExplainFunction(data, function); }
public void SendAnswer(WebData data) { if (data.Answer == null) { if (data.Result == null) { throw new Exception("Al least one of Result or Answer has to be set!"); } data.Answer = data.Result.ToAnswer(); data.Result = null; } else if (data.Result != null) { // add all headers present at result to answer. foreach (System.Collections.Generic.KeyValuePair <string, string> header in data.Result.Headers) { if (!data.Answer.Headers.ContainsKey(header.Key)) { data.Answer.Headers[header.Key] = header.Value; } } } data.Answer.Headers["To"] = data.Request.SourceAddress; if (!data.Answer.Headers.ContainsKey("Cache-Control")) { data.Answer.Headers["Cache-Control"] = "no-cache, must-revalidate, post-check=0, pre-check=0"; } if (data.Session != null) { switch (WebServer.SessionMode) { case WebServerSessionMode.Cookie: data.Answer.Headers["Session"] = data.Session.ID.ToString(); if (WebServer.SessionMode == WebServerSessionMode.Cookie) { data.Answer.Headers["Set-Cookie"] = $"Session={data.Session.ID}; Path=/; Max-Age=" + (int)data.Server.SessionTimeout.TotalSeconds; } break; case WebServerSessionMode.SenderID: data.Answer.Headers["Session"] = data.Session.ID.ToString(); break; case WebServerSessionMode.None: break; default: throw new NotImplementedException(); } } SendAnswer(data.Answer); }
/// <summary>Initializes a new instance of the <see cref="WebServerAuthEventArgs" /> class.</summary> public WebServerAuthEventArgs(WebData data) { Data = data; // basic auth ? data.Request.Headers.TryGetValue("authorization", out string value); if (value != null) { string[] auth = value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); switch (auth[0].ToLower()) { case "basic": string[] parts = Base64.Default.DecodeUtf8(auth[1]).Split(new char[] { ':' }, 2); Username = parts[0]; Password = parts[1]; AuthType = WebServerAuthType.Basic; return; default: throw new NotImplementedException(); } } }
void ExplainType(WebData data, string name) { Type t = AppDom.FindType(name, AppDom.LoadMode.NoException); if (t == null) { throw new WebServerException(WebError.InvalidParameters, 0, string.Format("Type {0} is unknown!", name)); } if (t != null) { if (t.IsEnum) { ExplainEnum(data, t); return; } if (!t.IsPrimitive && t.IsValueType) { ExplainStruct(data, t); return; } } throw new WebServerException(WebError.InvalidOperation, 0, string.Format("Type {0} cannot be explained!", name)); }
void GetStaticFileListing(WebData data) { string url = Uri.UnescapeDataString(data.Request.DecodedUrl).Trim('/'); string path = FileSystem.Combine(StaticFilesPath, url); var entries = new List <WebDirectoryEntry>(); string root = FileSystem.Combine(path, ".."); if (FileSystem.IsRelative(root, StaticFilesPath)) { FileSystemInfo info = new DirectoryInfo(root); var entry = new WebDirectoryEntry() { DateTime = info.LastWriteTime, Name = "..", Type = WebDirectoryEntryType.Directory, Link = "/" + url + "/..", }; entries.Add(entry); } if (FileSystem.IsRelative(path, StaticFilesPath)) { foreach (string dir in Directory.GetDirectories(path)) { FileSystemInfo info = new DirectoryInfo(dir); var entry = new WebDirectoryEntry() { DateTime = info.LastWriteTime, Name = info.Name, Type = WebDirectoryEntryType.Directory, Link = "/" + FileSystem.Combine('/', url, info.Name), }; entries.Add(entry); } foreach (string file in Directory.GetFiles(path)) { var info = new FileInfo(file); var entry = new WebDirectoryEntry() { DateTime = info.LastWriteTime, Size = info.Length, Name = info.Name, Type = WebDirectoryEntryType.File, Link = "/" + FileSystem.Combine('/', url, info.Name), }; entries.Add(entry); } } var pb = new HtmlPageBuilder(data.Request); pb.Content.CardOpenText($"File Listing:"); pb.Content.ParagraphText($"{entries.Count} entries"); pb.Content.TableOpen(new string[] { "Type", "Size", "Name" }, "table-striped table-responsive"); foreach (WebDirectoryEntry entry in entries) { pb.Content.TableRowOpen(); pb.Content.TableHtmlCell(Bootstrap4.GetBadge(entry.Type.ToString(), "badge-default")); pb.Content.TableCell(entry.Type == WebDirectoryEntryType.Directory ? string.Empty : entry.Size.FormatBinarySize()); pb.Content.TableHtmlCell(Bootstrap4.GetLink(entry.Name, entry.Link)); pb.Content.TableRowClose(); } pb.Content.TableClose(); pb.Content.CardClose(); pb.Content.AddHtml(" "); data.Answer = pb.ToAnswer(WebMessage.Create("FileListing", "File listing retrieved.")); }
void HandleRequest(WebServerClient client, WebData data) { // add acl headers if (Certificate != null) { data.Result.Headers["Strict-Transport-Security"] = "max-age=604800; includeSubDomains"; } data.Result.Headers["Access-Control-Allow-Headers"] = "Session"; if (data.Method?.PageAttribute?.AuthType == WebServerAuthType.Basic) { data.Result.Headers["Access-Control-Allow-Credentials"] = "true"; data.Result.Headers["Access-Control-Allow-Headers"] += ", Authorization"; } if (!data.Result.Headers.ContainsKey("Access-Control-Allow-Origin")) { data.Result.Headers["Access-Control-Allow-Origin"] = string.IsNullOrEmpty(data.Request.Origin) ? "*" : data.Request.Origin; } if (!data.Result.Headers.ContainsKey("Access-Control-Allow-Methods")) { data.Result.Headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"; } if (data.Method?.PageAttribute?.AllowHeaders != null) { data.Result.Headers["Access-Control-Allow-Headers"] += ", " + data.Method.PageAttribute.AllowHeaders; } if (data.Request.Command == WebCommand.OPTIONS) { data.Result.AddMessage(data.Method, "Options transfered successfully."); client.SendAnswer(data); return; } data.Request.LoadPost(client); if (data.Method == null) { Trace.TraceInformation("Static Request: {0}", data.Request); if (StaticRequest != null) { var e = new WebPageEventArgs(data); StaticRequest(this, e); if (e.Handled) { client.SendAnswer(data); return; } } if (EnableTemplates && RunTemplate(data)) { Trace.TraceInformation("Template: {0} {1}", data.Request, data.Result); client.SendAnswer(data); return; } // no method - send static file ? WebAnswer staticFile = GetStaticFile(data.Request); if (staticFile != null) { // file present, send answer Trace.TraceInformation("Static file: {0} {1}", data.Request, staticFile); SetStaticCacheTime(staticFile, StaticPathCacheTime); client.SendAnswer(staticFile); return; } // static path access -> set cache time SetStaticCacheTime(data, StaticPathCacheTime); // file not present, check special functions if (EnableExplain && (data.Request.DecodedUrl.ToLower() == "/explain" || data.Request.DecodedUrl.ToLower() == "/functionlist")) { // special page (function list / explain) explain.Explain(data); } else if (EnableFileListing) { // list files GetStaticFileListing(data); } else { // no static -> error data.Result.AddMessage(data.Request.PlainUrl, WebError.NotFound, $"The requested URL {data.Request.DecodedUrl} was not found on this server."); } client.SendAnswer(data); return; } // invoke method CallMethod(data); // send answer client.SendAnswer(data); }
/// <summary>Handles a client stage1 (preparations).</summary> /// <remarks>Performs the firewall checks and enters stage2.</remarks> internal void HandleClient(WebServerClient client) { System.Globalization.CultureInfo threadCulture = Thread.CurrentThread.CurrentCulture; int threadId = Thread.CurrentThread.ManagedThreadId; WebResultBuilder result = null; try { // callback for connected client ClientConnected?.Invoke(this, new WebClientEventArgs(client)); // do request handling int requestNumber = 0; if (PerformanceChecks) { Trace.TraceInformation( $"HandleClient [{threadId}] <cyan>{client.RemoteEndPoint}<default> ready to receive request. " + $"Elapsed <cyan>{client.StopWatch.Elapsed.FormatTime()}<default>."); } while (client.IsConnected) { result = null; if (PerformanceChecks && requestNumber > 0) { Trace.TraceInformation( $"HandleClient [{threadId}] <cyan>{client.RemoteEndPoint}<default> request <green>{requestNumber}<default> handling completed. " + $"Elapsed <cyan>{client.StopWatch.Elapsed.FormatTime()}<default>."); } // read first request line string firstLine = client.Reader.ReadLine(); client.StopWatch.Reset(); if (PerformanceChecks) { Trace.TraceInformation( $"HandleClient [{threadId}] <cyan>{client.RemoteEndPoint}<default> start handling request <cyan>{++requestNumber}<default>. " + $"Elapsed <cyan>{client.StopWatch.Elapsed.FormatTime()}<default>."); } // load request var request = WebRequest.Load(this, firstLine, client); // prepare web data object var data = new WebData(request, client.StopWatch); result = data.Result; // update thread culture Thread.CurrentThread.CurrentCulture = data.Request.Culture; // handle request but change some default exceptions to web exceptions try { HandleRequest(client, data); } catch (ObjectDisposedException) { Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed"); } catch (InvalidOperationException ex) { throw new WebServerException(ex, WebError.InvalidOperation, 0, ex.Message); } catch (ArgumentException ex) { throw new WebServerException(ex, WebError.InvalidParameters, 0, ex.Message); } } } catch (WebServerException ex) { Trace.TraceInformation(ex.ToString()); if (result == null) { result = new WebResultBuilder(this); } result.AddMessage(WebMessage.Create(ex)); if (ex.Error == WebError.AuthenticationRequired || ex.Error == WebError.InvalidTransactionKey) { result.Headers["WWW-Authenticate"] = $"Basic realm=\"{AssemblyVersionInfo.Program.Company} - {AssemblyVersionInfo.Program.Product}\""; } result.CloseAfterAnswer = true; client.SendAnswer(result.ToAnswer()); } catch (SocketException) { Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed"); /*client closed connection*/ } catch (EndOfStreamException) { /*client closed connection*/ Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed"); } catch (Exception ex) { if (ex.InnerException is SocketException) { Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed"); return; } string supportCode = Base32.Safe.Encode(Environment.TickCount); Trace.TraceError("<red>Unhandled Internal Server Error<default> Code {1}\n{0}", ex.ToString(), supportCode); if (result == null) { result = new WebResultBuilder(this); } result.AddMessage(ex.Source, WebError.InternalServerError, $"Internal Server Error\nUnexpected result on request.\nPlease contact support!\nSupport Code = {supportCode}"); result.CloseAfterAnswer = true; client.SendAnswer(result.ToAnswer()); } finally { while (client.IsConnected && client.Reader.Available == 0) { Thread.Sleep(1); } client.Close(); if (client != null) { ClientDisconnected?.Invoke(this, new WebClientEventArgs(client)); } // reset thread culture if (Thread.CurrentThread.CurrentCulture != threadCulture) { Thread.CurrentThread.CurrentCulture = threadCulture; } } }
/// <summary>Builds the template.</summary> /// <param name="data">The data.</param> /// <returns>Returns true on success, false otherwise.</returns> public bool Render(WebData data) { if (data.Server != server) { throw new ArgumentOutOfRangeException(nameof(data.Server)); } { // need reload ? DateTime lastChanged = FileSystem.GetLastWriteTimeUtc(FileName); if (lastChanged != LastChanged) { Reload(); } } // do auth and load user session (if any) data.Result.SkipMainObject = true; data.Result.TransmitLayout = false; // call functions IDictionary <string, string> templateParameters = data.Request.Parameters; var tables = new Set <string>(); var parameterDescription = new List <WebTemplateParameter>(); for (int i = 0; i < functions.Length; i++) { Func function = functions[i]; parameterDescription.AddRange(function.ParameterDescriptions); if (function.NeededParameters.Count > 0) { // foreach neededparameters, any parameter is not at tmeplate parameters -> continue if (function.NeededParameters.Where(n => !templateParameters.ContainsKey(n)).Any()) { continue; } } var functionParameters = new Dictionary <string, string>(); foreach (System.Reflection.ParameterInfo methodParameter in function.Method.Parameters) { // lookup function parameter name from function section at template if (!function.Parameters.TryGetValue(methodParameter.Name, out string templateParameterName)) { continue; } // parameter name at template could be loaded if (!templateParameters.TryGetValue(templateParameterName, out string parameterValue)) { if (!methodParameter.IsOptional) { // no value given and is not optional throw new WebServerException(WebError.InvalidParameters, $"Template error: Missing {methodParameter.Name} is not for function {function} is not set. Define {templateParameterName} at template call!"); } continue; } functionParameters[methodParameter.Name] = parameterValue; } data.Request.Parameters = new ReadOnlyDictionary <string, string>(functionParameters); // invoke method data.Method = function.Method; data.Server.CallMethod(data); } Stopwatch renderWatch = server.PerformanceChecks ? Stopwatch.StartNew() : null; // replace content byte[] result = staticData ?? BuildStaticData(content); if (renderWatch != null) { Trace.TraceInformation("Template static data generation took {0}", renderWatch.Elapsed.FormatTime()); } // render data { data.Result.Type = WebResultType.Json; data.Result.AddStructs(parameterDescription); WebAnswer answer = data.Result.ToAnswer(); result = result.ReplaceFirst(Tag, ScriptStart, answer.ContentData, ScriptEnd); } // set result data.Result = null; WebMessage message; if (data.Method != null) { message = WebMessage.Create(data.Method, $"Template call <cyan>{data.Request}<default> succeeded."); } else { message = WebMessage.Create($"Static {data.Request.PlainUrl}", $"Template call <cyan>{data.Request}<default> succeeded."); } data.Answer = WebAnswer.Raw(data.Request, message, result, "text/html"); return(true); }
/// <summary>Invokes the method using the specified data.</summary> /// <param name="data">The data.</param> /// <exception cref="WebServerException"> /// Could not convert parameter {0} value {1} to type {2} /// or /// Function {0}\nParameter {1} is missing!. /// </exception> public void Invoke(WebData data) { data.Server.OnCheckSession(data); // auth required ? if (PageAttribute.AuthType != WebServerAuthType.None) { if (!data.Session.IsAuthenticated()) { Trace.TraceInformation("{0} {1}: Error call to <red>{2}<default> requires a valid user account. Elapsed {3}", data.Request.SourceAddress, data.Session, data.Request.DecodedUrl, data.Elapsed.FormatTime()); data.Result.AddMessage(data.Method, WebError.AuthenticationRequired, $"The requested URL {data.Request.DecodedUrl} requires a valid user account."); if (data.Method?.PageAttribute?.AuthType == WebServerAuthType.Basic) { data.Result.Headers["WWW-Authenticate"] = $"Basic realm=\"{AssemblyVersionInfo.Program.Company} - {AssemblyVersionInfo.Program.Product}\""; } return; } data.Server.OnCheckAccess(data); } if (data.Server.VerboseMode) { Trace.TraceInformation("Request {0} Invoke Method {1}", data.Request, data.Method); } if (instance is Action <WebData> func) { func.Invoke(data); if (data.Server.VerboseMode) { Trace.TraceInformation("{0} {1}: Completed call to <green>{2}<default>. Elapsed {3}", data.Request.SourceAddress, data.Session, Name, data.Elapsed.FormatTime()); } return; } var usedParameters = new Set <string>(data.Request.Parameters.Keys); var parameters = new ArrayList(); foreach (ParameterInfo p in Method.GetParameters()) { if (p.ParameterType == typeof(WebData)) { parameters.Add(data); continue; } if (data.Request.Parameters.ContainsKey(p.Name)) { string value = data.Request.Parameters[p.Name]; if (value.Trim().Length > 0) { try { parameters.Add(Fields.ConvertValue(p.ParameterType, value, CultureInfo.InvariantCulture)); } catch (Exception ex) { throw new WebServerException(ex, WebError.InvalidParameters, 0, string.Format("Could not convert parameter {0} value {1} to type {2}", p.Name, value, p.ParameterType.Name)); } continue; } } if (data.Request.MultiPartFormData != null) { if (data.Request.MultiPartFormData.TryGet(p.Name, out WebSinglePart part)) { // binary ? if (p.ParameterType == typeof(byte[])) { parameters.Add(part.Content); } else { // no convert from string string value = Encoding.UTF8.GetString(part.Content); parameters.Add(Fields.ConvertValue(p.ParameterType, value, CultureInfo.InvariantCulture)); } continue; } } if (p.IsOptional) { try { #if NET_45 if (p.HasDefaultValue) #endif { parameters.Add(p.DefaultValue); continue; } } catch { Trace.TraceError("Invalid default value at parameter {0}!", p); } parameters.Add(null); continue; } throw new WebServerException(WebError.InvalidParameters, 0, string.Format("Function {0}\nParameter {1} is missing!", this, p.Name)); } if (!PageAttribute.AllowAnyParameters) { foreach (string name in Method.GetParameters().Select(p => p.Name)) { usedParameters.TryRemove(name); } if (usedParameters.Count > 0) { throw new WebServerException(WebError.InvalidParameters, 0, string.Format("Function {0}\nParameter {1} is unknown!", this, usedParameters.First())); } } Method.Invoke(instance, parameters.ToArray()); if (data.Server.VerboseMode) { Trace.TraceInformation("{0} {1}: Completed call to <green>{2}<default>. Elapsed {3}", data.Request.SourceAddress, data.Session, Name, data.Elapsed.FormatTime()); } }
void ExplainFunction(WebData data, WebServerMethod function) { var html = new HtmlPageBuilder(data.Request); { string path = string.Empty; string[] parts = function.FullPaths.First().Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); int last = parts.Length - 1; for (int n = 0; n < parts.Length; n++) { path += "/" + parts[n]; html.Breadcrump.Add(new WebLink() { Text = parts[n], Link = (n != last) ? $"/Explain?functions={path}" : $"/Explain?function={path}" }); } } Bootstrap4 content = html.Content; WebServerAuthType authType = function.PageAttribute?.AuthType ?? WebServerAuthType.None; { string link = function.FullPaths.First(); var head = new Bootstrap4(); if (authType != WebServerAuthType.None) { // head.DivOpen(Bootstrap4.Item.float_right); head.DivOpen(Bootstrap4.Item.float_right); AddBadges(head, function.PageAttribute); head.DivClose(Bootstrap4.Item.float_right); // head.AddHtml("<br/>"); } head.AddHtml("<h2>"); head.AddHtml(function.Method.Name.SplitCamelCase().Join(" ")); if (function.Parameters.Length > 0) { head.AddHtml(" ("); head.AddHtml(function.ParameterString()); head.AddHtml(")"); } head.AddHtml("</h2>"); head.DivOpen(Bootstrap4.Item.float_right); head.Link("html", link + ".html", "btn btn-sm btn-outline-primary"); head.Link("json", link + ".json", "btn btn-sm btn-outline-primary"); head.Link("xml", link + ".xml", "btn btn-sm btn-outline-primary"); head.Link("plain", link + ".txt", "btn btn-sm btn-outline-primary"); head.DivClose(Bootstrap4.Item.float_right); head.AddHtml(function.Method.DeclaringType.AssemblyQualifiedName); content.CardOpen(head.ToString()); } XNetDocItem doc = documentation.GetMethod(function.Method); DocumentHtml(content, doc, function.IsAction ? "Generic action" : function.Method.ToString()); content.ListGroupOpen(); int i = 0; foreach (ParameterInfo parameter in function.Parameters) { if (parameter.ParameterType == typeof(WebData)) { continue; } ParameterHtml(content, i++, parameter, doc); } content.ListGroupClose(); content.CardClose(); content.AddHtml(" "); var message = WebMessage.Create("Explain " + function.Name, string.Format("Explain function {0}", function)); data.Answer = html.ToAnswer(message); }
void ExplainFunctionList(WebData data) { var html = new HtmlPageBuilder(data.Request); IEnumerable <KeyValuePair <string, WebServerMethod> > paths = data.Server.RegisteredPaths; if (data.Request.Parameters.TryGetValue("functions", out string functions)) { string path = string.Empty; string[] parts = functions.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); for (int n = 0; n < parts.Length; n++) { path += "/" + parts[n]; html.Breadcrump.Add(new WebLink() { Text = parts[n], Link = $"/Explain?functions={path}" }); } paths = paths.Where(p => p.Key.StartsWith(functions)); } Bootstrap4 content = html.Content; content.ListGroupOpen(); int i = 0; ILookup <WebServerMethod, string> lookup = paths.ToLookup(p => p.Value, p => p.Key); foreach (IGrouping <WebServerMethod, string> item in lookup) { WebServerMethod function = item.Key; // if (item.Key == "/") continue; content.ListGroupItemOpen(i++ % 2 == 0 ? " list-group-item-info" : null); XNetDocItem doc = documentation.GetMethod(function.Method); content.AddHtml("<div style=\"width:100%\">"); content.DivOpen(Bootstrap4.Item.row); WebServerAuthType authType = function.PageAttribute?.AuthType ?? WebServerAuthType.None; if (authType != WebServerAuthType.None) { content.DivOpen(Bootstrap4.Item.col, "col-12 col-sm-auto flex-sm-last"); AddBadges(content, function.PageAttribute); content.DivClose(Bootstrap4.Item.col); } content.DivOpen(Bootstrap4.Item.col); content.AddHtml("<h4>"); content.AddHtml(function.Method.Name.SplitCamelCase().Join(" ")); if (function.Parameters.Length > 0) { content.AddHtml(" ("); content.AddHtml(function.ParameterString()); content.AddHtml(")"); } content.AddHtml("</h4>"); content.DivClose(Bootstrap4.Item.col); content.DivClose(Bootstrap4.Item.row); foreach (string path in item) { content.DivOpen(Bootstrap4.Item.row); content.DivOpen(Bootstrap4.Item.col); content.Link(path, $"Explain?function={path}"); content.DivClose(Bootstrap4.Item.col); content.DivOpen(Bootstrap4.Item.col, "col-12 col-sm-auto"); content.Link("html", path + ".html", "btn btn-sm btn-outline-primary"); content.Link("json", path + ".json", "btn btn-sm btn-outline-primary"); content.Link("xml", path + ".xml", "btn btn-sm btn-outline-primary"); content.Link("plain", path + ".txt", "btn btn-sm btn-outline-primary"); content.DivClose(Bootstrap4.Item.col); content.DivClose(Bootstrap4.Item.row); } if (doc?.Summary != null) { content.DivOpen(Bootstrap4.Item.row); content.DivOpen(Bootstrap4.Item.col); content.AddHtml("<strong>Description:</strong>"); int cdata = doc.Summary.IndexOf("<![CDATA["); if (cdata > -1) { content.Text(doc.Summary.Substring(0, cdata)); content.AddHtml("<br/><code>"); string code = doc.Summary.Substring(cdata + 9); int cdataEnd = code.IndexOf("]]>"); content.AddHtml(code.Substring(0, cdata)); content.AddHtml("</code>"); content.Text(doc.Summary.Substring(cdata + cdataEnd + 9 + 3)); } else { content.Text(doc.Summary); } content.DivClose(Bootstrap4.Item.col); content.DivClose(Bootstrap4.Item.row); } content.AddHtml("</div>"); content.ListGroupItemClose(); } content.ListGroupClose(); content.AddHtml(" "); var message = WebMessage.Create("Explain", functions == null ? "Explain functions." : $"Explain {functions} functions."); data.Answer = html.ToAnswer(message); }