Compare commits

..

28 Commits

Author SHA1 Message Date
3da97d4fff Upgraded project to DotNet 8.0 and upgraded nuget packages to the latest version. Bumped package version to 1.8.9 2025-06-17 09:53:06 -07:00
d0d64ef570 Modified the RestClientGenerator to prevent infinite dependency tracing. This method is a little cleaner too. Bumped package version to 1.8.8 2025-06-02 11:31:15 -07:00
f116be9908 Added readme to nuget package. 2024-09-11 17:52:08 -07:00
2c2b498223 Added mimeType option to the WithStream extension. Bumped package version to 1.8.7 2024-08-29 10:18:36 -07:00
bc82aeb8c2 Added new WithStream extension for responses. Bumped package version to 1.8.6 2024-08-29 10:12:44 -07:00
670605ce91 Bumped package version to 1.8.5. Added enum route argument converting support. Cleaned up code and added unit test. 2024-07-10 06:37:02 -07:00
a698e71e4b Fixed a minor issue where the request parameter was the same name as the request object in javascript route functions. Bumped package version to 1.8.4 2024-03-06 08:14:40 -08:00
51b8ba073c Bumped package version to 1.8.3. Added code to escape the names of fields, properties when generating the constructor. 2024-02-23 16:55:40 -08:00
b8e8e1dd86 Bumped package version to 1.8.2. Added the ability to use json names in the Javascript Client generator if a JsonProperty exists on a field or a property. 2024-02-23 16:23:54 -08:00
cc83f99612 Bumped package version to 1.8.1. Javascript client now includes a urlHandler and requestHandler that can be used to modify the request before it's sent. 2024-02-19 08:42:58 -08:00
8747b5fb3e Modifying client generator to allow modifying requests before they are sent. 2024-02-19 08:05:48 -08:00
50861d5381 Bumped package version to 1.8.0. Added support for automatic gzip decompression in HttpListenerRequestExtensions. Added a new compress parameter for json requests in client generated code that auto compresses the json if set. Fixed a few bugs and cleaned up code. 2024-01-13 10:53:44 -08:00
5f83b30cb2 Added ability to specify a RouteResponse and RouteRequest as a dynamic. Improved documentation and fixed some null checks that were missing with this change. Bumped package version to 1.7.6 2023-12-14 11:33:42 -08:00
38ef135b8a Fixed a stackoverflow bug when generating a Rest Client and the object has it's type as a sub type. Bumped package versino to 1.7.5 2023-12-14 11:14:27 -08:00
9633e211a1 Fixed an accidental bug introduced to the client generation. Bumped package version to 1.7.4 2023-09-24 19:49:25 -07:00
b9260dbdb1 Fixed a bug where a double empty constructor could be generated for a C# client if there is no fields/properties. Bumped package version to 1.7.3 2023-09-24 19:34:26 -07:00
4c64f1c134 Modified Javascript Client generator to throw the response instead of a message so that behavior of the code using the Client can changed based on the response returned. Bumped package version to 1.7.2 2023-07-28 15:23:29 -07:00
2f71c18b65 Fixed an issue where if a route didn't end with a /, the generated code would still contain an ending slash. Bumped package version to 1.7.1 2023-07-11 09:52:15 -07:00
69a1d9c3a8 Improved RouteMatcher and added more unit tests to cover some edge cases. Cleaned up code. Bumped package version to 1.7.0 2023-07-09 11:14:33 -07:00
8c44d56ab4 Added utf16 response extensions to help support some legacy code. Changed HttpListener to protected to help with overriding the default behavior of the RouteListener if needed. Bumped package version to 1.6.9 2023-07-05 08:04:56 -07:00
4b05b1b6b9 Fixed an absolutely stupid bug. Bumped package version to 1.6.8 2023-06-29 10:00:59 -07:00
7934f807ef Improved file serving to support startPaths. Improved code and simplified a few things. Bumped package version to 1.6.7 2023-06-29 09:50:12 -07:00
36872164c5 Improved SinglePage algorithm. Bumped package version to 1.6.6 2023-06-29 09:29:21 -07:00
dc1abd516b Added missing code to the ServeSinglePage. Bumped package version to 1.6.5 2023-06-29 08:37:45 -07:00
4617f861fc Added support for cases where the parent directory is included in the request path. Added unit tests to check this case. Bumped package version to 1.6.4 2023-06-29 08:21:54 -07:00
46aab308fa Merge pull request 'ServeFileExtension' (#2) from ServeFileExtension into master
Reviewed-on: #2
2023-06-29 01:39:07 +00:00
c2f60ff19f Bumped package version to 1.6.3. Added unit tests for serving files. Fixed issues and improved the security of the serve file extensions and the ability to compress files. 2023-06-28 18:37:46 -07:00
d96c44e542 Working on code and tests for a serve file extension to make developing a small web server easier. 2023-06-28 17:41:31 -07:00
23 changed files with 1523 additions and 345 deletions

View File

@ -1,18 +1,16 @@
//Generated using MontoyaTech.Rest.Net //Generated using MontoyaTech.Rest.Net - 2/18/2024
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;
public class Client public class Client
{ {
public string BaseUrl; public string BaseUrl;
public CookieContainer CookieContainer; public System.Net.CookieContainer CookieContainer;
public HttpMessageHandler MessageHandler; public System.Net.Http.HttpMessageHandler MessageHandler;
public HttpClient HttpClient; public System.Net.Http.HttpClient HttpClient;
public System.Action<System.Net.Http.HttpRequestMessage> RequestHandler;
public TestApi Test; public TestApi Test;
@ -20,32 +18,36 @@ public class Client
public StreamApi Stream; public StreamApi Stream;
public Client(string baseUrl, HttpMessageHandler handler = null) 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)) if (string.IsNullOrWhiteSpace(baseUrl))
throw new ArgumentException("baseUrl must not be null or whitespace."); throw new System.ArgumentException("baseUrl must not be null or whitespace.");
if (baseUrl.EndsWith('/')) if (baseUrl.EndsWith('/'))
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
this.BaseUrl = baseUrl; this.BaseUrl = baseUrl;
this.CookieContainer = new CookieContainer(); this.CookieContainer = new System.Net.CookieContainer();
if (handler == null) if (handler == null)
{ {
handler = new HttpClientHandler() handler = new System.Net.Http.HttpClientHandler()
{ {
AllowAutoRedirect = true, AllowAutoRedirect = true,
UseCookies = true, UseCookies = true,
CookieContainer = this.CookieContainer, CookieContainer = this.CookieContainer,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
}; };
} }
this.MessageHandler = handler; this.MessageHandler = handler;
this.HttpClient = new HttpClient(handler); this.RequestHandler = requestHandler;
this.HttpClient = new System.Net.Http.HttpClient(handler);
this.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*"); this.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*");
@ -58,6 +60,8 @@ public class Client
this.Auth = new AuthApi(this); this.Auth = new AuthApi(this);
this.Stream = new StreamApi(this); this.Stream = new StreamApi(this);
this.Form = new FormApi(this);
} }
public class TestApi public class TestApi
@ -71,9 +75,11 @@ public class Client
public string Status() public string Status()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/status"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/status");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -82,19 +88,21 @@ public class Client
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public string Add(double a, double b) public string Add(double a, double b)
{ {
var message = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/add/{a}/{b}"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{this.Client.BaseUrl}/add/{a}/{b}");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -103,19 +111,21 @@ public class Client
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public string Compress() public string Compress()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/compress"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/compress");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -124,19 +134,21 @@ public class Client
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public string CompressFile() public string CompressFile()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/file/compress"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/file/compress");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -145,11 +157,11 @@ public class Client
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
} }
@ -165,9 +177,11 @@ public class Client
public bool UserExists(string name) public bool UserExists(string name)
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/auth/{name}"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/{name}");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -180,27 +194,51 @@ public class Client
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public void Signup(UserDto request) public void Signup(UserDto request, bool compress = false)
{ {
var message = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/auth/signup"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{this.Client.BaseUrl}/auth/signup");
message.Content = new StringContent(JsonConvert.SerializeObject(request)); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); 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) if (!response.IsSuccessStatusCode)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
public UserDto Get() public UserDto Get()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/auth"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -209,19 +247,44 @@ public class Client
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<UserDto>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<UserDto>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); 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() public UserRole GetRole()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/auth/role"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/auth/role");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -230,11 +293,11 @@ public class Client
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<UserRole>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<UserRole>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
} }
@ -248,25 +311,29 @@ public class Client
this.Client = client; this.Client = client;
} }
public void Upload(System.IO.MemoryStream request) public void Upload(System.IO.MemoryStream request, bool compress = false)
{ {
var message = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/upload"); 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); request.Seek(0, System.IO.SeekOrigin.Begin);
message.Content = new StreamContent(request); message.Content = new System.Net.Http.StreamContent(request);
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
public System.IO.MemoryStream Download() public System.IO.MemoryStream Download()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/download"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{this.Client.BaseUrl}/download");
var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); this.Client.RequestHandler?.Invoke(message);
var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -278,7 +345,40 @@ public class Client
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); 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);
} }
} }
} }

View File

@ -8,17 +8,25 @@ using System.IO;
using MontoyaTech.Rest.Net; using MontoyaTech.Rest.Net;
using System.Net.Mime; using System.Net.Mime;
using System.Collections; 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 public class BaseUser
{ {
[JsonProperty("id")]
public string Id; public string Id;
[JsonProperty("firstInitial")]
public char FirstInitial; public char FirstInitial;
[JsonProperty("role")]
public UserRole Role { get; set; } public UserRole Role { get; set; }
[JsonProperty("permissions")]
public List<Permission> Permissions; public List<Permission> Permissions;
public class Permission public class Permission
@ -80,6 +88,7 @@ namespace MontoyaTech.Rest.Net.Example
new Route<string>(HttpRequestMethod.Get, "/auth/{username}", Exists), new Route<string>(HttpRequestMethod.Get, "/auth/{username}", Exists),
new Route(HttpRequestMethod.Post, "/auth/signup", Signup), new Route(HttpRequestMethod.Post, "/auth/signup", Signup),
new Route(HttpRequestMethod.Get, "/auth/", Json), new Route(HttpRequestMethod.Get, "/auth/", Json),
new Route(HttpRequestMethod.Get, "/auth/dynamic", Dynamic),
new Route(HttpRequestMethod.Get, "/auth/role", GetRole), new Route(HttpRequestMethod.Get, "/auth/role", GetRole),
new Route(HttpRequestMethod.Post, "/upload", Upload), new Route(HttpRequestMethod.Post, "/upload", Upload),
new Route(HttpRequestMethod.Get, "/download", Download), new Route(HttpRequestMethod.Get, "/download", Download),
@ -90,11 +99,11 @@ namespace MontoyaTech.Rest.Net.Example
File.WriteAllText("Client.cs", listener.GenerateCSharpClient()); File.WriteAllText("Client.cs", listener.GenerateCSharpClient());
File.WriteAllText("Client.js", listener.GenerateJavascriptClient()); File.WriteAllText("Client.js", listener.GenerateJavascriptClient(useJsonNames: true));
File.WriteAllText("StaticClient.cs", listener.GenerateCSharpClient("StaticClient", staticCode: true)); File.WriteAllText("StaticClient.cs", listener.GenerateCSharpClient("StaticClient", staticCode: true));
File.WriteAllText("StaticClient.js", listener.GenerateJavascriptClient("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"); Console.WriteLine("Generated Client.cs, Client.js, StaticClient.cs, StaticClient.js");
@ -117,7 +126,18 @@ namespace MontoyaTech.Rest.Net.Example
Console.WriteLine($"Rest api server running at {listener.BaseUrl}"); Console.WriteLine($"Rest api server running at {listener.BaseUrl}");
StaticClient.Init(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()) using (var stream = new MemoryStream())
{ {
@ -208,6 +228,13 @@ namespace MontoyaTech.Rest.Net.Example
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")] [RouteGroup("Stream")]
[RouteRequest(typeof(MemoryStream))] [RouteRequest(typeof(MemoryStream))]
public static HttpListenerResponse Upload(HttpListenerContext context) public static HttpListenerResponse Upload(HttpListenerContext context)

View File

@ -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>

View File

@ -1,45 +1,45 @@
//Generated using MontoyaTech.Rest.Net //Generated using MontoyaTech.Rest.Net - 2/18/2024
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;
public class StaticClient public class StaticClient
{ {
public static string BaseUrl; public static string BaseUrl;
public static CookieContainer CookieContainer; public static System.Net.CookieContainer CookieContainer;
public static HttpMessageHandler MessageHandler; public static System.Net.Http.HttpMessageHandler MessageHandler;
public static HttpClient HttpClient; public static System.Net.Http.HttpClient HttpClient;
public static void Init(string baseUrl, HttpMessageHandler handler = null) 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)) if (string.IsNullOrWhiteSpace(baseUrl))
throw new ArgumentException("baseUrl must not be null or whitespace."); throw new System.ArgumentException("baseUrl must not be null or whitespace.");
if (baseUrl.EndsWith('/')) if (baseUrl.EndsWith('/'))
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
StaticClient.BaseUrl = baseUrl; StaticClient.BaseUrl = baseUrl;
StaticClient.CookieContainer = new CookieContainer(); StaticClient.CookieContainer = new System.Net.CookieContainer();
if (handler == null) if (handler == null)
{ {
handler = new HttpClientHandler() handler = new System.Net.Http.HttpClientHandler()
{ {
AllowAutoRedirect = true, AllowAutoRedirect = true,
UseCookies = true, UseCookies = true,
CookieContainer = StaticClient.CookieContainer, CookieContainer = StaticClient.CookieContainer,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
}; };
} }
StaticClient.MessageHandler = handler; StaticClient.MessageHandler = handler;
StaticClient.HttpClient = new HttpClient(handler); StaticClient.RequestHandler = requestHandler;
StaticClient.HttpClient = new System.Net.Http.HttpClient(handler);
StaticClient.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*"); StaticClient.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*");
@ -52,9 +52,11 @@ public class StaticClient
{ {
public static string Status() public static string Status()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/status"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/status");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -63,19 +65,21 @@ public class StaticClient
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public static string Add(double a, double b) public static string Add(double a, double b)
{ {
var message = new HttpRequestMessage(HttpMethod.Post, $"{StaticClient.BaseUrl}/add/{a}/{b}"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{StaticClient.BaseUrl}/add/{a}/{b}");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -84,19 +88,21 @@ public class StaticClient
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public static string Compress() public static string Compress()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/compress"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/compress");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -105,19 +111,21 @@ public class StaticClient
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public static string CompressFile() public static string CompressFile()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/file/compress"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/file/compress");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -126,11 +134,11 @@ public class StaticClient
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<string>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
} }
@ -139,9 +147,11 @@ public class StaticClient
{ {
public static bool UserExists(string name) public static bool UserExists(string name)
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/{name}"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/{name}");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -154,27 +164,51 @@ public class StaticClient
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
public static void Signup(UserDto request) public static void Signup(UserDto request, bool compress = false)
{ {
var message = new HttpRequestMessage(HttpMethod.Post, $"{StaticClient.BaseUrl}/auth/signup"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{StaticClient.BaseUrl}/auth/signup");
message.Content = new StringContent(JsonConvert.SerializeObject(request)); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); 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) if (!response.IsSuccessStatusCode)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
public static UserDto Get() public static UserDto Get()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/auth"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -183,19 +217,44 @@ public class StaticClient
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<UserDto>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<UserDto>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); 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() public static UserRole GetRole()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/role"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/auth/role");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -204,36 +263,40 @@ public class StaticClient
if (string.IsNullOrEmpty(content)) if (string.IsNullOrEmpty(content))
return default; return default;
return JsonConvert.DeserializeObject<UserRole>(content); return Newtonsoft.Json.JsonConvert.DeserializeObject<UserRole>(content);
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
} }
} }
public class Stream public class Stream
{ {
public static void Upload(System.IO.MemoryStream request) public static void Upload(System.IO.MemoryStream request, bool compress = false)
{ {
var message = new HttpRequestMessage(HttpMethod.Post, $"{StaticClient.BaseUrl}/upload"); 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); request.Seek(0, System.IO.SeekOrigin.Begin);
message.Content = new StreamContent(request); message.Content = new System.Net.Http.StreamContent(request);
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
} }
public static System.IO.MemoryStream Download() public static System.IO.MemoryStream Download()
{ {
var message = new HttpRequestMessage(HttpMethod.Get, $"{StaticClient.BaseUrl}/download"); var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, $"{StaticClient.BaseUrl}/download");
var response = StaticClient.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead); StaticClient.RequestHandler?.Invoke(message);
var response = StaticClient.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@ -245,7 +308,33 @@ public class StaticClient
} }
else else
{ {
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); 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);
} }
} }
} }

View File

@ -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>

View File

@ -8,7 +8,7 @@ using FluentAssertions;
using MontoyaTech.Rest.Net; using MontoyaTech.Rest.Net;
using System.Net; using System.Net;
namespace Rest.Net.Tests namespace MontoyaTech.Rest.Net.Tests
{ {
public class RestClientGeneratorTests public class RestClientGeneratorTests
{ {

View 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);
}
}
}

View File

@ -22,6 +22,12 @@ namespace MontoyaTech.Rest.Net.Tests
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()
{ {
@ -29,9 +35,33 @@ namespace MontoyaTech.Rest.Net.Tests
} }
[Fact] [Fact]
public void SyntaxCatchAllEmptyShouldMatch() public void SyntaxNonSlashShouldNotMatch()
{ {
RouteMatcher.Matches("http://localhost/test1", "/test1/**", out _).Should().BeTrue(); 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]
@ -43,7 +73,7 @@ namespace MontoyaTech.Rest.Net.Tests
[Fact] [Fact]
public void SyntaxWildCardEmptyShouldMatch() public void SyntaxWildCardEmptyShouldMatch()
{ {
RouteMatcher.Matches("http://localhost/test1", "/test1/*", out _).Should().BeTrue(); RouteMatcher.Matches("http://localhost/test1/", "/test1/*", out _).Should().BeTrue();
} }
[Fact] [Fact]
@ -117,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();
} }

View 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);
}
}
}

View File

@ -406,6 +406,18 @@ namespace MontoyaTech.Rest.Net
return this; 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> /// <summary>
/// Gets all the written data from the writer. /// Gets all the written data from the writer.
/// </summary> /// </summary>

View File

@ -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;
} }

View File

@ -49,6 +49,27 @@ namespace MontoyaTech.Rest.Net
return response; 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";
var bytes = Encoding.Unicode.GetBytes(text);
response.ContentLength64 = bytes.Length;
response.OutputStream.Write(bytes, 0, bytes.Length);
response.OutputStream.Dispose();
return response;
}
/// <summary> /// <summary>
/// Sets the response content type to text and writes the given text compressed to it. /// Sets the response content type to text and writes the given text compressed to it.
/// </summary> /// </summary>
@ -61,10 +82,10 @@ namespace MontoyaTech.Rest.Net
response.Headers.Add("Content-Encoding", "gzip"); response.Headers.Add("Content-Encoding", "gzip");
var bytes = Encoding.UTF8.GetBytes(text);
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var bytes = Encoding.UTF8.GetBytes(text);
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
compressedStream.Write(bytes, 0, bytes.Length); compressedStream.Write(bytes, 0, bytes.Length);
@ -101,6 +122,27 @@ namespace MontoyaTech.Rest.Net
return response; 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";
var bytes = Encoding.Unicode.GetBytes(JsonConvert.SerializeObject(obj));
response.ContentLength64 = bytes.Length;
response.OutputStream.Write(bytes, 0, bytes.Length);
response.OutputStream.Dispose();
return response;
}
/// <summary> /// <summary>
/// Sets the response content type to json and writes the given json compressed to it. /// Sets the response content type to json and writes the given json compressed to it.
/// </summary> /// </summary>
@ -110,12 +152,13 @@ namespace MontoyaTech.Rest.Net
public static HttpListenerResponse WithCompressedJson(this HttpListenerResponse response, object obj) public static HttpListenerResponse WithCompressedJson(this HttpListenerResponse response, object obj)
{ {
response.ContentType = "application/json; charset=utf-8"; response.ContentType = "application/json; charset=utf-8";
response.Headers.Add("Content-Encoding", "gzip");
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj)); response.Headers.Add("Content-Encoding", "gzip");
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
compressedStream.Write(bytes, 0, bytes.Length); compressedStream.Write(bytes, 0, bytes.Length);
@ -312,6 +355,56 @@ namespace MontoyaTech.Rest.Net
return response; 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";
var bytes = Encoding.Unicode.GetBytes(html);
response.ContentLength64 = bytes.Length;
response.OutputStream.Write(bytes, 0, bytes.Length);
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>
@ -501,21 +594,286 @@ namespace MontoyaTech.Rest.Net
/// <summary> /// <summary>
/// Sets the response to serve a file in the context of a multi page application. /// Sets the response to serve a file in the context of a multi page application.
/// </summary> /// </summary>
/// <param name="response"></param> /// <param name="response">The response to modify</param>
/// <param name="request"></param> /// <param name="basePath">The base path where to serve files from</param>
/// <returns></returns> /// <param name="request">The request to serve</param>
public static HttpListenerResponse ServeMultiPage(this HttpListenerResponse response, HttpListenerRequest request) /// <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> /// <summary>
/// Sets the response to serve a file in the context of a single page application. /// Sets the response to serve a file in the context of a single page application.
/// </summary> /// </summary>
/// <param name="response"></param> /// <param name="response">The response to modify</param>
/// <param name="request"></param> /// <param name="basePath">The base path where to serve files from</param>
/// <returns></returns> /// <param name="startPath">The starting path that should be removed from requests, if null or empty, requests won't be affected</param>
public static HttpListenerResponse ServeSinglePage(this HttpListenerResponse response, HttpListenerRequest request) /// <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;
}
} }
} }
} }

View File

@ -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.6.2</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,14 @@
<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>
<ItemGroup> <ItemGroup>

View File

@ -33,12 +33,7 @@ namespace MontoyaTech.Rest.Net
var writer = new CodeWriter(); var writer = new CodeWriter();
writer.WriteLine("//Generated using MontoyaTech.Rest.Net"); writer.WriteLine($"//Generated using MontoyaTech.Rest.Net - {DateTime.Now.ToShortDateString()}");
writer.WriteLine("using System;");
writer.WriteLine("using System.Net;");
writer.WriteLine("using System.Net.Http;");
writer.WriteLine("using Newtonsoft.Json;");
writer.WriteBreak().WriteLine($"public class {this.ClientName}").WriteLine("{").Indent(); writer.WriteBreak().WriteLine($"public class {this.ClientName}").WriteLine("{").Indent();
@ -50,21 +45,27 @@ namespace MontoyaTech.Rest.Net
//Create the cookie container field //Create the cookie container field
if (this.StaticCode) if (this.StaticCode)
writer.WriteBreak().WriteLine("public static CookieContainer CookieContainer;"); writer.WriteBreak().WriteLine("public static System.Net.CookieContainer CookieContainer;");
else else
writer.WriteBreak().WriteLine("public CookieContainer CookieContainer;"); writer.WriteBreak().WriteLine("public System.Net.CookieContainer CookieContainer;");
//Create the client handler field //Create the client handler field
if (this.StaticCode) if (this.StaticCode)
writer.WriteBreak().WriteLine("public static HttpMessageHandler MessageHandler;"); writer.WriteBreak().WriteLine("public static System.Net.Http.HttpMessageHandler MessageHandler;");
else else
writer.WriteBreak().WriteLine("public HttpMessageHandler MessageHandler;"); writer.WriteBreak().WriteLine("public System.Net.Http.HttpMessageHandler MessageHandler;");
//Create the http client field //Create the http client field
if (this.StaticCode) if (this.StaticCode)
writer.WriteBreak().WriteLine("public static HttpClient HttpClient;"); writer.WriteBreak().WriteLine("public static System.Net.Http.HttpClient HttpClient;");
else else
writer.WriteBreak().WriteLine("public HttpClient HttpClient;"); 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. //Create fields foreach route group so they can be accessed.
if (!this.StaticCode) if (!this.StaticCode)
@ -74,11 +75,11 @@ namespace MontoyaTech.Rest.Net
//Create the client constructor or init method //Create the client constructor or init method
if (this.StaticCode) if (this.StaticCode)
{ {
writer.WriteBreak().WriteLine("public static void Init(string baseUrl, HttpMessageHandler handler = null)").WriteLine("{").Indent(); 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 //Make sure the base url isn't null or whitespace
writer.WriteBreak().WriteLine("if (string.IsNullOrWhiteSpace(baseUrl))"); writer.WriteBreak().WriteLine("if (string.IsNullOrWhiteSpace(baseUrl))");
writer.Indent().WriteLine(@"throw new ArgumentException(""baseUrl must not be null or whitespace."");").Outdent(); writer.Indent().WriteLine(@"throw new System.ArgumentException(""baseUrl must not be null or whitespace."");").Outdent();
//If the baseUrl ends with a /, remove it. //If the baseUrl ends with a /, remove it.
writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))"); writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))");
@ -88,25 +89,28 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;"); writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;");
//Init the cookie container //Init the cookie container
writer.WriteBreak().WriteLine($"{this.ClientName}.CookieContainer = new CookieContainer();"); writer.WriteBreak().WriteLine($"{this.ClientName}.CookieContainer = new System.Net.CookieContainer();");
//Init the client handler //Init the client handler
writer.WriteBreak().WriteLine("if (handler == null)"); writer.WriteBreak().WriteLine("if (messageHandler == null)");
writer.WriteLine("{").Indent(); writer.WriteLine("{").Indent();
writer.WriteLine($"handler = new HttpClientHandler()"); writer.WriteLine($"messageHandler = new System.Net.Http.HttpClientHandler()");
writer.WriteLine("{").Indent(); writer.WriteLine("{").Indent();
writer.WriteLine("AllowAutoRedirect = true,"); writer.WriteLine("AllowAutoRedirect = true,");
writer.WriteLine("UseCookies = true,"); writer.WriteLine("UseCookies = true,");
writer.WriteLine($"CookieContainer = {this.ClientName}.CookieContainer,"); writer.WriteLine($"CookieContainer = {this.ClientName}.CookieContainer,");
writer.WriteLine("AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate"); writer.WriteLine("AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate");
writer.Outdent().WriteLine("};"); writer.Outdent().WriteLine("};");
writer.Outdent().WriteLine("}"); writer.Outdent().WriteLine("}");
//Store the message handler //Store the message handler
writer.WriteBreak().WriteLine($"{this.ClientName}.MessageHandler = handler;"); writer.WriteBreak().WriteLine($"{this.ClientName}.MessageHandler = messageHandler;");
//Store the request handler
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler = requestHandler;");
//Init the http client //Init the http client
writer.WriteBreak().WriteLine($"{this.ClientName}.HttpClient = new HttpClient(handler);"); 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(""Accept"", ""*/*"");");
writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");"); writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");");
writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");"); writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");");
@ -115,11 +119,11 @@ namespace MontoyaTech.Rest.Net
} }
else else
{ {
writer.WriteBreak().WriteLine("public Client(string baseUrl, HttpMessageHandler handler = null)").WriteLine("{").Indent(); 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 //Make sure the base url isn't null or whitespace
writer.WriteBreak().WriteLine("if (string.IsNullOrWhiteSpace(baseUrl))"); writer.WriteBreak().WriteLine("if (string.IsNullOrWhiteSpace(baseUrl))");
writer.Indent().WriteLine(@"throw new ArgumentException(""baseUrl must not be null or whitespace."");").Outdent(); writer.Indent().WriteLine(@"throw new System.ArgumentException(""baseUrl must not be null or whitespace."");").Outdent();
//If the baseUrl ends with a /, remove it. //If the baseUrl ends with a /, remove it.
writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))"); writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))");
@ -129,25 +133,28 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;"); writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;");
//Init the cookie container //Init the cookie container
writer.WriteBreak().WriteLine("this.CookieContainer = new CookieContainer();"); writer.WriteBreak().WriteLine("this.CookieContainer = new System.Net.CookieContainer();");
//Init the client handler //Init the client handler
writer.WriteBreak().WriteLine("if (handler == null)"); writer.WriteBreak().WriteLine("if (messageHandler == null)");
writer.WriteLine("{").Indent(); writer.WriteLine("{").Indent();
writer.WriteLine("handler = new HttpClientHandler()"); writer.WriteLine("messageHandler = new System.Net.Http.HttpClientHandler()");
writer.WriteLine("{").Indent(); writer.WriteLine("{").Indent();
writer.WriteLine("AllowAutoRedirect = true,"); writer.WriteLine("AllowAutoRedirect = true,");
writer.WriteLine("UseCookies = true,"); writer.WriteLine("UseCookies = true,");
writer.WriteLine("CookieContainer = this.CookieContainer,"); writer.WriteLine("CookieContainer = this.CookieContainer,");
writer.WriteLine("AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate"); writer.WriteLine("AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate");
writer.Outdent().WriteLine("};"); writer.Outdent().WriteLine("};");
writer.Outdent().WriteLine("}"); writer.Outdent().WriteLine("}");
//Store the message handler //Store the message handler
writer.WriteBreak().WriteLine("this.MessageHandler = handler;"); writer.WriteBreak().WriteLine("this.MessageHandler = messageHandler;");
//Store the request handler
writer.WriteBreak().WriteLine("this.RequestHandler = requestHandler;");
//Init the http client //Init the http client
writer.WriteBreak().WriteLine("this.HttpClient = new HttpClient(handler);"); 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(""Accept"", ""*/*"");");
writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");"); writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");");
writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");"); writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");");
@ -250,25 +257,28 @@ namespace MontoyaTech.Rest.Net
writer.Outdent().WriteLine('}'); writer.Outdent().WriteLine('}');
//Generate a constructor to set all the fields/properties with optional default values //Generate a constructor to set all the fields/properties with optional default values
writer.WriteBreak(); if (fields.Length > 0 || properties.Length > 0)
writer.Write($"public {(newName != null ? newName.Name : type.Name)}("); {
writer.WriteBreak();
writer.Write($"public {(newName != null ? newName.Name : type.Name)}(");
foreach (var field in fields) foreach (var field in fields)
writer.WriteSeparator().Write($"{this.GetTypeFullyResolvedName(field.FieldType)} {field.Name} = {this.GetTypeDefaultValue(field.FieldType)}"); writer.WriteSeparator().Write($"{this.GetTypeFullyResolvedName(field.FieldType)} {field.Name} = {this.GetTypeDefaultValue(field.FieldType)}");
foreach (var property in properties) foreach (var property in properties)
writer.WriteSeparator().Write($"{this.GetTypeFullyResolvedName(property.PropertyType)} {property.Name} = {this.GetTypeDefaultValue(property.PropertyType)}"); writer.WriteSeparator().Write($"{this.GetTypeFullyResolvedName(property.PropertyType)} {property.Name} = {this.GetTypeDefaultValue(property.PropertyType)}");
writer.WriteLine(")"); writer.WriteLine(")");
writer.WriteLine('{').Indent(); writer.WriteLine('{').Indent();
foreach (var field in fields) foreach (var field in fields)
writer.WriteLine($"this.{field.Name} = {field.Name};"); writer.WriteLine($"this.{field.Name} = {field.Name};");
foreach (var property in properties) foreach (var property in properties)
writer.WriteLine($"this.{property.Name} = {property.Name};"); writer.WriteLine($"this.{property.Name} = {property.Name};");
writer.Outdent().WriteLine('}'); writer.Outdent().WriteLine('}');
}
} }
//Generate C# for any types that belong to this one. //Generate C# for any types that belong to this one.
@ -375,9 +385,9 @@ namespace MontoyaTech.Rest.Net
//Generate the route function header //Generate the route function header
if (this.StaticCode) if (this.StaticCode)
writer.Write($"public static {(routeResponse == null ? "void" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))} {(routeName == null ? methodInfo.Name : routeName.Name)}("); writer.Write($"public static {(routeResponse == null ? "void" : (routeResponse.Dynamic ? "dynamic" : this.GetTypeFullyResolvedName(routeResponse.ResponseType)))} {(routeName == null ? methodInfo.Name : routeName.Name)}(");
else else
writer.Write($"public {(routeResponse == null ? "void" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))} {(routeName == null ? methodInfo.Name : routeName.Name)}("); writer.Write($"public {(routeResponse == null ? "void" : (routeResponse.Dynamic ? "dynamic" : this.GetTypeFullyResolvedName(routeResponse.ResponseType)))} {(routeName == null ? methodInfo.Name : routeName.Name)}(");
//Generate the functions parameters //Generate the functions parameters
var parameters = methodInfo.GetParameters(); var parameters = methodInfo.GetParameters();
@ -394,45 +404,64 @@ namespace MontoyaTech.Rest.Net
if (routeRequest != null) if (routeRequest != null)
{ {
writer.WriteSeparator(); writer.WriteSeparator();
writer.Write(this.GetTypeFullyResolvedName(routeRequest.RequestType)).Write(" request");
if (routeRequest.Dynamic)
writer.Write("dynamic");
else
writer.Write(this.GetTypeFullyResolvedName(routeRequest.RequestType));
writer.Write(" request");
} }
if (routeResponse != null && routeResponse.Parameter) if (routeResponse != null && routeResponse.Parameter)
{ {
writer.WriteSeparator(); writer.WriteSeparator();
writer.Write(this.GetTypeFullyResolvedName(routeResponse.ResponseType)).Write(" input");
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(); writer.WriteLine(")").WriteLine("{").Indent();
//Generate the message code //Generate the message code
writer.WriteBreak().Write($"var message = new HttpRequestMessage("); writer.WriteBreak().Write($"var message = new System.Net.Http.HttpRequestMessage(");
switch (route.Method.ToLower()) switch (route.Method.ToLower())
{ {
case "post": case "post":
writer.Write("HttpMethod.Post"); writer.Write("System.Net.Http.HttpMethod.Post");
break; break;
case "get": case "get":
writer.Write("HttpMethod.Get"); writer.Write("System.Net.Http.HttpMethod.Get");
break; break;
case "delete": case "delete":
writer.Write("HttpMethod.Delete"); writer.Write("System.Net.Http.HttpMethod.Delete");
break; break;
case "put": case "put":
writer.Write("HttpMethod.Put"); writer.Write("System.Net.Http.HttpMethod.Put");
break; break;
case "options": case "options":
writer.Write("HttpMethod.Options"); writer.Write("System.Net.Http.HttpMethod.Options");
break; break;
case "patch": case "patch":
writer.Write("HttpMethod.Patch"); writer.Write("System.Net.Http.HttpMethod.Patch");
break; break;
case "head": case "head":
writer.Write("HttpMethod.Head"); writer.Write("System.Net.Http.HttpMethod.Head");
break; break;
case "trace": case "trace":
writer.Write("HttpMethod.Trace"); writer.Write("System.Net.Http.HttpMethod.Trace");
break; break;
default: default:
throw new NotSupportedException("Unsupport route method:" + route.Method); throw new NotSupportedException("Unsupport route method:" + route.Method);
@ -448,10 +477,11 @@ namespace MontoyaTech.Rest.Net
int argumentIndex = 0; int argumentIndex = 0;
foreach (var component in components) foreach (var component in components)
{ {
if (!string.IsNullOrWhiteSpace(component)) if (writer.Peek() != '/')
{
writer.Write('/'); writer.Write('/');
if (!string.IsNullOrWhiteSpace(component))
{
if (component.StartsWith("{")) if (component.StartsWith("{"))
{ {
writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}"); writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}");
@ -473,6 +503,12 @@ namespace MontoyaTech.Rest.Net
writer.Write('"').WriteLine(");"); 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. //Add the request content if any.
if (routeRequest != null) if (routeRequest != null)
{ {
@ -480,30 +516,53 @@ namespace MontoyaTech.Rest.Net
{ {
writer.WriteBreak().WriteLine("request.Seek(0, System.IO.SeekOrigin.Begin);"); writer.WriteBreak().WriteLine("request.Seek(0, System.IO.SeekOrigin.Begin);");
writer.WriteBreak().WriteLine("message.Content = new StreamContent(request);"); writer.WriteBreak().WriteLine("message.Content = new System.Net.Http.StreamContent(request);");
} }
else if (routeRequest.Json) else if (routeRequest.Json)
{ {
writer.WriteBreak().WriteLine("message.Content = new StringContent(JsonConvert.SerializeObject(request));"); 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 else
{ {
writer.WriteBreak().WriteLine("message.Content = new StringContent(request.ToString());"); writer.WriteBreak().WriteLine("message.Content = new System.Net.Http.StringContent(request.ToString());");
} }
} }
//Generate the response code //Generate the response code
if (this.StaticCode) if (this.StaticCode)
writer.WriteBreak().WriteLine($"var response = {this.ClientName}.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead);"); writer.WriteBreak().WriteLine($"var response = {this.ClientName}.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);");
else else
writer.WriteBreak().WriteLine("var response = this.Client.HttpClient.Send(message, HttpCompletionOption.ResponseHeadersRead);"); writer.WriteBreak().WriteLine("var response = this.Client.HttpClient.Send(message, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);");
//Handle the response //Handle the response
if (routeResponse != null) if (routeResponse != null)
{ {
writer.WriteBreak().WriteLine("if (response.IsSuccessStatusCode)").WriteLine("{").Indent(); writer.WriteBreak().WriteLine("if (response.IsSuccessStatusCode)").WriteLine("{").Indent();
if (routeResponse.ResponseType.IsAssignableTo(typeof(Stream))) if (routeResponse.ResponseType != null && routeResponse.ResponseType.IsAssignableTo(typeof(Stream)))
{ {
if (routeResponse.Parameter) if (routeResponse.Parameter)
{ {
@ -528,7 +587,7 @@ namespace MontoyaTech.Rest.Net
if (routeResponse.Json) if (routeResponse.Json)
{ {
writer.WriteBreak().WriteLine($"return JsonConvert.DeserializeObject<{this.GetTypeFullyResolvedName(routeResponse.ResponseType)}>(content);"); writer.WriteBreak().WriteLine($"return Newtonsoft.Json.JsonConvert.DeserializeObject<{(routeResponse.Dynamic ? "dynamic" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))}>(content);");
} }
else else
{ {
@ -547,7 +606,7 @@ namespace MontoyaTech.Rest.Net
break; break;
case TypeCode.DateTime: case TypeCode.DateTime:
writer.WriteBreak().WriteLine("return DateTime.Parse(content);"); writer.WriteBreak().WriteLine("return System.DateTime.Parse(content);");
break; break;
case TypeCode.Decimal: case TypeCode.Decimal:
@ -595,19 +654,19 @@ namespace MontoyaTech.Rest.Net
break; break;
case TypeCode.Object: case TypeCode.Object:
throw new NotSupportedException("ResponseType isn't JSON but is an object."); throw new NotSupportedException("RouteResponse has JSON=false but ResponseType is an object.");
} }
} }
} }
writer.Outdent().WriteLine("}").WriteLine("else").WriteLine("{").Indent(); writer.Outdent().WriteLine("}").WriteLine("else").WriteLine("{").Indent();
writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);"); writer.WriteLine(@"throw new System.Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);");
writer.Outdent().WriteLine("}"); writer.Outdent().WriteLine("}");
} }
else else
{ {
writer.WriteBreak().WriteLine("if (!response.IsSuccessStatusCode)").Indent(); writer.WriteBreak().WriteLine("if (!response.IsSuccessStatusCode)").Indent();
writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); writer.WriteLine(@"throw new System.Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent();
} }
//Close off the route function. //Close off the route function.

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.Design; using System.ComponentModel.Design;
@ -58,76 +58,49 @@ namespace MontoyaTech.Rest.Net
/// Finds all the dependencies for a given type and returns them. /// Finds all the dependencies for a given type and returns them.
/// </summary> /// </summary>
/// <param name="type"></param> /// <param name="type"></param>
/// <param name="dependencies"></param>
/// <returns></returns> /// <returns></returns>
protected internal virtual List<Type> FindTypeDependencies(Type type) protected internal virtual HashSet<Type> FindTypeDependencies(Type type, HashSet<Type> dependencies = null)
{ {
var dependencies = new HashSet<Type>(); 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(); var arguments = type.GetGenericArguments();
if (arguments != null) if (arguments != null)
{
foreach (var argument in arguments) foreach (var argument in arguments)
{ if (argument != type)
var types = this.FindTypeDependencies(argument); this.FindTypeDependencies(argument, dependencies);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
}
if (this.IsTypeDotNet(type)) if (this.IsTypeDotNet(type))
return dependencies.ToList(); return dependencies;
dependencies.Add(type); dependencies.Add(type);
if (type.IsEnum) if (type.IsEnum)
return dependencies.ToList(); return dependencies;
{ this.FindTypeDependencies(type.BaseType, dependencies);
var types = this.FindTypeDependencies(type.BaseType);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
var fields = type.GetFields(); var fields = type.GetFields();
if (fields != null) if (fields != null)
{
foreach (var field in fields) foreach (var field in fields)
{ if (field.IsPublic && !field.IsSpecialName && field.FieldType != type)
if (field.IsPublic && !field.IsSpecialName) this.FindTypeDependencies(field.FieldType, dependencies);
{
var types = this.FindTypeDependencies(field.FieldType);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
}
}
var properties = type.GetProperties(); var properties = type.GetProperties();
if (properties != null) if (properties != null)
{
foreach (var property in properties) foreach (var property in properties)
{ if (!property.IsSpecialName && property.GetSetMethod() != null && property.GetGetMethod() != null && property.PropertyType != type)
if (!property.IsSpecialName && property.GetSetMethod() != null && property.GetGetMethod() != null) this.FindTypeDependencies(property.PropertyType, dependencies);
{
var types = this.FindTypeDependencies(property.PropertyType);
for (int i = 0; i < types.Count; i++) return dependencies;
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
}
}
return dependencies.ToList();
} }
/// <summary> /// <summary>
@ -163,7 +136,7 @@ namespace MontoyaTech.Rest.Net
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>(); var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
if (routeRequest != null) if (routeRequest != null && routeRequest.RequestType != null)
{ {
var types = this.FindTypeDependencies(routeRequest.RequestType); var types = this.FindTypeDependencies(routeRequest.RequestType);
@ -174,7 +147,7 @@ namespace MontoyaTech.Rest.Net
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>(); var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
if (routeResponse != null) if (routeResponse != null && routeResponse.ResponseType != null)
{ {
var types = this.FindTypeDependencies(routeResponse.ResponseType); var types = this.FindTypeDependencies(routeResponse.ResponseType);
@ -245,7 +218,7 @@ namespace MontoyaTech.Rest.Net
case TypeCode.Char: case TypeCode.Char:
return "char"; return "char";
case TypeCode.DateTime: case TypeCode.DateTime:
return "DateTime"; return "System.DateTime";
case TypeCode.Decimal: case TypeCode.Decimal:
return "decimal"; return "decimal";
case TypeCode.Double: case TypeCode.Double:

View File

@ -20,6 +20,11 @@ namespace MontoyaTech.Rest.Net
/// </summary> /// </summary>
public bool StaticCode = false; 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> /// <summary>
/// Generates a Javascript Client from a given set of routes and returns it. /// Generates a Javascript Client from a given set of routes and returns it.
/// </summary> /// </summary>
@ -33,21 +38,21 @@ namespace MontoyaTech.Rest.Net
var writer = new CodeWriter(); var writer = new CodeWriter();
writer.WriteLine("//Generated using MontoyaTech.Rest.Net"); writer.WriteLine($"//Generated using MontoyaTech.Rest.Net - {DateTime.Now.ToShortDateString()}");
writer.WriteBreak().WriteLine($"class {this.ClientName} {{").Indent(); writer.WriteBreak().WriteLine($"class {this.ClientName} {{").Indent();
//Create the base url field //Create the base url field
if (this.StaticCode) writer.WriteBreak().WriteLine("/** @type {string} */");
{ writer.WriteAssert(this.StaticCode, "static ").WriteLine("BaseUrl = null;");
writer.WriteBreak().WriteLine("/** @type {string} */");
writer.WriteLine("static BaseUrl = null;"); //Create the url handler field
} writer.WriteBreak().WriteLine("/** @type {Function} */");
else writer.WriteAssert(this.StaticCode, "static ").WriteLine("UrlHandler = null;");
{
writer.WriteBreak().WriteLine("/** @type {string} */"); //Create the request handler field.
writer.WriteLine("BaseUrl = null;"); writer.WriteBreak().WriteLine("/** @type {Function} */");
} writer.WriteAssert(this.StaticCode, "static ").WriteLine("RequestHandler = null;");
//Create fields foreach route group so they can be accessed. //Create fields foreach route group so they can be accessed.
if (!this.StaticCode) if (!this.StaticCode)
@ -62,12 +67,13 @@ namespace MontoyaTech.Rest.Net
//Create the client constructor or init method //Create the client constructor or init method
if (this.StaticCode) if (this.StaticCode)
{ {
writer.WriteBreak().WriteLine("/**").Indent(); writer.WriteBreak().WriteLine("/**").Indent();
writer.WriteLine("Initializes the api client with a given baseUrl of where to send requests."); writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
writer.WriteLine("@param {string} baseUrl"); 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.Outdent().WriteLine("*/");
writer.Write("static Init(baseUrl) ").WriteLine("{").Indent(); writer.Write("static Init(baseUrl, urlHandler = null, requestHandler = null) ").WriteLine("{").Indent();
//Make sure the baseUrl isn't null or whitespace //Make sure the baseUrl isn't null or whitespace
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent(); writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
@ -82,12 +88,23 @@ namespace MontoyaTech.Rest.Net
//Store the baseUrl //Store the baseUrl
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = 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("}"); writer.Outdent().WriteLine("}");
} }
else else
{ {
writer.WriteBreak().WriteLine("/** @param {string} baseUrl */"); writer.WriteBreak().WriteLine("/**").Indent();
writer.Write("constructor(baseUrl) ").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 //Make sure the baseUrl isn't null or whitespace
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent(); writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
@ -102,8 +119,15 @@ namespace MontoyaTech.Rest.Net
//Store the baseUrl //Store the baseUrl
writer.WriteBreak().WriteLine("this.BaseUrl = 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 //Init all the route group fields
writer.WriteBreak(); writer.WriteBreak();
foreach (var group in routeGroups) foreach (var group in routeGroups)
writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);"); writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);");
@ -213,6 +237,64 @@ namespace MontoyaTech.Rest.Net
return base.GetTypeDefaultValue(type); 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) protected internal virtual void GenerateJavascriptIncludedTypes(List<Type> types, CodeWriter writer)
{ {
foreach (var type in types) foreach (var type in types)
@ -252,12 +334,12 @@ namespace MontoyaTech.Rest.Net
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public); fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
foreach (var field in fields) foreach (var field in fields)
this.GenerateJavascriptIncludedField(field, writer); this.GenerateJavascriptIncludedField(field, writer);
var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public).Where(property => property.GetSetMethod() != null && property.GetGetMethod() != null).ToArray(); var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public).Where(property => property.GetSetMethod() != null && property.GetGetMethod() != null).ToArray();
foreach (var property in properties) foreach (var property in properties)
this.GenerateJavascriptIncludedProperty(property, writer); this.GenerateJavascriptIncludedProperty(property, writer);
//Generate a helper constructor //Generate a helper constructor
if (!type.IsEnum) if (!type.IsEnum)
@ -265,28 +347,64 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak().WriteLine("/**").Indent(); writer.WriteBreak().WriteLine("/**").Indent();
writer.WriteLine("@function"); writer.WriteLine("@function");
//Docuemnt the fields
foreach (var field in fields) foreach (var field in fields)
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(field.FieldType)}}} {field.Name}"); {
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) foreach (var property in properties)
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(property.PropertyType)}}} {property.Name}"); {
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.Outdent().WriteLine("*/");
writer.Write("constructor("); writer.Write("constructor(");
//Write the default fields
foreach (var field in fields) foreach (var field in fields)
writer.WriteSeparator().Write(field.Name).Write(" = ").Write(this.GetTypeDefaultValue(field.FieldType)); {
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) foreach (var property in properties)
writer.WriteSeparator().Write(property.Name).Write(" = ").Write(this.GetTypeDefaultValue(property.PropertyType)); {
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(); writer.WriteLine(") {").Indent();
//Init the default fields
foreach (var field in fields) foreach (var field in fields)
writer.Write("this.").Write(field.Name).Write(" = ").Write(field.Name).WriteLine(";"); {
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) foreach (var property in properties)
writer.Write("this.").Write(property.Name).Write(" = ").Write(property.Name).WriteLine(";"); {
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("}"); writer.Outdent().WriteLine("}");
} }
@ -398,12 +516,20 @@ namespace MontoyaTech.Rest.Net
if (field.DeclaringType != null && field.DeclaringType.IsEnum) if (field.DeclaringType != null && field.DeclaringType.IsEnum)
{ {
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.DeclaringType)}}} */"); writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.DeclaringType)}}} */");
writer.WriteLine($"static {field.Name} = {field.GetRawConstantValue()};");
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 else
{ {
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.FieldType)}}} */"); writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.FieldType)}}} */");
writer.WriteLine($"{field.Name} = {GetTypeDefaultValue(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)};");
} }
} }
@ -412,7 +538,11 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak(); writer.WriteBreak();
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(property.PropertyType)}}} */"); writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(property.PropertyType)}}} */");
writer.WriteLine($"{property.Name} = {GetTypeDefaultValue(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) protected internal virtual void GenerateJavascriptRouteGroups(Dictionary<string, List<Route>> groups, CodeWriter writer)
@ -496,11 +626,13 @@ namespace MontoyaTech.Rest.Net
//Generate request doc if any //Generate request doc if any
if (routeRequest != null) if (routeRequest != null)
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(routeRequest.RequestType)}}} request"); writer.WriteLine($"@param {{{(routeRequest.Dynamic ? "Any" : this.GetTypeFullyResolvedName(routeRequest.RequestType))}}} body");
//Generate response doc if any //Generate response doc if any
if (routeResponse != null) if (routeResponse != null)
writer.WriteLine($"@returns {{{this.GetTypeFullyResolvedName(routeResponse.ResponseType)}}}"); writer.WriteLine($"@returns {{{(routeResponse.Dynamic ? "Any" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))}}}");
writer.WriteLine("@throws {Response} If response status was not ok.");
writer.Outdent().WriteLine("*/"); writer.Outdent().WriteLine("*/");
@ -523,7 +655,7 @@ namespace MontoyaTech.Rest.Net
if (routeRequest != null) if (routeRequest != null)
{ {
writer.WriteSeparator(); writer.WriteSeparator();
writer.Write("request"); writer.Write("body");
} }
if (routeResponse != null && routeResponse.Parameter) if (routeResponse != null && routeResponse.Parameter)
@ -534,25 +666,25 @@ namespace MontoyaTech.Rest.Net
writer.WriteLine(") {").Indent(); writer.WriteLine(") {").Indent();
//Generate function body //Generate the url
writer.WriteBreak().Write("var url = ");
writer.WriteLine("var response = await fetch(").Indent();
//Generate the request url //Generate the request url
if (this.StaticCode) if (this.StaticCode)
writer.WriteSeparator().Write('`').Write($"${{{this.ClientName}.BaseUrl}}"); writer.Write('`').Write($"${{{this.ClientName}.BaseUrl}}");
else else
writer.WriteSeparator().Write('`').Write("${this.Client.BaseUrl}"); writer.Write('`').Write("${this.Client.BaseUrl}");
//Reconstruct the route syntax into a request url. //Reconstruct the route syntax into a request url.
var components = route.Syntax.Split('/'); var components = route.Syntax.Split('/');
int argumentIndex = 0; int argumentIndex = 0;
foreach (var component in components) foreach (var component in components)
{ {
if (!string.IsNullOrWhiteSpace(component)) if (writer.Peek() != '/')
{
writer.Write('/'); writer.Write('/');
if (!string.IsNullOrWhiteSpace(component))
{
if (component.StartsWith("{")) if (component.StartsWith("{"))
{ {
writer.Write("${").Write(parameters[argumentIndex++ + 1].Name).Write("}"); writer.Write("${").Write(parameters[argumentIndex++ + 1].Name).Write("}");
@ -572,7 +704,10 @@ namespace MontoyaTech.Rest.Net
} }
} }
writer.WriteLine("`, {").Indent(); writer.WriteLine("`;");
//Generate the request
writer.WriteBreak().WriteLine("var request = {").Indent();
//Include credentials //Include credentials
writer.WriteLine("credentials: 'include',"); writer.WriteLine("credentials: 'include',");
@ -586,28 +721,44 @@ namespace MontoyaTech.Rest.Net
if (routeRequest.RequestType.IsAssignableTo(typeof(Stream))) if (routeRequest.RequestType.IsAssignableTo(typeof(Stream)))
{ {
writer.WriteLine("headers: new Headers({ 'Content-Type': 'application/octet-stream' }),"); writer.WriteLine("headers: new Headers({ 'Content-Type': 'application/octet-stream' }),");
writer.WriteLine("body: request,"); writer.WriteLine("body: body,");
} }
else if (routeRequest.Json) else if (routeRequest.Json)
{ {
writer.WriteLine("body: JSON.stringify(request),"); writer.WriteLine("body: JSON.stringify(body),");
} }
else else
{ {
writer.WriteLine("body: request.toString(),"); writer.WriteLine("body: body.toString(),");
} }
} }
writer.Outdent().WriteLine("}"); writer.Outdent().WriteLine("};");
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) if (routeResponse != null)
{ {
writer.WriteBreak().WriteLine("if (response.ok) {").Indent(); writer.WriteBreak().WriteLine("if (response.ok) {").Indent();
if (routeResponse.ResponseType.IsAssignableTo(typeof(Stream))) if (routeResponse.ResponseType != null && routeResponse.ResponseType.IsAssignableTo(typeof(Stream)))
{ {
if (routeResponse.Parameter) if (routeResponse.Parameter)
{ {
@ -664,14 +815,14 @@ namespace MontoyaTech.Rest.Net
} }
} }
writer.Outdent().WriteLine("} else {").Indent();
writer.WriteLine("throw `Unexpected http response status: ${response.status}`;");
writer.Outdent().WriteLine("}"); writer.Outdent().WriteLine("}");
writer.WriteBreak().WriteLine("throw response;");
} }
else else
{ {
writer.WriteBreak().WriteLine("if (!response.ok) {").Indent(); writer.WriteBreak().WriteLine("if (!response.ok) {").Indent();
writer.WriteLine("throw `Unexpected http response status: ${response.status}`;"); writer.WriteLine("throw response;");
writer.Outdent().WriteLine("}"); writer.Outdent().WriteLine("}");
} }

View File

@ -49,9 +49,9 @@ namespace MontoyaTech.Rest.Net
if (target == null) if (target == null)
throw new ArgumentNullException(nameof(target)); throw new ArgumentNullException(nameof(target));
else if (string.IsNullOrWhiteSpace(method)) else if (string.IsNullOrWhiteSpace(method))
throw new ArgumentException("Cannot be null or empty", nameof(method)); throw new ArgumentException("Method cannot be null or empty", nameof(method));
else if (string.IsNullOrWhiteSpace(syntax)) else if (string.IsNullOrWhiteSpace(syntax))
throw new ArgumentException("Cannot be null or empty", nameof(syntax)); throw new ArgumentException("Syntax cannot be null or empty", nameof(syntax));
this.Method = method; this.Method = method;
this.Syntax = syntax; this.Syntax = syntax;

View File

@ -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;
} }
} }
} }

View File

@ -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.
@ -256,12 +256,13 @@ namespace MontoyaTech.Rest.Net
/// <param name="clientName"></param> /// <param name="clientName"></param>
/// <param name="staticCode"></param> /// <param name="staticCode"></param>
/// <returns></returns> /// <returns></returns>
public string GenerateJavascriptClient(string clientName = "Client", bool staticCode = false) public string GenerateJavascriptClient(string clientName = "Client", bool staticCode = false, bool useJsonNames = false)
{ {
var generator = new RestJavascriptClientGenerator(); var generator = new RestJavascriptClientGenerator();
generator.ClientName = clientName; generator.ClientName = clientName;
generator.StaticCode = staticCode; generator.StaticCode = staticCode;
generator.UseJsonNames = useJsonNames;
return generator.Generate(this); return generator.Generate(this);
} }

View File

@ -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)
@ -85,20 +99,25 @@ namespace MontoyaTech.Rest.Net
return false; 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.
else if (syntaxSegment == "**") else if (syntaxSegment == "**" && urlSegment != null)
{ {
return true; return true;
} }
//If we ran out of url segments, and this syntax segment is a wild card, this is okay. //If we ran out of url segments, and this syntax segment is a wild card, this is not a match.
else if (urlSegment == null && syntaxSegment == "*") else if (syntaxSegment == "*" && urlSegment != null)
{ {
return true; //Allowed
} }
//If we have a syntaxSegment but no urlSegment, this can't be a match. //If we have a syntaxSegment but no urlSegment, this can't be a match.
else if (urlSegment == null && syntaxSegment != null) else if (urlSegment == null && syntaxSegment != null)
{ {
return false; 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. //If we have a url segment see if it matches against the syntax segment.
else if (urlSegment != null) else if (urlSegment != null)
{ {

View File

@ -22,6 +22,11 @@ namespace MontoyaTech.Rest.Net
/// </summary> /// </summary>
public bool Json = true; public bool Json = true;
/// <summary>
/// Whether or not the request is a dynamic type. Default is false.
/// </summary>
public bool Dynamic = false;
/// <summary> /// <summary>
/// Creates a default route request. /// Creates a default route request.
/// </summary> /// </summary>

View File

@ -23,10 +23,15 @@ namespace MontoyaTech.Rest.Net
public bool Json = true; public bool Json = true;
/// <summary> /// <summary>
/// Whether or not the response is passed as a parameter to the route function. /// Whether or not the response is passed as a parameter to the route function. Default is false.
/// </summary> /// </summary>
public bool Parameter = false; public bool Parameter = false;
/// <summary>
/// Whether or not the response is a dynamic type. Default is false.
/// </summary>
public bool Dynamic = false;
/// <summary> /// <summary>
/// Creates a default route response. /// Creates a default route response.
/// </summary> /// </summary>

View File

@ -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();
}
} }
} }