Compare commits
86 Commits
f8704e425b
...
master
Author | SHA1 | Date | |
---|---|---|---|
3da97d4fff | |||
d0d64ef570 | |||
f116be9908 | |||
2c2b498223 | |||
bc82aeb8c2 | |||
670605ce91 | |||
a698e71e4b | |||
51b8ba073c | |||
b8e8e1dd86 | |||
cc83f99612 | |||
8747b5fb3e | |||
50861d5381 | |||
5f83b30cb2 | |||
38ef135b8a | |||
9633e211a1 | |||
b9260dbdb1 | |||
4c64f1c134 | |||
2f71c18b65 | |||
69a1d9c3a8 | |||
8c44d56ab4 | |||
4b05b1b6b9 | |||
7934f807ef | |||
36872164c5 | |||
dc1abd516b | |||
4617f861fc | |||
46aab308fa | |||
c2f60ff19f | |||
d96c44e542 | |||
8467251a17 | |||
ac2f63229c | |||
9367ba0ff5 | |||
52a6db9e1a | |||
efd744974d | |||
22ff3c1312 | |||
a630f0e334 | |||
755a2399e4 | |||
5e814e81c6 | |||
5048640a53 | |||
16c1bb878c | |||
54e1fbefec | |||
bbd38496da | |||
fe5fc73d14 | |||
e2c5aba868 | |||
6a7f6ce096 | |||
b9e1f2ca7d | |||
cf477522c0 | |||
fe99ba9b9d | |||
4bc388d86b | |||
71941f5dd0 | |||
0c8467f942 | |||
f2db3bddbf | |||
ff7c356655 | |||
1345e326a8 | |||
e8735d8764 | |||
fafdb48d51 | |||
7fd42ae81b | |||
97fecdbe6f | |||
69e71bff0b | |||
c0f029ce08 | |||
f4fe34e461 | |||
bd4f5f63b6 | |||
2b892dfd66 | |||
662bd03ddc | |||
f882a74c6d | |||
22633ec94f | |||
f8b726634e | |||
55d75a7bb0 | |||
0ee5e98768 | |||
85889973c8 | |||
12fe2da2a3 | |||
7ffa2d43ef | |||
6ae73aaa7d | |||
a1f2f4a91c | |||
5a1efa90c7 | |||
612a658b77 | |||
e4c3ee0673 | |||
2076a1d02b | |||
ed1d10ba9d | |||
60768b1b3e | |||
5c2b2ea4ef | |||
887cd687e0 | |||
42c4682c89 | |||
1475159f1c | |||
74f8921f7a | |||
6bb01464e7 | |||
19ccdb9026 |
1419
Logo/Logo.ai
Normal file
1419
Logo/Logo.ai
Normal file
File diff suppressed because one or more lines are too long
BIN
Logo/Logo.png
Normal file
BIN
Logo/Logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
6
Logo/Logo.svg
Normal file
6
Logo/Logo.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.51 215.12">
|
||||||
|
<path d="m0,117.58v-20.04c5.58,0,9.9-1.38,12.97-4.13,3.06-2.75,4.6-6.65,4.6-11.68v-42.26c0-12.54,3.5-22.25,10.49-29.14C35.04,3.45,44.91,0,57.65,0v20.04c-6.06,0-10.75,1.48-14.05,4.44-3.31,2.96-4.96,7.16-4.96,12.61v44.74c0,11.37-3.36,20.17-10.07,26.4-6.72,6.23-16.24,9.35-28.57,9.35Zm57.65,97.54c-12.74,0-22.61-3.45-29.6-10.33-6.99-6.89-10.49-16.57-10.49-29.03v-42.36c0-5.03-1.53-8.92-4.6-11.68-3.07-2.75-7.39-4.13-12.97-4.13v-20.04c12.33,0,21.85,3.22,28.57,9.66,6.72,6.44,10.07,15.55,10.07,27.33v43.4c0,5.44,1.65,9.66,4.96,12.66,3.31,3,7.99,4.49,14.05,4.49v20.04Z"/>
|
||||||
|
<path d="m80.38,174.51V24.18h21.08v150.33h-21.08Zm9.2-63.54v-20.04h51.25c6.13,0,11.06-2.13,14.77-6.41,3.72-4.27,5.58-9.92,5.58-16.94s-1.86-12.67-5.58-16.94c-3.72-4.27-8.65-6.41-14.77-6.41h-51.25v-20.04h50.42c8.75,0,16.39,1.81,22.94,5.42,6.54,3.62,11.62,8.66,15.24,15.14,3.62,6.48,5.42,14.09,5.42,22.83s-1.83,16.27-5.48,22.78c-3.65,6.51-8.73,11.57-15.24,15.19s-14.14,5.42-22.89,5.42h-50.42Zm75.32,63.54l-34.41-68.19,20.97-4.75,38.44,72.95h-25Z"/>
|
||||||
|
<path d="m261.51,117.58c-12.26,0-21.77-3.12-28.52-9.35-6.75-6.23-10.13-15.03-10.13-26.4v-44.74c0-5.44-1.65-9.64-4.96-12.61-3.31-2.96-7.99-4.44-14.05-4.44V0c12.74,0,22.61,3.45,29.6,10.33,6.99,6.89,10.49,16.6,10.49,29.14v42.26c0,5.03,1.53,8.92,4.6,11.68,3.06,2.76,7.39,4.13,12.97,4.13v20.04Zm-57.65,97.54v-20.04c6.06,0,10.75-1.5,14.05-4.49,3.31-3,4.96-7.22,4.96-12.66v-43.4c0-11.78,3.37-20.89,10.13-27.33,6.75-6.44,16.26-9.66,28.52-9.66v20.04c-5.58,0-9.9,1.38-12.97,4.13-3.07,2.76-4.6,6.65-4.6,11.68v42.36c0,12.47-3.5,22.15-10.49,29.03-6.99,6.89-16.86,10.33-29.6,10.33Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 20 KiB |
501
Rest.Net.Example/Client.cs
Normal file
501
Rest.Net.Example/Client.cs
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
//Generated using MontoyaTech.Rest.Net - 2/18/2024
|
||||||
|
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
public string BaseUrl;
|
||||||
|
|
||||||
|
public System.Net.CookieContainer CookieContainer;
|
||||||
|
|
||||||
|
public System.Net.Http.HttpMessageHandler MessageHandler;
|
||||||
|
|
||||||
|
public System.Net.Http.HttpClient HttpClient;
|
||||||
|
|
||||||
|
public System.Action<System.Net.Http.HttpRequestMessage> RequestHandler;
|
||||||
|
|
||||||
|
public TestApi Test;
|
||||||
|
|
||||||
|
public AuthApi Auth;
|
||||||
|
|
||||||
|
public StreamApi Stream;
|
||||||
|
|
||||||
|
public FormApi Form;
|
||||||
|
|
||||||
|
public Client(string baseUrl, System.Net.Http.HttpMessageHandler handler = null, System.Action<System.Net.Http.HttpRequestMessage> requestHandler = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||||
|
throw new System.ArgumentException("baseUrl must not be null or whitespace.");
|
||||||
|
|
||||||
|
if (baseUrl.EndsWith('/'))
|
||||||
|
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
|
||||||
|
|
||||||
|
this.BaseUrl = baseUrl;
|
||||||
|
|
||||||
|
this.CookieContainer = new System.Net.CookieContainer();
|
||||||
|
|
||||||
|
if (handler == null)
|
||||||
|
{
|
||||||
|
handler = new System.Net.Http.HttpClientHandler()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = true,
|
||||||
|
UseCookies = true,
|
||||||
|
CookieContainer = this.CookieContainer,
|
||||||
|
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.MessageHandler = handler;
|
||||||
|
|
||||||
|
this.RequestHandler = requestHandler;
|
||||||
|
|
||||||
|
this.HttpClient = new System.Net.Http.HttpClient(handler);
|
||||||
|
|
||||||
|
this.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*");
|
||||||
|
|
||||||
|
this.HttpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
|
||||||
|
|
||||||
|
this.HttpClient.DefaultRequestHeaders.Add("Accept-Encoding", "identity");
|
||||||
|
|
||||||
|
this.Test = new TestApi(this);
|
||||||
|
|
||||||
|
this.Auth = new AuthApi(this);
|
||||||
|
|
||||||
|
this.Stream = new StreamApi(this);
|
||||||
|
|
||||||
|
this.Form = new FormApi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestApi
|
||||||
|
{
|
||||||
|
public Client Client;
|
||||||
|
|
||||||
|
public TestApi(Client client)
|
||||||
|
{
|
||||||
|
this.Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Status()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/status");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Add(double a, double b)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{this.Client.BaseUrl}/add/{a}/{b}");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Compress()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/compress");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CompressFile()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/file/compress");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthApi
|
||||||
|
{
|
||||||
|
public Client Client;
|
||||||
|
|
||||||
|
public AuthApi(Client client)
|
||||||
|
{
|
||||||
|
this.Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UserExists(string name)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/{name}");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return bool.Parse(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Signup(UserDto request, bool compress = false)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{this.Client.BaseUrl}/auth/signup");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
if (compress)
|
||||||
|
{
|
||||||
|
using (var uncompressedStream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(request))))
|
||||||
|
{
|
||||||
|
using (var compressedStream = new System.IO.MemoryStream())
|
||||||
|
{
|
||||||
|
using (var gzipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Compress, true))
|
||||||
|
uncompressedStream.CopyTo(gzipStream);
|
||||||
|
|
||||||
|
message.Content = new System.Net.Http.ByteArrayContent(compressedStream.ToArray());
|
||||||
|
message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Application.Json);
|
||||||
|
message.Content.Headers.ContentEncoding.Add("gzip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message.Content = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request));
|
||||||
|
|
||||||
|
message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Application.Json);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDto Get()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserDto>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dynamic Dynamic()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/dynamic");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserRole GetRole()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/role");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserRole>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StreamApi
|
||||||
|
{
|
||||||
|
public Client Client;
|
||||||
|
|
||||||
|
public StreamApi(Client client)
|
||||||
|
{
|
||||||
|
this.Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Upload(System.IO.MemoryStream request, bool compress = false)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{this.Client.BaseUrl}/upload");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
request.Seek(0, System.IO.SeekOrigin.Begin);
|
||||||
|
|
||||||
|
message.Content = new System.Net.Http.StreamContent(request);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public System.IO.MemoryStream Download()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/download");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var stream = new System.IO.MemoryStream();
|
||||||
|
|
||||||
|
response.Content.CopyToAsync(stream).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FormApi
|
||||||
|
{
|
||||||
|
public Client Client;
|
||||||
|
|
||||||
|
public FormApi(Client client)
|
||||||
|
{
|
||||||
|
this.Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public System.Collections.Generic.Dictionary<string, string> FormTest()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{this.Client.BaseUrl}/form");
|
||||||
|
|
||||||
|
this.Client.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<System.Collections.Generic.Dictionary<string, string>>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IncludedType
|
||||||
|
{
|
||||||
|
public int Test;
|
||||||
|
|
||||||
|
public IncludedType() { }
|
||||||
|
|
||||||
|
public IncludedType(IncludedType instance)
|
||||||
|
{
|
||||||
|
this.Test = instance.Test;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncludedType(int Test = 0)
|
||||||
|
{
|
||||||
|
this.Test = Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserDto : BaseUser
|
||||||
|
{
|
||||||
|
public System.PlatformID MachineType;
|
||||||
|
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public System.Collections.Generic.List<string> List;
|
||||||
|
|
||||||
|
public System.String[] Array;
|
||||||
|
|
||||||
|
public ulong Property { get; set; }
|
||||||
|
|
||||||
|
public UserDto() { }
|
||||||
|
|
||||||
|
public UserDto(UserDto instance)
|
||||||
|
{
|
||||||
|
this.MachineType = instance.MachineType;
|
||||||
|
this.Name = instance.Name;
|
||||||
|
this.List = instance.List;
|
||||||
|
this.Array = instance.Array;
|
||||||
|
this.Property = instance.Property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDto(System.PlatformID MachineType = 0, string Name = null, System.Collections.Generic.List<string> List = null, System.String[] Array = null, ulong Property = 0)
|
||||||
|
{
|
||||||
|
this.MachineType = MachineType;
|
||||||
|
this.Name = Name;
|
||||||
|
this.List = List;
|
||||||
|
this.Array = Array;
|
||||||
|
this.Property = Property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BaseUser
|
||||||
|
{
|
||||||
|
public string Id;
|
||||||
|
|
||||||
|
public char FirstInitial;
|
||||||
|
|
||||||
|
public System.Collections.Generic.List<Permission> Permissions;
|
||||||
|
|
||||||
|
public UserRole Role { get; set; }
|
||||||
|
|
||||||
|
public BaseUser() { }
|
||||||
|
|
||||||
|
public BaseUser(BaseUser instance)
|
||||||
|
{
|
||||||
|
this.Id = instance.Id;
|
||||||
|
this.FirstInitial = instance.FirstInitial;
|
||||||
|
this.Permissions = instance.Permissions;
|
||||||
|
this.Role = instance.Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseUser(string Id = null, char FirstInitial = '\0', System.Collections.Generic.List<Permission> Permissions = null, UserRole Role = 0)
|
||||||
|
{
|
||||||
|
this.Id = Id;
|
||||||
|
this.FirstInitial = FirstInitial;
|
||||||
|
this.Permissions = Permissions;
|
||||||
|
this.Role = Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Permission
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public Types Type;
|
||||||
|
|
||||||
|
public Permission() { }
|
||||||
|
|
||||||
|
public Permission(Permission instance)
|
||||||
|
{
|
||||||
|
this.Name = instance.Name;
|
||||||
|
this.Type = instance.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Permission(string Name = null, Types Type = 0)
|
||||||
|
{
|
||||||
|
this.Name = Name;
|
||||||
|
this.Type = Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Types : int
|
||||||
|
{
|
||||||
|
Read = 0,
|
||||||
|
|
||||||
|
Write = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum UserRole : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
|
||||||
|
Admin = 2,
|
||||||
|
|
||||||
|
User = 1,
|
||||||
|
}
|
||||||
|
}
|
@ -4,44 +4,117 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.IO;
|
||||||
using MontoyaTech.Rest.Net;
|
using MontoyaTech.Rest.Net;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Collections;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Web;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace MontoyaTech.Rest.Net.Example
|
namespace MontoyaTech.Rest.Net.Example
|
||||||
{
|
{
|
||||||
|
public class BaseUser
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string Id;
|
||||||
|
|
||||||
|
[JsonProperty("firstInitial")]
|
||||||
|
public char FirstInitial;
|
||||||
|
|
||||||
|
[JsonProperty("role")]
|
||||||
|
public UserRole Role { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("permissions")]
|
||||||
|
public List<Permission> Permissions;
|
||||||
|
|
||||||
|
public class Permission
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public Types Type;
|
||||||
|
|
||||||
|
public enum Types { Read, Write }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteTypeName("UserDto")]
|
||||||
|
public class User : BaseUser
|
||||||
|
{
|
||||||
|
public PlatformID MachineType;
|
||||||
|
|
||||||
|
public string Name = null;
|
||||||
|
|
||||||
|
public List<string> List = null;
|
||||||
|
|
||||||
|
public string[] Array = null;
|
||||||
|
|
||||||
|
public ulong Property { get; set; }
|
||||||
|
|
||||||
|
public User() { }
|
||||||
|
|
||||||
|
public User(string name)
|
||||||
|
{
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum UserRole : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Admin = 2,
|
||||||
|
User = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IncludedType
|
||||||
|
{
|
||||||
|
public int Test;
|
||||||
|
}
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public class User
|
public static RouteFileCache FileCache = new RouteFileCache(100 * 1024 * 1024);
|
||||||
{
|
|
||||||
public string Name = null;
|
|
||||||
|
|
||||||
public User(string name)
|
|
||||||
{
|
|
||||||
this.Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static explicit operator User(string input)
|
|
||||||
{
|
|
||||||
return new User(input.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var listener = new RouteListener(8080,
|
File.WriteAllText("test.txt", "hello from a file");
|
||||||
|
|
||||||
|
var listener = new RouteListener(8088,
|
||||||
new Route(HttpRequestMethod.Get, "/status", Status),
|
new Route(HttpRequestMethod.Get, "/status", Status),
|
||||||
new Route<double, double>(HttpRequestMethod.Post, "/add/{a}/{b}", Add),
|
new Route<double, double>(HttpRequestMethod.Post, "/add/{a}/{b}", Add),
|
||||||
new Route<User>(HttpRequestMethod.Post, "/signup/{username}", Signup),
|
new Route(HttpRequestMethod.Get, "/compress", Compress),
|
||||||
new Route(HttpRequestMethod.Get, "/json", Json)
|
new Route(HttpRequestMethod.Get, "/file/compress", CompressFile),
|
||||||
|
new Route<string>(HttpRequestMethod.Get, "/auth/{username}", Exists),
|
||||||
|
new Route(HttpRequestMethod.Post, "/auth/signup", Signup),
|
||||||
|
new Route(HttpRequestMethod.Get, "/auth/", Json),
|
||||||
|
new Route(HttpRequestMethod.Get, "/auth/dynamic", Dynamic),
|
||||||
|
new Route(HttpRequestMethod.Get, "/auth/role", GetRole),
|
||||||
|
new Route(HttpRequestMethod.Post, "/upload", Upload),
|
||||||
|
new Route(HttpRequestMethod.Get, "/download", Download),
|
||||||
|
new Route(HttpRequestMethod.Post, "/form", FormTest)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
string code = listener.GenerateCSharpClient();
|
||||||
|
|
||||||
|
File.WriteAllText("Client.cs", listener.GenerateCSharpClient());
|
||||||
|
|
||||||
|
File.WriteAllText("Client.js", listener.GenerateJavascriptClient(useJsonNames: true));
|
||||||
|
|
||||||
|
File.WriteAllText("StaticClient.cs", listener.GenerateCSharpClient("StaticClient", staticCode: true));
|
||||||
|
|
||||||
|
File.WriteAllText("StaticClient.js", listener.GenerateJavascriptClient("StaticClient", staticCode: true, useJsonNames: true));
|
||||||
|
|
||||||
|
Console.WriteLine("Generated Client.cs, Client.js, StaticClient.cs, StaticClient.js");
|
||||||
|
|
||||||
listener.RequestPreProcessEvent += (HttpListenerContext context) => {
|
listener.RequestPreProcessEvent += (HttpListenerContext context) => {
|
||||||
Console.WriteLine("Request start: " + context.Request.RawUrl);
|
Console.WriteLine($"[{context.Request.HttpMethod}] Request start: " + context.Request.RawUrl);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
listener.RequestPostProcessEvent += (HttpListenerContext context) =>
|
listener.RequestPostProcessEvent += (HttpListenerContext context) =>
|
||||||
{
|
{
|
||||||
Console.WriteLine("Request end: " + context.Request.RawUrl);
|
Console.WriteLine($"[{context.Request.HttpMethod}] Request end: " + context.Request.RawUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
listener.Start();
|
listener.Start();
|
||||||
@ -51,29 +124,140 @@ namespace MontoyaTech.Rest.Net.Example
|
|||||||
foreach (var route in listener.Routes)
|
foreach (var route in listener.Routes)
|
||||||
Console.WriteLine($"- [{route.Method}] {route.Syntax}");
|
Console.WriteLine($"- [{route.Method}] {route.Syntax}");
|
||||||
|
|
||||||
Console.WriteLine($"Rest api server running at http://localhost:{listener.Port}");
|
Console.WriteLine($"Rest api server running at {listener.BaseUrl}");
|
||||||
|
|
||||||
|
StaticClient.Init(listener.BaseUrl, requestHandler: (message) =>
|
||||||
|
{
|
||||||
|
var builder = new UriBuilder(message.RequestUri);
|
||||||
|
|
||||||
|
var query = HttpUtility.ParseQueryString(builder.Query);
|
||||||
|
query.Add("authToken", "test");
|
||||||
|
builder.Query = query.ToString();
|
||||||
|
|
||||||
|
message.RequestUri = builder.Uri;
|
||||||
|
|
||||||
|
message.Headers.Add("Auth", "Test");
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes("hello world!");
|
||||||
|
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
StaticClient.Stream.Upload(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = StaticClient.Stream.Download())
|
||||||
|
{
|
||||||
|
var str = Encoding.UTF8.GetString(stream.ToArray());
|
||||||
|
|
||||||
|
Console.WriteLine("Download output:" + str);
|
||||||
|
}
|
||||||
|
|
||||||
listener.Block();
|
listener.Block();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Test")]
|
||||||
|
[RouteResponse(typeof(string))]
|
||||||
|
[RouteInclude(typeof(IncludedType))]
|
||||||
public static HttpListenerResponse Status(HttpListenerContext context)
|
public static HttpListenerResponse Status(HttpListenerContext context)
|
||||||
{
|
{
|
||||||
return context.Response.WithStatus(HttpStatusCode.OK).WithText("Everything is operational. 👍");
|
return context.Response.WithStatus(HttpStatusCode.OK).WithText("Everything is operational. 👍");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Test")]
|
||||||
|
[RouteResponse(typeof(string))]
|
||||||
public static HttpListenerResponse Add(HttpListenerContext context, double a, double b)
|
public static HttpListenerResponse Add(HttpListenerContext context, double a, double b)
|
||||||
{
|
{
|
||||||
return context.Response.WithStatus(HttpStatusCode.OK).WithText((a + b).ToString());
|
return context.Response.WithStatus(HttpStatusCode.OK).WithText((a + b).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpListenerResponse Signup(HttpListenerContext context, User user)
|
[RouteGroup("Test")]
|
||||||
|
[RouteResponse(typeof(string))]
|
||||||
|
public static HttpListenerResponse Compress(HttpListenerContext context)
|
||||||
{
|
{
|
||||||
return context.Response.WithStatus(HttpStatusCode.OK).WithText("User:" + user.Name);
|
return context.Response.WithStatus(HttpStatusCode.OK).WithCompressedText("hello world");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Test")]
|
||||||
|
[RouteResponse(typeof(string))]
|
||||||
|
public static HttpListenerResponse CompressFile(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
var content = FileCache.Cache("test.txt");
|
||||||
|
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithCompressedFile("test.txt", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Test")]
|
||||||
|
[RouteRequest(typeof(User))]
|
||||||
|
public static HttpListenerResponse SignupRequest(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Auth")]
|
||||||
|
[RouteName("UserExists")]
|
||||||
|
[RouteResponse(typeof(bool), Json = false)]
|
||||||
|
public static HttpListenerResponse Exists(HttpListenerContext context, string name)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Auth.Exists called, name:" + name);
|
||||||
|
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithText("true");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Auth")]
|
||||||
|
[RouteRequest(typeof(User))]
|
||||||
|
public static HttpListenerResponse Signup(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Auth")]
|
||||||
|
[RouteResponse(typeof(UserRole))]
|
||||||
|
public static HttpListenerResponse GetRole(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(UserRole.Admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Auth")]
|
||||||
|
[RouteName("Get")]
|
||||||
|
[RouteResponse(typeof(User))]
|
||||||
public static HttpListenerResponse Json(HttpListenerContext context)
|
public static HttpListenerResponse Json(HttpListenerContext context)
|
||||||
{
|
{
|
||||||
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(new User("Rest.Net"));
|
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(new User("Rest.Net"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Auth")]
|
||||||
|
[RouteResponse(Dynamic = true)]
|
||||||
|
public static HttpListenerResponse Dynamic(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(777);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Stream")]
|
||||||
|
[RouteRequest(typeof(MemoryStream))]
|
||||||
|
public static HttpListenerResponse Upload(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
var content = context.Request.ReadAsString();
|
||||||
|
|
||||||
|
Console.WriteLine("Uploaded:" + content);
|
||||||
|
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Stream")]
|
||||||
|
[RouteResponse(typeof(MemoryStream), Parameter = false)]
|
||||||
|
public static HttpListenerResponse Download(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithText("Hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RouteGroup("Form")]
|
||||||
|
[RouteResponse(typeof(Dictionary<string, string>))]
|
||||||
|
public static HttpListenerResponse FormTest(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(context.Request.ReadAsForm());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>exe</OutputType>
|
<OutputType>exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<AssemblyName>MontoyaTech.Rest.Net.Example</AssemblyName>
|
<AssemblyName>MontoyaTech.Rest.Net.Example</AssemblyName>
|
||||||
|
457
Rest.Net.Example/StaticClient.cs
Normal file
457
Rest.Net.Example/StaticClient.cs
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
//Generated using MontoyaTech.Rest.Net - 2/18/2024
|
||||||
|
|
||||||
|
public class StaticClient
|
||||||
|
{
|
||||||
|
public static string BaseUrl;
|
||||||
|
|
||||||
|
public static System.Net.CookieContainer CookieContainer;
|
||||||
|
|
||||||
|
public static System.Net.Http.HttpMessageHandler MessageHandler;
|
||||||
|
|
||||||
|
public static System.Net.Http.HttpClient HttpClient;
|
||||||
|
|
||||||
|
public static System.Action<System.Net.Http.HttpRequestMessage> RequestHandler;
|
||||||
|
|
||||||
|
public static void Init(string baseUrl, System.Net.Http.HttpMessageHandler handler = null, System.Action<System.Net.Http.HttpRequestMessage> requestHandler = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||||
|
throw new System.ArgumentException("baseUrl must not be null or whitespace.");
|
||||||
|
|
||||||
|
if (baseUrl.EndsWith('/'))
|
||||||
|
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
|
||||||
|
|
||||||
|
StaticClient.BaseUrl = baseUrl;
|
||||||
|
|
||||||
|
StaticClient.CookieContainer = new System.Net.CookieContainer();
|
||||||
|
|
||||||
|
if (handler == null)
|
||||||
|
{
|
||||||
|
handler = new System.Net.Http.HttpClientHandler()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = true,
|
||||||
|
UseCookies = true,
|
||||||
|
CookieContainer = StaticClient.CookieContainer,
|
||||||
|
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticClient.MessageHandler = handler;
|
||||||
|
|
||||||
|
StaticClient.RequestHandler = requestHandler;
|
||||||
|
|
||||||
|
StaticClient.HttpClient = new System.Net.Http.HttpClient(handler);
|
||||||
|
|
||||||
|
StaticClient.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*");
|
||||||
|
|
||||||
|
StaticClient.HttpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
|
||||||
|
|
||||||
|
StaticClient.HttpClient.DefaultRequestHeaders.Add("Accept-Encoding", "identity");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Test
|
||||||
|
{
|
||||||
|
public static string Status()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/status");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Add(double a, double b)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{StaticClient.BaseUrl}/add/{a}/{b}");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Compress()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/compress");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CompressFile()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/file/compress");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Auth
|
||||||
|
{
|
||||||
|
public static bool UserExists(string name)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/{name}");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return bool.Parse(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Signup(UserDto request, bool compress = false)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{StaticClient.BaseUrl}/auth/signup");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
if (compress)
|
||||||
|
{
|
||||||
|
using (var uncompressedStream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(request))))
|
||||||
|
{
|
||||||
|
using (var compressedStream = new System.IO.MemoryStream())
|
||||||
|
{
|
||||||
|
using (var gzipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Compress, true))
|
||||||
|
uncompressedStream.CopyTo(gzipStream);
|
||||||
|
|
||||||
|
message.Content = new System.Net.Http.ByteArrayContent(compressedStream.ToArray());
|
||||||
|
message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Application.Json);
|
||||||
|
message.Content.Headers.ContentEncoding.Add("gzip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message.Content = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request));
|
||||||
|
|
||||||
|
message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Application.Json);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserDto Get()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserDto>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static dynamic Dynamic()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/dynamic");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserRole GetRole()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/role");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserRole>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Stream
|
||||||
|
{
|
||||||
|
public static void Upload(System.IO.MemoryStream request, bool compress = false)
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{StaticClient.BaseUrl}/upload");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
request.Seek(0, System.IO.SeekOrigin.Begin);
|
||||||
|
|
||||||
|
message.Content = new System.Net.Http.StreamContent(request);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static System.IO.MemoryStream Download()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/download");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var stream = new System.IO.MemoryStream();
|
||||||
|
|
||||||
|
response.Content.CopyToAsync(stream).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Form
|
||||||
|
{
|
||||||
|
public static System.Collections.Generic.Dictionary<string, string> FormTest()
|
||||||
|
{
|
||||||
|
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{StaticClient.BaseUrl}/form");
|
||||||
|
|
||||||
|
StaticClient.RequestHandler?.Invoke(message);
|
||||||
|
|
||||||
|
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<System.Collections.Generic.Dictionary<string, string>>(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IncludedType
|
||||||
|
{
|
||||||
|
public int Test;
|
||||||
|
|
||||||
|
public IncludedType() { }
|
||||||
|
|
||||||
|
public IncludedType(IncludedType instance)
|
||||||
|
{
|
||||||
|
this.Test = instance.Test;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncludedType(int Test = 0)
|
||||||
|
{
|
||||||
|
this.Test = Test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserDto : BaseUser
|
||||||
|
{
|
||||||
|
public System.PlatformID MachineType;
|
||||||
|
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public System.Collections.Generic.List<string> List;
|
||||||
|
|
||||||
|
public System.String[] Array;
|
||||||
|
|
||||||
|
public ulong Property { get; set; }
|
||||||
|
|
||||||
|
public UserDto() { }
|
||||||
|
|
||||||
|
public UserDto(UserDto instance)
|
||||||
|
{
|
||||||
|
this.MachineType = instance.MachineType;
|
||||||
|
this.Name = instance.Name;
|
||||||
|
this.List = instance.List;
|
||||||
|
this.Array = instance.Array;
|
||||||
|
this.Property = instance.Property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDto(System.PlatformID MachineType = 0, string Name = null, System.Collections.Generic.List<string> List = null, System.String[] Array = null, ulong Property = 0)
|
||||||
|
{
|
||||||
|
this.MachineType = MachineType;
|
||||||
|
this.Name = Name;
|
||||||
|
this.List = List;
|
||||||
|
this.Array = Array;
|
||||||
|
this.Property = Property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BaseUser
|
||||||
|
{
|
||||||
|
public string Id;
|
||||||
|
|
||||||
|
public char FirstInitial;
|
||||||
|
|
||||||
|
public System.Collections.Generic.List<Permission> Permissions;
|
||||||
|
|
||||||
|
public UserRole Role { get; set; }
|
||||||
|
|
||||||
|
public BaseUser() { }
|
||||||
|
|
||||||
|
public BaseUser(BaseUser instance)
|
||||||
|
{
|
||||||
|
this.Id = instance.Id;
|
||||||
|
this.FirstInitial = instance.FirstInitial;
|
||||||
|
this.Permissions = instance.Permissions;
|
||||||
|
this.Role = instance.Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseUser(string Id = null, char FirstInitial = '\0', System.Collections.Generic.List<Permission> Permissions = null, UserRole Role = 0)
|
||||||
|
{
|
||||||
|
this.Id = Id;
|
||||||
|
this.FirstInitial = FirstInitial;
|
||||||
|
this.Permissions = Permissions;
|
||||||
|
this.Role = Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Permission
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public Types Type;
|
||||||
|
|
||||||
|
public Permission() { }
|
||||||
|
|
||||||
|
public Permission(Permission instance)
|
||||||
|
{
|
||||||
|
this.Name = instance.Name;
|
||||||
|
this.Type = instance.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Permission(string Name = null, Types Type = 0)
|
||||||
|
{
|
||||||
|
this.Name = Name;
|
||||||
|
this.Type = Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Types : int
|
||||||
|
{
|
||||||
|
Read = 0,
|
||||||
|
|
||||||
|
Write = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum UserRole : byte
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
|
||||||
|
Admin = 2,
|
||||||
|
|
||||||
|
User = 1,
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
@ -9,14 +9,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.5.1" />
|
<PackageReference Include="FluentAssertions" Version="8.3.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
23
Rest.Net.Tests/RestClientGeneratorTests.cs
Normal file
23
Rest.Net.Tests/RestClientGeneratorTests.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
using FluentAssertions;
|
||||||
|
using MontoyaTech.Rest.Net;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net.Tests
|
||||||
|
{
|
||||||
|
public class RestClientGeneratorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RestClientGenerator_HttpStatusCode_Should_BeSystemType()
|
||||||
|
{
|
||||||
|
var generator = new RestClientGenerator();
|
||||||
|
|
||||||
|
generator.IsTypeDotNet(typeof(HttpStatusCode)).Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Rest.Net.Tests/RouteArgumentConverterTests.cs
Normal file
41
Rest.Net.Tests/RouteArgumentConverterTests.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using MontoyaTech.Rest.Net;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Rest.Net.Tests
|
||||||
|
{
|
||||||
|
public class RouteArgumentConverterTests
|
||||||
|
{
|
||||||
|
public enum TestEnum : long
|
||||||
|
{
|
||||||
|
A = 1,
|
||||||
|
B = 2,
|
||||||
|
C = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public static void RouteArgumentConverter_Should_Convert_To_Enum()
|
||||||
|
{
|
||||||
|
var converted = RouteArgumentConverter.Convert<TestEnum>("1");
|
||||||
|
|
||||||
|
converted.GetType().IsEquivalentTo(typeof(TestEnum)).Should().BeTrue();
|
||||||
|
|
||||||
|
converted.Should().Be(TestEnum.A);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public static void RouteArgumentConverter_Should_Convert_OutOfRange_To_Enum()
|
||||||
|
{
|
||||||
|
var converted = RouteArgumentConverter.Convert<TestEnum>("4");
|
||||||
|
|
||||||
|
converted.GetType().IsEquivalentTo(typeof(TestEnum)).Should().BeTrue();
|
||||||
|
|
||||||
|
((int)converted).Should().Be(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,24 +10,72 @@ namespace MontoyaTech.Rest.Net.Tests
|
|||||||
{
|
{
|
||||||
public class RouteMatcherTests
|
public class RouteMatcherTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxWithPathShouldNotMatchRoot()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/", "/test", out _).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SyntaxWithRootShouldMatch()
|
public void SyntaxWithRootShouldMatch()
|
||||||
{
|
{
|
||||||
RouteMatcher.Matches("http://localhost/", "/", out _).Should().BeTrue();
|
RouteMatcher.Matches("http://localhost/", "/", out _).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxWithRootNoSlashShouldMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost", "/", out _).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SyntaxWithRootCatchAllShouldMatch()
|
public void SyntaxWithRootCatchAllShouldMatch()
|
||||||
{
|
{
|
||||||
RouteMatcher.Matches("http://localhost/test1/test2", "/**", out _).Should().BeTrue();
|
RouteMatcher.Matches("http://localhost/test1/test2", "/**", out _).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxNonSlashShouldNotMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/test1/", "/test1", out _).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxSlashShouldMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/test1/", "/test1/", out _).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxSlashExtraRouteShouldNotMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/test1/test2", "/test1/", out _).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxSlashCatchAllEmptyShouldMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/test1/", "/test1/**", out _).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxSlashCatchAllEmptyNoSlashShouldNotMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/test1", "/test1/**", out _).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SyntaxWithRootWildcardShouldMatch()
|
public void SyntaxWithRootWildcardShouldMatch()
|
||||||
{
|
{
|
||||||
RouteMatcher.Matches("http://localhost/test1", "/*", out _).Should().BeTrue();
|
RouteMatcher.Matches("http://localhost/test1", "/*", out _).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyntaxWildCardEmptyShouldMatch()
|
||||||
|
{
|
||||||
|
RouteMatcher.Matches("http://localhost/test1/", "/test1/*", out _).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SyntaxWithRootWildcardShouldNotMatch()
|
public void SyntaxWithRootWildcardShouldNotMatch()
|
||||||
{
|
{
|
||||||
@ -99,6 +147,7 @@ namespace MontoyaTech.Rest.Net.Tests
|
|||||||
public void SyntaxWithOrShouldMatch()
|
public void SyntaxWithOrShouldMatch()
|
||||||
{
|
{
|
||||||
RouteMatcher.Matches("http://localhost/a/b", "/a/b|c", out _).Should().BeTrue();
|
RouteMatcher.Matches("http://localhost/a/b", "/a/b|c", out _).Should().BeTrue();
|
||||||
|
|
||||||
RouteMatcher.Matches("http://localhost/a/c", "/a/b|c", out _).Should().BeTrue();
|
RouteMatcher.Matches("http://localhost/a/c", "/a/b|c", out _).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
178
Rest.Net.Tests/ServeFileTests.cs
Normal file
178
Rest.Net.Tests/ServeFileTests.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using MontoyaTech.Rest.Net;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net.Tests
|
||||||
|
{
|
||||||
|
public class ServeFileTests
|
||||||
|
{
|
||||||
|
public string BaseDirectory = null;
|
||||||
|
|
||||||
|
public string TestDirectory = null;
|
||||||
|
|
||||||
|
public string TestFile = null;
|
||||||
|
|
||||||
|
public string IndexFile = null;
|
||||||
|
|
||||||
|
public ServeFileTests()
|
||||||
|
{
|
||||||
|
this.BaseDirectory = Path.Combine(Environment.CurrentDirectory, "test");
|
||||||
|
|
||||||
|
if (!Directory.Exists(this.BaseDirectory))
|
||||||
|
Directory.CreateDirectory(this.BaseDirectory);
|
||||||
|
|
||||||
|
this.TestDirectory = Path.Combine(this.BaseDirectory, "test2");
|
||||||
|
|
||||||
|
if (!Directory.Exists(this.TestDirectory))
|
||||||
|
Directory.CreateDirectory(this.TestDirectory);
|
||||||
|
|
||||||
|
this.TestFile = Path.Combine(this.BaseDirectory, "test.html");
|
||||||
|
|
||||||
|
if (!File.Exists(this.TestFile))
|
||||||
|
File.WriteAllText(this.TestFile, "hello world");
|
||||||
|
|
||||||
|
this.IndexFile = Path.Combine(this.BaseDirectory, "index.html");
|
||||||
|
|
||||||
|
if (!File.Exists(this.IndexFile))
|
||||||
|
File.WriteAllText(this.IndexFile, "hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeMultiple_File_ShouldWork()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeMultiple_Directory_ShouldWork()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeTrue();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeMultiple_NavigatingUp_Should_NotWork()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeMultiple_Correct_NavigatingUp_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "a/b/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeMultiple_NavigatingUp_Multiple_Should_NotWork()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "test/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_Empty_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.IndexFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_File_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_Directory_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeTrue();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_File_Route_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_Directory_Route_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeTrue();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_File_Route_Invalid_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/c", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.IndexFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_File_WithoutExtension_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_File_Route_WithoutExtension_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/c/test", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServeSingle_File_StartPath_Should_Work()
|
||||||
|
{
|
||||||
|
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test", "test/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||||
|
|
||||||
|
isDirectory.Should().BeFalse();
|
||||||
|
|
||||||
|
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
430
Rest.Net/CodeWriter.cs
Normal file
430
Rest.Net/CodeWriter.cs
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a writer that helps with generating code.
|
||||||
|
/// </summary>
|
||||||
|
public class CodeWriter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The internal string builder.
|
||||||
|
/// </summary>
|
||||||
|
private StringBuilder Builder = new StringBuilder();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current number of characters written..
|
||||||
|
/// </summary>
|
||||||
|
public int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Builder.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current number of indents.
|
||||||
|
/// </summary>
|
||||||
|
private int Indents = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the writer is pending an indent that needs
|
||||||
|
/// to be handled.
|
||||||
|
/// </summary>
|
||||||
|
private bool PendingIndent = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new default CodeWriter.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new default text writer and copies the indent data from the passed
|
||||||
|
/// text writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
public CodeWriter(CodeWriter writer)
|
||||||
|
{
|
||||||
|
this.Indents = writer.Indents;
|
||||||
|
|
||||||
|
//If we have indents, then we must set pending indent.
|
||||||
|
if (this.Indents > 0)
|
||||||
|
this.PendingIndent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes text to the writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
public CodeWriter Write(string text)
|
||||||
|
{
|
||||||
|
if (this.PendingIndent)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Indents; i++)
|
||||||
|
this.Builder.Append(' ');
|
||||||
|
|
||||||
|
this.PendingIndent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Builder.Append(text);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes text to the writer surrounded by quotes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteString(string text)
|
||||||
|
{
|
||||||
|
if (this.PendingIndent)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Indents; i++)
|
||||||
|
this.Builder.Append(' ');
|
||||||
|
|
||||||
|
this.PendingIndent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Builder.Append('"').Append(text).Append('"');
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a character to the writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="char"></param>
|
||||||
|
public CodeWriter Write(char @char)
|
||||||
|
{
|
||||||
|
if (this.PendingIndent)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Indents; i++)
|
||||||
|
this.Builder.Append(' ');
|
||||||
|
|
||||||
|
this.PendingIndent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Builder.Append(@char);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes text to the writer and a newline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
public CodeWriter WriteLine(string text)
|
||||||
|
{
|
||||||
|
if (this.PendingIndent)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Indents; i++)
|
||||||
|
this.Builder.Append(' ');
|
||||||
|
|
||||||
|
this.PendingIndent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Builder.Append(text);
|
||||||
|
this.Builder.Append('\n');
|
||||||
|
this.PendingIndent = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a character and new line to the writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="char"></param>
|
||||||
|
public CodeWriter WriteLine(char @char)
|
||||||
|
{
|
||||||
|
if (this.PendingIndent)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Indents; i++)
|
||||||
|
this.Builder.Append(' ');
|
||||||
|
|
||||||
|
this.PendingIndent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Builder.Append(@char);
|
||||||
|
this.Builder.Append('\n');
|
||||||
|
this.PendingIndent = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes text to the writer if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition"></param>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteAssert(bool condition, string text)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return this.Write(text);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a character to the writer if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition"></param>
|
||||||
|
/// <param name="char"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteAssert(bool condition, char @char)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return this.Write(@char);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes text to the writer and a newline if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition"></param>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteLineAssert(bool condition, string text)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return this.WriteLine(text);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a character and new line to the writer if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition"></param>
|
||||||
|
/// <param name="char"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteLineAssert(bool condition, char @char)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return this.WriteLine(@char);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the contents of another text writer into this one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
public CodeWriter Write(CodeWriter writer)
|
||||||
|
{
|
||||||
|
this.Builder.Append(writer.Builder.ToString());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new line to the writer.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter NewLine()
|
||||||
|
{
|
||||||
|
this.Builder.Append('\n');
|
||||||
|
this.PendingIndent = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new line to the writer if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter NewLineAssert(bool condition)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
{
|
||||||
|
this.Builder.Append('\n');
|
||||||
|
this.PendingIndent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a space to the writer unless there is already one.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteSpacer()
|
||||||
|
{
|
||||||
|
if (this.Builder.Length >= 1 && this.Builder[this.Builder.Length - 1] != ' ')
|
||||||
|
this.Builder.Append(' ');
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a separator to the writer unless one wouldn't make sense.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteSeparator()
|
||||||
|
{
|
||||||
|
if (this.Builder.Length > 0)
|
||||||
|
{
|
||||||
|
var offset = this.Builder.Length - 1;
|
||||||
|
var prev = this.Builder[offset];
|
||||||
|
while (offset >= 0 && (prev == ' ' || prev == '\t' || prev == '\n' || prev == '\r'))
|
||||||
|
{
|
||||||
|
offset--;
|
||||||
|
prev = this.Builder[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev != '(' && prev != '{' && prev != '[' && prev != ',')
|
||||||
|
this.Builder.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a separator to the writer and text if a condition is met unless a separator here isn't needed, then just text is written.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition"></param>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter WriteSeparatorAssert(bool condition, string text)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
{
|
||||||
|
if (this.Builder.Length > 0)
|
||||||
|
{
|
||||||
|
var offset = this.Builder.Length - 1;
|
||||||
|
var prev = this.Builder[offset];
|
||||||
|
while (offset >= 0 && (prev == ' ' || prev == '\t' || prev == '\n' || prev == '\r'))
|
||||||
|
{
|
||||||
|
offset--;
|
||||||
|
prev = this.Builder[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev != '(' && prev != '{' && prev != '[' && prev != ',')
|
||||||
|
this.Builder.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != null)
|
||||||
|
this.Builder.Append(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a blank line as a spacer to the writer
|
||||||
|
/// unless there is already one.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter WriteBreak()
|
||||||
|
{
|
||||||
|
if (this.Builder.Length >= 2)
|
||||||
|
{
|
||||||
|
var prevChar = this.Builder[this.Builder.Length - 2];
|
||||||
|
|
||||||
|
if (prevChar != '\n' && prevChar != '{' && prevChar != '(' && prevChar != '[')
|
||||||
|
{
|
||||||
|
this.Builder.Append('\n');
|
||||||
|
this.PendingIndent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Builder.Append('\n');
|
||||||
|
this.PendingIndent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indents the writer one time.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter Indent()
|
||||||
|
{
|
||||||
|
this.Indents += 4;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indents the writer one time if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter IndentAssert(bool condition)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
this.Indents += 4;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes one indent from the writer.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter Outdent()
|
||||||
|
{
|
||||||
|
if (this.Indents > 0)
|
||||||
|
this.Indents -= 4;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes one indent from the writer if the condition is met.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter OutdentAssert(bool condition)
|
||||||
|
{
|
||||||
|
if (condition && this.Indents > 0)
|
||||||
|
this.Indents -= 4;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets all indents from the writer.
|
||||||
|
/// </summary>
|
||||||
|
public CodeWriter ResetIndents()
|
||||||
|
{
|
||||||
|
this.Indents = 0;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the last character from the writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CodeWriter Remove()
|
||||||
|
{
|
||||||
|
if (this.Builder.Length > 0)
|
||||||
|
this.Builder.Remove(this.Builder.Length - 1, 1);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the last character written to the writer if there was one, otherwise it returns character 0.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public char Peek()
|
||||||
|
{
|
||||||
|
if (this.Builder.Length > 0)
|
||||||
|
return this.Builder[this.Builder.Length - 1];
|
||||||
|
else
|
||||||
|
return (char)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all the written data from the writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return this.Builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
namespace MontoyaTech.Rest.Net
|
namespace MontoyaTech.Rest.Net
|
||||||
{
|
{
|
||||||
@ -23,9 +24,20 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var input = request.InputStream)
|
//If the request has been compressed, automatically handle it.
|
||||||
|
if (request.Headers["Content-Encoding"] == "gzip")
|
||||||
|
{
|
||||||
|
using (var inputStream = request.InputStream)
|
||||||
|
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress, true))
|
||||||
|
using (var stream = new StreamReader(gzipStream))
|
||||||
|
return stream.ReadToEnd();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var input = request.InputStream)
|
||||||
using (var stream = new StreamReader(input))
|
using (var stream = new StreamReader(input))
|
||||||
return stream.ReadToEnd();
|
return stream.ReadToEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -43,9 +55,20 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var input = request.InputStream)
|
//If the request has been compressed, automatically handle it.
|
||||||
using (var stream = new StreamReader(input))
|
if (request.Headers["Content-Encoding"] == "gzip")
|
||||||
|
{
|
||||||
|
using (var inputStream = request.InputStream)
|
||||||
|
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress, true))
|
||||||
|
using (var stream = new StreamReader(gzipStream))
|
||||||
return JsonConvert.DeserializeObject<T>(stream.ReadToEnd());
|
return JsonConvert.DeserializeObject<T>(stream.ReadToEnd());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var inputStream = request.InputStream)
|
||||||
|
using (var stream = new StreamReader(inputStream))
|
||||||
|
return JsonConvert.DeserializeObject<T>(stream.ReadToEnd());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -62,12 +85,31 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var input = request.InputStream)
|
//If the request has been compressed, automatically handle it.
|
||||||
|
if (request.Headers["Content-Encoding"] == "gzip")
|
||||||
{
|
{
|
||||||
using (var stream = new MemoryStream())
|
using (var inputStream = request.InputStream)
|
||||||
{
|
{
|
||||||
input.CopyTo(stream);
|
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress, true))
|
||||||
return stream.ToArray();
|
{
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
gzipStream.CopyTo(memoryStream);
|
||||||
|
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var input = request.InputStream)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
input.CopyTo(stream);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,8 +129,22 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var input = request.InputStream)
|
//If the request has been compressed, automatically handle it.
|
||||||
input.CopyTo(stream);
|
if (request.Headers["Content-Encoding"] == "gzip")
|
||||||
|
{
|
||||||
|
using (var inputStream = request.InputStream)
|
||||||
|
{
|
||||||
|
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress, true))
|
||||||
|
{
|
||||||
|
gzipStream.CopyTo(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var input = request.InputStream)
|
||||||
|
input.CopyTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -97,5 +153,24 @@ namespace MontoyaTech.Rest.Net
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the content of a HttpListenerRequest as http form data and returns it as a dictionary. Any duplicate keys are ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns>A dictionary of keyvalue pairs from the form data.</returns>
|
||||||
|
public static Dictionary<string, string> ReadAsForm(this HttpListenerRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var input = request.InputStream)
|
||||||
|
using (var stream = new StreamReader(input))
|
||||||
|
return stream.ReadToEnd().Trim().Split('&').Where(value => value.Contains('=')).ToLookup(value => value.Split('=')[0].Trim(), value => value.Split('=')[1].Trim()).ToDictionary(group => group.Key, group => group.FirstOrDefault());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
|
||||||
namespace MontoyaTech.Rest.Net
|
namespace MontoyaTech.Rest.Net
|
||||||
{
|
{
|
||||||
@ -14,6 +16,18 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class HttpListenerResponseExtensions
|
public static class HttpListenerResponseExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response to have no body.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static HttpListenerResponse WithNoBody(this HttpListenerResponse response)
|
||||||
|
{
|
||||||
|
response.ContentLength64 = 0;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the response content type to text and writes the given text to it.
|
/// Sets the response content type to text and writes the given text to it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -21,12 +35,69 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <returns>This response.</returns>
|
/// <returns>This response.</returns>
|
||||||
public static HttpListenerResponse WithText(this HttpListenerResponse response, string text)
|
public static HttpListenerResponse WithText(this HttpListenerResponse response, string text)
|
||||||
|
{
|
||||||
|
response.ContentType = "text/plain; charset=utf-8";
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
|
||||||
|
response.ContentLength64 = bytes.Length;
|
||||||
|
|
||||||
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to text encoded as utf16 and writes the given text to it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
public static HttpListenerResponse WithText16(this HttpListenerResponse response, string text)
|
||||||
{
|
{
|
||||||
response.ContentType = "text/plain; charset=utf-16";
|
response.ContentType = "text/plain; charset=utf-16";
|
||||||
|
|
||||||
var bytes = Encoding.Unicode.GetBytes(text);
|
var bytes = Encoding.Unicode.GetBytes(text);
|
||||||
|
|
||||||
|
response.ContentLength64 = bytes.Length;
|
||||||
|
|
||||||
response.OutputStream.Write(bytes, 0, bytes.Length);
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to text and writes the given text compressed to it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
public static HttpListenerResponse WithCompressedText(this HttpListenerResponse response, string text)
|
||||||
|
{
|
||||||
|
response.ContentType = "text/plain; charset=utf-8";
|
||||||
|
|
||||||
|
response.Headers.Add("Content-Encoding", "gzip");
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
|
||||||
|
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
|
||||||
|
compressedStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
response.ContentLength64 = memoryStream.Length;
|
||||||
|
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
memoryStream.CopyTo(response.OutputStream);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,12 +108,69 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <param name="obj"></param>
|
/// <param name="obj"></param>
|
||||||
/// <returns>This response.</returns>
|
/// <returns>This response.</returns>
|
||||||
public static HttpListenerResponse WithJson(this HttpListenerResponse response, object obj)
|
public static HttpListenerResponse WithJson(this HttpListenerResponse response, object obj)
|
||||||
|
{
|
||||||
|
response.ContentType = "application/json; charset=utf-8";
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
|
||||||
|
|
||||||
|
response.ContentLength64 = bytes.Length;
|
||||||
|
|
||||||
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to json encoded as utf16 and serializes the object as json and writes it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
public static HttpListenerResponse WithJson16(this HttpListenerResponse response, object obj)
|
||||||
{
|
{
|
||||||
response.ContentType = "application/json; charset=utf-16";
|
response.ContentType = "application/json; charset=utf-16";
|
||||||
|
|
||||||
var bytes = Encoding.Unicode.GetBytes(JsonConvert.SerializeObject(obj));
|
var bytes = Encoding.Unicode.GetBytes(JsonConvert.SerializeObject(obj));
|
||||||
|
|
||||||
|
response.ContentLength64 = bytes.Length;
|
||||||
|
|
||||||
response.OutputStream.Write(bytes, 0, bytes.Length);
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to json and writes the given json compressed to it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
public static HttpListenerResponse WithCompressedJson(this HttpListenerResponse response, object obj)
|
||||||
|
{
|
||||||
|
response.ContentType = "application/json; charset=utf-8";
|
||||||
|
|
||||||
|
response.Headers.Add("Content-Encoding", "gzip");
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
|
||||||
|
|
||||||
|
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
|
||||||
|
compressedStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
response.ContentLength64 = memoryStream.Length;
|
||||||
|
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
memoryStream.CopyTo(response.OutputStream);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,17 +178,160 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// Sets the response content type to a file and writes the given file content to the response.
|
/// Sets the response content type to a file and writes the given file content to the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="response"></param>
|
/// <param name="response"></param>
|
||||||
/// <param name="filePath"></param>
|
/// <param name="filePath">The path of the file to send.</param>
|
||||||
|
/// <param name="mimeType">The mime type of the file to send, if null, it will be auto detected if possible.</param>
|
||||||
/// <returns>This response.</returns>
|
/// <returns>This response.</returns>
|
||||||
public static HttpListenerResponse WithFile(this HttpListenerResponse response, string filePath)
|
public static HttpListenerResponse WithFile(this HttpListenerResponse response, string filePath, string mimeType = null)
|
||||||
{
|
{
|
||||||
response.ContentType = "application/octet-stream";
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
|
throw new ArgumentException("filePath must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(filePath).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(filePath)}""");
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(filePath)}""");
|
||||||
response.SendChunked = true;
|
|
||||||
|
|
||||||
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||||
using (var responseStream = response.OutputStream)
|
{
|
||||||
fileStream.CopyTo(responseStream);
|
response.ContentLength64 = fileStream.Length;
|
||||||
|
|
||||||
|
fileStream.CopyTo(response.OutputStream);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to a file and writes the file content with name to the response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="mimeType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static HttpListenerResponse WithFile(this HttpListenerResponse response, string filePath, byte[] content, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
|
throw new ArgumentException("filePath must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(filePath).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(filePath)}""");
|
||||||
|
response.ContentLength64 = content.Length;
|
||||||
|
|
||||||
|
response.OutputStream.Write(content, 0, content.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to a file and compresses the given file content to the response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="filePath">The path of the file to send.</param>
|
||||||
|
/// <param name="mimeType">The mime type of the file to send, if null, it will be auto detected if possible.</param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
public static HttpListenerResponse WithCompressedFile(this HttpListenerResponse response, string filePath, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
|
throw new ArgumentException("filePath must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(filePath).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(filePath)}""");
|
||||||
|
response.Headers.Add("Content-Encoding", "gzip");
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
|
||||||
|
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||||
|
fileStream.CopyTo(compressedStream);
|
||||||
|
|
||||||
|
response.ContentLength64 = memoryStream.Length;
|
||||||
|
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
memoryStream.CopyTo(response.OutputStream);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to a file and writes the file content with name to the response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="mimeType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static HttpListenerResponse WithCompressedFile(this HttpListenerResponse response, string filePath, byte[] content, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
|
throw new ArgumentException("filePath must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(filePath).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(filePath)}""");
|
||||||
|
response.Headers.Add("Content-Encoding", "gzip");
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
|
||||||
|
compressedStream.Write(content, 0, content.Length);
|
||||||
|
|
||||||
|
response.ContentLength64 = memoryStream.Length;
|
||||||
|
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
memoryStream.CopyTo(response.OutputStream);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to a precompressed file and writes the file contents to the response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <param name="mimeType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static HttpListenerResponse WithPreCompressedFile(this HttpListenerResponse response, string filePath, byte[] content, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
|
throw new ArgumentException("filePath must not be null or empty");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
mimeType = Path.GetExtension(filePath).GetMimeType();
|
||||||
|
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
response.Headers.Add("Content-Deposition", $@"attachment; filename=""{Path.GetFileName(filePath)}""");
|
||||||
|
response.Headers.Add("Content-Encoding", "gzip");
|
||||||
|
response.ContentLength64 = content.Length;
|
||||||
|
|
||||||
|
response.OutputStream.Write(content, 0, content.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -72,15 +343,68 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <param name="html"></param>
|
/// <param name="html"></param>
|
||||||
/// <returns>This response.</returns>
|
/// <returns>This response.</returns>
|
||||||
public static HttpListenerResponse WithHtml(this HttpListenerResponse response, string html)
|
public static HttpListenerResponse WithHtml(this HttpListenerResponse response, string html)
|
||||||
|
{
|
||||||
|
response.ContentType = "text/html; charset=utf-8";
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(html);
|
||||||
|
|
||||||
|
response.ContentLength64 = bytes.Length;
|
||||||
|
|
||||||
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response content type to html encoded in utf 16 and writes the given html to it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="html"></param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
public static HttpListenerResponse WithHtml16(this HttpListenerResponse response, string html)
|
||||||
{
|
{
|
||||||
response.ContentType = "text/html; charset=utf-16";
|
response.ContentType = "text/html; charset=utf-16";
|
||||||
|
|
||||||
var bytes = Encoding.Unicode.GetBytes(html);
|
var bytes = Encoding.Unicode.GetBytes(html);
|
||||||
|
|
||||||
|
response.ContentLength64 = bytes.Length;
|
||||||
|
|
||||||
response.OutputStream.Write(bytes, 0, bytes.Length);
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response to include the given stream and sets the length and content type if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
/// <param name="mimeType">If set, sets the content type to this value. If null, and no content type is set, sets it to octet-stream.</param>
|
||||||
|
/// <returns>This response.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"></exception>
|
||||||
|
public static HttpListenerResponse WithStream(this HttpListenerResponse response, Stream stream, string mimeType = null)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
throw new ArgumentNullException($"{nameof(stream)} cannot be null.");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mimeType))
|
||||||
|
response.ContentType = mimeType;
|
||||||
|
else if (string.IsNullOrWhiteSpace(response.ContentType))
|
||||||
|
response.ContentType = "application/octet-stream";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.ContentLength64 = stream.Length;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
stream.CopyTo(response.OutputStream);
|
||||||
|
|
||||||
|
response.OutputStream.Dispose();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the status code for a given response.
|
/// Sets the status code for a given response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -205,7 +529,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a redirect for a given response.
|
/// Sets the status code for a given response to redirect with the url to redirect to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="response"></param>
|
/// <param name="response"></param>
|
||||||
/// <param name="url"></param>
|
/// <param name="url"></param>
|
||||||
@ -266,5 +590,290 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
return response.WithStatus(HttpStatusCode.BadRequest).WithText($"{fieldName} in request is out of range.");
|
return response.WithStatus(HttpStatusCode.BadRequest).WithText($"{fieldName} in request is out of range.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response to serve a file in the context of a multi page application.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response">The response to modify</param>
|
||||||
|
/// <param name="basePath">The base path where to serve files from</param>
|
||||||
|
/// <param name="request">The request to serve</param>
|
||||||
|
/// <param name="indexFile">The name of the index file, default is index.html</param>
|
||||||
|
/// <param name="compress">Whether or not to compress files served. Default is false.</param>
|
||||||
|
/// <param name="compressExtensions">A collection of file extensions that should be compressed, example: .jpg, default is null. If and compress is true, all files will be compressed.</param>
|
||||||
|
/// <returns>The modified response</returns>
|
||||||
|
public static HttpListenerResponse ServeMultiPage(this HttpListenerResponse response, string basePath, string startPath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
|
||||||
|
{
|
||||||
|
if (ResolveMultiPagePath(basePath, startPath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
|
||||||
|
{
|
||||||
|
if (isDirectory)
|
||||||
|
{
|
||||||
|
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (request.HttpMethod.Equals("head", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (compress && (compressExtensions == null || compressExtensions.Contains(Path.GetExtension(resolvedPath))))
|
||||||
|
return response.WithStatus(HttpStatusCode.OK).WithCompressedFile(resolvedPath);
|
||||||
|
else
|
||||||
|
return response.WithStatus(HttpStatusCode.OK).WithFile(resolvedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return response.WithStatus(HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool ResolveMultiPagePath(string basePath, string startPath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
|
||||||
|
{
|
||||||
|
resolvedPath = null;
|
||||||
|
|
||||||
|
isDirectory = false;
|
||||||
|
|
||||||
|
//If the requestPath is pointing to nothing change that to the index file.
|
||||||
|
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
|
||||||
|
requestPath = indexFile;
|
||||||
|
|
||||||
|
//Break the startPath into it's components
|
||||||
|
var startComponents = startPath?.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
|
||||||
|
//Break the request path into it's components so we can enfore staying in the base path.
|
||||||
|
var requestComponents = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
|
||||||
|
//If we have start components, remove request components that match.
|
||||||
|
if (startComponents != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < startComponents.Count; i++)
|
||||||
|
{
|
||||||
|
if (requestComponents.Count > 0 && requestComponents[0].Equals(startComponents[i], StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
requestComponents.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Quirk, if the components is now empty, point to the indexFile
|
||||||
|
if (requestComponents.Count == 0)
|
||||||
|
requestComponents.Add(indexFile);
|
||||||
|
|
||||||
|
//Process the request components and handle directory changes
|
||||||
|
for (int i = 0; i < requestComponents.Count; i++)
|
||||||
|
{
|
||||||
|
if (requestComponents[i].Trim() == "..")
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
else
|
||||||
|
return false; //Trying to jump outside of basePath
|
||||||
|
}
|
||||||
|
else if (requestComponents[i].Trim() == "...")
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
else
|
||||||
|
return false; //Trying to jump outside of basePath
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
else
|
||||||
|
return false; //Trying to jump outside of basePath
|
||||||
|
}
|
||||||
|
else if (requestComponents[i].Trim() == ".")
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestComponents.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var absolutePath = Path.Combine(basePath, requestComponents.Separate(Path.DirectorySeparatorChar));
|
||||||
|
|
||||||
|
if (File.Exists(absolutePath))
|
||||||
|
{
|
||||||
|
resolvedPath = absolutePath;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(absolutePath))
|
||||||
|
{
|
||||||
|
resolvedPath = absolutePath;
|
||||||
|
|
||||||
|
isDirectory = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the response to serve a file in the context of a single page application.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response">The response to modify</param>
|
||||||
|
/// <param name="basePath">The base path where to serve files from</param>
|
||||||
|
/// <param name="startPath">The starting path that should be removed from requests, if null or empty, requests won't be affected</param>
|
||||||
|
/// <param name="request">The request to serve</param>
|
||||||
|
/// <param name="indexFile">The name of the index file, default is index.html</param>
|
||||||
|
/// <param name="compress">Whether or not to compress files served. Default is false.</param>
|
||||||
|
/// <param name="compressExtensions">A collection of file extensions that should be compressed, example: .jpg, default is null. If and compress is true, all files will be compressed.</param>
|
||||||
|
/// <returns>The modified response</returns>
|
||||||
|
public static HttpListenerResponse ServeSinglePage(this HttpListenerResponse response, string basePath, string startPath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
|
||||||
|
{
|
||||||
|
if (ResolveSinglePagePath(basePath, startPath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
|
||||||
|
{
|
||||||
|
if (isDirectory)
|
||||||
|
{
|
||||||
|
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (request.HttpMethod.Equals("head", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (compress && (compressExtensions == null || compressExtensions.Contains(Path.GetExtension(resolvedPath))))
|
||||||
|
return response.WithStatus(HttpStatusCode.OK).WithCompressedFile(resolvedPath);
|
||||||
|
else
|
||||||
|
return response.WithStatus(HttpStatusCode.OK).WithFile(resolvedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return response.WithStatus(HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool ResolveSinglePagePath(string basePath, string startPath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
|
||||||
|
{
|
||||||
|
resolvedPath = null;
|
||||||
|
|
||||||
|
isDirectory = false;
|
||||||
|
|
||||||
|
//If the requestPath is pointing to nothing change that to the index file.
|
||||||
|
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
|
||||||
|
requestPath = indexFile;
|
||||||
|
|
||||||
|
//Break the startPath into it's components
|
||||||
|
var startComponents = startPath?.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
|
||||||
|
//Break the request path into it's components so we can enfore staying in the base path.
|
||||||
|
var requestComponents = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
|
||||||
|
//If we have start components, remove request components that match.
|
||||||
|
if (startComponents != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < startComponents.Count; i++)
|
||||||
|
{
|
||||||
|
if (requestComponents.Count > 0 && requestComponents[0].Equals(startComponents[i], StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
requestComponents.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Process the request components and handle directory changes
|
||||||
|
for (int i = 0; i < requestComponents.Count; i++)
|
||||||
|
{
|
||||||
|
if (requestComponents[i].Trim() == "..")
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
else
|
||||||
|
return false; //Trying to jump outside of basePath
|
||||||
|
}
|
||||||
|
else if (requestComponents[i].Trim() == "...")
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
else
|
||||||
|
return false; //Trying to jump outside of basePath
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
else
|
||||||
|
return false; //Trying to jump outside of basePath
|
||||||
|
}
|
||||||
|
else if (requestComponents[i].Trim() == ".")
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check the components and remove any that are invalid.
|
||||||
|
while (requestComponents.Count > 0)
|
||||||
|
{
|
||||||
|
string path = Path.Combine(basePath, requestComponents[0]);
|
||||||
|
|
||||||
|
if (File.Exists(path) || Directory.Exists(path))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//If we have no more components and we are missing an extension and with .html a file exists, then use it.
|
||||||
|
else if (requestComponents.Count == 1 && !requestComponents[0].Contains('.') && File.Exists(path + ".html"))
|
||||||
|
{
|
||||||
|
requestComponents[0] = requestComponents[0] + ".html";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
requestComponents.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Quirk, if the components is now empty, point to the indexFile
|
||||||
|
if (requestComponents.Count == 0)
|
||||||
|
requestComponents.Add(indexFile);
|
||||||
|
|
||||||
|
//Combine the path into an absolute path
|
||||||
|
var absolutePath = Path.Combine(basePath, requestComponents.Separate(Path.DirectorySeparatorChar));
|
||||||
|
|
||||||
|
//If a file exists, return true
|
||||||
|
if (File.Exists(absolutePath))
|
||||||
|
{
|
||||||
|
resolvedPath = absolutePath;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//If a file exists with adding .html then use that
|
||||||
|
else if (File.Exists(absolutePath + ".html"))
|
||||||
|
{
|
||||||
|
resolvedPath = absolutePath + ".html";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//If a directory exists then use that
|
||||||
|
else if (Directory.Exists(absolutePath))
|
||||||
|
{
|
||||||
|
resolvedPath = absolutePath;
|
||||||
|
|
||||||
|
isDirectory = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//Otherwise redirect to index.html
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedPath = Path.Combine(basePath, indexFile);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
592
Rest.Net/MimeTypeExtensions.cs
Normal file
592
Rest.Net/MimeTypeExtensions.cs
Normal file
@ -0,0 +1,592 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
internal static class MimeTypeExtensions
|
||||||
|
{
|
||||||
|
public static string GetMimeType(this string extension)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(extension))
|
||||||
|
return "application/octet-stream";
|
||||||
|
|
||||||
|
if (extension.StartsWith("."))
|
||||||
|
extension = extension.Substring(1);
|
||||||
|
|
||||||
|
switch (extension.ToLower())
|
||||||
|
{
|
||||||
|
case "323": return "text/h323";
|
||||||
|
case "3g2": return "video/3gpp2";
|
||||||
|
case "3gp": return "video/3gpp";
|
||||||
|
case "3gp2": return "video/3gpp2";
|
||||||
|
case "3gpp": return "video/3gpp";
|
||||||
|
case "7z": return "application/x-7z-compressed";
|
||||||
|
case "aa": return "audio/audible";
|
||||||
|
case "aac": return "audio/aac";
|
||||||
|
case "aaf": return "application/octet-stream";
|
||||||
|
case "aax": return "audio/vnd.audible.aax";
|
||||||
|
case "ac3": return "audio/ac3";
|
||||||
|
case "aca": return "application/octet-stream";
|
||||||
|
case "accda": return "application/msaccess.addin";
|
||||||
|
case "accdb": return "application/msaccess";
|
||||||
|
case "accdc": return "application/msaccess.cab";
|
||||||
|
case "accde": return "application/msaccess";
|
||||||
|
case "accdr": return "application/msaccess.runtime";
|
||||||
|
case "accdt": return "application/msaccess";
|
||||||
|
case "accdw": return "application/msaccess.webapplication";
|
||||||
|
case "accft": return "application/msaccess.ftemplate";
|
||||||
|
case "acx": return "application/internet-property-stream";
|
||||||
|
case "addin": return "text/xml";
|
||||||
|
case "ade": return "application/msaccess";
|
||||||
|
case "adobebridge": return "application/x-bridge-url";
|
||||||
|
case "adp": return "application/msaccess";
|
||||||
|
case "adt": return "audio/vnd.dlna.adts";
|
||||||
|
case "adts": return "audio/aac";
|
||||||
|
case "afm": return "application/octet-stream";
|
||||||
|
case "ai": return "application/postscript";
|
||||||
|
case "aif": return "audio/x-aiff";
|
||||||
|
case "aifc": return "audio/aiff";
|
||||||
|
case "aiff": return "audio/aiff";
|
||||||
|
case "air": return "application/vnd.adobe.air-application-installer-package+zip";
|
||||||
|
case "amc": return "application/x-mpeg";
|
||||||
|
case "application": return "application/x-ms-application";
|
||||||
|
case "art": return "image/x-jg";
|
||||||
|
case "asa": return "application/xml";
|
||||||
|
case "asax": return "application/xml";
|
||||||
|
case "ascx": return "application/xml";
|
||||||
|
case "asd": return "application/octet-stream";
|
||||||
|
case "asf": return "video/x-ms-asf";
|
||||||
|
case "ashx": return "application/xml";
|
||||||
|
case "asi": return "application/octet-stream";
|
||||||
|
case "asm": return "text/plain";
|
||||||
|
case "asmx": return "application/xml";
|
||||||
|
case "aspx": return "application/xml";
|
||||||
|
case "asr": return "video/x-ms-asf";
|
||||||
|
case "asx": return "video/x-ms-asf";
|
||||||
|
case "atom": return "application/atom+xml";
|
||||||
|
case "au": return "audio/basic";
|
||||||
|
case "avi": return "video/x-msvideo";
|
||||||
|
case "axs": return "application/olescript";
|
||||||
|
case "bas": return "text/plain";
|
||||||
|
case "bcpio": return "application/x-bcpio";
|
||||||
|
case "bin": return "application/octet-stream";
|
||||||
|
case "bmp": return "image/bmp";
|
||||||
|
case "c": return "text/plain";
|
||||||
|
case "cab": return "application/octet-stream";
|
||||||
|
case "caf": return "audio/x-caf";
|
||||||
|
case "calx": return "application/vnd.ms-office.calx";
|
||||||
|
case "cat": return "application/vnd.ms-pki.seccat";
|
||||||
|
case "cc": return "text/plain";
|
||||||
|
case "cd": return "text/plain";
|
||||||
|
case "cdda": return "audio/aiff";
|
||||||
|
case "cdf": return "application/x-cdf";
|
||||||
|
case "cer": return "application/x-x509-ca-cert";
|
||||||
|
case "chm": return "application/octet-stream";
|
||||||
|
case "class": return "application/x-java-applet";
|
||||||
|
case "clp": return "application/x-msclip";
|
||||||
|
case "cmx": return "image/x-cmx";
|
||||||
|
case "cnf": return "text/plain";
|
||||||
|
case "cod": return "image/cis-cod";
|
||||||
|
case "config": return "application/xml";
|
||||||
|
case "contact": return "text/x-ms-contact";
|
||||||
|
case "coverage": return "application/xml";
|
||||||
|
case "cpio": return "application/x-cpio";
|
||||||
|
case "cpp": return "text/plain";
|
||||||
|
case "crd": return "application/x-mscardfile";
|
||||||
|
case "crl": return "application/pkix-crl";
|
||||||
|
case "crt": return "application/x-x509-ca-cert";
|
||||||
|
case "cs": return "text/plain";
|
||||||
|
case "csdproj": return "text/plain";
|
||||||
|
case "csh": return "application/x-csh";
|
||||||
|
case "csproj": return "text/plain";
|
||||||
|
case "css": return "text/css";
|
||||||
|
case "csv": return "text/csv";
|
||||||
|
case "cur": return "application/octet-stream";
|
||||||
|
case "cxx": return "text/plain";
|
||||||
|
case "dat": return "application/octet-stream";
|
||||||
|
case "datasource": return "application/xml";
|
||||||
|
case "dbproj": return "text/plain";
|
||||||
|
case "dcr": return "application/x-director";
|
||||||
|
case "def": return "text/plain";
|
||||||
|
case "deploy": return "application/octet-stream";
|
||||||
|
case "der": return "application/x-x509-ca-cert";
|
||||||
|
case "dgml": return "application/xml";
|
||||||
|
case "dib": return "image/bmp";
|
||||||
|
case "dif": return "video/x-dv";
|
||||||
|
case "dir": return "application/x-director";
|
||||||
|
case "disco": return "text/xml";
|
||||||
|
case "dll": return "application/x-msdownload";
|
||||||
|
case "dll.config": return "text/xml";
|
||||||
|
case "dlm": return "text/dlm";
|
||||||
|
case "doc": return "application/msword";
|
||||||
|
case "docm": return "application/vnd.ms-word.document.macroenabled.12";
|
||||||
|
case "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
|
case "dot": return "application/msword";
|
||||||
|
case "dotm": return "application/vnd.ms-word.template.macroenabled.12";
|
||||||
|
case "dotx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.template";
|
||||||
|
case "dsp": return "application/octet-stream";
|
||||||
|
case "dsw": return "text/plain";
|
||||||
|
case "dtd": return "text/xml";
|
||||||
|
case "dtsconfig": return "text/xml";
|
||||||
|
case "dv": return "video/x-dv";
|
||||||
|
case "dvi": return "application/x-dvi";
|
||||||
|
case "dwf": return "drawing/x-dwf";
|
||||||
|
case "dwp": return "application/octet-stream";
|
||||||
|
case "dxr": return "application/x-director";
|
||||||
|
case "eml": return "message/rfc822";
|
||||||
|
case "emz": return "application/octet-stream";
|
||||||
|
case "eot": return "application/octet-stream";
|
||||||
|
case "eps": return "application/postscript";
|
||||||
|
case "etl": return "application/etl";
|
||||||
|
case "etx": return "text/x-setext";
|
||||||
|
case "evy": return "application/envoy";
|
||||||
|
case "exe": return "application/octet-stream";
|
||||||
|
case "exe.config": return "text/xml";
|
||||||
|
case "fdf": return "application/vnd.fdf";
|
||||||
|
case "fif": return "application/fractals";
|
||||||
|
case "filters": return "application/xml";
|
||||||
|
case "fla": return "application/octet-stream";
|
||||||
|
case "flr": return "x-world/x-vrml";
|
||||||
|
case "flv": return "video/x-flv";
|
||||||
|
case "fas": return "text/xml";
|
||||||
|
case "signxml": return "text/xml";
|
||||||
|
case "a2xml": return "text/xml";
|
||||||
|
case "fsscript": return "application/fsharp-script";
|
||||||
|
case "fsx": return "application/fsharp-script";
|
||||||
|
case "generictest": return "application/xml";
|
||||||
|
case "gif": return "image/gif";
|
||||||
|
case "group": return "text/x-ms-group";
|
||||||
|
case "gsm": return "audio/x-gsm";
|
||||||
|
case "gtar": return "application/x-gtar";
|
||||||
|
case "gz": return "application/x-gzip";
|
||||||
|
case "h": return "text/plain";
|
||||||
|
case "hdf": return "application/x-hdf";
|
||||||
|
case "hdml": return "text/x-hdml";
|
||||||
|
case "hhc": return "application/x-oleobject";
|
||||||
|
case "hhk": return "application/octet-stream";
|
||||||
|
case "hhp": return "application/octet-stream";
|
||||||
|
case "hlp": return "application/winhlp";
|
||||||
|
case "hpp": return "text/plain";
|
||||||
|
case "hqx": return "application/mac-binhex40";
|
||||||
|
case "hta": return "application/hta";
|
||||||
|
case "htc": return "text/x-component";
|
||||||
|
case "htm": return "text/html";
|
||||||
|
case "html": return "text/html";
|
||||||
|
case "htt": return "text/webviewhtml";
|
||||||
|
case "hxa": return "application/xml";
|
||||||
|
case "hxc": return "application/xml";
|
||||||
|
case "hxd": return "application/octet-stream";
|
||||||
|
case "hxe": return "application/xml";
|
||||||
|
case "hxf": return "application/xml";
|
||||||
|
case "hxh": return "application/octet-stream";
|
||||||
|
case "hxi": return "application/octet-stream";
|
||||||
|
case "hxk": return "application/xml";
|
||||||
|
case "hxq": return "application/octet-stream";
|
||||||
|
case "hxr": return "application/octet-stream";
|
||||||
|
case "hxs": return "application/octet-stream";
|
||||||
|
case "hxt": return "text/html";
|
||||||
|
case "hxv": return "application/xml";
|
||||||
|
case "hxw": return "application/octet-stream";
|
||||||
|
case "hxx": return "text/plain";
|
||||||
|
case "i": return "text/plain";
|
||||||
|
case "ico": return "image/x-icon";
|
||||||
|
case "ics": return "application/octet-stream";
|
||||||
|
case "idl": return "text/plain";
|
||||||
|
case "ief": return "image/ief";
|
||||||
|
case "iii": return "application/x-iphone";
|
||||||
|
case "inc": return "text/plain";
|
||||||
|
case "inf": return "application/octet-stream";
|
||||||
|
case "inl": return "text/plain";
|
||||||
|
case "ins": return "application/x-internet-signup";
|
||||||
|
case "ipa": return "application/x-itunes-ipa";
|
||||||
|
case "ipg": return "application/x-itunes-ipg";
|
||||||
|
case "ipproj": return "text/plain";
|
||||||
|
case "ipsw": return "application/x-itunes-ipsw";
|
||||||
|
case "iqy": return "text/x-ms-iqy";
|
||||||
|
case "isp": return "application/x-internet-signup";
|
||||||
|
case "ite": return "application/x-itunes-ite";
|
||||||
|
case "itlp": return "application/x-itunes-itlp";
|
||||||
|
case "itms": return "application/x-itunes-itms";
|
||||||
|
case "itpc": return "application/x-itunes-itpc";
|
||||||
|
case "ivf": return "video/x-ivf";
|
||||||
|
case "jar": return "application/java-archive";
|
||||||
|
case "java": return "application/octet-stream";
|
||||||
|
case "jck": return "application/liquidmotion";
|
||||||
|
case "jcz": return "application/liquidmotion";
|
||||||
|
case "jfif": return "image/pjpeg";
|
||||||
|
case "jnlp": return "application/x-java-jnlp-file";
|
||||||
|
case "jpb": return "application/octet-stream";
|
||||||
|
case "jpe": return "image/jpeg";
|
||||||
|
case "jpeg": return "image/jpeg";
|
||||||
|
case "jpg": return "image/jpeg";
|
||||||
|
case "js": return "text/javascript";
|
||||||
|
case "json": return "application/json";
|
||||||
|
case "jsx": return "text/jscript";
|
||||||
|
case "jsxbin": return "text/plain";
|
||||||
|
case "latex": return "application/x-latex";
|
||||||
|
case "library-ms": return "application/windows-library+xml";
|
||||||
|
case "lit": return "application/x-ms-reader";
|
||||||
|
case "loadtest": return "application/xml";
|
||||||
|
case "lpk": return "application/octet-stream";
|
||||||
|
case "lsf": return "video/x-la-asf";
|
||||||
|
case "lst": return "text/plain";
|
||||||
|
case "lsx": return "video/x-la-asf";
|
||||||
|
case "lzh": return "application/octet-stream";
|
||||||
|
case "m13": return "application/x-msmediaview";
|
||||||
|
case "m14": return "application/x-msmediaview";
|
||||||
|
case "m1v": return "video/mpeg";
|
||||||
|
case "m2t": return "video/vnd.dlna.mpeg-tts";
|
||||||
|
case "m2ts": return "video/vnd.dlna.mpeg-tts";
|
||||||
|
case "m2v": return "video/mpeg";
|
||||||
|
case "m3u": return "audio/x-mpegurl";
|
||||||
|
case "m3u8": return "audio/x-mpegurl";
|
||||||
|
case "m4a": return "audio/m4a";
|
||||||
|
case "m4b": return "audio/m4b";
|
||||||
|
case "m4p": return "audio/m4p";
|
||||||
|
case "m4r": return "audio/x-m4r";
|
||||||
|
case "m4v": return "video/x-m4v";
|
||||||
|
case "mac": return "image/x-macpaint";
|
||||||
|
case "mak": return "text/plain";
|
||||||
|
case "man": return "application/x-troff-man";
|
||||||
|
case "manifest": return "application/x-ms-manifest";
|
||||||
|
case "map": return "text/plain";
|
||||||
|
case "master": return "application/xml";
|
||||||
|
case "mda": return "application/msaccess";
|
||||||
|
case "mdb": return "application/x-msaccess";
|
||||||
|
case "mde": return "application/msaccess";
|
||||||
|
case "mdp": return "application/octet-stream";
|
||||||
|
case "me": return "application/x-troff-me";
|
||||||
|
case "mfp": return "application/x-shockwave-flash";
|
||||||
|
case "mht": return "message/rfc822";
|
||||||
|
case "mhtml": return "message/rfc822";
|
||||||
|
case "mid": return "audio/mid";
|
||||||
|
case "midi": return "audio/mid";
|
||||||
|
case "mix": return "application/octet-stream";
|
||||||
|
case "mk": return "text/plain";
|
||||||
|
case "mmf": return "application/x-smaf";
|
||||||
|
case "mno": return "text/xml";
|
||||||
|
case "mny": return "application/x-msmoney";
|
||||||
|
case "mod": return "video/mpeg";
|
||||||
|
case "mov": return "video/quicktime";
|
||||||
|
case "movie": return "video/x-sgi-movie";
|
||||||
|
case "mp2": return "video/mpeg";
|
||||||
|
case "mp2v": return "video/mpeg";
|
||||||
|
case "mp3": return "audio/mpeg";
|
||||||
|
case "mp4": return "video/mp4";
|
||||||
|
case "mp4v": return "video/mp4";
|
||||||
|
case "mpa": return "video/mpeg";
|
||||||
|
case "mpe": return "video/mpeg";
|
||||||
|
case "mpeg": return "video/mpeg";
|
||||||
|
case "mpf": return "application/vnd.ms-mediapackage";
|
||||||
|
case "mpg": return "video/mpeg";
|
||||||
|
case "mpp": return "application/vnd.ms-project";
|
||||||
|
case "mpv2": return "video/mpeg";
|
||||||
|
case "mqv": return "video/quicktime";
|
||||||
|
case "ms": return "application/x-troff-ms";
|
||||||
|
case "msi": return "application/octet-stream";
|
||||||
|
case "mso": return "application/octet-stream";
|
||||||
|
case "mts": return "video/vnd.dlna.mpeg-tts";
|
||||||
|
case "mtx": return "application/xml";
|
||||||
|
case "mvb": return "application/x-msmediaview";
|
||||||
|
case "mvc": return "application/x-miva-compiled";
|
||||||
|
case "mxp": return "application/x-mmxp";
|
||||||
|
case "nc": return "application/x-netcdf";
|
||||||
|
case "nsc": return "video/x-ms-asf";
|
||||||
|
case "nws": return "message/rfc822";
|
||||||
|
case "ocx": return "application/octet-stream";
|
||||||
|
case "oda": return "application/oda";
|
||||||
|
case "odc": return "text/x-ms-odc";
|
||||||
|
case "odh": return "text/plain";
|
||||||
|
case "odl": return "text/plain";
|
||||||
|
case "odp": return "application/vnd.oasis.opendocument.presentation";
|
||||||
|
case "ods": return "application/oleobject";
|
||||||
|
case "odt": return "application/vnd.oasis.opendocument.text";
|
||||||
|
case "one": return "application/onenote";
|
||||||
|
case "onea": return "application/onenote";
|
||||||
|
case "onepkg": return "application/onenote";
|
||||||
|
case "onetmp": return "application/onenote";
|
||||||
|
case "onetoc": return "application/onenote";
|
||||||
|
case "onetoc2": return "application/onenote";
|
||||||
|
case "orderedtest": return "application/xml";
|
||||||
|
case "osdx": return "application/opensearchdescription+xml";
|
||||||
|
case "p10": return "application/pkcs10";
|
||||||
|
case "p12": return "application/x-pkcs12";
|
||||||
|
case "p7b": return "application/x-pkcs7-certificates";
|
||||||
|
case "p7c": return "application/pkcs7-mime";
|
||||||
|
case "p7m": return "application/pkcs7-mime";
|
||||||
|
case "p7r": return "application/x-pkcs7-certreqresp";
|
||||||
|
case "p7s": return "application/pkcs7-signature";
|
||||||
|
case "pbm": return "image/x-portable-bitmap";
|
||||||
|
case "pcast": return "application/x-podcast";
|
||||||
|
case "pct": return "image/pict";
|
||||||
|
case "pcx": return "application/octet-stream";
|
||||||
|
case "pcz": return "application/octet-stream";
|
||||||
|
case "pdf": return "application/pdf";
|
||||||
|
case "pfb": return "application/octet-stream";
|
||||||
|
case "pfm": return "application/octet-stream";
|
||||||
|
case "pfx": return "application/x-pkcs12";
|
||||||
|
case "pgm": return "image/x-portable-graymap";
|
||||||
|
case "pic": return "image/pict";
|
||||||
|
case "pict": return "image/pict";
|
||||||
|
case "pkgdef": return "text/plain";
|
||||||
|
case "pkgundef": return "text/plain";
|
||||||
|
case "pko": return "application/vnd.ms-pki.pko";
|
||||||
|
case "pls": return "audio/scpls";
|
||||||
|
case "pma": return "application/x-perfmon";
|
||||||
|
case "pmc": return "application/x-perfmon";
|
||||||
|
case "pml": return "application/x-perfmon";
|
||||||
|
case "pmr": return "application/x-perfmon";
|
||||||
|
case "pmw": return "application/x-perfmon";
|
||||||
|
case "png": return "image/png";
|
||||||
|
case "pnm": return "image/x-portable-anymap";
|
||||||
|
case "pnt": return "image/x-macpaint";
|
||||||
|
case "pntg": return "image/x-macpaint";
|
||||||
|
case "pnz": return "image/png";
|
||||||
|
case "pot": return "application/vnd.ms-powerpoint";
|
||||||
|
case "potm": return "application/vnd.ms-powerpoint.template.macroenabled.12";
|
||||||
|
case "potx": return "application/vnd.openxmlformats-officedocument.presentationml.template";
|
||||||
|
case "ppa": return "application/vnd.ms-powerpoint";
|
||||||
|
case "ppam": return "application/vnd.ms-powerpoint.addin.macroenabled.12";
|
||||||
|
case "ppm": return "image/x-portable-pixmap";
|
||||||
|
case "pps": return "application/vnd.ms-powerpoint";
|
||||||
|
case "ppsm": return "application/vnd.ms-powerpoint.slideshow.macroenabled.12";
|
||||||
|
case "ppsx": return "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
|
||||||
|
case "ppt": return "application/vnd.ms-powerpoint";
|
||||||
|
case "pptm": return "application/vnd.ms-powerpoint.presentation.macroenabled.12";
|
||||||
|
case "pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
||||||
|
case "prf": return "application/pics-rules";
|
||||||
|
case "prm": return "application/octet-stream";
|
||||||
|
case "prx": return "application/octet-stream";
|
||||||
|
case "ps": return "application/postscript";
|
||||||
|
case "psc1": return "application/powershell";
|
||||||
|
case "psd": return "application/octet-stream";
|
||||||
|
case "psess": return "application/xml";
|
||||||
|
case "psm": return "application/octet-stream";
|
||||||
|
case "psp": return "application/octet-stream";
|
||||||
|
case "pub": return "application/x-mspublisher";
|
||||||
|
case "pwz": return "application/vnd.ms-powerpoint";
|
||||||
|
case "qht": return "text/x-html-insertion";
|
||||||
|
case "qhtm": return "text/x-html-insertion";
|
||||||
|
case "qt": return "video/quicktime";
|
||||||
|
case "qti": return "image/x-quicktime";
|
||||||
|
case "qtif": return "image/x-quicktime";
|
||||||
|
case "qtl": return "application/x-quicktimeplayer";
|
||||||
|
case "qxd": return "application/octet-stream";
|
||||||
|
case "ra": return "audio/x-pn-realaudio";
|
||||||
|
case "ram": return "audio/x-pn-realaudio";
|
||||||
|
case "rar": return "application/octet-stream";
|
||||||
|
case "ras": return "image/x-cmu-raster";
|
||||||
|
case "rat": return "application/rat-file";
|
||||||
|
case "rc": return "text/plain";
|
||||||
|
case "rc2": return "text/plain";
|
||||||
|
case "rct": return "text/plain";
|
||||||
|
case "rdlc": return "application/xml";
|
||||||
|
case "resx": return "application/xml";
|
||||||
|
case "rf": return "image/vnd.rn-realflash";
|
||||||
|
case "rgb": return "image/x-rgb";
|
||||||
|
case "rgs": return "text/plain";
|
||||||
|
case "rm": return "application/vnd.rn-realmedia";
|
||||||
|
case "rmi": return "audio/mid";
|
||||||
|
case "rmp": return "application/vnd.rn-rn_music_package";
|
||||||
|
case "roff": return "application/x-troff";
|
||||||
|
case "rpm": return "audio/x-pn-realaudio-plugin";
|
||||||
|
case "rqy": return "text/x-ms-rqy";
|
||||||
|
case "rtf": return "application/rtf";
|
||||||
|
case "rtx": return "text/richtext";
|
||||||
|
case "ruleset": return "application/xml";
|
||||||
|
case "s": return "text/plain";
|
||||||
|
case "safariextz": return "application/x-safari-safariextz";
|
||||||
|
case "scd": return "application/x-msschedule";
|
||||||
|
case "sct": return "text/scriptlet";
|
||||||
|
case "sd2": return "audio/x-sd2";
|
||||||
|
case "sdp": return "application/sdp";
|
||||||
|
case "sea": return "application/octet-stream";
|
||||||
|
case "searchconnector-ms": return "application/windows-search-connector+xml";
|
||||||
|
case "setpay": return "application/set-payment-initiation";
|
||||||
|
case "setreg": return "application/set-registration-initiation";
|
||||||
|
case "settings": return "application/xml";
|
||||||
|
case "sgimb": return "application/x-sgimb";
|
||||||
|
case "sgml": return "text/sgml";
|
||||||
|
case "sh": return "application/x-sh";
|
||||||
|
case "shar": return "application/x-shar";
|
||||||
|
case "shtml": return "text/html";
|
||||||
|
case "sit": return "application/x-stuffit";
|
||||||
|
case "sitemap": return "application/xml";
|
||||||
|
case "skin": return "application/xml";
|
||||||
|
case "sldm": return "application/vnd.ms-powerpoint.slide.macroenabled.12";
|
||||||
|
case "sldx": return "application/vnd.openxmlformats-officedocument.presentationml.slide";
|
||||||
|
case "slk": return "application/vnd.ms-excel";
|
||||||
|
case "sln": return "text/plain";
|
||||||
|
case "slupkg-ms": return "application/x-ms-license";
|
||||||
|
case "smd": return "audio/x-smd";
|
||||||
|
case "smi": return "application/octet-stream";
|
||||||
|
case "smx": return "audio/x-smd";
|
||||||
|
case "smz": return "audio/x-smd";
|
||||||
|
case "snd": return "audio/basic";
|
||||||
|
case "snippet": return "application/xml";
|
||||||
|
case "snp": return "application/octet-stream";
|
||||||
|
case "sol": return "text/plain";
|
||||||
|
case "sor": return "text/plain";
|
||||||
|
case "spc": return "application/x-pkcs7-certificates";
|
||||||
|
case "spl": return "application/futuresplash";
|
||||||
|
case "src": return "application/x-wais-source";
|
||||||
|
case "srf": return "text/plain";
|
||||||
|
case "ssisdeploymentmanifest": return "text/xml";
|
||||||
|
case "ssm": return "application/streamingmedia";
|
||||||
|
case "sst": return "application/vnd.ms-pki.certstore";
|
||||||
|
case "stl": return "application/vnd.ms-pki.stl";
|
||||||
|
case "sv4cpio": return "application/x-sv4cpio";
|
||||||
|
case "sv4crc": return "application/x-sv4crc";
|
||||||
|
case "svc": return "application/xml";
|
||||||
|
case "svg": return "image/svg+xml";
|
||||||
|
case "swf": return "application/x-shockwave-flash";
|
||||||
|
case "t": return "application/x-troff";
|
||||||
|
case "tar": return "application/x-tar";
|
||||||
|
case "tcl": return "application/x-tcl";
|
||||||
|
case "testrunconfig": return "application/xml";
|
||||||
|
case "testsettings": return "application/xml";
|
||||||
|
case "tex": return "application/x-tex";
|
||||||
|
case "texi": return "application/x-texinfo";
|
||||||
|
case "texinfo": return "application/x-texinfo";
|
||||||
|
case "tgz": return "application/x-compressed";
|
||||||
|
case "thmx": return "application/vnd.ms-officetheme";
|
||||||
|
case "thn": return "application/octet-stream";
|
||||||
|
case "tif": return "image/tiff";
|
||||||
|
case "tiff": return "image/tiff";
|
||||||
|
case "tlh": return "text/plain";
|
||||||
|
case "tli": return "text/plain";
|
||||||
|
case "toc": return "application/octet-stream";
|
||||||
|
case "tr": return "application/x-troff";
|
||||||
|
case "trm": return "application/x-msterminal";
|
||||||
|
case "trx": return "application/xml";
|
||||||
|
case "ts": return "video/vnd.dlna.mpeg-tts";
|
||||||
|
case "tsv": return "text/tab-separated-values";
|
||||||
|
case "ttf": return "application/octet-stream";
|
||||||
|
case "tts": return "video/vnd.dlna.mpeg-tts";
|
||||||
|
case "txt": return "text/plain";
|
||||||
|
case "u32": return "application/octet-stream";
|
||||||
|
case "uls": return "text/iuls";
|
||||||
|
case "user": return "text/plain";
|
||||||
|
case "ustar": return "application/x-ustar";
|
||||||
|
case "vb": return "text/plain";
|
||||||
|
case "vbdproj": return "text/plain";
|
||||||
|
case "vbk": return "video/mpeg";
|
||||||
|
case "vbproj": return "text/plain";
|
||||||
|
case "vbs": return "text/vbscript";
|
||||||
|
case "vcf": return "text/x-vcard";
|
||||||
|
case "vcproj": return "application/xml";
|
||||||
|
case "vcs": return "text/plain";
|
||||||
|
case "vcxproj": return "application/xml";
|
||||||
|
case "vddproj": return "text/plain";
|
||||||
|
case "vdp": return "text/plain";
|
||||||
|
case "vdproj": return "text/plain";
|
||||||
|
case "vdx": return "application/vnd.ms-visio.viewer";
|
||||||
|
case "vml": return "text/xml";
|
||||||
|
case "vscontent": return "application/xml";
|
||||||
|
case "vsct": return "text/xml";
|
||||||
|
case "vsd": return "application/vnd.visio";
|
||||||
|
case "vsi": return "application/ms-vsi";
|
||||||
|
case "vsix": return "application/vsix";
|
||||||
|
case "vsixlangpack": return "text/xml";
|
||||||
|
case "vsixmanifest": return "text/xml";
|
||||||
|
case "vsmdi": return "application/xml";
|
||||||
|
case "vspscc": return "text/plain";
|
||||||
|
case "vss": return "application/vnd.visio";
|
||||||
|
case "vsscc": return "text/plain";
|
||||||
|
case "vssettings": return "text/xml";
|
||||||
|
case "vssscc": return "text/plain";
|
||||||
|
case "vst": return "application/vnd.visio";
|
||||||
|
case "vstemplate": return "text/xml";
|
||||||
|
case "vsto": return "application/x-ms-vsto";
|
||||||
|
case "vsw": return "application/vnd.visio";
|
||||||
|
case "vsx": return "application/vnd.visio";
|
||||||
|
case "vtx": return "application/vnd.visio";
|
||||||
|
case "wav": return "audio/wav";
|
||||||
|
case "wave": return "audio/wav";
|
||||||
|
case "wax": return "audio/x-ms-wax";
|
||||||
|
case "wbk": return "application/msword";
|
||||||
|
case "wbmp": return "image/vnd.wap.wbmp";
|
||||||
|
case "wcm": return "application/vnd.ms-works";
|
||||||
|
case "wdb": return "application/vnd.ms-works";
|
||||||
|
case "wdp": return "image/vnd.ms-photo";
|
||||||
|
case "webarchive": return "application/x-safari-webarchive";
|
||||||
|
case "webtest": return "application/xml";
|
||||||
|
case "wiq": return "application/xml";
|
||||||
|
case "wiz": return "application/msword";
|
||||||
|
case "wks": return "application/vnd.ms-works";
|
||||||
|
case "wlmp": return "application/wlmoviemaker";
|
||||||
|
case "wlpginstall": return "application/x-wlpg-detect";
|
||||||
|
case "wlpginstall3": return "application/x-wlpg3-detect";
|
||||||
|
case "wm": return "video/x-ms-wm";
|
||||||
|
case "wma": return "audio/x-ms-wma";
|
||||||
|
case "wmd": return "application/x-ms-wmd";
|
||||||
|
case "wmf": return "application/x-msmetafile";
|
||||||
|
case "wml": return "text/vnd.wap.wml";
|
||||||
|
case "wmlc": return "application/vnd.wap.wmlc";
|
||||||
|
case "wmls": return "text/vnd.wap.wmlscript";
|
||||||
|
case "wmlsc": return "application/vnd.wap.wmlscriptc";
|
||||||
|
case "wmp": return "video/x-ms-wmp";
|
||||||
|
case "wmv": return "video/x-ms-wmv";
|
||||||
|
case "wmx": return "video/x-ms-wmx";
|
||||||
|
case "wmz": return "application/x-ms-wmz";
|
||||||
|
case "wpl": return "application/vnd.ms-wpl";
|
||||||
|
case "wps": return "application/vnd.ms-works";
|
||||||
|
case "wri": return "application/x-mswrite";
|
||||||
|
case "wrl": return "x-world/x-vrml";
|
||||||
|
case "wrz": return "x-world/x-vrml";
|
||||||
|
case "wsc": return "text/scriptlet";
|
||||||
|
case "wsdl": return "text/xml";
|
||||||
|
case "wvx": return "video/x-ms-wvx";
|
||||||
|
case "webp": return "image/webp";
|
||||||
|
case "woff": return "application/font-woff";
|
||||||
|
case "x": return "application/directx";
|
||||||
|
case "xaf": return "x-world/x-vrml";
|
||||||
|
case "xaml": return "application/xaml+xml";
|
||||||
|
case "xap": return "application/x-silverlight-app";
|
||||||
|
case "xbap": return "application/x-ms-xbap";
|
||||||
|
case "xbm": return "image/x-xbitmap";
|
||||||
|
case "xdr": return "text/plain";
|
||||||
|
case "xht": return "application/xhtml+xml";
|
||||||
|
case "xhtml": return "application/xhtml+xml";
|
||||||
|
case "xla": return "application/vnd.ms-excel";
|
||||||
|
case "xlam": return "application/vnd.ms-excel.addin.macroenabled.12";
|
||||||
|
case "xlc": return "application/vnd.ms-excel";
|
||||||
|
case "xld": return "application/vnd.ms-excel";
|
||||||
|
case "xlk": return "application/vnd.ms-excel";
|
||||||
|
case "xll": return "application/vnd.ms-excel";
|
||||||
|
case "xlm": return "application/vnd.ms-excel";
|
||||||
|
case "xls": return "application/vnd.ms-excel";
|
||||||
|
case "xlsb": return "application/vnd.ms-excel.sheet.binary.macroenabled.12";
|
||||||
|
case "xlsm": return "application/vnd.ms-excel.sheet.macroenabled.12";
|
||||||
|
case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
case "xlt": return "application/vnd.ms-excel";
|
||||||
|
case "xltm": return "application/vnd.ms-excel.template.macroenabled.12";
|
||||||
|
case "xltx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.template";
|
||||||
|
case "xlw": return "application/vnd.ms-excel";
|
||||||
|
case "xml": return "text/xml";
|
||||||
|
case "xmta": return "application/xml";
|
||||||
|
case "xof": return "x-world/x-vrml";
|
||||||
|
case "xoml": return "text/plain";
|
||||||
|
case "xpm": return "image/x-xpixmap";
|
||||||
|
case "xps": return "application/vnd.ms-xpsdocument";
|
||||||
|
case "xrm-ms": return "text/xml";
|
||||||
|
case "xsc": return "application/xml";
|
||||||
|
case "xsd": return "text/xml";
|
||||||
|
case "xsf": return "text/xml";
|
||||||
|
case "xsl": return "text/xml";
|
||||||
|
case "xslt": return "text/xml";
|
||||||
|
case "xsn": return "application/octet-stream";
|
||||||
|
case "xss": return "application/xml";
|
||||||
|
case "xtp": return "application/octet-stream";
|
||||||
|
case "xwd": return "image/x-xwindowdump";
|
||||||
|
case "z": return "application/x-compress";
|
||||||
|
case "zip": return "application/x-zip-compressed";
|
||||||
|
|
||||||
|
default: return "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>library</OutputType>
|
<OutputType>library</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
@ -17,9 +17,10 @@
|
|||||||
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
||||||
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<Version>1.1.8</Version>
|
<Version>1.8.9</Version>
|
||||||
<PackageReleaseNotes></PackageReleaseNotes>
|
<PackageReleaseNotes></PackageReleaseNotes>
|
||||||
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -27,10 +28,18 @@
|
|||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
|
<None Include="..\README.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="MontoyaTech.Rest.Net.Tests" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
676
Rest.Net/RestCSharpClientGenerator.cs
Normal file
676
Rest.Net/RestCSharpClientGenerator.cs
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a Rest Client Generator that can generate a C# Client.
|
||||||
|
/// </summary>
|
||||||
|
public class RestCSharpClientGenerator : RestClientGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not to generate static code, if true the client will be static.
|
||||||
|
/// </summary>
|
||||||
|
public bool StaticCode = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a CSharp Client from a given set of routes and returns it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string Generate(List<Route> routes)
|
||||||
|
{
|
||||||
|
var includedTypes = this.FindRoutesDependencies(routes);
|
||||||
|
|
||||||
|
var routeGroups = this.FindRouteGroups(routes);
|
||||||
|
|
||||||
|
var writer = new CodeWriter();
|
||||||
|
|
||||||
|
writer.WriteLine($"//Generated using MontoyaTech.Rest.Net - {DateTime.Now.ToShortDateString()}");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"public class {this.ClientName}").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Create the base url field
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine("public static string BaseUrl;");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine("public string BaseUrl;");
|
||||||
|
|
||||||
|
//Create the cookie container field
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine("public static System.Net.CookieContainer CookieContainer;");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine("public System.Net.CookieContainer CookieContainer;");
|
||||||
|
|
||||||
|
//Create the client handler field
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine("public static System.Net.Http.HttpMessageHandler MessageHandler;");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine("public System.Net.Http.HttpMessageHandler MessageHandler;");
|
||||||
|
|
||||||
|
//Create the http client field
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine("public static System.Net.Http.HttpClient HttpClient;");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine("public System.Net.Http.HttpClient HttpClient;");
|
||||||
|
|
||||||
|
//Create the request handler field
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine("public static System.Action<System.Net.Http.HttpRequestMessage> RequestHandler;");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine("public System.Action<System.Net.Http.HttpRequestMessage> RequestHandler;");
|
||||||
|
|
||||||
|
//Create fields foreach route group so they can be accessed.
|
||||||
|
if (!this.StaticCode)
|
||||||
|
foreach (var group in routeGroups)
|
||||||
|
writer.WriteBreak().WriteLine($"public {group.Key}Api {group.Key};");
|
||||||
|
|
||||||
|
//Create the client constructor or init method
|
||||||
|
if (this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("public static void Init(string baseUrl, System.Net.Http.HttpMessageHandler messageHandler = null, System.Action<System.Net.Http.HttpRequestMessage> requestHandler = null)").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Make sure the base url isn't null or whitespace
|
||||||
|
writer.WriteBreak().WriteLine("if (string.IsNullOrWhiteSpace(baseUrl))");
|
||||||
|
writer.Indent().WriteLine(@"throw new System.ArgumentException(""baseUrl must not be null or whitespace."");").Outdent();
|
||||||
|
|
||||||
|
//If the baseUrl ends with a /, remove it.
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))");
|
||||||
|
writer.Indent().WriteLine("baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);").Outdent();
|
||||||
|
|
||||||
|
//Init the base url
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;");
|
||||||
|
|
||||||
|
//Init the cookie container
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.CookieContainer = new System.Net.CookieContainer();");
|
||||||
|
|
||||||
|
//Init the client handler
|
||||||
|
writer.WriteBreak().WriteLine("if (messageHandler == null)");
|
||||||
|
writer.WriteLine("{").Indent();
|
||||||
|
writer.WriteLine($"messageHandler = new System.Net.Http.HttpClientHandler()");
|
||||||
|
writer.WriteLine("{").Indent();
|
||||||
|
writer.WriteLine("AllowAutoRedirect = true,");
|
||||||
|
writer.WriteLine("UseCookies = true,");
|
||||||
|
writer.WriteLine($"CookieContainer = {this.ClientName}.CookieContainer,");
|
||||||
|
writer.WriteLine("AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate");
|
||||||
|
writer.Outdent().WriteLine("};");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Store the message handler
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.MessageHandler = messageHandler;");
|
||||||
|
|
||||||
|
//Store the request handler
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler = requestHandler;");
|
||||||
|
|
||||||
|
//Init the http client
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.HttpClient = new System.Net.Http.HttpClient(handler);");
|
||||||
|
writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Accept"", ""*/*"");");
|
||||||
|
writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");");
|
||||||
|
writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"public {this.ClientName}(string baseUrl, System.Net.Http.HttpMessageHandler messageHandler = null, System.Action<System.Net.Http.HttpRequestMessage> requestHandler = null)").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Make sure the base url isn't null or whitespace
|
||||||
|
writer.WriteBreak().WriteLine("if (string.IsNullOrWhiteSpace(baseUrl))");
|
||||||
|
writer.Indent().WriteLine(@"throw new System.ArgumentException(""baseUrl must not be null or whitespace."");").Outdent();
|
||||||
|
|
||||||
|
//If the baseUrl ends with a /, remove it.
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))");
|
||||||
|
writer.Indent().WriteLine("baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);").Outdent();
|
||||||
|
|
||||||
|
//Init the base url
|
||||||
|
writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;");
|
||||||
|
|
||||||
|
//Init the cookie container
|
||||||
|
writer.WriteBreak().WriteLine("this.CookieContainer = new System.Net.CookieContainer();");
|
||||||
|
|
||||||
|
//Init the client handler
|
||||||
|
writer.WriteBreak().WriteLine("if (messageHandler == null)");
|
||||||
|
writer.WriteLine("{").Indent();
|
||||||
|
writer.WriteLine("messageHandler = new System.Net.Http.HttpClientHandler()");
|
||||||
|
writer.WriteLine("{").Indent();
|
||||||
|
writer.WriteLine("AllowAutoRedirect = true,");
|
||||||
|
writer.WriteLine("UseCookies = true,");
|
||||||
|
writer.WriteLine("CookieContainer = this.CookieContainer,");
|
||||||
|
writer.WriteLine("AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate");
|
||||||
|
writer.Outdent().WriteLine("};");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Store the message handler
|
||||||
|
writer.WriteBreak().WriteLine("this.MessageHandler = messageHandler;");
|
||||||
|
|
||||||
|
//Store the request handler
|
||||||
|
writer.WriteBreak().WriteLine("this.RequestHandler = requestHandler;");
|
||||||
|
|
||||||
|
//Init the http client
|
||||||
|
writer.WriteBreak().WriteLine("this.HttpClient = new System.Net.Http.HttpClient(handler);");
|
||||||
|
writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept"", ""*/*"");");
|
||||||
|
writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");");
|
||||||
|
writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");");
|
||||||
|
|
||||||
|
//Init all the route group fields
|
||||||
|
foreach (var group in routeGroups)
|
||||||
|
writer.WriteBreak().WriteLine($"this.{group.Key} = new {group.Key}Api(this);");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.GenerateCSharpRouteGroups(routeGroups, writer);
|
||||||
|
|
||||||
|
this.GenerateCSharpIncludedTypes(includedTypes, writer);
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# for a set of included types.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="types"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
protected internal virtual void GenerateCSharpIncludedTypes(List<Type> types, CodeWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
bool subType = false;
|
||||||
|
|
||||||
|
//See if this type belongs to another type in the list.
|
||||||
|
if (type.DeclaringType != null)
|
||||||
|
for (int i = 0; i < types.Count && !subType; i++)
|
||||||
|
if (type.DeclaringType == types[i])
|
||||||
|
subType = true;
|
||||||
|
|
||||||
|
//If not, generate the C# for this type.
|
||||||
|
if (!subType)
|
||||||
|
this.GenerateCSharpIncludedType(type, types, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# for a given included type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
protected internal virtual void GenerateCSharpIncludedType(Type type, List<Type> types, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
var newName = type.GetCustomAttribute<RouteTypeName>();
|
||||||
|
|
||||||
|
writer.Write($"public {(type.IsEnum ? "enum" : "class")} {(newName != null ? newName.Name : type.Name)}");
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
writer.Write(" : ").Write(this.GetTypeFullyResolvedName(Enum.GetUnderlyingType(type)));
|
||||||
|
else if (!(IsTypeDotNet(type.BaseType) && type.BaseType.Name == "Object"))
|
||||||
|
writer.Write(" : ").Write(this.GetTypeFullyResolvedName(type.BaseType));
|
||||||
|
|
||||||
|
writer.NewLine().WriteLine("{").Indent();
|
||||||
|
|
||||||
|
FieldInfo[] fields;
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
|
||||||
|
else
|
||||||
|
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
|
||||||
|
if (fields != null)
|
||||||
|
foreach (var field in fields)
|
||||||
|
if (field.IsPublic && !field.IsSpecialName)
|
||||||
|
this.GenerateCSharpIncludedField(field, writer);
|
||||||
|
|
||||||
|
var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
|
||||||
|
if (properties != null)
|
||||||
|
foreach (var property in properties)
|
||||||
|
if (!property.IsSpecialName && property.GetSetMethod() != null && property.GetGetMethod() != null)
|
||||||
|
this.GenerateCSharpIncludedProperty(property, writer);
|
||||||
|
|
||||||
|
if (!type.IsEnum)
|
||||||
|
{
|
||||||
|
//Generate an empty constructor
|
||||||
|
writer.WriteBreak();
|
||||||
|
writer.WriteLine($"public {(newName != null ? newName.Name : type.Name)}() {{ }}");
|
||||||
|
|
||||||
|
//Generate a constructor to set all the fields/properties from an existing instance
|
||||||
|
writer.WriteBreak();
|
||||||
|
writer.WriteLine($"public {(newName != null ? newName.Name : type.Name)}({(newName != null ? newName.Name : type.Name)} instance)");
|
||||||
|
writer.WriteLine('{').Indent();
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
writer.WriteLine($"this.{field.Name} = instance.{field.Name};");
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
writer.WriteLine($"this.{property.Name} = instance.{property.Name};");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine('}');
|
||||||
|
|
||||||
|
//Generate a constructor to set all the fields/properties with optional default values
|
||||||
|
if (fields.Length > 0 || properties.Length > 0)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
writer.Write($"public {(newName != null ? newName.Name : type.Name)}(");
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
writer.WriteSeparator().Write($"{this.GetTypeFullyResolvedName(field.FieldType)} {field.Name} = {this.GetTypeDefaultValue(field.FieldType)}");
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
writer.WriteSeparator().Write($"{this.GetTypeFullyResolvedName(property.PropertyType)} {property.Name} = {this.GetTypeDefaultValue(property.PropertyType)}");
|
||||||
|
|
||||||
|
writer.WriteLine(")");
|
||||||
|
writer.WriteLine('{').Indent();
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
writer.WriteLine($"this.{field.Name} = {field.Name};");
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
writer.WriteLine($"this.{property.Name} = {property.Name};");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate C# for any types that belong to this one.
|
||||||
|
for (int i = 0; i < types.Count; i++)
|
||||||
|
if (types[i].DeclaringType == type)
|
||||||
|
GenerateCSharpIncludedType(types[i], types, writer);
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# for a field inside an included type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
protected internal virtual void GenerateCSharpIncludedField(FieldInfo field, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
if (field.DeclaringType != null && field.DeclaringType.IsEnum)
|
||||||
|
writer.WriteLine($"{field.Name} = {field.GetRawConstantValue()},");
|
||||||
|
else
|
||||||
|
writer.WriteLine($"public {this.GetTypeFullyResolvedName(field.FieldType)} {field.Name};");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# for a property inside an included type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
protected internal virtual void GenerateCSharpIncludedProperty(PropertyInfo property, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
writer.WriteLine($"public {this.GetTypeFullyResolvedName(property.PropertyType)} {property.Name} {{ get; set; }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# for a set of route groups.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groups"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
protected internal virtual void GenerateCSharpRouteGroups(Dictionary<string, List<Route>> groups, CodeWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var group in groups)
|
||||||
|
this.GenerateCSharpRouteGroup(group.Key, group.Value, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates C# for a given route group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
protected internal virtual void GenerateCSharpRouteGroup(string name, List<Route> routes, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
//Output the class header
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteLine($"public class {name}").WriteLine("{").Indent();
|
||||||
|
else
|
||||||
|
writer.WriteLine($"public class {name}Api").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Output the client instance
|
||||||
|
if (!this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine($"public {this.ClientName} Client;");
|
||||||
|
|
||||||
|
|
||||||
|
//Output the constuctor if not static.
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"public {name}Api({this.ClientName} client)").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine("this.Client = client;");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Output all the route functions.
|
||||||
|
foreach (var route in routes)
|
||||||
|
this.GenerateCSharpRouteFunction(route, writer);
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a C# function for a given route.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="route"></param>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
/// <exception cref="NotSupportedException"></exception>
|
||||||
|
protected internal virtual void GenerateCSharpRouteFunction(Route route, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
var methodInfo = route.GetTarget().GetMethodInfo();
|
||||||
|
|
||||||
|
var routeName = methodInfo.GetCustomAttribute<RouteName>();
|
||||||
|
|
||||||
|
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
|
||||||
|
|
||||||
|
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
|
||||||
|
|
||||||
|
//Generate the route function header
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.Write($"public static {(routeResponse == null ? "void" : (routeResponse.Dynamic ? "dynamic" : this.GetTypeFullyResolvedName(routeResponse.ResponseType)))} {(routeName == null ? methodInfo.Name : routeName.Name)}(");
|
||||||
|
else
|
||||||
|
writer.Write($"public {(routeResponse == null ? "void" : (routeResponse.Dynamic ? "dynamic" : this.GetTypeFullyResolvedName(routeResponse.ResponseType)))} {(routeName == null ? methodInfo.Name : routeName.Name)}(");
|
||||||
|
|
||||||
|
//Generate the functions parameters
|
||||||
|
var parameters = methodInfo.GetParameters();
|
||||||
|
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write(this.GetTypeFullyResolvedName(parameters[i].ParameterType)).Write(" ").Write(parameters[i].Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeRequest != null)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
|
||||||
|
if (routeRequest.Dynamic)
|
||||||
|
writer.Write("dynamic");
|
||||||
|
else
|
||||||
|
writer.Write(this.GetTypeFullyResolvedName(routeRequest.RequestType));
|
||||||
|
|
||||||
|
writer.Write(" request");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeResponse != null && routeResponse.Parameter)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
|
||||||
|
if (routeResponse.Dynamic)
|
||||||
|
writer.Write("dynamic");
|
||||||
|
else
|
||||||
|
writer.Write(this.GetTypeFullyResolvedName(routeResponse.ResponseType));
|
||||||
|
|
||||||
|
writer.Write(" input");
|
||||||
|
}
|
||||||
|
|
||||||
|
//If JSON, add a compress parameter to control compressing the content.
|
||||||
|
if (routeRequest != null && routeRequest.Json)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write("bool compress = false");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine(")").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Generate the message code
|
||||||
|
writer.WriteBreak().Write($"var message = new System.Net.Http.HttpRequestMessage(");
|
||||||
|
|
||||||
|
switch (route.Method.ToLower())
|
||||||
|
{
|
||||||
|
case "post":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Post");
|
||||||
|
break;
|
||||||
|
case "get":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Get");
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Delete");
|
||||||
|
break;
|
||||||
|
case "put":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Put");
|
||||||
|
break;
|
||||||
|
case "options":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Options");
|
||||||
|
break;
|
||||||
|
case "patch":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Patch");
|
||||||
|
break;
|
||||||
|
case "head":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Head");
|
||||||
|
break;
|
||||||
|
case "trace":
|
||||||
|
writer.Write("System.Net.Http.HttpMethod.Trace");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException("Unsupport route method:" + route.Method);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteSeparator().Write('$').Write('"').Write($"{{{this.ClientName}.BaseUrl}}");
|
||||||
|
else
|
||||||
|
writer.WriteSeparator().Write('$').Write('"').Write("{this.Client.BaseUrl}");
|
||||||
|
|
||||||
|
//Reconstruct the route syntax into a request url.
|
||||||
|
var components = route.Syntax.Split('/');
|
||||||
|
int argumentIndex = 0;
|
||||||
|
foreach (var component in components)
|
||||||
|
{
|
||||||
|
if (writer.Peek() != '/')
|
||||||
|
writer.Write('/');
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(component))
|
||||||
|
{
|
||||||
|
if (component.StartsWith("{"))
|
||||||
|
{
|
||||||
|
writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}");
|
||||||
|
}
|
||||||
|
else if (component == "*")
|
||||||
|
{
|
||||||
|
writer.Write("*");
|
||||||
|
}
|
||||||
|
else if (component == "**")
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write('"').WriteLine(");");
|
||||||
|
|
||||||
|
//Invoke the request handler if needed.
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler?.Invoke(message);");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine($"this.Client.RequestHandler?.Invoke(message);");
|
||||||
|
|
||||||
|
//Add the request content if any.
|
||||||
|
if (routeRequest != null)
|
||||||
|
{
|
||||||
|
if (routeRequest.RequestType.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("request.Seek(0, System.IO.SeekOrigin.Begin);");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("message.Content = new System.Net.Http.StreamContent(request);");
|
||||||
|
}
|
||||||
|
else if (routeRequest.Json)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (compress)").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine("using (var uncompressedStream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(request))))");
|
||||||
|
writer.WriteLine("{").Indent();
|
||||||
|
writer.WriteLine("using (var compressedStream = new System.IO.MemoryStream())");
|
||||||
|
writer.WriteLine("{").Indent();
|
||||||
|
writer.WriteLine("using (var gzipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Compress, true))");
|
||||||
|
writer.Indent().WriteLine("uncompressedStream.CopyTo(gzipStream);").Outdent();
|
||||||
|
|
||||||
|
writer.WriteBreak();
|
||||||
|
writer.WriteLine("message.Content = new System.Net.Http.ByteArrayContent(compressedStream.ToArray());");
|
||||||
|
writer.WriteLine("message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Application.Json);");
|
||||||
|
writer.WriteLine("message.Content.Headers.ContentEncoding.Add(\"gzip\");");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
writer.WriteLine("else").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("message.Content = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request));");
|
||||||
|
writer.WriteBreak().WriteLine("message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Application.Json);");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("message.Content = new System.Net.Http.StringContent(request.ToString());");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate the response code
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine($"var response = {this.ClientName}.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine("var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);");
|
||||||
|
|
||||||
|
//Handle the response
|
||||||
|
if (routeResponse != null)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (response.IsSuccessStatusCode)").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
if (routeResponse.ResponseType != null && routeResponse.ResponseType.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
if (routeResponse.Parameter)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("response.Content.CopyToAsync(input).GetAwaiter().GetResult();");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("return input;");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"var stream = new {this.GetTypeFullyResolvedName(routeResponse.ResponseType)}();");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("response.Content.CopyToAsync(stream).GetAwaiter().GetResult();");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("return stream;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("if (string.IsNullOrEmpty(content))").Indent().WriteLine("return default;").Outdent();
|
||||||
|
|
||||||
|
if (routeResponse.Json)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"return Newtonsoft.Json.JsonConvert.DeserializeObject<{(routeResponse.Dynamic ? "dynamic" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))}>(content);");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (Type.GetTypeCode(routeResponse.ResponseType))
|
||||||
|
{
|
||||||
|
case TypeCode.Boolean:
|
||||||
|
writer.WriteBreak().WriteLine("return bool.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Byte:
|
||||||
|
writer.WriteBreak().WriteLine("return byte.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Char:
|
||||||
|
writer.WriteBreak().WriteLine("return content[0];");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.DateTime:
|
||||||
|
writer.WriteBreak().WriteLine("return System.DateTime.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Decimal:
|
||||||
|
writer.WriteBreak().WriteLine("return decimal.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Double:
|
||||||
|
writer.WriteBreak().WriteLine("return double.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Int16:
|
||||||
|
writer.WriteBreak().WriteLine("return short.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Int32:
|
||||||
|
writer.WriteBreak().WriteLine("return int.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Int64:
|
||||||
|
writer.WriteBreak().WriteLine("return long.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.SByte:
|
||||||
|
writer.WriteBreak().WriteLine("return sbyte.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Single:
|
||||||
|
writer.WriteBreak().WriteLine("return float.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.String:
|
||||||
|
writer.WriteBreak().WriteLine("return content;");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.UInt16:
|
||||||
|
writer.WriteBreak().WriteLine("return ushort.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.UInt32:
|
||||||
|
writer.WriteBreak().WriteLine("return uint.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.UInt64:
|
||||||
|
writer.WriteBreak().WriteLine("return ulong.Parse(content);");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Object:
|
||||||
|
throw new NotSupportedException("RouteResponse has JSON=false but ResponseType is an object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}").WriteLine("else").WriteLine("{").Indent();
|
||||||
|
writer.WriteLine(@"throw new System.Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (!response.IsSuccessStatusCode)").Indent();
|
||||||
|
writer.WriteLine(@"throw new System.Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close off the route function.
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
414
Rest.Net/RestClientGenerator.cs
Normal file
414
Rest.Net/RestClientGenerator.cs
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class that can take a set of routes and generate code
|
||||||
|
/// for a client that can be used to interact with them.
|
||||||
|
/// </summary>
|
||||||
|
public class RestClientGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the client to generate.
|
||||||
|
/// </summary>
|
||||||
|
public string ClientName = "Client";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Rest Client from a RouteListener and returns the code.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string Generate(RouteListener listener)
|
||||||
|
{
|
||||||
|
return this.Generate(listener.Routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Rest Client from a set of routes and returns the code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual string Generate(List<Route> routes)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not a given type belongs to DotNet.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual bool IsTypeDotNet(Type type)
|
||||||
|
{
|
||||||
|
var assemblyName = type.Assembly.GetName().Name;
|
||||||
|
|
||||||
|
return assemblyName == "System.Private.CoreLib" || assemblyName == "System.Net.Primitives";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all the dependencies for a given type and returns them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="dependencies"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual HashSet<Type> FindTypeDependencies(Type type, HashSet<Type> dependencies = null)
|
||||||
|
{
|
||||||
|
if (dependencies == null)
|
||||||
|
dependencies = new HashSet<Type>();
|
||||||
|
else if (dependencies.Contains(type))
|
||||||
|
return dependencies;
|
||||||
|
else if (!this.IsTypeDotNet(type))
|
||||||
|
dependencies.Add(type);
|
||||||
|
|
||||||
|
var arguments = type.GetGenericArguments();
|
||||||
|
|
||||||
|
if (arguments != null)
|
||||||
|
foreach (var argument in arguments)
|
||||||
|
if (argument != type)
|
||||||
|
this.FindTypeDependencies(argument, dependencies);
|
||||||
|
|
||||||
|
if (this.IsTypeDotNet(type))
|
||||||
|
return dependencies;
|
||||||
|
|
||||||
|
dependencies.Add(type);
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
return dependencies;
|
||||||
|
|
||||||
|
this.FindTypeDependencies(type.BaseType, dependencies);
|
||||||
|
|
||||||
|
var fields = type.GetFields();
|
||||||
|
|
||||||
|
if (fields != null)
|
||||||
|
foreach (var field in fields)
|
||||||
|
if (field.IsPublic && !field.IsSpecialName && field.FieldType != type)
|
||||||
|
this.FindTypeDependencies(field.FieldType, dependencies);
|
||||||
|
|
||||||
|
var properties = type.GetProperties();
|
||||||
|
|
||||||
|
if (properties != null)
|
||||||
|
foreach (var property in properties)
|
||||||
|
if (!property.IsSpecialName && property.GetSetMethod() != null && property.GetGetMethod() != null && property.PropertyType != type)
|
||||||
|
this.FindTypeDependencies(property.PropertyType, dependencies);
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all the types that a given route depends on to function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="route"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual List<Type> FindRouteDependencies(Route route)
|
||||||
|
{
|
||||||
|
var dependencies = new HashSet<Type>();
|
||||||
|
|
||||||
|
var methodInfo = route.GetTarget().GetMethodInfo();
|
||||||
|
|
||||||
|
//If this route is hidden, return nothing.
|
||||||
|
if (methodInfo.GetCustomAttribute<RouteHidden>() != null)
|
||||||
|
return dependencies.ToList();
|
||||||
|
|
||||||
|
if (methodInfo != null)
|
||||||
|
{
|
||||||
|
var parameters = methodInfo.GetParameters();
|
||||||
|
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
var types = this.FindTypeDependencies(parameters[i].ParameterType);
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
if (!dependencies.Contains(type))
|
||||||
|
dependencies.Add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
|
||||||
|
|
||||||
|
if (routeRequest != null && routeRequest.RequestType != null)
|
||||||
|
{
|
||||||
|
var types = this.FindTypeDependencies(routeRequest.RequestType);
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
if (!dependencies.Contains(type))
|
||||||
|
dependencies.Add(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
|
||||||
|
|
||||||
|
if (routeResponse != null && routeResponse.ResponseType != null)
|
||||||
|
{
|
||||||
|
var types = this.FindTypeDependencies(routeResponse.ResponseType);
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
if (!dependencies.Contains(type))
|
||||||
|
dependencies.Add(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeIncludes = methodInfo.GetCustomAttributes<RouteInclude>();
|
||||||
|
|
||||||
|
if (routeIncludes != null)
|
||||||
|
{
|
||||||
|
foreach (var include in routeIncludes)
|
||||||
|
{
|
||||||
|
var types = this.FindTypeDependencies(include.Type);
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
if (!dependencies.Contains(type))
|
||||||
|
dependencies.Add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all the types that a given set of routes depend on to function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual List<Type> FindRoutesDependencies(List<Route> routes)
|
||||||
|
{
|
||||||
|
var dependencies = new HashSet<Type>();
|
||||||
|
|
||||||
|
foreach (var route in routes)
|
||||||
|
{
|
||||||
|
var types = this.FindRouteDependencies(route);
|
||||||
|
|
||||||
|
if (types != null)
|
||||||
|
for (int i = 0; i < types.Count; i++)
|
||||||
|
if (!dependencies.Contains(types[i]))
|
||||||
|
dependencies.Add(types[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the fully resolve name for a given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual string GetTypeFullyResolvedName(Type type)
|
||||||
|
{
|
||||||
|
if (this.IsTypeDotNet(type))
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (typeCode != TypeCode.Object && !type.IsEnum)
|
||||||
|
{
|
||||||
|
switch (typeCode)
|
||||||
|
{
|
||||||
|
case TypeCode.Boolean:
|
||||||
|
return "bool";
|
||||||
|
case TypeCode.Byte:
|
||||||
|
return "byte";
|
||||||
|
case TypeCode.Char:
|
||||||
|
return "char";
|
||||||
|
case TypeCode.DateTime:
|
||||||
|
return "System.DateTime";
|
||||||
|
case TypeCode.Decimal:
|
||||||
|
return "decimal";
|
||||||
|
case TypeCode.Double:
|
||||||
|
return "double";
|
||||||
|
case TypeCode.Int16:
|
||||||
|
return "short";
|
||||||
|
case TypeCode.Int32:
|
||||||
|
return "int";
|
||||||
|
case TypeCode.Int64:
|
||||||
|
return "long";
|
||||||
|
case TypeCode.SByte:
|
||||||
|
return "sbyte";
|
||||||
|
case TypeCode.Single:
|
||||||
|
return "float";
|
||||||
|
case TypeCode.String:
|
||||||
|
return "string";
|
||||||
|
case TypeCode.UInt16:
|
||||||
|
return "ushort";
|
||||||
|
case TypeCode.UInt32:
|
||||||
|
return "uint";
|
||||||
|
case TypeCode.UInt64:
|
||||||
|
return "ulong";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append(type.Namespace);
|
||||||
|
|
||||||
|
int genericSymbol = type.Name.IndexOf('`');
|
||||||
|
|
||||||
|
if (genericSymbol == -1)
|
||||||
|
builder.Append(".").Append(type.Name);
|
||||||
|
else
|
||||||
|
builder.Append(".").Append(type.Name.Substring(0, genericSymbol));
|
||||||
|
|
||||||
|
var genericArguments = type.GetGenericArguments();
|
||||||
|
|
||||||
|
if (genericArguments != null && genericArguments.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append("<");
|
||||||
|
|
||||||
|
for (int i = 0; i < genericArguments.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
builder.Append(", ");
|
||||||
|
|
||||||
|
builder.Append(this.GetTypeFullyResolvedName(genericArguments[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
var newName = type.GetCustomAttribute<RouteTypeName>();
|
||||||
|
|
||||||
|
if (newName != null)
|
||||||
|
{
|
||||||
|
builder.Append(newName.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int genericSymbol = type.Name.IndexOf('`');
|
||||||
|
|
||||||
|
if (genericSymbol == -1)
|
||||||
|
builder.Append(type.Name);
|
||||||
|
else
|
||||||
|
builder.Append(type.Name.Substring(0, genericSymbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericArguments = type.GetGenericArguments();
|
||||||
|
|
||||||
|
if (genericArguments != null && genericArguments.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append("<");
|
||||||
|
|
||||||
|
for (int i = 0; i < genericArguments.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
builder.Append(", ");
|
||||||
|
|
||||||
|
builder.Append(this.GetTypeFullyResolvedName(genericArguments[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the default value for a given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual string GetTypeDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
return "0";
|
||||||
|
else if (typeCode == TypeCode.String || typeCode == TypeCode.Object)
|
||||||
|
return "null";
|
||||||
|
else if (typeCode == TypeCode.Char)
|
||||||
|
return "'\\0'";
|
||||||
|
else if (typeCode == TypeCode.Boolean)
|
||||||
|
return "false";
|
||||||
|
else
|
||||||
|
return Activator.CreateInstance(type).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all the route groupings from a set of routes and returns those groups.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual Dictionary<string, List<Route>> FindRouteGroups(List<Route> routes)
|
||||||
|
{
|
||||||
|
var groups = new Dictionary<string, List<Route>>();
|
||||||
|
|
||||||
|
foreach (var route in routes)
|
||||||
|
{
|
||||||
|
var methodInfo = route.GetTarget().GetMethodInfo();
|
||||||
|
|
||||||
|
//Skip any routes that are hidden.
|
||||||
|
if (methodInfo.GetCustomAttribute<RouteHidden>() != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var routeGroup = methodInfo.GetCustomAttribute<RouteGroup>();
|
||||||
|
|
||||||
|
//If there wasn't a route group on the method, see if there is one on the declaring type.
|
||||||
|
if (routeGroup == null)
|
||||||
|
routeGroup = methodInfo.DeclaringType.GetCustomAttribute<RouteGroup>();
|
||||||
|
|
||||||
|
//Group this route.
|
||||||
|
string group = (routeGroup != null ? routeGroup.Name : methodInfo.DeclaringType.Name);
|
||||||
|
|
||||||
|
if (groups.ContainsKey(group))
|
||||||
|
groups[group].Add(route);
|
||||||
|
else
|
||||||
|
groups.Add(group, new List<Route>() { route });
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts a list of types by the order of which should be declared first. Useful
|
||||||
|
/// for languages that require classes to be dependency ordered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="types"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual List<Type> SortTypesByDependencies(List<Type> types)
|
||||||
|
{
|
||||||
|
var dependencies = new Dictionary<Type, HashSet<Type>>();
|
||||||
|
|
||||||
|
//Find all the inner dependencies of the types
|
||||||
|
foreach (var type in types)
|
||||||
|
if (!dependencies.ContainsKey(type))
|
||||||
|
dependencies.Add(type, FindTypeDependencies(type).ToHashSet());
|
||||||
|
|
||||||
|
//Now begin sorting these dependencies
|
||||||
|
var sorted = new LinkedList<KeyValuePair<Type, HashSet<Type>>>();
|
||||||
|
foreach (var dependency in dependencies)
|
||||||
|
{
|
||||||
|
var best = sorted.Last;
|
||||||
|
var curr = sorted.Last;
|
||||||
|
while (curr != null)
|
||||||
|
{
|
||||||
|
if (dependency.Value.Contains(curr.Value.Key))
|
||||||
|
break;
|
||||||
|
else if (curr.Value.Value.Contains(dependency.Key))
|
||||||
|
best = curr.Previous;
|
||||||
|
|
||||||
|
curr = curr.Previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best == null)
|
||||||
|
sorted.AddFirst(dependency);
|
||||||
|
else
|
||||||
|
best.List.AddAfter(best, dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted.Select(value => value.Key).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
833
Rest.Net/RestJavascriptClientGenerator.cs
Normal file
833
Rest.Net/RestJavascriptClientGenerator.cs
Normal file
@ -0,0 +1,833 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a rest client generator that can generate a javascript client.
|
||||||
|
/// </summary>
|
||||||
|
public class RestJavascriptClientGenerator : RestClientGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not to generate static code, if true the client will be static.
|
||||||
|
/// </summary>
|
||||||
|
public bool StaticCode = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not to use Json Property names instead of the Field/Property names.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseJsonNames = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Javascript Client from a given set of routes and returns it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string Generate(List<Route> routes)
|
||||||
|
{
|
||||||
|
var includedTypes = this.FindRoutesDependencies(routes);
|
||||||
|
|
||||||
|
var routeGroups = this.FindRouteGroups(routes);
|
||||||
|
|
||||||
|
var writer = new CodeWriter();
|
||||||
|
|
||||||
|
writer.WriteLine($"//Generated using MontoyaTech.Rest.Net - {DateTime.Now.ToShortDateString()}");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"class {this.ClientName} {{").Indent();
|
||||||
|
|
||||||
|
//Create the base url field
|
||||||
|
writer.WriteBreak().WriteLine("/** @type {string} */");
|
||||||
|
writer.WriteAssert(this.StaticCode, "static ").WriteLine("BaseUrl = null;");
|
||||||
|
|
||||||
|
//Create the url handler field
|
||||||
|
writer.WriteBreak().WriteLine("/** @type {Function} */");
|
||||||
|
writer.WriteAssert(this.StaticCode, "static ").WriteLine("UrlHandler = null;");
|
||||||
|
|
||||||
|
//Create the request handler field.
|
||||||
|
writer.WriteBreak().WriteLine("/** @type {Function} */");
|
||||||
|
writer.WriteAssert(this.StaticCode, "static ").WriteLine("RequestHandler = null;");
|
||||||
|
|
||||||
|
//Create fields foreach route group so they can be accessed.
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
foreach (var group in routeGroups)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"/** @type {{{group.Key}Api}} */");
|
||||||
|
writer.WriteLine($"{group.Key} = null;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the client constructor or init method
|
||||||
|
if (this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
|
||||||
|
writer.WriteLine("@param {string} baseUrl Base url of the server to make requests against.");
|
||||||
|
writer.WriteLine("@param {Function} urlHandler An optional function to process request urls before they are sent. This must return the url.");
|
||||||
|
writer.WriteLine("@param {Function} requestHandler An optional function to process requests before they are sent. This must return the request.");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.Write("static Init(baseUrl, urlHandler = null, requestHandler = null) ").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Make sure the baseUrl isn't null or whitespace
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
|
||||||
|
writer.WriteLine("throw 'baseUrl cannot be null or empty.';");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//If the baseUrl ends with a /, remove it.
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl.endsWith('/')) {").Indent();
|
||||||
|
writer.WriteLine("baseUrl = baseUrl.substring(0, baseUrl.length - 1);");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Store the baseUrl
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;");
|
||||||
|
|
||||||
|
//Store the urlHandler
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.UrlHandler = urlHandler;");
|
||||||
|
|
||||||
|
//Store the requestHandler
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler = requestHandler;");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
|
||||||
|
writer.WriteLine("@param {string} baseUrl Base url of the server to make requests against.");
|
||||||
|
writer.WriteLine("@param {Function} urlHandler An optional function to process request urls before they are sent. This must return the url.");
|
||||||
|
writer.WriteLine("@param {Function} requestHandler An optional function to process requests before they are sent. This must return the request.");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.Write("constructor(baseUrl, urlHandler = null, requestHandler = null) ").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Make sure the baseUrl isn't null or whitespace
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
|
||||||
|
writer.WriteLine("throw 'baseUrl cannot be null or empty.';");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//If the baseUrl ends with a /, remove it.
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl.endsWith('/')) {").Indent();
|
||||||
|
writer.WriteLine("baseUrl = baseUrl.substring(0, baseUrl.length - 1);");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Store the baseUrl
|
||||||
|
writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;");
|
||||||
|
|
||||||
|
//Store the urlHandler
|
||||||
|
writer.WriteBreak().WriteLine($"this.UrlHandler = urlHandler;");
|
||||||
|
|
||||||
|
//Store the request handler
|
||||||
|
writer.WriteBreak().WriteLine("this.RequestHandler = requestHandler;");
|
||||||
|
|
||||||
|
//Init all the route group fields
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
foreach (var group in routeGroups)
|
||||||
|
writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
this.GenerateJavascriptRouteGroups(routeGroups, writer);
|
||||||
|
|
||||||
|
includedTypes = this.SortTypesByDependencies(includedTypes);
|
||||||
|
|
||||||
|
this.GenerateJavascriptIncludedTypes(includedTypes, writer);
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"window.{this.ClientName} = {this.ClientName};");
|
||||||
|
|
||||||
|
//Export the Client and all the Types
|
||||||
|
writer.WriteBreak().WriteLine("export {").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine($"{this.ClientName},");
|
||||||
|
|
||||||
|
foreach (var type in includedTypes)
|
||||||
|
{
|
||||||
|
var newName = type.GetCustomAttribute<RouteTypeName>();
|
||||||
|
|
||||||
|
writer.WriteLine($"{(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)},");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("};");
|
||||||
|
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal override string GetTypeFullyResolvedName(Type type)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (typeof(Array).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
return $"Array<{this.GetTypeFullyResolvedName(type.GetElementType())}>";
|
||||||
|
}
|
||||||
|
else if (typeof(IList).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append("Array");
|
||||||
|
|
||||||
|
var genericArguments = type.GetGenericArguments();
|
||||||
|
|
||||||
|
if (genericArguments != null && genericArguments.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append("<");
|
||||||
|
|
||||||
|
for (int i = 0; i < genericArguments.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
builder.Append(", ");
|
||||||
|
|
||||||
|
builder.Append(this.GetTypeFullyResolvedName(genericArguments[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
else if (type.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
return "Blob";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.String || typeCode == TypeCode.Char)
|
||||||
|
{
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.Boolean)
|
||||||
|
{
|
||||||
|
return "boolean";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.DBNull)
|
||||||
|
{
|
||||||
|
return "object";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.DateTime)
|
||||||
|
{
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.Object || type.IsEnum)
|
||||||
|
{
|
||||||
|
if (type.DeclaringType != null && !IsTypeDotNet(type.DeclaringType))
|
||||||
|
return $"{this.GetTypeFullyResolvedName(type.DeclaringType)}{base.GetTypeFullyResolvedName(type)}";
|
||||||
|
else
|
||||||
|
return base.GetTypeFullyResolvedName(type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "number";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal override string GetTypeDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (typeCode == TypeCode.Char || typeCode == TypeCode.DateTime)
|
||||||
|
return "null";
|
||||||
|
|
||||||
|
return base.GetTypeDefaultValue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal string EscapeName(string name)
|
||||||
|
{
|
||||||
|
if (name != null)
|
||||||
|
{
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "arguments":
|
||||||
|
case "await":
|
||||||
|
case "break":
|
||||||
|
case "case":
|
||||||
|
case "catch":
|
||||||
|
case "class":
|
||||||
|
case "const":
|
||||||
|
case "continue":
|
||||||
|
case "debugger":
|
||||||
|
case "default":
|
||||||
|
case "delete":
|
||||||
|
case "do":
|
||||||
|
case "double":
|
||||||
|
case "else":
|
||||||
|
case "enum":
|
||||||
|
case "eval":
|
||||||
|
case "export":
|
||||||
|
case "extends":
|
||||||
|
case "false":
|
||||||
|
case "finally":
|
||||||
|
case "for":
|
||||||
|
case "function":
|
||||||
|
case "goto":
|
||||||
|
case "if":
|
||||||
|
case "import":
|
||||||
|
case "in":
|
||||||
|
case "instanceof":
|
||||||
|
case "interface":
|
||||||
|
case "let":
|
||||||
|
case "new":
|
||||||
|
case "null":
|
||||||
|
case "return":
|
||||||
|
case "static":
|
||||||
|
case "super":
|
||||||
|
case "switch":
|
||||||
|
case "this":
|
||||||
|
case "throw":
|
||||||
|
case "true":
|
||||||
|
case "try":
|
||||||
|
case "typeof":
|
||||||
|
case "var":
|
||||||
|
case "void":
|
||||||
|
case "while":
|
||||||
|
case "with":
|
||||||
|
case "yield":
|
||||||
|
return "_" + name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedTypes(List<Type> types, CodeWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
bool subType = false;
|
||||||
|
|
||||||
|
//See if this type belongs to another type in the list.
|
||||||
|
if (type.DeclaringType != null)
|
||||||
|
for (int i = 0; i < types.Count && !subType; i++)
|
||||||
|
if (type.DeclaringType == types[i])
|
||||||
|
subType = true;
|
||||||
|
|
||||||
|
//If not, generate the code for this type.
|
||||||
|
if (!subType)
|
||||||
|
this.GenerateJavascriptIncludedType(type, types, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedType(Type type, List<Type> types, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
var newName = type.GetCustomAttribute<RouteTypeName>();
|
||||||
|
|
||||||
|
writer.Write($"class {(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)}");
|
||||||
|
|
||||||
|
if (!type.IsEnum && !(IsTypeDotNet(type.BaseType) && type.BaseType.Name == "Object"))
|
||||||
|
writer.Write(" extends ").Write(this.GetTypeFullyResolvedName(type.BaseType));
|
||||||
|
|
||||||
|
writer.WriteLine(" {").Indent();
|
||||||
|
|
||||||
|
FieldInfo[] fields;
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
|
||||||
|
else
|
||||||
|
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
this.GenerateJavascriptIncludedField(field, writer);
|
||||||
|
|
||||||
|
var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public).Where(property => property.GetSetMethod() != null && property.GetGetMethod() != null).ToArray();
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
this.GenerateJavascriptIncludedProperty(property, writer);
|
||||||
|
|
||||||
|
//Generate a helper constructor
|
||||||
|
if (!type.IsEnum)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
|
||||||
|
//Docuemnt the fields
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(field.FieldType)}}} {EscapeName(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)}");
|
||||||
|
else
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(field.FieldType)}}} {EscapeName(field.Name)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Document the properties
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(property.PropertyType)}}} {EscapeName(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)}");
|
||||||
|
else
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(property.PropertyType)}}} {EscapeName(property.Name)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.Write("constructor(");
|
||||||
|
|
||||||
|
//Write the default fields
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteSeparator().Write(EscapeName(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)).Write(" = ").Write(this.GetTypeDefaultValue(field.FieldType));
|
||||||
|
else
|
||||||
|
writer.WriteSeparator().Write(EscapeName(field.Name)).Write(" = ").Write(this.GetTypeDefaultValue(field.FieldType));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write the default properties
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteSeparator().Write(EscapeName(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)).Write(" = ").Write(this.GetTypeDefaultValue(property.PropertyType));
|
||||||
|
else
|
||||||
|
writer.WriteSeparator().Write(EscapeName(property.Name)).Write(" = ").Write(this.GetTypeDefaultValue(property.PropertyType));
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine(") {").Indent();
|
||||||
|
|
||||||
|
//Init the default fields
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.Write("this.").Write(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name).Write(" = ").Write(EscapeName(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)).WriteLine(";");
|
||||||
|
else
|
||||||
|
writer.Write("this.").Write(field.Name).Write(" = ").Write(EscapeName(field.Name)).WriteLine(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Init the default properties
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.Write("this.").Write(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name).Write(" = ").Write(EscapeName(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)).WriteLine(";");
|
||||||
|
else
|
||||||
|
writer.Write("this.").Write(property.Name).Write(" = ").Write(EscapeName(property.Name)).WriteLine(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//If this is an enum, generate a GetNames/GetValues/GetName function.
|
||||||
|
if (type.IsEnum)
|
||||||
|
{
|
||||||
|
var names = Enum.GetNames(type);
|
||||||
|
|
||||||
|
var values = Enum.GetValues(type);
|
||||||
|
|
||||||
|
//GetName function
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Returns the name of a value in this enum. Returns null if the value is invalid.");
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine("@param {number} value The value to get the name of in this enum.");
|
||||||
|
writer.WriteLine("@returns {string} The name for the given value.");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.WriteLine("static GetName(value) {").Indent();
|
||||||
|
writer.WriteLine("switch (value) {").Indent();
|
||||||
|
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
writer.WriteLine($"case {Convert.ToInt32(values.GetValue(i))}: return `{names.GetValue(i)}`;");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
writer.WriteLine("return null;");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//GetValue function
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Returns the value for a name in this enum. Returns null if the name is invalid.");
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine("@param {string} name The name of the item in this enum to get the value of.");
|
||||||
|
writer.WriteLine("@returns {number} The value associated with this name in this enum.");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.WriteLine("static GetValue(name) {").Indent();
|
||||||
|
writer.WriteLine("if (!name) {").Indent();
|
||||||
|
writer.WriteLine("return null;");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
writer.WriteLine("switch (name.toLowerCase().trim()) {").Indent();
|
||||||
|
|
||||||
|
for (int i = 0; i < names.Length; i++)
|
||||||
|
writer.WriteLine($"case '{names.GetValue(i).ToString().ToLower()}': return {Convert.ToInt32(values.GetValue(i))};");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
writer.WriteLine("return null;");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//GetNames function
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Returns the names of this enums values as an array of strings.");
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine("@returns {Array<string>}");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.WriteLine("static GetNames() {").Indent();
|
||||||
|
writer.WriteLine("return [").Indent();
|
||||||
|
|
||||||
|
foreach (var name in names)
|
||||||
|
writer.WriteLine($"'{name}',");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("];");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//GetValues function
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Returns the values of this enum as an arrray of numbers.");
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine("@returns {Array<number>}");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.WriteLine("static GetValues() {").Indent();
|
||||||
|
writer.WriteLine("return [").Indent();
|
||||||
|
|
||||||
|
foreach (var value in values)
|
||||||
|
writer.WriteLine($"{Convert.ToInt32(value)},");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("];");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("Returns the names and values of this enum in an array.");
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine("@returns {Array<object>} Where each element is an object and has a name and value field. Ex: { name: '', value: 0 }");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.WriteLine("static GetNamesValues() {").Indent();
|
||||||
|
writer.WriteLine("return [").Indent();
|
||||||
|
|
||||||
|
for (int i = 0; i < names.Length; i++)
|
||||||
|
writer.WriteLine($"{{ name: `{names[i]}`, value: {Convert.ToInt32(values.GetValue(i))} }},");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("];");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close off the class
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.{(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)} = {(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)};");
|
||||||
|
|
||||||
|
//Generate any types that belong to this.
|
||||||
|
for (int i = 0; i < types.Count; i++)
|
||||||
|
if (types[i].DeclaringType == type)
|
||||||
|
GenerateJavascriptIncludedType(types[i], types, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedField(FieldInfo field, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
if (field.DeclaringType != null && field.DeclaringType.IsEnum)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.DeclaringType)}}} */");
|
||||||
|
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteLine($"static {(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)} = {field.GetRawConstantValue()};");
|
||||||
|
else
|
||||||
|
writer.WriteLine($"static {field.Name} = {field.GetRawConstantValue()};");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.FieldType)}}} */");
|
||||||
|
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteLine($"{(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)} = {GetTypeDefaultValue(field.FieldType)};");
|
||||||
|
else
|
||||||
|
writer.WriteLine($"{field.Name} = {GetTypeDefaultValue(field.FieldType)};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedProperty(PropertyInfo property, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(property.PropertyType)}}} */");
|
||||||
|
|
||||||
|
if (this.UseJsonNames)
|
||||||
|
writer.WriteLine($"{(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)} = {GetTypeDefaultValue(property.PropertyType)};");
|
||||||
|
else
|
||||||
|
writer.WriteLine($"{property.Name} = {GetTypeDefaultValue(property.PropertyType)};");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptRouteGroups(Dictionary<string, List<Route>> groups, CodeWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var group in groups)
|
||||||
|
this.GenerateJavascriptRouteGroup(group.Key, group.Value, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptRouteGroup(string name, List<Route> routes, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
//Output the class header
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteLine($"class {name} {{").Indent();
|
||||||
|
else
|
||||||
|
writer.WriteLine($"class {name}Api {{").Indent();
|
||||||
|
|
||||||
|
//Output the client instance
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"/** @type {{{this.ClientName}}} */");
|
||||||
|
writer.WriteLine("Client = null;");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Output the constuctor if not static.
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
writer.WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine($"@param {{{this.ClientName}}} client");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
|
||||||
|
writer.Write($"constructor(client) ").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine("this.Client = client;");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Output all the route functions.
|
||||||
|
foreach (var route in routes)
|
||||||
|
this.GenerateJavascriptRouteFunction(route, writer);
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Expose this route group on the client.
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.{name} = {name};");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.{name}Api = {name}Api;");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptRouteFunction(Route route, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
var methodInfo = route.GetTarget().GetMethodInfo();
|
||||||
|
|
||||||
|
var routeName = methodInfo.GetCustomAttribute<RouteName>();
|
||||||
|
|
||||||
|
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
|
||||||
|
|
||||||
|
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
|
||||||
|
|
||||||
|
var parameters = methodInfo.GetParameters();
|
||||||
|
|
||||||
|
//Generate the function jsdoc tags
|
||||||
|
writer.WriteLine("/**").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine("@function");
|
||||||
|
writer.WriteLine("@async");
|
||||||
|
writer.WriteLine($"@name {(routeName == null ? methodInfo.Name : routeName.Name)}");
|
||||||
|
|
||||||
|
//Generate parameter docs
|
||||||
|
if (parameters != null)
|
||||||
|
for (int i = 1; i < parameters.Length; i++)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(parameters[i].ParameterType)}}} {parameters[i].Name}");
|
||||||
|
|
||||||
|
//Generate request doc if any
|
||||||
|
if (routeRequest != null)
|
||||||
|
writer.WriteLine($"@param {{{(routeRequest.Dynamic ? "Any" : this.GetTypeFullyResolvedName(routeRequest.RequestType))}}} body");
|
||||||
|
|
||||||
|
//Generate response doc if any
|
||||||
|
if (routeResponse != null)
|
||||||
|
writer.WriteLine($"@returns {{{(routeResponse.Dynamic ? "Any" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))}}}");
|
||||||
|
|
||||||
|
writer.WriteLine("@throws {Response} If response status was not ok.");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
|
||||||
|
//Generate the route function header
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.Write($"static async {(routeName == null ? methodInfo.Name : routeName.Name)}(");
|
||||||
|
else
|
||||||
|
writer.Write($"async {(routeName == null ? methodInfo.Name : routeName.Name)}(");
|
||||||
|
|
||||||
|
//Generate the functions parameters
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write(parameters[i].Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeRequest != null)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write("body");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeResponse != null && routeResponse.Parameter)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write("input");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine(") {").Indent();
|
||||||
|
|
||||||
|
//Generate the url
|
||||||
|
writer.WriteBreak().Write("var url = ");
|
||||||
|
|
||||||
|
//Generate the request url
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.Write('`').Write($"${{{this.ClientName}.BaseUrl}}");
|
||||||
|
else
|
||||||
|
writer.Write('`').Write("${this.Client.BaseUrl}");
|
||||||
|
|
||||||
|
//Reconstruct the route syntax into a request url.
|
||||||
|
var components = route.Syntax.Split('/');
|
||||||
|
int argumentIndex = 0;
|
||||||
|
foreach (var component in components)
|
||||||
|
{
|
||||||
|
if (writer.Peek() != '/')
|
||||||
|
writer.Write('/');
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(component))
|
||||||
|
{
|
||||||
|
if (component.StartsWith("{"))
|
||||||
|
{
|
||||||
|
writer.Write("${").Write(parameters[argumentIndex++ + 1].Name).Write("}");
|
||||||
|
}
|
||||||
|
else if (component == "*")
|
||||||
|
{
|
||||||
|
writer.Write("*");
|
||||||
|
}
|
||||||
|
else if (component == "**")
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine("`;");
|
||||||
|
|
||||||
|
//Generate the request
|
||||||
|
writer.WriteBreak().WriteLine("var request = {").Indent();
|
||||||
|
|
||||||
|
//Include credentials
|
||||||
|
writer.WriteLine("credentials: 'include',");
|
||||||
|
|
||||||
|
//Generate the method
|
||||||
|
writer.WriteLine($"method: '{route.Method.ToLower()}',");
|
||||||
|
|
||||||
|
//If we have a body, generate that
|
||||||
|
if (routeRequest != null)
|
||||||
|
{
|
||||||
|
if (routeRequest.RequestType.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
writer.WriteLine("headers: new Headers({ 'Content-Type': 'application/octet-stream' }),");
|
||||||
|
writer.WriteLine("body: body,");
|
||||||
|
}
|
||||||
|
else if (routeRequest.Json)
|
||||||
|
{
|
||||||
|
|
||||||
|
writer.WriteLine("body: JSON.stringify(body),");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteLine("body: body.toString(),");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("};");
|
||||||
|
|
||||||
|
//Generate the response
|
||||||
|
writer.WriteBreak().Write("var response = await fetch(");
|
||||||
|
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.Write($"{this.ClientName}.UrlHandler ? {this.ClientName}.UrlHandler(url) : url");
|
||||||
|
else
|
||||||
|
writer.Write($"this.Client.UrlHandler ? this.Client.UrlHandler(url) : url");
|
||||||
|
|
||||||
|
writer.WriteSeparator();
|
||||||
|
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.Write($"{this.ClientName}.RequestHandler ? {this.ClientName}.RequestHandler(request) : request");
|
||||||
|
else
|
||||||
|
writer.Write("this.Client.RequestHandler ? this.Client.RequestHandler(request) : request");
|
||||||
|
|
||||||
|
writer.WriteLine(");");
|
||||||
|
|
||||||
|
//Generate code to handle the response
|
||||||
|
if (routeResponse != null)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (response.ok) {").Indent();
|
||||||
|
|
||||||
|
if (routeResponse.ResponseType != null && routeResponse.ResponseType.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
if (routeResponse.Parameter)
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (routeResponse.Json)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("return await response.json();");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (Type.GetTypeCode(routeResponse.ResponseType))
|
||||||
|
{
|
||||||
|
case TypeCode.Boolean:
|
||||||
|
writer.WriteBreak().WriteLine("return (await(response.text())).toLowerCase() === 'true';");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Char:
|
||||||
|
writer.WriteBreak().WriteLine("return (await(response.text()))[0];");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.DateTime:
|
||||||
|
writer.WriteBreak().WriteLine("return Date.parse((await(response.text())));");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.String:
|
||||||
|
writer.WriteBreak().WriteLine("return await response.text();");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Byte:
|
||||||
|
case TypeCode.Decimal:
|
||||||
|
case TypeCode.Double:
|
||||||
|
case TypeCode.Int16:
|
||||||
|
case TypeCode.Int32:
|
||||||
|
case TypeCode.Int64:
|
||||||
|
case TypeCode.SByte:
|
||||||
|
case TypeCode.Single:
|
||||||
|
case TypeCode.UInt16:
|
||||||
|
case TypeCode.UInt32:
|
||||||
|
case TypeCode.UInt64:
|
||||||
|
writer.WriteBreak().WriteLine("return Number((await(response.text())));");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Object:
|
||||||
|
throw new NotSupportedException("ResponseType isn't JSON but is an object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine("throw response;");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (!response.ok) {").Indent();
|
||||||
|
writer.WriteLine("throw response;");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close off the route function.
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
private Func<HttpListenerContext, HttpListenerResponse> Target;
|
private Func<HttpListenerContext, HttpListenerResponse> Target;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not to close the response after the route is invoked.
|
/// Whether or not to close the response after the route is invoked, default is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CloseResponse = true;
|
public bool CloseResponse = true;
|
||||||
|
|
||||||
@ -38,15 +38,21 @@ namespace MontoyaTech.Rest.Net
|
|||||||
internal Route() { }
|
internal Route() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new route with a given method, syntax, target and optional close response flag.
|
/// Creates a new route with a given method, syntax, target and optional close response flag which defaults to true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name"></param>
|
|
||||||
/// <param name="method"></param>
|
/// <param name="method"></param>
|
||||||
/// <param name="syntax"></param>
|
/// <param name="syntax"></param>
|
||||||
/// <param name="target"></param>
|
/// <param name="target"></param>
|
||||||
/// <param name="closeResponse"></param>
|
/// <param name="closeResponse"></param>
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Method cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Syntax cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -56,7 +62,6 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new route with a given method, syntax, target and optional close response flag.
|
/// Creates a new route with a given method, syntax, target and optional close response flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name"></param>
|
|
||||||
/// <param name="method"></param>
|
/// <param name="method"></param>
|
||||||
/// <param name="syntax"></param>
|
/// <param name="syntax"></param>
|
||||||
/// <param name="target"></param>
|
/// <param name="target"></param>
|
||||||
@ -117,6 +122,15 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
this.Target.Invoke(context);
|
this.Target.Invoke(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the target delegate for this route.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1> : Route
|
public class Route<T1> : Route
|
||||||
@ -125,6 +139,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -138,6 +159,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
this.Target.DynamicInvoke(context, RouteArgumentConverter.Convert<T1>(arguments[0]));
|
this.Target.DynamicInvoke(context, RouteArgumentConverter.Convert<T1>(arguments[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2> : Route
|
public class Route<T1, T2> : Route
|
||||||
@ -146,6 +172,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -163,6 +196,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T2>(arguments[1])
|
RouteArgumentConverter.Convert<T2>(arguments[1])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2, T3> : Route
|
public class Route<T1, T2, T3> : Route
|
||||||
@ -171,6 +209,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -189,6 +234,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T3>(arguments[2])
|
RouteArgumentConverter.Convert<T3>(arguments[2])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2, T3, T4> : Route
|
public class Route<T1, T2, T3, T4> : Route
|
||||||
@ -197,6 +247,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -216,6 +273,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T4>(arguments[3])
|
RouteArgumentConverter.Convert<T4>(arguments[3])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2, T3, T4, T5> : Route
|
public class Route<T1, T2, T3, T4, T5> : Route
|
||||||
@ -224,6 +286,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -244,6 +313,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T5>(arguments[4])
|
RouteArgumentConverter.Convert<T5>(arguments[4])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2, T3, T4, T5, T6> : Route
|
public class Route<T1, T2, T3, T4, T5, T6> : Route
|
||||||
@ -252,6 +326,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, T6, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, T6, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -273,6 +354,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T6>(arguments[5])
|
RouteArgumentConverter.Convert<T6>(arguments[5])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2, T3, T4, T5, T6, T7> : Route
|
public class Route<T1, T2, T3, T4, T5, T6, T7> : Route
|
||||||
@ -281,6 +367,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -303,6 +396,11 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T7>(arguments[6])
|
RouteArgumentConverter.Convert<T7>(arguments[6])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Route<T1, T2, T3, T4, T5, T6, T7, T8> : Route
|
public class Route<T1, T2, T3, T4, T5, T6, T7, T8> : Route
|
||||||
@ -311,6 +409,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> target, bool closeResponse = true)
|
public Route(string method, string syntax, Func<HttpListenerContext, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> target, bool closeResponse = true)
|
||||||
{
|
{
|
||||||
|
if (target == null)
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
else if (string.IsNullOrWhiteSpace(method))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(method));
|
||||||
|
else if (string.IsNullOrWhiteSpace(syntax))
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(syntax));
|
||||||
|
|
||||||
this.Method = method;
|
this.Method = method;
|
||||||
this.Syntax = syntax;
|
this.Syntax = syntax;
|
||||||
this.Target = target;
|
this.Target = target;
|
||||||
@ -334,5 +439,10 @@ namespace MontoyaTech.Rest.Net
|
|||||||
RouteArgumentConverter.Convert<T8>(arguments[7])
|
RouteArgumentConverter.Convert<T8>(arguments[7])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Delegate GetTarget()
|
||||||
|
{
|
||||||
|
return this.Target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,96 +19,130 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static T Convert<T>(string input)
|
public static T Convert<T>(string input)
|
||||||
{
|
{
|
||||||
var typeCode = Type.GetTypeCode(typeof(T));
|
var result = Convert(typeof(T), input);
|
||||||
|
|
||||||
if (typeCode == TypeCode.String)
|
if (result == null)
|
||||||
|
return default(T);
|
||||||
|
|
||||||
|
return (T)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a string to a given type if possible. Otherwise returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static object Convert(Type type, string input)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
{
|
{
|
||||||
return (dynamic)input;
|
return Enum.Parse(type, input, true);
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.String)
|
||||||
|
{
|
||||||
|
return input;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Object)
|
else if (typeCode == TypeCode.Object)
|
||||||
{
|
{
|
||||||
var castOperator = typeof(T).GetMethod("op_Explicit", new[] { typeof(string) });
|
var castOperator = type.GetMethod("op_Explicit", new[] { typeof(string) });
|
||||||
|
|
||||||
if (castOperator == null)
|
if (castOperator == null)
|
||||||
return default(T);
|
return null;
|
||||||
|
|
||||||
return (T)castOperator.Invoke(null, new[] { input });
|
return castOperator.Invoke(null, new[] { input });
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Double)
|
else if (typeCode == TypeCode.Double)
|
||||||
{
|
{
|
||||||
double.TryParse(input, out double result);
|
double.TryParse(input, out double result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Single)
|
else if (typeCode == TypeCode.Single)
|
||||||
{
|
{
|
||||||
float.TryParse(input, out float result);
|
float.TryParse(input, out float result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Decimal)
|
else if (typeCode == TypeCode.Decimal)
|
||||||
{
|
{
|
||||||
decimal.TryParse(input, out decimal result);
|
decimal.TryParse(input, out decimal result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Int64)
|
else if (typeCode == TypeCode.Int64)
|
||||||
{
|
{
|
||||||
long.TryParse(input, out long result);
|
long.TryParse(input, out long result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Int32)
|
else if (typeCode == TypeCode.Int32)
|
||||||
{
|
{
|
||||||
int.TryParse(input, out int result);
|
int.TryParse(input, out int result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Int16)
|
else if (typeCode == TypeCode.Int16)
|
||||||
{
|
{
|
||||||
short.TryParse(input, out short result);
|
short.TryParse(input, out short result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.SByte)
|
else if (typeCode == TypeCode.SByte)
|
||||||
{
|
{
|
||||||
sbyte.TryParse(input, out sbyte result);
|
sbyte.TryParse(input, out sbyte result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.UInt64)
|
else if (typeCode == TypeCode.UInt64)
|
||||||
{
|
{
|
||||||
ulong.TryParse(input, out ulong result);
|
ulong.TryParse(input, out ulong result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.UInt32)
|
else if (typeCode == TypeCode.UInt32)
|
||||||
{
|
{
|
||||||
uint.TryParse(input, out uint result);
|
uint.TryParse(input, out uint result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.UInt16)
|
else if (typeCode == TypeCode.UInt16)
|
||||||
{
|
{
|
||||||
ushort.TryParse(input, out ushort result);
|
ushort.TryParse(input, out ushort result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Byte)
|
else if (typeCode == TypeCode.Byte)
|
||||||
{
|
{
|
||||||
byte.TryParse(input, out byte result);
|
byte.TryParse(input, out byte result);
|
||||||
return (dynamic)result;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Boolean)
|
else if (typeCode == TypeCode.Boolean)
|
||||||
{
|
{
|
||||||
if (input == "f" || input == "0" || input == "F")
|
if (input == "f" || input == "0" || input == "F")
|
||||||
return (dynamic)false;
|
return false;
|
||||||
|
|
||||||
bool.TryParse(input, out bool result);
|
bool.TryParse(input, out bool result);
|
||||||
return ((dynamic)result);
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.DateTime)
|
else if (typeCode == TypeCode.DateTime)
|
||||||
{
|
{
|
||||||
DateTime.TryParse(input, out DateTime result);
|
DateTime.TryParse(input, out DateTime result);
|
||||||
return ((dynamic)result);
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (typeCode == TypeCode.Char)
|
else if (typeCode == TypeCode.Char)
|
||||||
{
|
{
|
||||||
char.TryParse(input, out char result);
|
char.TryParse(input, out char result);
|
||||||
return ((dynamic)result);
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return default(T);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
196
Rest.Net/RouteFileCache.cs
Normal file
196
Rest.Net/RouteFileCache.cs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Security.Principal;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a FileCache that can be used with Routes to speed
|
||||||
|
/// up response times.
|
||||||
|
/// </summary>
|
||||||
|
public class RouteFileCache
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a CachedFile.
|
||||||
|
/// </summary>
|
||||||
|
public class CachedFile
|
||||||
|
{
|
||||||
|
public string FilePath;
|
||||||
|
|
||||||
|
public byte[] Content;
|
||||||
|
|
||||||
|
public int HitCount;
|
||||||
|
|
||||||
|
public DateTime Cached = DateTime.Now;
|
||||||
|
|
||||||
|
public DateTime LastAccess = DateTime.Now;
|
||||||
|
|
||||||
|
public CachedFile(string filePath, byte[] content)
|
||||||
|
{
|
||||||
|
this.FilePath = filePath;
|
||||||
|
this.Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The files that are currently cached.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, CachedFile> CachedFiles = new Dictionary<string, CachedFile>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current number of bytes cached.
|
||||||
|
/// </summary>
|
||||||
|
public int BytesCached { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max bytes that can be cached at a time.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxBytesCached { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RouteFileCache with the max bytes that can be cached.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxBytes"></param>
|
||||||
|
public RouteFileCache(int maxBytes)
|
||||||
|
{
|
||||||
|
this.MaxBytesCached = maxBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not a file was cached and the content of the file if it was.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Cached(string filePath, out byte[] content)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
{
|
||||||
|
content = null;
|
||||||
|
|
||||||
|
if (!this.CachedFiles.ContainsKey(filePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var cached = this.CachedFiles[filePath];
|
||||||
|
cached.HitCount++;
|
||||||
|
cached.LastAccess = DateTime.Now;
|
||||||
|
|
||||||
|
content = cached.Content;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not a file was cached.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Cached(string filePath)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
return this.CachedFiles.ContainsKey(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches a new file and returns the content, if the file was already cached the content will be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="FileNotFoundException"></exception>
|
||||||
|
public byte[] Cache(string filePath)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
{
|
||||||
|
if (!this.CachedFiles.ContainsKey(filePath))
|
||||||
|
{
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
var content = File.ReadAllBytes(filePath);
|
||||||
|
|
||||||
|
this.Cache(filePath, content);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Failed to find file: " + filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cached = this.CachedFiles[filePath];
|
||||||
|
|
||||||
|
cached.HitCount++;
|
||||||
|
cached.LastAccess = DateTime.Now;
|
||||||
|
|
||||||
|
return cached.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches a new file with the content, if the file was already cached this will just count as an access and overwrite the content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
public void Cache(string filePath, byte[] content)
|
||||||
|
{
|
||||||
|
lock (this.CachedFiles)
|
||||||
|
{
|
||||||
|
if (!this.CachedFiles.ContainsKey(filePath))
|
||||||
|
{
|
||||||
|
if ((this.BytesCached + content.Length) > this.MaxBytesCached)
|
||||||
|
this.Free((this.BytesCached + content.Length) - this.MaxBytesCached);
|
||||||
|
|
||||||
|
var cached = new CachedFile(filePath, content);
|
||||||
|
|
||||||
|
this.BytesCached += content.Length;
|
||||||
|
|
||||||
|
this.CachedFiles.Add(filePath, cached);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cached = this.CachedFiles[filePath];
|
||||||
|
|
||||||
|
cached.HitCount++;
|
||||||
|
cached.LastAccess = DateTime.Now;
|
||||||
|
cached.Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to free up space in the cache by a specific amount of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="neededBytes"></param>
|
||||||
|
public void Free(int neededBytes)
|
||||||
|
{
|
||||||
|
while (this.BytesCached + neededBytes >= this.MaxBytesCached)
|
||||||
|
{
|
||||||
|
CachedFile weakest = null;
|
||||||
|
|
||||||
|
foreach (var pair in this.CachedFiles)
|
||||||
|
{
|
||||||
|
if (weakest == null)
|
||||||
|
weakest = pair.Value;
|
||||||
|
else if (pair.Value.LastAccess < weakest.LastAccess && pair.Value.HitCount < weakest.HitCount)
|
||||||
|
weakest = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weakest != null)
|
||||||
|
{
|
||||||
|
this.CachedFiles.Remove(weakest.FilePath);
|
||||||
|
|
||||||
|
this.BytesCached -= weakest.Content.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
Rest.Net/RouteGroup.cs
Normal file
37
Rest.Net/RouteGroup.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an attribute that controls what group a route belongs to.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class RouteGroup : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the group this Route belongs to.
|
||||||
|
/// </summary>
|
||||||
|
public string Name = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new default route group.
|
||||||
|
/// </summary>
|
||||||
|
public RouteGroup() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new route group with the name of the group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
public RouteGroup(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
throw new ArgumentException("Must not be null or empty", nameof(name));
|
||||||
|
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
Rest.Net/RouteHidden.cs
Normal file
20
Rest.Net/RouteHidden.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an Attribute to hide a given route for client code generation.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class RouteHidden : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default RouteHidden attribute.
|
||||||
|
/// </summary>
|
||||||
|
public RouteHidden() { }
|
||||||
|
}
|
||||||
|
}
|
34
Rest.Net/RouteInclude.cs
Normal file
34
Rest.Net/RouteInclude.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an Attribute that includes a type when generating a client for this route.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||||
|
public class RouteInclude : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type to include.
|
||||||
|
/// </summary>
|
||||||
|
public Type Type = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default route include.
|
||||||
|
/// </summary>
|
||||||
|
public RouteInclude() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new route include with the type to include.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
public RouteInclude(Type type)
|
||||||
|
{
|
||||||
|
this.Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The internal http listener.
|
/// The internal http listener.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private HttpListener HttpListener = null;
|
protected HttpListener HttpListener = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of routes this RouteListener is listening for.
|
/// The list of routes this RouteListener is listening for.
|
||||||
@ -29,6 +29,17 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Port = 8081;
|
public ushort Port = 8081;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the BaseUrl for this RouteListener.
|
||||||
|
/// </summary>
|
||||||
|
public string BaseUrl
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return $"http://localhost:{this.Port}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event to preprocess routes before the route is executed.
|
/// An event to preprocess routes before the route is executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -82,18 +93,29 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
this.HttpListener = new HttpListener();
|
this.HttpListener = new HttpListener();
|
||||||
|
|
||||||
//Support listening on Windows & Linux.
|
//Attempt to listen on all interfaces first.
|
||||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
this.HttpListener.Prefixes.Add($"http://*:{this.Port}/");
|
||||||
{
|
|
||||||
this.HttpListener.Prefixes.Add($"http://localhost:{this.Port}/");
|
|
||||||
this.HttpListener.Prefixes.Add($"http://127.0.0.1:{this.Port}/");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.HttpListener.Prefixes.Add($"http://*:{this.Port}/");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.HttpListener.Start();
|
try
|
||||||
|
{
|
||||||
|
this.HttpListener.Start();
|
||||||
|
}
|
||||||
|
catch (HttpListenerException ex)
|
||||||
|
{
|
||||||
|
//If this failed and it's because we are not admin, try again with the local prefixes
|
||||||
|
if (ex.Message.ToLower().StartsWith("access is denied"))
|
||||||
|
{
|
||||||
|
this.HttpListener = new HttpListener();
|
||||||
|
this.HttpListener.Prefixes.Add($"http://localhost:{this.Port}/");
|
||||||
|
this.HttpListener.Prefixes.Add($"http://127.0.0.1:{this.Port}/");
|
||||||
|
|
||||||
|
this.HttpListener.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.Listen();
|
this.Listen();
|
||||||
}
|
}
|
||||||
@ -204,9 +226,45 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Block()
|
public void Block()
|
||||||
{
|
{
|
||||||
while (this.HttpListener != null && Thread.CurrentThread.ThreadState != ThreadState.AbortRequested && Thread.CurrentThread.ThreadState != ThreadState.Aborted)
|
try
|
||||||
if (!Thread.Yield())
|
{
|
||||||
Thread.Sleep(1000);
|
while (this.HttpListener != null && this.HttpListener.IsListening && Thread.CurrentThread.ThreadState != ThreadState.AbortRequested && Thread.CurrentThread.ThreadState != ThreadState.Aborted)
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a C# client from this Route Listener and returns the code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientName">The name of the Client. Default is Client.</param>
|
||||||
|
/// <param name="staticCode">Whether or not to generate a static client.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GenerateCSharpClient(string clientName = "Client", bool staticCode = false)
|
||||||
|
{
|
||||||
|
var generator = new RestCSharpClientGenerator();
|
||||||
|
|
||||||
|
generator.ClientName = clientName;
|
||||||
|
generator.StaticCode = staticCode;
|
||||||
|
|
||||||
|
return generator.Generate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Javascript client from this Route Listener and returns the code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientName"></param>
|
||||||
|
/// <param name="staticCode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GenerateJavascriptClient(string clientName = "Client", bool staticCode = false, bool useJsonNames = false)
|
||||||
|
{
|
||||||
|
var generator = new RestJavascriptClientGenerator();
|
||||||
|
|
||||||
|
generator.ClientName = clientName;
|
||||||
|
generator.StaticCode = staticCode;
|
||||||
|
generator.UseJsonNames = useJsonNames;
|
||||||
|
|
||||||
|
return generator.Generate(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,10 @@ namespace MontoyaTech.Rest.Net
|
|||||||
//Set the arguments to null, initially.
|
//Set the arguments to null, initially.
|
||||||
arguments = null;
|
arguments = null;
|
||||||
|
|
||||||
|
//If the input url is invalid, default to false.
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
return false;
|
||||||
|
|
||||||
//Trim the url and the syntax.
|
//Trim the url and the syntax.
|
||||||
url = url.Trim();
|
url = url.Trim();
|
||||||
syntax = syntax.Trim();
|
syntax = syntax.Trim();
|
||||||
@ -42,17 +46,27 @@ namespace MontoyaTech.Rest.Net
|
|||||||
if (url.StartsWith("http://"))
|
if (url.StartsWith("http://"))
|
||||||
{
|
{
|
||||||
url = url.Substring(7);
|
url = url.Substring(7);
|
||||||
url = url.Substring(url.IndexOf('/'));
|
|
||||||
|
//If there is a slash, skip to the first one, or default to empty path.
|
||||||
|
if (url.Contains('/'))
|
||||||
|
url = url.Substring(url.IndexOf('/'));
|
||||||
|
else
|
||||||
|
url = "/";
|
||||||
}
|
}
|
||||||
else if (url.StartsWith("https://"))
|
else if (url.StartsWith("https://"))
|
||||||
{
|
{
|
||||||
url = url.Substring(8);
|
url = url.Substring(8);
|
||||||
url = url.Substring(url.IndexOf('/'));
|
|
||||||
|
//If there is a slash, skip to the first one, or default to empty path.
|
||||||
|
if (url.Contains('/'))
|
||||||
|
url = url.Substring(url.IndexOf('/'));
|
||||||
|
else
|
||||||
|
url = "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
//Split the url and the syntax into path segments
|
//Split the url and the syntax into path segments
|
||||||
var urlSegments = url.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
|
var urlSegments = url.Split('/').Select(segment => segment.Trim()).ToArray();
|
||||||
var syntaxSegments = syntax.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
|
var syntaxSegments = syntax.Split('/').Select(segment => segment.Trim()).ToArray();
|
||||||
|
|
||||||
//If we have no url segments, and we have no syntax segments, this is a root match which is fine.
|
//If we have no url segments, and we have no syntax segments, this is a root match which is fine.
|
||||||
if (urlSegments.Length == 0 && syntaxSegments.Length == 0)
|
if (urlSegments.Length == 0 && syntaxSegments.Length == 0)
|
||||||
@ -72,19 +86,40 @@ namespace MontoyaTech.Rest.Net
|
|||||||
int argumentIndex = 0;
|
int argumentIndex = 0;
|
||||||
|
|
||||||
//Check each segment against the url.
|
//Check each segment against the url.
|
||||||
var max = Math.Min(urlSegments.Length, syntaxSegments.Length);
|
int max = Math.Max(urlSegments.Length, syntaxSegments.Length);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
{
|
{
|
||||||
var syntaxSegment = syntaxSegments[i];
|
var syntaxSegment = i >= syntaxSegments.Length ? null : syntaxSegments[i];
|
||||||
|
|
||||||
var urlSegment = urlSegments[i];
|
var urlSegment = i >= urlSegments.Length ? null : urlSegments[i];
|
||||||
|
|
||||||
|
//If the syntax segment is null, this is not a match.
|
||||||
|
if (syntaxSegment == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
//If the segments syntax is a double wild card then everything after this is a match.
|
//If the segments syntax is a double wild card then everything after this is a match.
|
||||||
if (syntaxSegment == "**")
|
else if (syntaxSegment == "**" && urlSegment != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
//If we ran out of url segments, and this syntax segment is a wild card, this is not a match.
|
||||||
|
else if (syntaxSegment == "*" && urlSegment != null)
|
||||||
|
{
|
||||||
|
//Allowed
|
||||||
|
}
|
||||||
|
//If we have a syntaxSegment but no urlSegment, this can't be a match.
|
||||||
|
else if (urlSegment == null && syntaxSegment != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//If both the url segment and syntax segment are empty then this is allowed
|
||||||
|
else if (urlSegment != null && urlSegment.Length == 0 && syntaxSegment.Length == 0)
|
||||||
|
{
|
||||||
|
//Allowed
|
||||||
|
}
|
||||||
|
//If we have a url segment see if it matches against the syntax segment.
|
||||||
|
else if (urlSegment != null)
|
||||||
{
|
{
|
||||||
var conditions = new List<string>();
|
var conditions = new List<string>();
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
@ -99,7 +134,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
else if (syntaxSegment[c] != ' ')
|
else if (syntaxSegment[c] != ' ')
|
||||||
{
|
{
|
||||||
builder.Append(syntaxSegment[c]);
|
builder.Append(syntaxSegment[c]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builder.Length > 0)
|
if (builder.Length > 0)
|
||||||
@ -145,12 +180,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlSegments.Length > syntaxSegments.Length)
|
return true;
|
||||||
return false;
|
|
||||||
else if (syntaxSegments.Length > urlSegments.Length)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
Rest.Net/RouteName.cs
Normal file
38
Rest.Net/RouteName.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an attribute that allows you to rename an attribute in
|
||||||
|
/// the output code generation.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class RouteName : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this Route.
|
||||||
|
/// </summary>
|
||||||
|
public string Name = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new default RouteName.
|
||||||
|
/// </summary>
|
||||||
|
public RouteName() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new RouteName with a given name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
public RouteName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
throw new ArgumentException("Cannot be null or whitespace", nameof(name));
|
||||||
|
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Rest.Net/RouteRequest.cs
Normal file
44
Rest.Net/RouteRequest.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an attribute that defines a routes request type.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class RouteRequest : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the request.
|
||||||
|
/// </summary>
|
||||||
|
public Type RequestType = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the Request is Json Serialized. Default is true.
|
||||||
|
/// </summary>
|
||||||
|
public bool Json = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the request is a dynamic type. Default is false.
|
||||||
|
/// </summary>
|
||||||
|
public bool Dynamic = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default route request.
|
||||||
|
/// </summary>
|
||||||
|
public RouteRequest() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a route request with the request type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestType"></param>
|
||||||
|
public RouteRequest(Type requestType)
|
||||||
|
{
|
||||||
|
this.RequestType = requestType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Rest.Net/RouteResponse.cs
Normal file
49
Rest.Net/RouteResponse.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an attribute that defines a routes response type.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class RouteResponse : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the request.
|
||||||
|
/// </summary>
|
||||||
|
public Type ResponseType = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the response is Json Serialized, default is true.
|
||||||
|
/// </summary>
|
||||||
|
public bool Json = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the response is passed as a parameter to the route function. Default is false.
|
||||||
|
/// </summary>
|
||||||
|
public bool Parameter = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the response is a dynamic type. Default is false.
|
||||||
|
/// </summary>
|
||||||
|
public bool Dynamic = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default route response.
|
||||||
|
/// </summary>
|
||||||
|
public RouteResponse() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a route response with the response type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="responseType"></param>
|
||||||
|
public RouteResponse(Type responseType)
|
||||||
|
{
|
||||||
|
this.ResponseType = responseType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
Rest.Net/RouteTypeName.cs
Normal file
25
Rest.Net/RouteTypeName.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of an attribute that can be used to rename a type when it's used in routes.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
|
||||||
|
public class RouteTypeName : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The new name for this type.
|
||||||
|
/// </summary>
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public RouteTypeName(string name)
|
||||||
|
{
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,5 +18,39 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string Separate(this IList<string> input, char separator)
|
||||||
|
{
|
||||||
|
if (input == null || input.Count == 0)
|
||||||
|
return "";
|
||||||
|
else if (input.Count < 2)
|
||||||
|
return input[0];
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append(input[0]);
|
||||||
|
|
||||||
|
for (int i = 1; i < input.Count; i++)
|
||||||
|
builder.Append(separator).Append(input[i]);
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Separate(this IList<string> input, string separator)
|
||||||
|
{
|
||||||
|
if (input == null || input.Count == 0)
|
||||||
|
return "";
|
||||||
|
else if (input.Count < 2)
|
||||||
|
return input[0];
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append(input[0]);
|
||||||
|
|
||||||
|
for (int i = 1; i < input.Count; i++)
|
||||||
|
builder.Append(separator).Append(input[i]);
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user