Simplified project struct naming. Cleaned up code and added missing documentation. Routes now use a RouteContext to allow extensions and future modules.

This commit is contained in:
MattMo 2022-02-06 21:14:36 -08:00
parent 87a9b961bd
commit ccad0dd66d
19 changed files with 353 additions and 94 deletions

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace MontoyaTech.Rest.Net
{
public static class HttpListenerRequestExtensions
{
public static string ReadAsString(this HttpListenerRequest request)
{
try
{
using (var input = request.InputStream)
using (var stream = new StreamReader(input))
return stream.ReadToEnd();
}
catch
{
return "";
}
}
}
}

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MontoyaTech.Rest.Net.Example", "MontoyaTech.Rest.Net.Example\MontoyaTech.Rest.Net.Example.csproj", "{D476199D-526A-4831-866F-790676F8BC37}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rest.Net.Example", "Rest.Net.Example\Rest.Net.Example.csproj", "{D476199D-526A-4831-866F-790676F8BC37}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -33,9 +33,6 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="MontoyaTech.Rest.Net">
<HintPath>..\..\MontoyaTech.Rest.Net\MontoyaTech.Rest.Net\bin\Debug\MontoyaTech.Rest.Net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@ -52,5 +49,11 @@
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Rest.Net\Rest.Net\Rest.Net.csproj">
<Project>{03d4578f-3239-4b12-88bb-5d877c2609d6}</Project>
<Name>Rest.Net</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

31
Rest.Net.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rest.Net", "Rest.Net\Rest.Net\Rest.Net.csproj", "{03D4578F-3239-4B12-88BB-5D877C2609D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rest.Net.Example", "Rest.Net.Example\Rest.Net.Example\Rest.Net.Example.csproj", "{D476199D-526A-4831-866F-790676F8BC37}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{03D4578F-3239-4B12-88BB-5D877C2609D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03D4578F-3239-4B12-88BB-5D877C2609D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03D4578F-3239-4B12-88BB-5D877C2609D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03D4578F-3239-4B12-88BB-5D877C2609D6}.Release|Any CPU.Build.0 = Release|Any CPU
{D476199D-526A-4831-866F-790676F8BC37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D476199D-526A-4831-866F-790676F8BC37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D476199D-526A-4831-866F-790676F8BC37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D476199D-526A-4831-866F-790676F8BC37}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {10D7CB88-DA0F-4A05-9A76-8FF600B040DA}
EndGlobalSection
EndGlobal

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MontoyaTech.Rest.Net", "MontoyaTech.Rest.Net\MontoyaTech.Rest.Net.csproj", "{C885E940-05C8-43BB-B80B-02F6AAF1AE09}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rest.Net", "Rest.Net\Rest.Net.csproj", "{C885E940-05C8-43BB-B80B-02F6AAF1AE09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// A set of extensions to help with HttpListenerRequests.
/// </summary>
public static class HttpListenerRequestExtensions
{
/// <summary>
/// Reads the content of a HttpListenerRequest as a string and returns it.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static string ReadAsString(this HttpListenerRequest request)
{
try
{
using (var input = request.InputStream)
using (var stream = new StreamReader(input))
return stream.ReadToEnd();
}
catch
{
return "";
}
}
/// <summary>
/// Reads the content of a HttpListenerRequest as a json object and returns it.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="request"></param>
/// <returns></returns>
public static T ReadAsJson<T>(this HttpListenerRequest request)
{
try
{
using (var input = request.InputStream)
using (var stream = new StreamReader(input))
return JsonConvert.DeserializeObject<T>(stream.ReadToEnd());
}
catch
{
return default(T);
}
}
}
}

View File

@ -9,28 +9,49 @@ using Newtonsoft.Json;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// A set of extensions to help with HttpListenerResponses.
/// </summary>
public static class HttpListenerResponseExtensions
{
/// <summary>
/// Sets the response content type to text and writes the given text to it.
/// </summary>
/// <param name="response"></param>
/// <param name="text"></param>
/// <returns></returns>
public static HttpListenerResponse WithText(this HttpListenerResponse response, string text)
{
response.ContentType = "text/plain";
var bytes = Encoding.UTF8.GetBytes(text);
var bytes = Encoding.Unicode.GetBytes(text);
response.OutputStream.Write(bytes, 0, bytes.Length);
return response;
}
/// <summary>
/// Sets the response content type to json and serializes the object as json and writes it.
/// </summary>
/// <param name="response"></param>
/// <param name="obj"></param>
/// <returns></returns>
public static HttpListenerResponse WithJson(this HttpListenerResponse response, object obj)
{
response.ContentType = "application/json";
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
var bytes = Encoding.Unicode.GetBytes(JsonConvert.SerializeObject(obj));
response.OutputStream.Write(bytes, 0, bytes.Length);
return response;
}
/// <summary>
/// Sets the response content type to a file and writes the given file content to the response.
/// </summary>
/// <param name="response"></param>
/// <param name="filePath"></param>
/// <returns></returns>
public static HttpListenerResponse WithFile(this HttpListenerResponse response, string filePath)
{
response.ContentType = "application/octet-stream";
@ -44,6 +65,12 @@ namespace MontoyaTech.Rest.Net
return response;
}
/// <summary>
/// Sets the status code for a given response.
/// </summary>
/// <param name="response"></param>
/// <param name="status"></param>
/// <returns></returns>
public static HttpListenerResponse WithStatus(this HttpListenerResponse response, HttpStatusCode status)
{
try

View File

@ -11,6 +11,10 @@
<PackageTags>MontoyaTech;Rest.Net</PackageTags>
<NeutralLanguage>en</NeutralLanguage>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>1.0.*</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json">

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>library</OutputType>
<TargetFramework>net472</TargetFramework>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>MontoyaTech</Authors>
<Company>MontoyaTech</Company>
<Copyright>MontoyaTech 2022</Copyright>
<PackageProjectUrl>https://code.montoyatech.com/MontoyaTech/Rest.Net</PackageProjectUrl>
<Description>A simple C# library for creating a rest api.</Description>
<PackageTags>MontoyaTech;Rest.Net</PackageTags>
<NeutralLanguage>en</NeutralLanguage>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
</Project>

View File

@ -7,19 +7,41 @@ using System.Net;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// The outline of a Http Route.
/// </summary>
public class Route
{
/// <summary>
/// The http method for this route.
/// </summary>
public string Method;
/// <summary>
/// The syntax of this route.
/// </summary>
public string Syntax;
private Func<HttpListenerRequest, HttpListenerResponse, HttpListenerResponse> Target;
/// <summary>
/// The target function to invoke if this route is invoked.
/// </summary>
private Func<RouteContext, HttpListenerResponse> Target;
/// <summary>
/// Whether or not to close the response after the route is invoked.
/// </summary>
public bool CloseResponse = true;
internal Route() { }
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, HttpListenerResponse> target, bool closeResponse = true)
/// <summary>
/// Creates a new route with a given method, syntax, target and optional close response flag.
/// </summary>
/// <param name="method"></param>
/// <param name="syntax"></param>
/// <param name="target"></param>
/// <param name="closeResponse"></param>
public Route(string method, string syntax, Func<RouteContext, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -27,20 +49,32 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, HttpListenerResponse> target, bool closeResponse = true)
/// <summary>
/// Creates a new route with a given method, syntax, target and optional close response flag.
/// </summary>
/// <param name="method"></param>
/// <param name="syntax"></param>
/// <param name="target"></param>
/// <param name="closeResponse"></param>
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public virtual void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
/// <summary>
/// Invokes this route with a context and a given set of string arguments.
/// </summary>
/// <param name="context"></param>
/// <param name="arguments"></param>
public virtual void Invoke(RouteContext context, params string[] arguments)
{
this.Target.Invoke(request, response);
this.Target.Invoke(context);
}
}
public class Route<T1> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, HttpListenerResponse> Target;
private Func<RouteContext, T1, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -48,20 +82,20 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(request, response, RouteArgumentConverter.Convert<T1>(arguments[0]));
this.Target.DynamicInvoke(context, RouteArgumentConverter.Convert<T1>(arguments[0]));
}
}
public class Route<T1, T2> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -69,25 +103,24 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1])
); ;
);
}
}
public class Route<T1, T2, T3> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, T3, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, T3, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -95,14 +128,13 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, T3, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1]),
RouteArgumentConverter.Convert<T3>(arguments[2])
@ -112,9 +144,9 @@ namespace MontoyaTech.Rest.Net
public class Route<T1, T2, T3, T4> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, T3, T4, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, T3, T4, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -122,14 +154,13 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, T3, T4, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1]),
RouteArgumentConverter.Convert<T3>(arguments[2]),
@ -140,9 +171,9 @@ namespace MontoyaTech.Rest.Net
public class Route<T1, T2, T3, T4, T5> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, T3, T4, T5, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -150,14 +181,13 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1]),
RouteArgumentConverter.Convert<T3>(arguments[2]),
@ -169,9 +199,9 @@ namespace MontoyaTech.Rest.Net
public class Route<T1, T2, T3, T4, T5, T6> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, T3, T4, T5, T6, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, T6, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -179,14 +209,13 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, T6, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1]),
RouteArgumentConverter.Convert<T3>(arguments[2]),
@ -199,9 +228,9 @@ namespace MontoyaTech.Rest.Net
public class Route<T1, T2, T3, T4, T5, T6, T7> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -209,14 +238,13 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, T6, T7, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1]),
RouteArgumentConverter.Convert<T3>(arguments[2]),
@ -230,9 +258,9 @@ namespace MontoyaTech.Rest.Net
public class Route<T1, T2, T3, T4, T5, T6, T7, T8> : Route
{
private Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> Target;
private Func<RouteContext, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> Target;
public Route(string method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> target, bool closeResponse = true)
public Route(string method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> target, bool closeResponse = true)
{
this.Method = method;
this.Syntax = syntax;
@ -240,14 +268,13 @@ namespace MontoyaTech.Rest.Net
this.CloseResponse = closeResponse;
}
public Route(HttpRequestMethod method, string syntax, Func<HttpListenerRequest, HttpListenerResponse, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> target, bool closeResponse = true)
public Route(HttpRequestMethod method, string syntax, Func<RouteContext, T1, T2, T3, T4, T5, T6, T7, T8, HttpListenerResponse> target, bool closeResponse = true)
: this(method.ToString(), syntax, target, closeResponse) { }
public override void Invoke(HttpListenerRequest request, HttpListenerResponse response, params string[] arguments)
public override void Invoke(RouteContext context, params string[] arguments)
{
this.Target.DynamicInvoke(
request,
response,
context,
RouteArgumentConverter.Convert<T1>(arguments[0]),
RouteArgumentConverter.Convert<T2>(arguments[1]),
RouteArgumentConverter.Convert<T3>(arguments[2]),

View File

@ -6,8 +6,17 @@ using System.Threading.Tasks;
namespace MontoyaTech.Rest.Net
{
public class RouteArgumentConverter
/// <summary>
/// A class to help convert route arguments.
/// </summary>
internal class RouteArgumentConverter
{
/// <summary>
/// Converts a string to a given type if possible. Otherwise returns default of T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
/// <returns></returns>
public static T Convert<T>(string input)
{
var typeCode = Type.GetTypeCode(typeof(T));

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// An outline of a Route Context which includes
/// the request and response for a route.
/// </summary>
public class RouteContext
{
/// <summary>
/// The Http Request that requested this route.
/// </summary>
public HttpListenerRequest Request = null;
/// <summary>
/// The Http Response for this route.
/// </summary>
public HttpListenerResponse Response = null;
/// <summary>
/// Creates a new RouteContext with a given request and response.
/// </summary>
/// <param name="request"></param>
/// <param name="response"></param>
public RouteContext(HttpListenerRequest request, HttpListenerResponse response)
{
this.Request = request;
this.Response = response;
}
}
}

View File

@ -8,24 +8,49 @@ using System.Threading;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// The outline of a Route listener that listens for http requests and invokes
/// matching routes.
/// </summary>
public class RouteListener
{
/// <summary>
/// The internal http listener.
/// </summary>
private HttpListener HttpListener = null;
/// <summary>
/// The list of routes this RouteListener is listening for.
/// </summary>
public List<Route> Routes = new List<Route>();
/// <summary>
/// The port this RouteListener is listening on.
/// </summary>
public ushort Port = 8081;
/// <summary>
/// Creates a new RouteListener with the default port number.
/// </summary>
public RouteListener()
{
}
/// <summary>
/// Creates a nwe RouteListener with a series of routes.
/// </summary>
/// <param name="routes">The routes to listen for.</param>
public RouteListener(params Route[] routes)
{
this.Routes = routes.ToList();
}
/// <summary>
/// Creates a new RouteListener with a port to listen on and a series of routes.
/// </summary>
/// <param name="port">The port number the listener should use.</param>
/// <param name="routes">The routes to listen for.</param>
public RouteListener(ushort port, params Route[] routes)
{
this.Routes = routes.ToList();
@ -33,12 +58,16 @@ namespace MontoyaTech.Rest.Net
this.Port = port;
}
/// <summary>
/// Starts this RouteListener if it's not already running.
/// </summary>
public void Start()
{
if (this.HttpListener == null)
{
this.HttpListener = new HttpListener();
//Support listening on Windows & Linux.
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
this.HttpListener.Prefixes.Add($"http://localhost:{this.Port}/");
@ -55,6 +84,9 @@ namespace MontoyaTech.Rest.Net
}
}
/// <summary>
/// Function that does the actual listening.
/// </summary>
private void Listen()
{
ThreadPool.QueueUserWorkItem((o) =>
@ -78,7 +110,17 @@ namespace MontoyaTech.Rest.Net
{
handled = true;
close = this.Routes[i].CloseResponse;
this.Routes[i].Invoke(ctx.Request, ctx.Response, arguments);
//Make sure if the route fails we don't die here, just set the response to internal server error.
try
{
this.Routes[i].Invoke(new RouteContext(ctx.Request, ctx.Response), arguments);
}
catch
{
ctx.Response.WithStatus(HttpStatusCode.InternalServerError);
}
break;
}
}
@ -101,16 +143,29 @@ namespace MontoyaTech.Rest.Net
});
}
/// <summary>
/// Stops this RouteListener if it's running.
/// </summary>
public void Stop()
{
if (this.HttpListener != null)
{
try
{
this.HttpListener.Stop();
this.HttpListener = null;
}
catch
{
this.HttpListener = null;
}
}
}
/// <summary>
/// Blocks the current thread indenfinitly while this RouteListner is running.
/// </summary>
public void Block()
{
while (this.HttpListener != null)

View File

@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// A class to help match route syntaxs against requests.
/// </summary>
public class RouteMatcher
{
/// <summary>