Compare commits

..

25 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
23 changed files with 1255 additions and 449 deletions

View File

@ -1,18 +1,16 @@
//Generated using MontoyaTech.Rest.Net
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;
//Generated using MontoyaTech.Rest.Net - 2/18/2024
public class Client
{
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;
@ -20,32 +18,36 @@ public class Client
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))
throw new ArgumentException("baseUrl must not be null or whitespace.");
throw new System.ArgumentException("baseUrl must not be null or whitespace.");
if (baseUrl.EndsWith('/'))
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
this.BaseUrl = baseUrl;
this.CookieContainer = new CookieContainer();
this.CookieContainer = new System.Net.CookieContainer();
if (handler == null)
{
handler = new HttpClientHandler()
handler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = true,
UseCookies = true,
CookieContainer = this.CookieContainer,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
};
}
this.MessageHandler = handler;
this.HttpClient = new HttpClient(handler);
this.RequestHandler = requestHandler;
this.HttpClient = new System.Net.Http.HttpClient(handler);
this.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*");
@ -58,6 +60,8 @@ public class Client
this.Auth = new AuthApi(this);
this.Stream = new StreamApi(this);
this.Form = new FormApi(this);
}
public class TestApi
@ -71,9 +75,11 @@ public class Client
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)
{
@ -82,19 +88,21 @@ public class Client
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
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)
{
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)
{
@ -103,19 +111,21 @@ public class Client
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
else
{
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
}
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)
{
@ -124,19 +134,21 @@ public class Client
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
else
{
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
}
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)
{
@ -145,11 +157,11 @@ public class Client
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
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)
{
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)
{
@ -180,27 +194,51 @@ public class Client
}
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)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
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)
{
@ -209,19 +247,44 @@ public class Client
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<UserDto>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserDto>(content);
}
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()
{
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)
{
@ -230,11 +293,11 @@ public class Client
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<UserRole>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserRole>(content);
}
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;
}
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);
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)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
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)
{
@ -278,7 +345,40 @@ public class Client
}
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 System.Net.Mime;
using System.Collections;
using Newtonsoft.Json.Linq;
using System.Web;
using System.Net.Http;
using Newtonsoft.Json;
namespace MontoyaTech.Rest.Net.Example
{
public class BaseUser
{
[JsonProperty("id")]
public string Id;
[JsonProperty("firstInitial")]
public char FirstInitial;
[JsonProperty("role")]
public UserRole Role { get; set; }
[JsonProperty("permissions")]
public List<Permission> Permissions;
public class Permission
@ -80,6 +88,7 @@ namespace MontoyaTech.Rest.Net.Example
new Route<string>(HttpRequestMethod.Get, "/auth/{username}", Exists),
new Route(HttpRequestMethod.Post, "/auth/signup", Signup),
new Route(HttpRequestMethod.Get, "/auth/", Json),
new Route(HttpRequestMethod.Get, "/auth/dynamic", Dynamic),
new Route(HttpRequestMethod.Get, "/auth/role", GetRole),
new Route(HttpRequestMethod.Post, "/upload", Upload),
new Route(HttpRequestMethod.Get, "/download", Download),
@ -90,11 +99,11 @@ namespace MontoyaTech.Rest.Net.Example
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.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");
@ -117,7 +126,18 @@ namespace MontoyaTech.Rest.Net.Example
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())
{
@ -208,6 +228,13 @@ namespace MontoyaTech.Rest.Net.Example
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(new User("Rest.Net"));
}
[RouteGroup("Auth")]
[RouteResponse(Dynamic = true)]
public static HttpListenerResponse Dynamic(HttpListenerContext context)
{
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(777);
}
[RouteGroup("Stream")]
[RouteRequest(typeof(MemoryStream))]
public static HttpListenerResponse Upload(HttpListenerContext context)

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<AssemblyName>MontoyaTech.Rest.Net.Example</AssemblyName>

View File

@ -1,45 +1,45 @@
//Generated using MontoyaTech.Rest.Net
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;
//Generated using MontoyaTech.Rest.Net - 2/18/2024
public class StaticClient
{
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))
throw new ArgumentException("baseUrl must not be null or whitespace.");
throw new System.ArgumentException("baseUrl must not be null or whitespace.");
if (baseUrl.EndsWith('/'))
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
StaticClient.BaseUrl = baseUrl;
StaticClient.CookieContainer = new CookieContainer();
StaticClient.CookieContainer = new System.Net.CookieContainer();
if (handler == null)
{
handler = new HttpClientHandler()
handler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = true,
UseCookies = true,
CookieContainer = StaticClient.CookieContainer,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
};
}
StaticClient.MessageHandler = handler;
StaticClient.HttpClient = new HttpClient(handler);
StaticClient.RequestHandler = requestHandler;
StaticClient.HttpClient = new System.Net.Http.HttpClient(handler);
StaticClient.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*");
@ -52,9 +52,11 @@ public class StaticClient
{
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)
{
@ -63,19 +65,21 @@ public class StaticClient
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
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)
{
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)
{
@ -84,19 +88,21 @@ public class StaticClient
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
else
{
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
}
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)
{
@ -105,19 +111,21 @@ public class StaticClient
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
else
{
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
}
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)
{
@ -126,11 +134,11 @@ public class StaticClient
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<string>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<string>(content);
}
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)
{
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)
{
@ -154,27 +164,51 @@ public class StaticClient
}
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)
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
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)
{
@ -183,19 +217,44 @@ public class StaticClient
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<UserDto>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserDto>(content);
}
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()
{
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)
{
@ -204,36 +263,40 @@ public class StaticClient
if (string.IsNullOrEmpty(content))
return default;
return JsonConvert.DeserializeObject<UserRole>(content);
return Newtonsoft.Json.JsonConvert.DeserializeObject<UserRole>(content);
}
else
{
throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
throw new System.Exception("Unexpected Http Response StatusCode:" + response.StatusCode);
}
}
}
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);
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)
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()
{
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)
{
@ -245,7 +308,33 @@ public class StaticClient
}
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">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<IsPackable>false</IsPackable>
@ -9,14 +9,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="FluentAssertions" Version="8.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</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>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -8,7 +8,7 @@ using FluentAssertions;
using MontoyaTech.Rest.Net;
using System.Net;
namespace Rest.Net.Tests
namespace MontoyaTech.Rest.Net.Tests
{
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();
}
[Fact]
public void SyntaxWithRootNoSlashShouldMatch()
{
RouteMatcher.Matches("http://localhost", "/", out _).Should().BeTrue();
}
[Fact]
public void SyntaxWithRootCatchAllShouldMatch()
{
@ -29,9 +35,33 @@ namespace MontoyaTech.Rest.Net.Tests
}
[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]
@ -43,7 +73,7 @@ namespace MontoyaTech.Rest.Net.Tests
[Fact]
public void SyntaxWildCardEmptyShouldMatch()
{
RouteMatcher.Matches("http://localhost/test1", "/test1/*", out _).Should().BeTrue();
RouteMatcher.Matches("http://localhost/test1/", "/test1/*", out _).Should().BeTrue();
}
[Fact]
@ -117,6 +147,7 @@ namespace MontoyaTech.Rest.Net.Tests
public void SyntaxWithOrShouldMatch()
{
RouteMatcher.Matches("http://localhost/a/b", "/a/b|c", out _).Should().BeTrue();
RouteMatcher.Matches("http://localhost/a/c", "/a/b|c", out _).Should().BeTrue();
}

View File

@ -8,7 +8,7 @@ using FluentAssertions;
using MontoyaTech.Rest.Net;
using Xunit;
namespace Rest.Net.Tests
namespace MontoyaTech.Rest.Net.Tests
{
public class ServeFileTests
{
@ -18,6 +18,8 @@ namespace Rest.Net.Tests
public string TestFile = null;
public string IndexFile = null;
public ServeFileTests()
{
this.BaseDirectory = Path.Combine(Environment.CurrentDirectory, "test");
@ -34,12 +36,17 @@ namespace Rest.Net.Tests
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, "/test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -49,7 +56,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_Directory_ShouldWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "/test2", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeTrue();
@ -59,13 +66,13 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_NavigatingUp_Should_NotWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "../test.html", null, out string resolvedPath, out bool isDirectory).Should().BeFalse();
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, "a/b/../../test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "a/b/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -75,13 +82,23 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_NavigatingUp_Multiple_Should_NotWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "test/../../test.html", null, out string resolvedPath, out bool isDirectory).Should().BeFalse();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "test/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
}
[Fact]
public void ServeSingle_File_ShouldWork()
public void ServeSingle_Empty_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
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();
@ -89,9 +106,9 @@ namespace Rest.Net.Tests
}
[Fact]
public void ServeSingle_Directory_ShouldWork()
public void ServeSingle_Directory_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test2", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeTrue();
@ -99,9 +116,59 @@ namespace Rest.Net.Tests
}
[Fact]
public void ServeSingle_File_Route_ShouldWork()
public void ServeSingle_File_Route_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/a/b/test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
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();

View File

@ -406,6 +406,18 @@ namespace MontoyaTech.Rest.Net
return this;
}
/// <summary>
/// Returns the last character written to the writer if there was one, otherwise it returns character 0.
/// </summary>
/// <returns></returns>
public char Peek()
{
if (this.Builder.Length > 0)
return this.Builder[this.Builder.Length - 1];
else
return (char)0;
}
/// <summary>
/// Gets all the written data from the writer.
/// </summary>

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;
using System.IO.Compression;
namespace MontoyaTech.Rest.Net
{
@ -22,11 +23,22 @@ namespace MontoyaTech.Rest.Net
public static string ReadAsString(this HttpListenerRequest request)
{
try
{
//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))
return stream.ReadToEnd();
}
}
catch
{
return "";
@ -43,10 +55,21 @@ namespace MontoyaTech.Rest.Net
{
try
{
using (var input = request.InputStream)
using (var stream = new StreamReader(input))
//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 JsonConvert.DeserializeObject<T>(stream.ReadToEnd());
}
else
{
using (var inputStream = request.InputStream)
using (var stream = new StreamReader(inputStream))
return JsonConvert.DeserializeObject<T>(stream.ReadToEnd());
}
}
catch
{
return default(T);
@ -61,6 +84,24 @@ namespace MontoyaTech.Rest.Net
public static byte[] ReadAsBytes(this HttpListenerRequest request)
{
try
{
//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 memoryStream = new MemoryStream())
{
gzipStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
}
else
{
using (var input = request.InputStream)
{
@ -71,6 +112,7 @@ namespace MontoyaTech.Rest.Net
}
}
}
}
catch
{
return null;
@ -86,9 +128,23 @@ namespace MontoyaTech.Rest.Net
public static bool ReadToStream(this HttpListenerRequest request, Stream stream)
{
try
{
//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))
{
gzipStream.CopyTo(stream);
}
}
}
else
{
using (var input = request.InputStream)
input.CopyTo(stream);
}
return true;
}

View File

@ -49,6 +49,27 @@ namespace MontoyaTech.Rest.Net
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>
/// Sets the response content type to text and writes the given text compressed to it.
/// </summary>
@ -61,10 +82,10 @@ namespace MontoyaTech.Rest.Net
response.Headers.Add("Content-Encoding", "gzip");
var bytes = Encoding.UTF8.GetBytes(text);
using (var memoryStream = new MemoryStream())
{
var bytes = Encoding.UTF8.GetBytes(text);
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
compressedStream.Write(bytes, 0, bytes.Length);
@ -101,6 +122,27 @@ namespace MontoyaTech.Rest.Net
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>
/// Sets the response content type to json and writes the given json compressed to it.
/// </summary>
@ -110,12 +152,13 @@ namespace MontoyaTech.Rest.Net
public static HttpListenerResponse WithCompressedJson(this HttpListenerResponse response, object obj)
{
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())
{
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
using (var compressedStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
compressedStream.Write(bytes, 0, bytes.Length);
@ -312,6 +355,56 @@ namespace MontoyaTech.Rest.Net
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>
/// Sets the status code for a given response.
/// </summary>
@ -508,9 +601,9 @@ namespace MontoyaTech.Rest.Net
/// <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, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
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, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
if (ResolveMultiPagePath(basePath, startPath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
{
if (isDirectory)
{
@ -537,7 +630,7 @@ namespace MontoyaTech.Rest.Net
}
}
internal static bool ResolveMultiPagePath(string basePath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
internal static bool ResolveMultiPagePath(string basePath, string startPath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
{
resolvedPath = null;
@ -547,44 +640,62 @@ namespace MontoyaTech.Rest.Net
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
requestPath = indexFile;
//Break th erequest path into it's components so we can enfore staying in the base path.
var components = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//Break the startPath into it's components
var startComponents = startPath?.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
for (int i = 0; i < components.Count; i++)
//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)
{
if (components[i].Trim() == "..")
for (int i = 0; i < startComponents.Count; i++)
{
components.RemoveAt(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)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == "...")
else if (requestComponents[i].Trim() == "...")
{
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == ".")
else if (requestComponents[i].Trim() == ".")
{
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
}
}
if (components.Count == 0)
if (requestComponents.Count == 0)
return false;
var absolutePath = Path.Combine(basePath, components.Separate(Path.DirectorySeparatorChar));
var absolutePath = Path.Combine(basePath, requestComponents.Separate(Path.DirectorySeparatorChar));
if (File.Exists(absolutePath))
{
@ -611,14 +722,15 @@ namespace MontoyaTech.Rest.Net
/// </summary>
/// <param name="response">The response to modify</param>
/// <param name="basePath">The base path where to serve files from</param>
/// <param name="startPath">The starting path that should be removed from requests, if null or empty, requests won't be affected</param>
/// <param name="request">The request to serve</param>
/// <param name="indexFile">The name of the index file, default is index.html</param>
/// <param name="compress">Whether or not to compress files served. Default is false.</param>
/// <param name="compressExtensions">A collection of file extensions that should be compressed, example: .jpg, default is null. If and compress is true, all files will be compressed.</param>
/// <returns>The modified response</returns>
public static HttpListenerResponse ServeSinglePage(this HttpListenerResponse response, string basePath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
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, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
if (ResolveSinglePagePath(basePath, startPath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
{
if (isDirectory)
{
@ -645,7 +757,7 @@ namespace MontoyaTech.Rest.Net
}
}
internal static bool ResolveSinglePagePath(string basePath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
internal static bool ResolveSinglePagePath(string basePath, string startPath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
{
resolvedPath = null;
@ -655,83 +767,98 @@ namespace MontoyaTech.Rest.Net
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
requestPath = indexFile;
//Break th erequest path into it's components so we can enfore staying in the base path.
var components = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//Break the startPath into it's components
var startComponents = startPath?.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
for (int i = 0; i < components.Count; i++)
//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)
{
if (components[i].Trim() == "..")
for (int i = 0; i < startComponents.Count; i++)
{
components.RemoveAt(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)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == "...")
else if (requestComponents[i].Trim() == "...")
{
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == ".")
else if (requestComponents[i].Trim() == ".")
{
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
}
}
if (components.Count == 0)
return false;
var absolutePath = Path.Combine(basePath, components.Separate(Path.DirectorySeparatorChar));
if (File.Exists(absolutePath))
//Check the components and remove any that are invalid.
while (requestComponents.Count > 0)
{
resolvedPath = absolutePath;
return true;
}
else if (Directory.Exists(absolutePath))
{
resolvedPath = absolutePath;
isDirectory = true;
return true;
}
else
{
//Try to components that don't exist and try again
while (components.Count > 0)
{
string path = Path.Combine(basePath, components[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
components.RemoveAt(0);
{
requestComponents.RemoveAt(0);
}
}
if (components.Count == 0)
return false;
//Quirk, if the components is now empty, point to the indexFile
if (requestComponents.Count == 0)
requestComponents.Add(indexFile);
absolutePath = Path.Combine(basePath, components.Separate(Path.PathSeparator));
//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;
@ -740,8 +867,12 @@ namespace MontoyaTech.Rest.Net
return true;
}
//Otherwise redirect to index.html
else
{
resolvedPath = Path.Combine(basePath, indexFile);
return false;
return true;
}
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>library</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@ -17,9 +17,10 @@
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>1.6.3</Version>
<Version>1.8.9</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
@ -27,10 +28,14 @@
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>

View File

@ -33,12 +33,7 @@ namespace MontoyaTech.Rest.Net
var writer = new CodeWriter();
writer.WriteLine("//Generated using MontoyaTech.Rest.Net");
writer.WriteLine("using System;");
writer.WriteLine("using System.Net;");
writer.WriteLine("using System.Net.Http;");
writer.WriteLine("using Newtonsoft.Json;");
writer.WriteLine($"//Generated using MontoyaTech.Rest.Net - {DateTime.Now.ToShortDateString()}");
writer.WriteBreak().WriteLine($"public class {this.ClientName}").WriteLine("{").Indent();
@ -50,21 +45,27 @@ namespace MontoyaTech.Rest.Net
//Create the cookie container field
if (this.StaticCode)
writer.WriteBreak().WriteLine("public static CookieContainer CookieContainer;");
writer.WriteBreak().WriteLine("public static System.Net.CookieContainer CookieContainer;");
else
writer.WriteBreak().WriteLine("public CookieContainer CookieContainer;");
writer.WriteBreak().WriteLine("public System.Net.CookieContainer CookieContainer;");
//Create the client handler field
if (this.StaticCode)
writer.WriteBreak().WriteLine("public static HttpMessageHandler MessageHandler;");
writer.WriteBreak().WriteLine("public static System.Net.Http.HttpMessageHandler MessageHandler;");
else
writer.WriteBreak().WriteLine("public HttpMessageHandler MessageHandler;");
writer.WriteBreak().WriteLine("public System.Net.Http.HttpMessageHandler MessageHandler;");
//Create the http client field
if (this.StaticCode)
writer.WriteBreak().WriteLine("public static HttpClient HttpClient;");
writer.WriteBreak().WriteLine("public static System.Net.Http.HttpClient HttpClient;");
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.
if (!this.StaticCode)
@ -74,11 +75,11 @@ namespace MontoyaTech.Rest.Net
//Create the client constructor or init method
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
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.
writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))");
@ -88,25 +89,28 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;");
//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
writer.WriteBreak().WriteLine("if (handler == null)");
writer.WriteBreak().WriteLine("if (messageHandler == null)");
writer.WriteLine("{").Indent();
writer.WriteLine($"handler = new HttpClientHandler()");
writer.WriteLine($"messageHandler = new System.Net.Http.HttpClientHandler()");
writer.WriteLine("{").Indent();
writer.WriteLine("AllowAutoRedirect = true,");
writer.WriteLine("UseCookies = true,");
writer.WriteLine($"CookieContainer = {this.ClientName}.CookieContainer,");
writer.WriteLine("AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate");
writer.WriteLine("AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate");
writer.Outdent().WriteLine("};");
writer.Outdent().WriteLine("}");
//Store the message handler
writer.WriteBreak().WriteLine($"{this.ClientName}.MessageHandler = handler;");
writer.WriteBreak().WriteLine($"{this.ClientName}.MessageHandler = messageHandler;");
//Store the request handler
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler = requestHandler;");
//Init the http client
writer.WriteBreak().WriteLine($"{this.ClientName}.HttpClient = new 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(""Connection"", ""keep-alive"");");
writer.WriteBreak().WriteLine(@$"{this.ClientName}.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");");
@ -115,11 +119,11 @@ namespace MontoyaTech.Rest.Net
}
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
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.
writer.WriteBreak().WriteLine("if (baseUrl.EndsWith('/'))");
@ -129,25 +133,28 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;");
//Init the cookie container
writer.WriteBreak().WriteLine("this.CookieContainer = new CookieContainer();");
writer.WriteBreak().WriteLine("this.CookieContainer = new System.Net.CookieContainer();");
//Init the client handler
writer.WriteBreak().WriteLine("if (handler == null)");
writer.WriteBreak().WriteLine("if (messageHandler == null)");
writer.WriteLine("{").Indent();
writer.WriteLine("handler = new HttpClientHandler()");
writer.WriteLine("messageHandler = new System.Net.Http.HttpClientHandler()");
writer.WriteLine("{").Indent();
writer.WriteLine("AllowAutoRedirect = true,");
writer.WriteLine("UseCookies = true,");
writer.WriteLine("CookieContainer = this.CookieContainer,");
writer.WriteLine("AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate");
writer.WriteLine("AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate");
writer.Outdent().WriteLine("};");
writer.Outdent().WriteLine("}");
//Store the message handler
writer.WriteBreak().WriteLine("this.MessageHandler = handler;");
writer.WriteBreak().WriteLine("this.MessageHandler = messageHandler;");
//Store the request handler
writer.WriteBreak().WriteLine("this.RequestHandler = requestHandler;");
//Init the http client
writer.WriteBreak().WriteLine("this.HttpClient = new 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(""Connection"", ""keep-alive"");");
writer.WriteBreak().WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");");
@ -250,6 +257,8 @@ namespace MontoyaTech.Rest.Net
writer.Outdent().WriteLine('}');
//Generate a constructor to set all the fields/properties with optional default values
if (fields.Length > 0 || properties.Length > 0)
{
writer.WriteBreak();
writer.Write($"public {(newName != null ? newName.Name : type.Name)}(");
@ -270,6 +279,7 @@ namespace MontoyaTech.Rest.Net
writer.Outdent().WriteLine('}');
}
}
//Generate C# for any types that belong to this one.
for (int i = 0; i < types.Count; i++)
@ -375,9 +385,9 @@ namespace MontoyaTech.Rest.Net
//Generate the route function header
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
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
var parameters = methodInfo.GetParameters();
@ -394,45 +404,64 @@ namespace MontoyaTech.Rest.Net
if (routeRequest != null)
{
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)
{
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();
//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())
{
case "post":
writer.Write("HttpMethod.Post");
writer.Write("System.Net.Http.HttpMethod.Post");
break;
case "get":
writer.Write("HttpMethod.Get");
writer.Write("System.Net.Http.HttpMethod.Get");
break;
case "delete":
writer.Write("HttpMethod.Delete");
writer.Write("System.Net.Http.HttpMethod.Delete");
break;
case "put":
writer.Write("HttpMethod.Put");
writer.Write("System.Net.Http.HttpMethod.Put");
break;
case "options":
writer.Write("HttpMethod.Options");
writer.Write("System.Net.Http.HttpMethod.Options");
break;
case "patch":
writer.Write("HttpMethod.Patch");
writer.Write("System.Net.Http.HttpMethod.Patch");
break;
case "head":
writer.Write("HttpMethod.Head");
writer.Write("System.Net.Http.HttpMethod.Head");
break;
case "trace":
writer.Write("HttpMethod.Trace");
writer.Write("System.Net.Http.HttpMethod.Trace");
break;
default:
throw new NotSupportedException("Unsupport route method:" + route.Method);
@ -448,10 +477,11 @@ namespace MontoyaTech.Rest.Net
int argumentIndex = 0;
foreach (var component in components)
{
if (!string.IsNullOrWhiteSpace(component))
{
if (writer.Peek() != '/')
writer.Write('/');
if (!string.IsNullOrWhiteSpace(component))
{
if (component.StartsWith("{"))
{
writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}");
@ -473,6 +503,12 @@ namespace MontoyaTech.Rest.Net
writer.Write('"').WriteLine(");");
//Invoke the request handler if needed.
if (this.StaticCode)
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler?.Invoke(message);");
else
writer.WriteBreak().WriteLine($"this.Client.RequestHandler?.Invoke(message);");
//Add the request content if any.
if (routeRequest != null)
{
@ -480,30 +516,53 @@ namespace MontoyaTech.Rest.Net
{
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)
{
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
{
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
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
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
if (routeResponse != null)
{
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)
{
@ -528,7 +587,7 @@ namespace MontoyaTech.Rest.Net
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
{
@ -547,7 +606,7 @@ namespace MontoyaTech.Rest.Net
break;
case TypeCode.DateTime:
writer.WriteBreak().WriteLine("return DateTime.Parse(content);");
writer.WriteBreak().WriteLine("return System.DateTime.Parse(content);");
break;
case TypeCode.Decimal:
@ -595,19 +654,19 @@ namespace MontoyaTech.Rest.Net
break;
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.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("}");
}
else
{
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.

View File

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

View File

@ -20,6 +20,11 @@ namespace MontoyaTech.Rest.Net
/// </summary>
public bool StaticCode = false;
/// <summary>
/// Whether or not to use Json Property names instead of the Field/Property names.
/// </summary>
public bool UseJsonNames = false;
/// <summary>
/// Generates a Javascript Client from a given set of routes and returns it.
/// </summary>
@ -33,21 +38,21 @@ namespace MontoyaTech.Rest.Net
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();
//Create the base url field
if (this.StaticCode)
{
writer.WriteBreak().WriteLine("/** @type {string} */");
writer.WriteLine("static BaseUrl = null;");
}
else
{
writer.WriteBreak().WriteLine("/** @type {string} */");
writer.WriteLine("BaseUrl = null;");
}
writer.WriteAssert(this.StaticCode, "static ").WriteLine("BaseUrl = null;");
//Create the url handler field
writer.WriteBreak().WriteLine("/** @type {Function} */");
writer.WriteAssert(this.StaticCode, "static ").WriteLine("UrlHandler = null;");
//Create the request handler field.
writer.WriteBreak().WriteLine("/** @type {Function} */");
writer.WriteAssert(this.StaticCode, "static ").WriteLine("RequestHandler = null;");
//Create fields foreach route group so they can be accessed.
if (!this.StaticCode)
@ -62,12 +67,13 @@ namespace MontoyaTech.Rest.Net
//Create the client constructor or init method
if (this.StaticCode)
{
writer.WriteBreak().WriteLine("/**").Indent();
writer.WriteLine("Initializes the api client with a given baseUrl of where to send requests.");
writer.WriteLine("@param {string} baseUrl");
writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
writer.WriteLine("@param {string} baseUrl Base url of the server to make requests against.");
writer.WriteLine("@param {Function} urlHandler An optional function to process request urls before they are sent. This must return the url.");
writer.WriteLine("@param {Function} requestHandler An optional function to process requests before they are sent. This must return the request.");
writer.Outdent().WriteLine("*/");
writer.Write("static Init(baseUrl) ").WriteLine("{").Indent();
writer.Write("static Init(baseUrl, urlHandler = null, requestHandler = null) ").WriteLine("{").Indent();
//Make sure the baseUrl isn't null or whitespace
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
@ -82,12 +88,23 @@ namespace MontoyaTech.Rest.Net
//Store the baseUrl
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;");
//Store the urlHandler
writer.WriteBreak().WriteLine($"{this.ClientName}.UrlHandler = urlHandler;");
//Store the requestHandler
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler = requestHandler;");
writer.Outdent().WriteLine("}");
}
else
{
writer.WriteBreak().WriteLine("/** @param {string} baseUrl */");
writer.Write("constructor(baseUrl) ").WriteLine("{").Indent();
writer.WriteBreak().WriteLine("/**").Indent();
writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
writer.WriteLine("@param {string} baseUrl Base url of the server to make requests against.");
writer.WriteLine("@param {Function} urlHandler An optional function to process request urls before they are sent. This must return the url.");
writer.WriteLine("@param {Function} requestHandler An optional function to process requests before they are sent. This must return the request.");
writer.Outdent().WriteLine("*/");
writer.Write("constructor(baseUrl, urlHandler = null, requestHandler = null) ").WriteLine("{").Indent();
//Make sure the baseUrl isn't null or whitespace
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
@ -102,8 +119,15 @@ namespace MontoyaTech.Rest.Net
//Store the baseUrl
writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;");
//Store the urlHandler
writer.WriteBreak().WriteLine($"this.UrlHandler = urlHandler;");
//Store the request handler
writer.WriteBreak().WriteLine("this.RequestHandler = requestHandler;");
//Init all the route group fields
writer.WriteBreak();
foreach (var group in routeGroups)
writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);");
@ -213,6 +237,64 @@ namespace MontoyaTech.Rest.Net
return base.GetTypeDefaultValue(type);
}
protected internal string EscapeName(string name)
{
if (name != null)
{
switch (name)
{
case "arguments":
case "await":
case "break":
case "case":
case "catch":
case "class":
case "const":
case "continue":
case "debugger":
case "default":
case "delete":
case "do":
case "double":
case "else":
case "enum":
case "eval":
case "export":
case "extends":
case "false":
case "finally":
case "for":
case "function":
case "goto":
case "if":
case "import":
case "in":
case "instanceof":
case "interface":
case "let":
case "new":
case "null":
case "return":
case "static":
case "super":
case "switch":
case "this":
case "throw":
case "true":
case "try":
case "typeof":
case "var":
case "void":
case "while":
case "with":
case "yield":
return "_" + name;
}
}
return name;
}
protected internal virtual void GenerateJavascriptIncludedTypes(List<Type> types, CodeWriter writer)
{
foreach (var type in types)
@ -265,28 +347,64 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak().WriteLine("/**").Indent();
writer.WriteLine("@function");
//Docuemnt the 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)
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.Write("constructor(");
//Write the default 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)
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();
//Init the default 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)
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("}");
}
@ -398,11 +516,19 @@ namespace MontoyaTech.Rest.Net
if (field.DeclaringType != null && field.DeclaringType.IsEnum)
{
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.DeclaringType)}}} */");
if (this.UseJsonNames)
writer.WriteLine($"static {(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)} = {field.GetRawConstantValue()};");
else
writer.WriteLine($"static {field.Name} = {field.GetRawConstantValue()};");
}
else
{
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.FieldType)}}} */");
if (this.UseJsonNames)
writer.WriteLine($"{(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)} = {GetTypeDefaultValue(field.FieldType)};");
else
writer.WriteLine($"{field.Name} = {GetTypeDefaultValue(field.FieldType)};");
}
}
@ -412,6 +538,10 @@ namespace MontoyaTech.Rest.Net
writer.WriteBreak();
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(property.PropertyType)}}} */");
if (this.UseJsonNames)
writer.WriteLine($"{(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)} = {GetTypeDefaultValue(property.PropertyType)};");
else
writer.WriteLine($"{property.Name} = {GetTypeDefaultValue(property.PropertyType)};");
}
@ -496,11 +626,13 @@ namespace MontoyaTech.Rest.Net
//Generate request doc if any
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
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("*/");
@ -523,7 +655,7 @@ namespace MontoyaTech.Rest.Net
if (routeRequest != null)
{
writer.WriteSeparator();
writer.Write("request");
writer.Write("body");
}
if (routeResponse != null && routeResponse.Parameter)
@ -534,25 +666,25 @@ namespace MontoyaTech.Rest.Net
writer.WriteLine(") {").Indent();
//Generate function body
writer.WriteLine("var response = await fetch(").Indent();
//Generate the url
writer.WriteBreak().Write("var url = ");
//Generate the request url
if (this.StaticCode)
writer.WriteSeparator().Write('`').Write($"${{{this.ClientName}.BaseUrl}}");
writer.Write('`').Write($"${{{this.ClientName}.BaseUrl}}");
else
writer.WriteSeparator().Write('`').Write("${this.Client.BaseUrl}");
writer.Write('`').Write("${this.Client.BaseUrl}");
//Reconstruct the route syntax into a request url.
var components = route.Syntax.Split('/');
int argumentIndex = 0;
foreach (var component in components)
{
if (!string.IsNullOrWhiteSpace(component))
{
if (writer.Peek() != '/')
writer.Write('/');
if (!string.IsNullOrWhiteSpace(component))
{
if (component.StartsWith("{"))
{
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
writer.WriteLine("credentials: 'include',");
@ -586,28 +721,44 @@ namespace MontoyaTech.Rest.Net
if (routeRequest.RequestType.IsAssignableTo(typeof(Stream)))
{
writer.WriteLine("headers: new Headers({ 'Content-Type': 'application/octet-stream' }),");
writer.WriteLine("body: request,");
writer.WriteLine("body: body,");
}
else if (routeRequest.Json)
{
writer.WriteLine("body: JSON.stringify(request),");
writer.WriteLine("body: JSON.stringify(body),");
}
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)
{
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)
{
@ -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.WriteBreak().WriteLine("throw response;");
}
else
{
writer.WriteBreak().WriteLine("if (!response.ok) {").Indent();
writer.WriteLine("throw `Unexpected http response status: ${response.status}`;");
writer.WriteLine("throw response;");
writer.Outdent().WriteLine("}");
}

View File

@ -49,9 +49,9 @@ namespace MontoyaTech.Rest.Net
if (target == null)
throw new ArgumentNullException(nameof(target));
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))
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.Syntax = syntax;

View File

@ -19,96 +19,130 @@ namespace MontoyaTech.Rest.Net
/// <returns></returns>
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)
{
return (dynamic)input;
var typeCode = Type.GetTypeCode(type);
if (type.IsEnum)
{
return Enum.Parse(type, input, true);
}
else if (typeCode == TypeCode.String)
{
return input;
}
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)
return default(T);
return null;
return (T)castOperator.Invoke(null, new[] { input });
return castOperator.Invoke(null, new[] { input });
}
else if (typeCode == TypeCode.Double)
{
double.TryParse(input, out double result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Single)
{
float.TryParse(input, out float result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Decimal)
{
decimal.TryParse(input, out decimal result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Int64)
{
long.TryParse(input, out long result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Int32)
{
int.TryParse(input, out int result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Int16)
{
short.TryParse(input, out short result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.SByte)
{
sbyte.TryParse(input, out sbyte result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.UInt64)
{
ulong.TryParse(input, out ulong result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.UInt32)
{
uint.TryParse(input, out uint result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.UInt16)
{
ushort.TryParse(input, out ushort result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Byte)
{
byte.TryParse(input, out byte result);
return (dynamic)result;
return result;
}
else if (typeCode == TypeCode.Boolean)
{
if (input == "f" || input == "0" || input == "F")
return (dynamic)false;
return false;
bool.TryParse(input, out bool result);
return ((dynamic)result);
return result;
}
else if (typeCode == TypeCode.DateTime)
{
DateTime.TryParse(input, out DateTime result);
return ((dynamic)result);
return result;
}
else if (typeCode == TypeCode.Char)
{
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>
/// The internal http listener.
/// </summary>
private HttpListener HttpListener = null;
protected HttpListener HttpListener = null;
/// <summary>
/// The list of routes this RouteListener is listening for.
@ -256,12 +256,13 @@ namespace MontoyaTech.Rest.Net
/// <param name="clientName"></param>
/// <param name="staticCode"></param>
/// <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();
generator.ClientName = clientName;
generator.StaticCode = staticCode;
generator.UseJsonNames = useJsonNames;
return generator.Generate(this);
}

View File

@ -34,6 +34,10 @@ namespace MontoyaTech.Rest.Net
//Set the arguments to null, initially.
arguments = null;
//If the input url is invalid, default to false.
if (string.IsNullOrWhiteSpace(url))
return false;
//Trim the url and the syntax.
url = url.Trim();
syntax = syntax.Trim();
@ -42,17 +46,27 @@ namespace MontoyaTech.Rest.Net
if (url.StartsWith("http://"))
{
url = url.Substring(7);
//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://"))
{
url = url.Substring(8);
//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
var urlSegments = url.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
var syntaxSegments = syntax.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
var urlSegments = url.Split('/').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 (urlSegments.Length == 0 && syntaxSegments.Length == 0)
@ -85,20 +99,25 @@ namespace MontoyaTech.Rest.Net
return false;
}
//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;
}
//If we ran out of url segments, and this syntax segment is a wild card, this is okay.
else if (urlSegment == null && syntaxSegment == "*")
//If we ran out of url segments, and this syntax segment is a wild card, this is not a match.
else if (syntaxSegment == "*" && urlSegment != null)
{
return true;
//Allowed
}
//If we have a syntaxSegment but no urlSegment, this can't be a match.
else if (urlSegment == null && syntaxSegment != null)
{
return false;
}
//If both the url segment and syntax segment are empty then this is allowed
else if (urlSegment != null && urlSegment.Length == 0 && syntaxSegment.Length == 0)
{
//Allowed
}
//If we have a url segment see if it matches against the syntax segment.
else if (urlSegment != null)
{

View File

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

View File

@ -23,10 +23,15 @@ namespace MontoyaTech.Rest.Net
public bool Json = true;
/// <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>
public bool Parameter = false;
/// <summary>
/// Whether or not the response is a dynamic type. Default is false.
/// </summary>
public bool Dynamic = false;
/// <summary>
/// Creates a default route response.
/// </summary>

View File

@ -31,7 +31,7 @@ namespace MontoyaTech.Rest.Net
builder.Append(input[0]);
for (int i = 1; i < input.Count; i++)
builder.Append(separator).Append(input[1]);
builder.Append(separator).Append(input[i]);
return builder.ToString();
}
@ -48,7 +48,7 @@ namespace MontoyaTech.Rest.Net
builder.Append(input[0]);
for (int i = 1; i < input.Count; i++)
builder.Append(separator).Append(input[1]);
builder.Append(separator).Append(input[i]);
return builder.ToString();
}