diff --git a/Rest.Net.Example/Client.cs b/Rest.Net.Example/Client.cs index 7a4392b..8947e6b 100644 --- a/Rest.Net.Example/Client.cs +++ b/Rest.Net.Example/Client.cs @@ -13,14 +13,17 @@ namespace MontoyaTech.Rest.Net.Example { public string BaseUrl; - private HttpClient HttpClient; + public HttpClient HttpClient; public TestApi Test; + public AuthApi Auth; + public Client(string baseUrl) { this.BaseUrl = baseUrl; this.Test = new TestApi(this); + this.Auth = new AuthApi(this); this.HttpClient = new HttpClient(); this.HttpClient.DefaultRequestHeaders.Add("Accept", "*/*"); this.HttpClient.DefaultRequestHeaders.Add("Connection", "keep-alive"); @@ -38,26 +41,72 @@ namespace MontoyaTech.Rest.Net.Example public string Status() { - var request = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/status"); - return default; + var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/status"); + + var response = this.Client.HttpClient.Send(message); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + return Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + else + throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); } public string Add(double a, double b) { - var request = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/add/{a}/{b}"); - return default; + var message = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/add/{a}/{b}"); + + var response = this.Client.HttpClient.Send(message); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + return Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + else + throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); + } + } + + public class AuthApi + { + public Client Client; + + public AuthApi(Client client) + { + this.Client = client; } - public string Signup(User user) + public bool UserExists(string name) { - var request = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/signup/{user}"); - return default; + var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/auth/{name}"); + + var response = this.Client.HttpClient.Send(message); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + return Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + else + throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); } - public User Json() + public void Signup(User request) { - var request = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/json"); - return default; + var message = new HttpRequestMessage(HttpMethod.Post, $"{this.Client.BaseUrl}/auth/signup"); + + message.Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request)); + + var response = this.Client.HttpClient.Send(message); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); + } + + public User Get() + { + var message = new HttpRequestMessage(HttpMethod.Get, $"{this.Client.BaseUrl}/auth"); + + var response = this.Client.HttpClient.Send(message); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + return Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + else + throw new Exception("Unexpected Http Response StatusCode:" + response.StatusCode); } } diff --git a/Rest.Net.Example/Program.cs b/Rest.Net.Example/Program.cs index febc655..b434522 100644 --- a/Rest.Net.Example/Program.cs +++ b/Rest.Net.Example/Program.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.Net; using MontoyaTech.Rest.Net; +using System.Net.Mime; namespace MontoyaTech.Rest.Net.Example { @@ -18,15 +19,12 @@ namespace MontoyaTech.Rest.Net.Example public ulong Property { get; set; } + public User() { } + public User(string name) { this.Name = name; } - - public static explicit operator User(string input) - { - return new User(input.ToString()); - } } public static void Main(string[] args) @@ -34,24 +32,23 @@ namespace MontoyaTech.Rest.Net.Example var listener = new RouteListener(8080, new Route(HttpRequestMethod.Get, "/status", Status), new Route(HttpRequestMethod.Post, "/add/{a}/{b}", Add), - new Route(HttpRequestMethod.Post, "/signup/{username}", Signup), - new Route(HttpRequestMethod.Get, "/json", Json) + new Route(HttpRequestMethod.Get, "/auth/{username}", Exists), + new Route(HttpRequestMethod.Post, "/auth/signup", Signup), + new Route(HttpRequestMethod.Get, "/auth/", Json) ); string code = CodeGenerator.GenerateCSharpClient(listener.Routes); Console.WriteLine(code); - Console.ReadLine(); - listener.RequestPreProcessEvent += (HttpListenerContext context) => { - Console.WriteLine("Request start: " + context.Request.RawUrl); + Console.WriteLine($"[{context.Request.HttpMethod}] Request start: " + context.Request.RawUrl); return true; }; listener.RequestPostProcessEvent += (HttpListenerContext context) => { - Console.WriteLine("Request end: " + context.Request.RawUrl); + Console.WriteLine($"[{context.Request.HttpMethod}] Request end: " + context.Request.RawUrl); }; listener.Start(); @@ -61,7 +58,11 @@ namespace MontoyaTech.Rest.Net.Example foreach (var route in listener.Routes) Console.WriteLine($"- [{route.Method}] {route.Syntax}"); - Console.WriteLine($"Rest api server running at http://localhost:{listener.Port}"); + Console.WriteLine($"Rest api server running at {listener.BaseUrl}"); + + var client = new Client(listener.BaseUrl); + + var result = client.Auth.Get(); listener.Block(); } @@ -80,12 +81,6 @@ namespace MontoyaTech.Rest.Net.Example return context.Response.WithStatus(HttpStatusCode.OK).WithText((a + b).ToString()); } - [RouteGroup("Test")] - [RouteResponse(typeof(string))] - public static HttpListenerResponse Signup(HttpListenerContext context, User user) - { - return context.Response.WithStatus(HttpStatusCode.OK).WithText("User:" + user.Name); - } [RouteGroup("Test")] [RouteRequest(typeof(User))] @@ -94,7 +89,25 @@ namespace MontoyaTech.Rest.Net.Example return context.Response.WithStatus(HttpStatusCode.OK); } - [RouteGroup("Test")] + [RouteGroup("Auth")] + [RouteName("UserExists")] + [RouteResponse(typeof(bool))] + public static HttpListenerResponse Exists(HttpListenerContext context, string name) + { + Console.WriteLine("Auth.Exists called, name:" + name); + + return context.Response.WithStatus(HttpStatusCode.OK).WithJson(true); + } + + [RouteGroup("Auth")] + [RouteRequest(typeof(User))] + public static HttpListenerResponse Signup(HttpListenerContext context) + { + return context.Response.WithStatus(HttpStatusCode.OK); + } + + [RouteGroup("Auth")] + [RouteName("Get")] [RouteResponse(typeof(User))] public static HttpListenerResponse Json(HttpListenerContext context) { diff --git a/Rest.Net/CodeGenerator.cs b/Rest.Net/CodeGenerator.cs index b10f414..c1f6b79 100644 --- a/Rest.Net/CodeGenerator.cs +++ b/Rest.Net/CodeGenerator.cs @@ -293,7 +293,7 @@ namespace MontoyaTech.Rest.Net writer.WriteBreak().WriteLine("public string BaseUrl;"); - writer.WriteBreak().WriteLine("private HttpClient HttpClient;"); + writer.WriteBreak().WriteLine("public HttpClient HttpClient;"); //Create fields foreach route group so they can be accessed. foreach (var group in routeGroups) @@ -353,8 +353,6 @@ namespace MontoyaTech.Rest.Net GenerateCSharpIncludedProperty(property, writer); writer.Outdent().WriteLine("}"); - - System.Diagnostics.Debugger.Break(); } private static void GenerateCSharpIncludedField(FieldInfo field, CodeWriter writer) @@ -407,11 +405,14 @@ namespace MontoyaTech.Rest.Net var methodInfo = route.GetTarget().GetMethodInfo(); + var routeName = methodInfo.GetCustomAttribute(); + var routeRequest = methodInfo.GetCustomAttribute(); var routeResponse = methodInfo.GetCustomAttribute(); - writer.Write($"public {(routeResponse == null ? "void" : GetTypeFullyResolvedName(routeResponse.ResponseType))} {methodInfo.Name}("); + //Construct the routes request function + writer.Write($"public {(routeResponse == null ? "void" : GetTypeFullyResolvedName(routeResponse.ResponseType))} {(routeName == null ? methodInfo.Name : routeName.Name)}("); var parameters = methodInfo.GetParameters(); @@ -432,21 +433,8 @@ namespace MontoyaTech.Rest.Net writer.WriteLine(")").WriteLine("{").Indent(); - //Generate code to send a request - /* - * var response = HttpClient.Send(new HttpRequestMessage(HttpMethod.Post, $"{Auth0Url}/oauth/token") - { - Content = new FormUrlEncodedContent(new[] - { - new KeyValuePair("grant_type", "authorization_code"), - new KeyValuePair("code", code), - new KeyValuePair("redirect_uri", redirectUrl), - new KeyValuePair("client_id", ClientId) - }) - }) - */ - - writer.Write($"var request = new HttpRequestMessage("); + //Generate the message code + writer.WriteBreak().Write($"var message = new HttpRequestMessage("); switch (route.Method.ToLower()) { @@ -460,12 +448,60 @@ namespace MontoyaTech.Rest.Net throw new NotSupportedException("Unsupport route method:" + route.Method); } - writer.WriteSeparator().Write('$').WriteString($"{{this.Client.BaseUrl}}/{route.Syntax}"); + writer.WriteSeparator().Write('$').Write('"').Write("{this.Client.BaseUrl}"); - writer.WriteLine(");"); + //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)) + { + writer.Write('/'); + if (component.StartsWith("{")) + { + writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}"); + } + else if (component == "*") + { + writer.Write("*"); + } + else if (component == "**") + { + break; + } + else + { + writer.Write(component); + } + } + } + + writer.Write('"').WriteLine(");"); + + //Add the request content if any. + if (routeRequest != null) + { + writer.WriteBreak().WriteLine("message.Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request));"); + } + + //Generate the response code + writer.WriteBreak().WriteLine("var response = this.Client.HttpClient.Send(message);"); + + //Handle the response if (routeResponse != null) - writer.WriteLine("return default;"); + { + writer.WriteBreak().WriteLine("if (response.StatusCode == System.Net.HttpStatusCode.OK)").Indent(); + writer.WriteLine($"return Newtonsoft.Json.JsonConvert.DeserializeObject<{GetTypeFullyResolvedName(routeResponse.ResponseType)}>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + writer.Outdent().WriteLine("else").Indent(); + writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); + } + else + { + writer.WriteBreak().WriteLine("if (response.StatusCode == System.Net.HttpStatusCode.OK)").Indent(); + writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); + } writer.Outdent().WriteLine("}"); } diff --git a/Rest.Net/RouteListener.cs b/Rest.Net/RouteListener.cs index 60ab12f..f5ce862 100644 --- a/Rest.Net/RouteListener.cs +++ b/Rest.Net/RouteListener.cs @@ -29,6 +29,17 @@ namespace MontoyaTech.Rest.Net /// public ushort Port = 8081; + /// + /// Returns the BaseUrl for this RouteListener. + /// + public string BaseUrl + { + get + { + return $"http://localhost:{this.Port}"; + } + } + /// /// An event to preprocess routes before the route is executed. /// diff --git a/Rest.Net/RouteName.cs b/Rest.Net/RouteName.cs new file mode 100644 index 0000000..467c268 --- /dev/null +++ b/Rest.Net/RouteName.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MontoyaTech.Rest.Net +{ + /// + /// The outline of an attribute that allows you to rename an attribute in + /// the output code generation. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class RouteName : Attribute + { + /// + /// The name of this Route. + /// + public string Name = null; + + /// + /// Creates a new default RouteName. + /// + public RouteName() { } + + /// + /// Creates a new RouteName with a given name. + /// + /// + public RouteName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Cannot be null or whitespace", nameof(name)); + + this.Name = name; + } + } +}