@fui-org/mlis-cli 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -2
- package/dist/mlis.js +86 -0
- package/package.json +32 -4
- package/render/MlisRender.cs +298 -0
- package/render/SampleDataConnection.cs +320 -0
- package/skills/mlis/SKILL.md +147 -0
- package/skills/mlis/references/cdn-files.md +38 -0
- package/skills/mlis/references/controls.md +161 -0
- package/skills/mlis/references/design-patterns.md +94 -0
- package/skills/mlis/references/news.md +65 -0
- package/skills/mlis/references/rendered-html.md +91 -0
- package/skills/mlis/references/viewpage-structure.md +105 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// mlis render: runs a local copy of the MLIS site (its real Default.aspx, App_Code and MLIS controls) inside this
|
|
2
|
+
// process with ASP.NET's ApplicationHost, behind an HttpListener on http://localhost:<port>/. Used only by `mlis render`.
|
|
3
|
+
// MlisRender.exe --site <render site folder> --port 5080 --host lhu.edu.vn [--origin https://lhu.edu.vn --cache <dir>]
|
|
4
|
+
// The site's ViewPage folder is a junction to the designer's workspace, so saved .ascx/.css show on the next reload.
|
|
5
|
+
// Static files missing locally (the local platform copy may be older than the live site, images not pulled) are
|
|
6
|
+
// fetched once from --origin with a plain GET and kept in --cache; nothing is ever sent to the live site.
|
|
7
|
+
using System;
|
|
8
|
+
using System.Collections.Generic;
|
|
9
|
+
using System.IO;
|
|
10
|
+
using System.Net;
|
|
11
|
+
using System.Text;
|
|
12
|
+
using System.Threading;
|
|
13
|
+
using System.Web;
|
|
14
|
+
using System.Web.Hosting;
|
|
15
|
+
|
|
16
|
+
[Serializable]
|
|
17
|
+
public class RenderResult
|
|
18
|
+
{
|
|
19
|
+
public int Status = 200;
|
|
20
|
+
public List<string> Headers = new List<string>();
|
|
21
|
+
public byte[] Body = new byte[0];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public class RenderRequest : SimpleWorkerRequest
|
|
25
|
+
{
|
|
26
|
+
readonly string host, cookie, verb, contentType;
|
|
27
|
+
readonly byte[] body;
|
|
28
|
+
readonly MemoryStream output = new MemoryStream();
|
|
29
|
+
readonly ManualResetEvent done = new ManualResetEvent(false);
|
|
30
|
+
public int Status = 200;
|
|
31
|
+
public readonly List<string> Headers = new List<string>();
|
|
32
|
+
|
|
33
|
+
public RenderRequest(string page, string query, string host, string cookie, string verb, string contentType, byte[] body)
|
|
34
|
+
: base(page, query, new StringWriter())
|
|
35
|
+
{
|
|
36
|
+
this.host = host; this.cookie = cookie; this.verb = verb; this.contentType = contentType; this.body = body;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public override void SendStatus(int statusCode, string statusDescription) { Status = statusCode; }
|
|
40
|
+
public override void SendKnownResponseHeader(int index, string value) { Headers.Add(GetKnownResponseHeaderName(index) + ": " + value); }
|
|
41
|
+
public override void SendUnknownResponseHeader(string name, string value) { Headers.Add(name + ": " + value); }
|
|
42
|
+
public override void SendResponseFromMemory(byte[] data, int length) { output.Write(data, 0, length); }
|
|
43
|
+
public override void EndOfRequest() { done.Set(); }
|
|
44
|
+
public override string GetServerName() { return host; }
|
|
45
|
+
public override string GetHttpVerbName() { return verb; }
|
|
46
|
+
public override string GetKnownRequestHeader(int index)
|
|
47
|
+
{
|
|
48
|
+
if (index == HeaderHost) return host;
|
|
49
|
+
if (index == HeaderCookie) return cookie;
|
|
50
|
+
if (index == HeaderContentType) return contentType;
|
|
51
|
+
if (index == HeaderContentLength) return body == null ? null : body.Length.ToString();
|
|
52
|
+
return base.GetKnownRequestHeader(index);
|
|
53
|
+
}
|
|
54
|
+
public override byte[] GetPreloadedEntityBody() { return body; }
|
|
55
|
+
public override bool IsEntireEntityBodyIsPreloaded() { return body != null; }
|
|
56
|
+
public override int GetLocalPort() { return 443; }
|
|
57
|
+
public override bool IsSecure() { return true; }
|
|
58
|
+
public override string GetProtocol() { return "https"; }
|
|
59
|
+
|
|
60
|
+
public RenderResult Wait()
|
|
61
|
+
{
|
|
62
|
+
done.WaitOne(TimeSpan.FromMinutes(2));
|
|
63
|
+
return new RenderResult { Status = Status, Headers = Headers, Body = output.ToArray() };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Lives in the ASP.NET AppDomain; must be in the render site's bin (this same file is copied there).
|
|
68
|
+
public class RenderBridge : MarshalByRefObject
|
|
69
|
+
{
|
|
70
|
+
public override object InitializeLifetimeService() { return null; }
|
|
71
|
+
|
|
72
|
+
public RenderResult Process(string verb, string path, string query, string host, string cookie, string contentType, byte[] body)
|
|
73
|
+
{
|
|
74
|
+
var request = new RenderRequest(path.TrimStart('/'), query, host, cookie, verb, contentType, body);
|
|
75
|
+
HttpRuntime.ProcessRequest(request);
|
|
76
|
+
return request.Wait();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Parse + compile one .ascx the way the site would ("" = fine). Needed because <MLIS:LoadPage> swallows the
|
|
80
|
+
// exception of a broken include and the page renders without it.
|
|
81
|
+
public string Compile(string virtualPath)
|
|
82
|
+
{
|
|
83
|
+
try
|
|
84
|
+
{
|
|
85
|
+
System.Web.Compilation.BuildManager.GetCompiledType(virtualPath);
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
catch (HttpParseException e)
|
|
89
|
+
{
|
|
90
|
+
return e.Message + " (line " + e.Line + ")";
|
|
91
|
+
}
|
|
92
|
+
catch (HttpCompileException e)
|
|
93
|
+
{
|
|
94
|
+
if (e.Results != null)
|
|
95
|
+
foreach (System.CodeDom.Compiler.CompilerError err in e.Results.Errors)
|
|
96
|
+
if (!err.IsWarning) return err.ErrorText + " (line " + err.Line + ")";
|
|
97
|
+
return e.Message;
|
|
98
|
+
}
|
|
99
|
+
catch (Exception e)
|
|
100
|
+
{
|
|
101
|
+
return e.GetBaseException().Message;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public static class MlisRender
|
|
107
|
+
{
|
|
108
|
+
static string site, host, origin, cache;
|
|
109
|
+
static readonly HashSet<string> LiveMissing = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
110
|
+
static RenderBridge bridge;
|
|
111
|
+
static readonly object BridgeGate = new object();
|
|
112
|
+
static readonly HashSet<string> NeverStatic = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
113
|
+
{ ".aspx", ".ascx", ".ashx", ".asmx", ".asax", ".config", ".vb", ".cs", ".master", ".svc", ".dll", ".exe", ".pdb", ".compiled" };
|
|
114
|
+
static readonly Dictionary<string, string> Mime = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
115
|
+
{
|
|
116
|
+
{ ".css", "text/css" }, { ".js", "application/javascript" }, { ".png", "image/png" }, { ".jpg", "image/jpeg" },
|
|
117
|
+
{ ".jpeg", "image/jpeg" }, { ".gif", "image/gif" }, { ".webp", "image/webp" }, { ".svg", "image/svg+xml" },
|
|
118
|
+
{ ".ico", "image/x-icon" }, { ".woff", "font/woff" }, { ".woff2", "font/woff2" }, { ".ttf", "font/ttf" },
|
|
119
|
+
{ ".otf", "font/otf" }, { ".eot", "application/vnd.ms-fontobject" }, { ".json", "application/json" },
|
|
120
|
+
{ ".txt", "text/plain" }, { ".xml", "text/xml" }, { ".mp4", "video/mp4" }, { ".pdf", "application/pdf" },
|
|
121
|
+
{ ".html", "text/html" }, { ".avif", "image/avif" }, { ".bmp", "image/bmp" },
|
|
122
|
+
};
|
|
123
|
+
// Extensions MLIS also serves through routes (/1/2/title.html, robots.txt, llms.txt, sitemap.xml)
|
|
124
|
+
static readonly HashSet<string> Routed = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".html", ".htm", ".txt", ".xml" };
|
|
125
|
+
static readonly HashSet<string> Images = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
126
|
+
{ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".avif" };
|
|
127
|
+
|
|
128
|
+
public static int Main(string[] args)
|
|
129
|
+
{
|
|
130
|
+
site = Path.GetFullPath(Arg(args, "--site", "."));
|
|
131
|
+
host = Arg(args, "--host", "lhu.edu.vn");
|
|
132
|
+
int port = int.Parse(Arg(args, "--port", "5080"));
|
|
133
|
+
origin = Arg(args, "--origin", "").TrimEnd('/');
|
|
134
|
+
cache = Arg(args, "--cache", "");
|
|
135
|
+
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
|
136
|
+
var listener = new HttpListener();
|
|
137
|
+
listener.Prefixes.Add("http://localhost:" + port + "/");
|
|
138
|
+
listener.Start();
|
|
139
|
+
Console.WriteLine("ready http://localhost:" + port + "/");
|
|
140
|
+
Console.Out.Flush();
|
|
141
|
+
while (true)
|
|
142
|
+
{
|
|
143
|
+
var ctx = listener.GetContext();
|
|
144
|
+
ThreadPool.QueueUserWorkItem(_ => Handle(ctx));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static string Arg(string[] args, string name, string fallback)
|
|
149
|
+
{
|
|
150
|
+
int i = Array.IndexOf(args, name);
|
|
151
|
+
return i >= 0 && i + 1 < args.Length ? args[i + 1] : fallback;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static RenderBridge Bridge(bool fresh)
|
|
155
|
+
{
|
|
156
|
+
lock (BridgeGate)
|
|
157
|
+
{
|
|
158
|
+
if (bridge == null || fresh) bridge = (RenderBridge)ApplicationHost.CreateApplicationHost(typeof(RenderBridge), "/", site);
|
|
159
|
+
return bridge;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static void Handle(HttpListenerContext ctx)
|
|
164
|
+
{
|
|
165
|
+
var res = ctx.Response;
|
|
166
|
+
try
|
|
167
|
+
{
|
|
168
|
+
var req = ctx.Request;
|
|
169
|
+
string path = req.Url.AbsolutePath;
|
|
170
|
+
if (path == "/__mlis/ping") { Send(res, 200, "text/plain", Encoding.UTF8.GetBytes("mlis render")); return; }
|
|
171
|
+
if (path == "/__mlis/stop") { Send(res, 200, "text/plain", Encoding.UTF8.GetBytes("stopping")); res.Close(); Environment.Exit(0); }
|
|
172
|
+
if (path == "/__mlis/compile")
|
|
173
|
+
{
|
|
174
|
+
string target = "~/" + (ctx.Request.QueryString["path"] ?? "").TrimStart('~', '/');
|
|
175
|
+
string error;
|
|
176
|
+
try { error = Bridge(false).Compile(target); } catch (Exception) { error = Bridge(true).Compile(target); }
|
|
177
|
+
Send(res, 200, "text/plain; charset=utf-8", Encoding.UTF8.GetBytes(error));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (path.StartsWith("/__mlis/ph/")) { Send(res, 200, "image/svg+xml", Placeholder(path)); return; }
|
|
181
|
+
|
|
182
|
+
string ext = Path.GetExtension(path);
|
|
183
|
+
if (req.HttpMethod == "GET" && ext != "" && !NeverStatic.Contains(ext))
|
|
184
|
+
{
|
|
185
|
+
string physical = Path.Combine(site, Uri.UnescapeDataString(path.TrimStart('/')).Replace('/', Path.DirectorySeparatorChar));
|
|
186
|
+
if (File.Exists(physical))
|
|
187
|
+
{
|
|
188
|
+
string type;
|
|
189
|
+
Send(res, 200, Mime.TryGetValue(ext, out type) ? type : "application/octet-stream", File.ReadAllBytes(physical));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (req.HttpMethod == "GET" && ext != "" && !NeverStatic.Contains(ext) && !Routed.Contains(ext))
|
|
194
|
+
{
|
|
195
|
+
byte[] live = FromLive(path);
|
|
196
|
+
if (live != null)
|
|
197
|
+
{
|
|
198
|
+
string type;
|
|
199
|
+
Send(res, 200, Mime.TryGetValue(ext, out type) ? type : "application/octet-stream", live);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Pictures that do not exist yet (the design references a file not uploaded): a labelled placeholder
|
|
203
|
+
if (Images.Contains(ext)) { Send(res, 200, "image/svg+xml", Placeholder("/__mlis/ph/800x500/" + Path.GetFileNameWithoutExtension(path) + ".svg")); return; }
|
|
204
|
+
Send(res, 404, "text/plain", Encoding.UTF8.GetBytes("not found: " + path));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
byte[] body = null;
|
|
209
|
+
if (req.HasEntityBody) using (var ms = new MemoryStream()) { req.InputStream.CopyTo(ms); body = ms.ToArray(); }
|
|
210
|
+
// A jwtAuth cookie (not a valid token, so nobody is signed in) keeps HtmlPageCache out: every reload renders afresh
|
|
211
|
+
string cookie = req.Headers["Cookie"];
|
|
212
|
+
if (cookie == null || cookie.IndexOf("jwtAuth=", StringComparison.OrdinalIgnoreCase) < 0) cookie = (cookie == null ? "" : cookie + "; ") + "jwtAuth=mlis-render";
|
|
213
|
+
RenderResult result;
|
|
214
|
+
try { result = Bridge(false).Process(req.HttpMethod, path, req.Url.Query.TrimStart('?'), host, cookie, req.ContentType, body); }
|
|
215
|
+
catch (Exception) { result = Bridge(true).Process(req.HttpMethod, path, req.Url.Query.TrimStart('?'), host, cookie, req.ContentType, body); }
|
|
216
|
+
|
|
217
|
+
res.StatusCode = result.Status;
|
|
218
|
+
foreach (var h in result.Headers)
|
|
219
|
+
{
|
|
220
|
+
int colon = h.IndexOf(':');
|
|
221
|
+
if (colon <= 0) continue;
|
|
222
|
+
string name = h.Substring(0, colon).Trim(), value = h.Substring(colon + 1).Trim();
|
|
223
|
+
if (name.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) continue;
|
|
224
|
+
if (name.Equals("Location", StringComparison.OrdinalIgnoreCase)) value = value.Replace("https://" + host, "");
|
|
225
|
+
try { res.Headers.Add(name, value); } catch { }
|
|
226
|
+
}
|
|
227
|
+
res.ContentLength64 = result.Body.Length;
|
|
228
|
+
res.OutputStream.Write(result.Body, 0, result.Body.Length);
|
|
229
|
+
}
|
|
230
|
+
catch (Exception ex)
|
|
231
|
+
{
|
|
232
|
+
try { Send(res, 500, "text/plain; charset=utf-8", Encoding.UTF8.GetBytes(ex.ToString())); } catch { }
|
|
233
|
+
}
|
|
234
|
+
finally
|
|
235
|
+
{
|
|
236
|
+
try { res.Close(); } catch { }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
static byte[] FromLive(string path)
|
|
241
|
+
{
|
|
242
|
+
if (origin == "" || cache == "") return null;
|
|
243
|
+
string file = Path.Combine(cache, Uri.UnescapeDataString(path.TrimStart('/')).Replace('/', Path.DirectorySeparatorChar));
|
|
244
|
+
if (!Path.GetFullPath(file).StartsWith(Path.GetFullPath(cache), StringComparison.OrdinalIgnoreCase)) return null;
|
|
245
|
+
if (File.Exists(file)) return File.ReadAllBytes(file);
|
|
246
|
+
lock (LiveMissing) if (LiveMissing.Contains(path)) return null;
|
|
247
|
+
try
|
|
248
|
+
{
|
|
249
|
+
var request = (HttpWebRequest)WebRequest.Create(origin + path);
|
|
250
|
+
request.Timeout = 15000;
|
|
251
|
+
request.UserAgent = "mlis-render";
|
|
252
|
+
using (var response = (HttpWebResponse)request.GetResponse())
|
|
253
|
+
using (var stream = response.GetResponseStream())
|
|
254
|
+
using (var ms = new MemoryStream())
|
|
255
|
+
{
|
|
256
|
+
stream.CopyTo(ms);
|
|
257
|
+
byte[] data = ms.ToArray();
|
|
258
|
+
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
|
259
|
+
File.WriteAllBytes(file, data);
|
|
260
|
+
return data;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (Exception)
|
|
264
|
+
{
|
|
265
|
+
lock (LiveMissing) LiveMissing.Add(path);
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static void Send(HttpListenerResponse res, int status, string type, byte[] data)
|
|
271
|
+
{
|
|
272
|
+
res.StatusCode = status;
|
|
273
|
+
res.ContentType = type;
|
|
274
|
+
res.ContentLength64 = data.Length;
|
|
275
|
+
res.OutputStream.Write(data, 0, data.Length);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// /__mlis/ph/800x500/label.svg → grey box with the size and label, so layout and cropping are visible
|
|
279
|
+
static byte[] Placeholder(string path)
|
|
280
|
+
{
|
|
281
|
+
string[] parts = path.Substring("/__mlis/ph/".Length).Split('/');
|
|
282
|
+
int w = 800, h = 500;
|
|
283
|
+
if (parts.Length > 1)
|
|
284
|
+
{
|
|
285
|
+
string[] size = parts[0].Split('x');
|
|
286
|
+
if (size.Length == 2) { int.TryParse(size[0], out w); int.TryParse(size[1], out h); }
|
|
287
|
+
}
|
|
288
|
+
string label = WebUtility.HtmlEncode(Path.GetFileNameWithoutExtension(parts[parts.Length - 1]));
|
|
289
|
+
int hue = Math.Abs(label.GetHashCode()) % 360;
|
|
290
|
+
string svg = string.Format(
|
|
291
|
+
"<svg xmlns='http://www.w3.org/2000/svg' width='{0}' height='{1}' viewBox='0 0 {0} {1}'>" +
|
|
292
|
+
"<defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='hsl({3},45%,72%)'/><stop offset='1' stop-color='hsl({4},45%,52%)'/></linearGradient></defs>" +
|
|
293
|
+
"<rect width='100%' height='100%' fill='url(#g)'/>" +
|
|
294
|
+
"<text x='50%' y='50%' font-family='Arial' font-size='{5}' fill='#fff' text-anchor='middle' dominant-baseline='middle'>{2} · {0}×{1}</text></svg>",
|
|
295
|
+
w, h, label, hue, (hue + 40) % 360, Math.Max(12, Math.Min(w, h) / 12));
|
|
296
|
+
return Encoding.UTF8.GetBytes(svg);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// mlis render: stand-in for MLIS bin\DataConnection.dll that answers the site's stored procedures with SAMPLE data,
|
|
2
|
+
// so ViewPage templates render on a designer's machine without the database. Used only in the local render site.
|
|
3
|
+
// Categories come from the JSON file named by MLIS_RENDER_DATA (written by `mlis render`); news, events and
|
|
4
|
+
// pictures are generated, so any category in a template has content.
|
|
5
|
+
using System;
|
|
6
|
+
using System.Collections.Generic;
|
|
7
|
+
using System.Data;
|
|
8
|
+
using System.Data.SqlClient;
|
|
9
|
+
using System.IO;
|
|
10
|
+
using System.Linq;
|
|
11
|
+
using Newtonsoft.Json.Linq;
|
|
12
|
+
|
|
13
|
+
namespace DataConnection
|
|
14
|
+
{
|
|
15
|
+
public class ConnectionDB
|
|
16
|
+
{
|
|
17
|
+
public void OpenConnection(string connectionString) { }
|
|
18
|
+
public void CloseConnection() { }
|
|
19
|
+
public void TransactionBegin() { }
|
|
20
|
+
public void TransactionCommit() { }
|
|
21
|
+
public void TransactionRollback() { }
|
|
22
|
+
|
|
23
|
+
public int GetDataTable(ref DataTable dataTable, string spName, params object[] parameterValues)
|
|
24
|
+
{
|
|
25
|
+
var tables = Sample.Answer(spName, parameterValues);
|
|
26
|
+
dataTable = tables.Count > 0 ? tables[0] : new DataTable();
|
|
27
|
+
return dataTable.Rows.Count;
|
|
28
|
+
}
|
|
29
|
+
public int GetDataTable(bool isTransaction, ref DataTable dataTable, string spName, params object[] parameterValues)
|
|
30
|
+
{
|
|
31
|
+
return GetDataTable(ref dataTable, spName, parameterValues);
|
|
32
|
+
}
|
|
33
|
+
public bool GetOneRow(ref DataRow dataRow, string spName, params object[] parameterValues)
|
|
34
|
+
{
|
|
35
|
+
var tables = Sample.Answer(spName, parameterValues);
|
|
36
|
+
if (tables.Count == 0 || tables[0].Rows.Count == 0) return false;
|
|
37
|
+
dataRow = tables[0].Rows[0];
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
public bool GetOneRow(bool isTransaction, ref DataRow dataRow, string spName, params object[] parameterValues)
|
|
41
|
+
{
|
|
42
|
+
return GetOneRow(ref dataRow, spName, parameterValues);
|
|
43
|
+
}
|
|
44
|
+
public void FillDataset(DataSet dataSet, string tableName, string spName, params object[] parameterValues)
|
|
45
|
+
{
|
|
46
|
+
FillDataset(dataSet, new[] { tableName }, spName, parameterValues);
|
|
47
|
+
}
|
|
48
|
+
public void FillDataset(bool isTransaction, DataSet dataSet, string tableName, string spName, params object[] parameterValues)
|
|
49
|
+
{
|
|
50
|
+
FillDataset(dataSet, new[] { tableName }, spName, parameterValues);
|
|
51
|
+
}
|
|
52
|
+
public void FillDataset(bool isTransaction, DataSet dataSet, string[] tableNames, string spName, params object[] parameterValues)
|
|
53
|
+
{
|
|
54
|
+
FillDataset(dataSet, tableNames, spName, parameterValues);
|
|
55
|
+
}
|
|
56
|
+
public void FillDataset(DataSet dataSet, string[] tableNames, string spName, params object[] parameterValues)
|
|
57
|
+
{
|
|
58
|
+
var tables = Sample.Answer(spName, parameterValues);
|
|
59
|
+
for (int i = 0; i < tableNames.Length && i < tables.Count; i++)
|
|
60
|
+
{
|
|
61
|
+
DataTable target = dataSet.Tables[tableNames[i]];
|
|
62
|
+
if (target == null) { target = new DataTable(tableNames[i]); dataSet.Tables.Add(target); }
|
|
63
|
+
target.Load(tables[i].CreateDataReader(), LoadOption.OverwriteChanges);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
public int ExecCommand(string spName, params object[] parameterValues) { return 0; }
|
|
67
|
+
public int ExecCommand(bool isTransaction, string spName, params object[] parameterValues) { return 0; }
|
|
68
|
+
public int ExecCommand(string spName, ref SqlParameter[] OutParam, params object[] parameterValues) { return 0; }
|
|
69
|
+
public int ExecCommand(bool isTransaction, string spName, ref SqlParameter[] OutParam, params object[] parameterValues) { return 0; }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
internal class Cat
|
|
73
|
+
{
|
|
74
|
+
public int Id, Parent, Bits, Type;
|
|
75
|
+
public string Name, Alias, Description, Html, PageStyle, PageRead, Link, Picture, Icon;
|
|
76
|
+
public int[] Locations = new int[3];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
internal static class Sample
|
|
80
|
+
{
|
|
81
|
+
static readonly object Gate = new object();
|
|
82
|
+
static DateTime loadedStamp = DateTime.MinValue;
|
|
83
|
+
static Dictionary<int, Cat> cats = new Dictionary<int, Cat>();
|
|
84
|
+
static int homeId = 1;
|
|
85
|
+
static int newsCount = 12;
|
|
86
|
+
|
|
87
|
+
static readonly string[] Titles = {
|
|
88
|
+
"Sinh viên đạt giải nhất cuộc thi sáng tạo kỹ thuật cấp quốc gia năm học mới",
|
|
89
|
+
"Hội thảo khoa học quốc tế về chuyển đổi số trong giáo dục đại học",
|
|
90
|
+
"Thông báo tuyển sinh đại học chính quy và các chương trình liên kết",
|
|
91
|
+
"Lễ trao học bổng khuyến khích học tập cho sinh viên có thành tích xuất sắc",
|
|
92
|
+
"Ký kết hợp tác đào tạo và nghiên cứu với doanh nghiệp công nghệ",
|
|
93
|
+
"Ngày hội việc làm kết nối sinh viên với hơn 50 doanh nghiệp tuyển dụng",
|
|
94
|
+
"Giảng viên công bố bài báo khoa học trên tạp chí quốc tế uy tín",
|
|
95
|
+
"Chương trình trao đổi sinh viên với các trường đại học đối tác nước ngoài",
|
|
96
|
+
"Hoạt động tình nguyện mùa hè xanh tại các địa phương vùng sâu vùng xa",
|
|
97
|
+
"Khai giảng năm học mới và chào đón tân sinh viên",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
static void Load()
|
|
101
|
+
{
|
|
102
|
+
string path = Environment.GetEnvironmentVariable("MLIS_RENDER_DATA");
|
|
103
|
+
if (string.IsNullOrEmpty(path) || !File.Exists(path)) { if (cats.Count == 0) Defaults(); return; }
|
|
104
|
+
DateTime stamp = File.GetLastWriteTimeUtc(path);
|
|
105
|
+
if (stamp == loadedStamp) return;
|
|
106
|
+
var json = JObject.Parse(File.ReadAllText(path));
|
|
107
|
+
var next = new Dictionary<int, Cat>();
|
|
108
|
+
foreach (JObject c in (json["categories"] as JArray) ?? new JArray())
|
|
109
|
+
{
|
|
110
|
+
var cat = new Cat
|
|
111
|
+
{
|
|
112
|
+
Id = (int?)c["id"] ?? 0, Parent = (int?)c["parent"] ?? 0, Bits = (int?)c["bits"] ?? 8, Type = (int?)c["type"] ?? 2,
|
|
113
|
+
Name = (string)c["name"] ?? "", Alias = (string)c["alias"] ?? "", Description = (string)c["description"] ?? "",
|
|
114
|
+
Html = (string)c["html"] ?? "", PageStyle = (string)c["pageStyle"] ?? "", PageRead = (string)c["pageRead"] ?? "",
|
|
115
|
+
Link = (string)c["link"] ?? "", Picture = (string)c["picture"] ?? "", Icon = (string)c["icon"] ?? "",
|
|
116
|
+
};
|
|
117
|
+
var locs = c["locations"] as JArray;
|
|
118
|
+
for (int i = 0; i < 3; i++) cat.Locations[i] = locs != null && locs.Count > i ? (int)locs[i] : 0;
|
|
119
|
+
next[cat.Id] = cat;
|
|
120
|
+
}
|
|
121
|
+
cats = next;
|
|
122
|
+
homeId = (int?)json["home"] ?? 1;
|
|
123
|
+
newsCount = (int?)json["newsCount"] ?? 12;
|
|
124
|
+
loadedStamp = stamp;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static void Defaults()
|
|
128
|
+
{
|
|
129
|
+
cats[1] = new Cat { Id = 1, Parent = 0, Name = "Trang chủ mẫu", Alias = "Trang chủ mẫu", Description = "Mô tả mẫu", Html = "", PageStyle = "", PageRead = "", Link = "", Picture = "", Icon = "", Bits = 8, Type = 1 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public static List<DataTable> Answer(string sp, object[] p)
|
|
133
|
+
{
|
|
134
|
+
lock (Gate)
|
|
135
|
+
{
|
|
136
|
+
Load();
|
|
137
|
+
return Dispatch(sp, p ?? new object[0]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static int I(object[] p, int i) { int v; return p.Length > i && int.TryParse(Convert.ToString(p[i]), out v) ? v : 0; }
|
|
142
|
+
static Cat Get(int id) { Cat c; return cats.TryGetValue(id, out c) ? c : null; }
|
|
143
|
+
static IEnumerable<Cat> Children(int id) { return cats.Values.Where(c => c.Parent == id && c.Id != id).OrderBy(c => c.Id); }
|
|
144
|
+
static IEnumerable<Cat> ByLocation(int id, int location)
|
|
145
|
+
{
|
|
146
|
+
return Children(id).Where(c => location == 0 || c.Locations.Contains(location));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static List<Cat> Path(int id)
|
|
150
|
+
{
|
|
151
|
+
var path = new List<Cat>();
|
|
152
|
+
var seen = new HashSet<int>();
|
|
153
|
+
var cur = Get(id);
|
|
154
|
+
while (cur != null && seen.Add(cur.Id))
|
|
155
|
+
{
|
|
156
|
+
path.Insert(0, cur);
|
|
157
|
+
if (cur.Id == homeId) break;
|
|
158
|
+
cur = Get(cur.Parent);
|
|
159
|
+
}
|
|
160
|
+
return path;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static DataTable CatTable(IEnumerable<Cat> list, string pageStyleOverride = null)
|
|
164
|
+
{
|
|
165
|
+
var t = new DataTable();
|
|
166
|
+
foreach (var col in new[] { "CatName", "CatAlias", "CatPicture", "CatIcon", "CatAvatar", "CatDescription", "HTMLDoc", "LinkURL", "PageStyle", "PageRead" })
|
|
167
|
+
t.Columns.Add(col, typeof(string));
|
|
168
|
+
foreach (var col in new[] { "CategoryID", "ParentID", "BitOption", "HitCounter", "CatType" })
|
|
169
|
+
t.Columns.Add(col, typeof(int));
|
|
170
|
+
var items = list.ToList();
|
|
171
|
+
for (int i = 0; i < items.Count; i++)
|
|
172
|
+
{
|
|
173
|
+
var c = items[i];
|
|
174
|
+
string style = (i == items.Count - 1 && pageStyleOverride != null) ? pageStyleOverride : c.PageStyle;
|
|
175
|
+
t.Rows.Add(c.Name, c.Alias, c.Picture, c.Icon, "", c.Description, c.Html, c.Link, style, c.PageRead, c.Id, c.Parent, c.Bits, 1234, c.Type);
|
|
176
|
+
}
|
|
177
|
+
return t;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static string Picture(int catId, int k, int w = 800, int h = 500)
|
|
181
|
+
{
|
|
182
|
+
return string.Format("/__mlis/ph/{0}x{1}/tin-{2}-{3}.svg", w, h, catId, k);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static DataTable NewsTable(int catId, int skip, int count, bool eventDates)
|
|
186
|
+
{
|
|
187
|
+
var t = new DataTable();
|
|
188
|
+
t.Columns.Add("NewsID", typeof(int));
|
|
189
|
+
t.Columns.Add("CategoryID", typeof(int));
|
|
190
|
+
t.Columns.Add("NewsTitle", typeof(string));
|
|
191
|
+
t.Columns.Add("NewsIntro", typeof(string));
|
|
192
|
+
t.Columns.Add("NewsContent", typeof(string));
|
|
193
|
+
t.Columns.Add("NewsImages", typeof(string));
|
|
194
|
+
t.Columns.Add("CreateTime", typeof(DateTime));
|
|
195
|
+
t.Columns.Add("UpdateTime", typeof(DateTime));
|
|
196
|
+
t.Columns.Add("HitCounter", typeof(int));
|
|
197
|
+
t.Columns.Add("NewsKeyword", eventDates ? typeof(DateTime) : typeof(string));
|
|
198
|
+
var cat = Get(catId);
|
|
199
|
+
string catName = cat != null ? cat.Name : "Thư mục " + catId;
|
|
200
|
+
for (int k = skip + 1; k <= skip + count; k++)
|
|
201
|
+
{
|
|
202
|
+
string title = Titles[(catId + k) % Titles.Length] + (k % 3 == 0 ? " – " + catName : "");
|
|
203
|
+
DateTime created = DateTime.Today.AddHours(9).AddDays(-k);
|
|
204
|
+
object keyword = eventDates ? (object)DateTime.Today.AddDays(k / 2).AddHours(8 + (k % 4) * 2) : "từ khoá mẫu, " + catName;
|
|
205
|
+
t.Rows.Add(catId * 1000 + k, catId, title,
|
|
206
|
+
"Tóm tắt mẫu của bài viết số " + k + ": nội dung ngắn giới thiệu sự kiện và hoạt động nổi bật.",
|
|
207
|
+
SampleContent(k), Picture(catId, k), created, created.AddHours(2), 100 * k, keyword);
|
|
208
|
+
}
|
|
209
|
+
return t;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static string SampleContent(int k)
|
|
213
|
+
{
|
|
214
|
+
return "<p>Đây là <strong>nội dung mẫu</strong> do mlis render tạo để xem bố cục trang đọc tin. Đoạn văn thứ nhất mô tả bối cảnh, thời gian và địa điểm của sự kiện.</p>" +
|
|
215
|
+
"<p><img src=\"/__mlis/ph/1200x700/noi-dung-" + k + ".svg\" alt=\"Hình minh hoạ mẫu\" /></p>" +
|
|
216
|
+
"<h2>Tiêu đề phụ trong bài</h2><p>Đoạn văn thứ hai trình bày chi tiết, kèm danh sách:</p>" +
|
|
217
|
+
"<ul><li>Ý thứ nhất của bài viết mẫu</li><li>Ý thứ hai với nội dung dài hơn một chút để kiểm tra xuống dòng</li><li>Ý thứ ba</li></ul>" +
|
|
218
|
+
"<p>Đoạn kết luận của bài viết mẫu.</p>";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static DataTable NewsDetail(int newsId)
|
|
222
|
+
{
|
|
223
|
+
int catId = newsId / 1000, k = Math.Max(1, newsId % 1000);
|
|
224
|
+
var t = NewsTable(catId, k - 1, 1, false);
|
|
225
|
+
t.Columns.Add("CatName", typeof(string));
|
|
226
|
+
t.Columns.Add("NewsSource", typeof(string));
|
|
227
|
+
t.Columns.Add("PenName", typeof(string));
|
|
228
|
+
var cat = Get(catId);
|
|
229
|
+
t.Rows[0]["CatName"] = cat != null ? cat.Name : "Thư mục " + catId;
|
|
230
|
+
t.Rows[0]["NewsSource"] = "";
|
|
231
|
+
t.Rows[0]["PenName"] = "Phòng Truyền thông";
|
|
232
|
+
return t;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static List<DataTable> One(DataTable t) { return new List<DataTable> { t }; }
|
|
236
|
+
|
|
237
|
+
static List<DataTable> Dispatch(string sp, object[] p)
|
|
238
|
+
{
|
|
239
|
+
switch (sp)
|
|
240
|
+
{
|
|
241
|
+
case "spN_CatSelectHomePage":
|
|
242
|
+
{
|
|
243
|
+
int catId = I(p, 1), newsId = I(p, 2);
|
|
244
|
+
if (catId == 0 && newsId > 0) catId = newsId / 1000;
|
|
245
|
+
if (catId == 0 || Get(catId) == null) return One(CatTable(Path(homeId)));
|
|
246
|
+
var path = Path(catId);
|
|
247
|
+
var selected = path[path.Count - 1];
|
|
248
|
+
// Reading a news item: the category's read page (PageRead) is the page, like the admin setting
|
|
249
|
+
string style = newsId > 0 && selected.PageRead != "" ? selected.PageRead : null;
|
|
250
|
+
return One(CatTable(path, style));
|
|
251
|
+
}
|
|
252
|
+
case "spN_CatSelectByID":
|
|
253
|
+
{
|
|
254
|
+
var c = Get(I(p, 0));
|
|
255
|
+
return One(CatTable(c != null ? new[] { c } : new Cat[0]));
|
|
256
|
+
}
|
|
257
|
+
case "spN_CatSelectChild":
|
|
258
|
+
case "spN_CatShow":
|
|
259
|
+
return One(CatTable(ByLocation(I(p, 0), I(p, 1))));
|
|
260
|
+
case "spN_CatSelectRelated":
|
|
261
|
+
{
|
|
262
|
+
var c = Get(I(p, 0));
|
|
263
|
+
return One(CatTable(c == null ? new Cat[0] : Children(c.Parent).Where(x => x.Id != c.Id)));
|
|
264
|
+
}
|
|
265
|
+
case "spN_CatSelectMenu":
|
|
266
|
+
case "spN_CatSelectMenuRelated":
|
|
267
|
+
{
|
|
268
|
+
int catId = I(p, 0);
|
|
269
|
+
IEnumerable<Cat> items;
|
|
270
|
+
if (sp == "spN_CatSelectMenu") items = ByLocation(catId, I(p, 1));
|
|
271
|
+
else { var c = Get(catId); items = c == null ? new Cat[0] : Children(c.Parent).Where(x => x.Id != c.Id); }
|
|
272
|
+
var list = items.ToList();
|
|
273
|
+
var subs = new DataTable();
|
|
274
|
+
subs.Columns.Add("CategoryID", typeof(int));
|
|
275
|
+
subs.Columns.Add("CatName", typeof(string));
|
|
276
|
+
subs.Columns.Add("CatIcon", typeof(string));
|
|
277
|
+
subs.Columns.Add("ParentID", typeof(int));
|
|
278
|
+
subs.Columns.Add("LinkURL", typeof(string));
|
|
279
|
+
subs.Columns.Add("BitOption", typeof(int));
|
|
280
|
+
foreach (var parent in list)
|
|
281
|
+
foreach (var s in Children(parent.Id)) subs.Rows.Add(s.Id, s.Name, s.Icon, s.Parent, s.Link, s.Bits);
|
|
282
|
+
return new List<DataTable> { CatTable(list), subs };
|
|
283
|
+
}
|
|
284
|
+
case "spN_NewsSelectList":
|
|
285
|
+
case "spN_NewsSelectRelated":
|
|
286
|
+
{
|
|
287
|
+
int number = I(p, 3), other = I(p, 4);
|
|
288
|
+
return One(NewsTable(I(p, 1), I(p, 2), Math.Max(newsCount, number + other), false));
|
|
289
|
+
}
|
|
290
|
+
case "spN_EventSelectList":
|
|
291
|
+
return One(NewsTable(I(p, 1), I(p, 3), Math.Max(6, I(p, 4)), true));
|
|
292
|
+
case "spN_NewsSelectOther":
|
|
293
|
+
case "spN_NewsSelectRelatedStream":
|
|
294
|
+
case "spN_NewsSelectRelatedKeyword":
|
|
295
|
+
{
|
|
296
|
+
int newsId = I(p, 0);
|
|
297
|
+
return One(NewsTable(newsId / 1000, (newsId % 1000) + I(p, 1), Math.Max(1, I(p, 2)), false));
|
|
298
|
+
}
|
|
299
|
+
case "spN_NewsSelect":
|
|
300
|
+
return One(NewsDetail(I(p, 0)));
|
|
301
|
+
case "spN_CatSelectForRSS":
|
|
302
|
+
{
|
|
303
|
+
var t = CatTable(cats.Values.Where(c => (c.Bits & 4) != 0 || c.Id == homeId));
|
|
304
|
+
return One(t);
|
|
305
|
+
}
|
|
306
|
+
case "spN_CatSelectForIndexAZ":
|
|
307
|
+
{
|
|
308
|
+
string letter = Convert.ToString(p.Length > 0 ? p[0] : "A");
|
|
309
|
+
var alphabet = new DataTable();
|
|
310
|
+
alphabet.Columns.Add("Alphabet", typeof(string));
|
|
311
|
+
foreach (var ch in cats.Values.Select(c => c.Name.Length > 0 ? c.Name.Substring(0, 1).ToUpperInvariant() : "").Where(x => x != "").Distinct().OrderBy(x => x))
|
|
312
|
+
alphabet.Rows.Add(ch);
|
|
313
|
+
var list = CatTable(cats.Values.Where(c => c.Name.StartsWith(letter, StringComparison.OrdinalIgnoreCase)));
|
|
314
|
+
return new List<DataTable> { alphabet, list };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return One(new DataTable());
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|