-
Notifications
You must be signed in to change notification settings - Fork 0
Builder System
This is a feature that was introduced in Version 3.3.0
The Builder System let you define new web services without much code. For this you write a little class with some methods that will listen to incoming requests, mark them with attributes and a Builder will use Reflections to create the web services for you. At runtime there are no more reflections in use and most of the stuff is running as normal (with a little overhead because of casting).
All to do is to add the following usings to your code:
using MaxLib.WebServer; // base classes for all the stuff
using MaxLib.WebServer.Builder; // here goes the fancy builder magicThen you create a public, non-abstract, non-generic class that inherits the MaxLib.WebServer.Builder.Service class:
public class MyServices : ServiceHere you can add one or more public, non-abstract, non-generic methods in mostly any kind you like. The builder system will try to fit the stuff in the kind it is needed. Keep in mind: The builder won't throw exceptions if something doesn't fit - it will just ignore your methods.
Let's add a simple "Hello World" method that listens to /hello:
[Path("/hello")]
public string Hello()
{
return "Hello World";
}Currently there exists only one kind of rule: Path. This will check if the path matches (or starts) with the specified pattern. This will ignore GET parameters (for this is some other stuff reserved). The path is split at any / (ignoring empty paths) and checked one by one. If you want to use some part of the path as a variable you can wrap the part with a name in curly braces { and } like so:
[Path("/api/add/{a}/{b}")]If you want to access the variables in your method body you have to mark you parameter with the Var attribute:
[Path("/api/add/{a}/{b}")]
public string ApiAdd([Var] double a, [Var] double b)
{
return $"{a} + {b} = {a + b}";
}More tricks:
- You can set Path to prefix mode like so:
[Path("/api", Prefix=true)]- You can also set the Path attribute to the class. This will add this rule to all contained methods.
- You can use nested classes with nested Path attributes. This will extend the rules.
- If you need to name your parameter differently you can specify the name like so:
[Var("varName")] string paramName
You can access any GET parameter similar to the vars if you add the Get attribute to the parameter.
[Path("/api/add")]
public string ApiAddGet([Get] double a, [Get] double b)
{
return $"{a} + {b} = {a + b}";
}All GET parameter that are defined like so are required.
It is quite easy to access any of these members. You only need to define the type and the builder system will look if it knows a method how the access it.
public string DirectAccess(WebProgressTask task)
{
return "it works";
}Currently the builder system knows how to access the following types:
MaxLib.WebServer.WebProgressTaskMaxLib.WebServer.HttpDocumentMaxLib.WebServer.ServerMaxLib.WebServer.HttpConnectionMaxLib.WebServer.Sessions.SessionMaxLib.WebServer.HttpRequestHeaderMaxLib.WebServer.HttpResponseHeaderMaxLib.WebServer.HttpLocationMaxLib.WebServer.HttpPostMaxLib.WebServer.HttpConnectionTypeMaxLib.WebServer.HttpCookieMaxLib.WebServer.Post.IPostDataSystem.Threading.Tasks.Task<MaxLib.WebServer.IPostData>MaxLib.WebServer.Post.MultipartFormDataMaxLib.WebServer.Post.UnknownPostDataMaxLib.WebServer.Post.UrlEncodedData-
System.Net.IPEndPoint(endpoint of client) -
System.Net.IPAddress(ip of client)
The builder systems knows some methods how to transform the [Var] and [Get] parameters. First it will check if the current value can be directly assigned (that is possible if you inherit from string) and then it will check if the type inherits System.IConvertible which is true for all .NET primitives (int, double, string, ...).
If you want to use your variable differently (e.g. as an array, different encoding, ...) you have to define your own converter. For this you need to create a class which implements the interface MaxLib.WebServer.Builder.Tools.IConverter. Here you define the GetConverter method which receives the input (from the source) and output (expected by the method parameter) types and return a function which can transform between this.
To use this converter you have to add the [Converter] attribute:
public string GetPoint([Converter(typeof(PointConverter))] [Get] Point p)
{
return p.ToString();
}The [Get] and [Var] attributes always provide string.
First of all it will check if the return type fits in one of the following categories:
voidSystem.Threading.Tasks.Task<T>System.Threading.Tasks.ValueTask<T>T
In case 1 it will do nothing special after finishing your method. In case 2 and 3 it will await the completion of the task and then handle the result in the same way like in case 4.
In case 4 the result would be transformed in MaxLib.WebServer.HttpDataSource and then added to the HttpDocument (which is returned to the client). For the following cases this works fine:
-
MaxLib.WebServer.HttpDataSourceor any class that derives it -
string: will be transformed inMaxLib.WebServer.HttpStringDataSourcewith the mime typetext/html -
System.IO.Stream: will be transformed inMaxLib.WebServer.HttpStreamDataSourcewith the mime typetext/html -
System.IO.FileInfo: will be transformed inMaxLib.WebServer.HttpFileDataSource. The mime type is derived from the file extension and defaults toapplication/octet-stream.
If you want to define your own conversion method for different types, you need to define your own converter which inherits MaxLib.WebServer.Builder.Tools.IDataConverter. The GetConverter method receives only the returned type T (without any Tasks our such) and returns a method which can convert this into a HttpDataSource.
[return: DataConverter(typeof(PointConverter))]
public async Task<Point> GetPoint([Get] string id)
{
return await Database.GetPoint(id);
}The builder system will ignore the methods that is derived from object. If you want to ignore you own methods or types you have to attach the [Ignore] attribute.
[Ignore]
public string GetId()
{
return "secret";
}It is quite easy to add your services to the web server. For this you need to convert this to a WebService. The MaxLib.WebServer.Builder.Service provides some static methods for it:
-
Service.Build<T>()builds the specific service -
Service.Build(Type)build the specified service -
Service.Build(Assembly)build all services in the specified assembly -
Service.Build(AppDomain)build all services in all assemblies in the specifiedAppDomain -
Service.Buildbuild all services in all assemblies in the currentAppDomain
These methods can return null if the type doesn't follow the rules or no service was found. Before adding to the server you should of course check this.
var service = Service.Build<MyServices>();
if (service != null)
webServer.AddWebService(service);- Every method receives its own instance of the class it resides in. There is currently no way that all these methods shares the same instance.
- The build system won't throw exceptions and ignore invalid configurations. If you use you own extensions this behavior can change.
- You can throw
MaxLib.WebServer.HttpExceptionto cancel the execution. You can optionally add some status to this exception. - You can change the priority of the method using the
[Priotity(WebServicePriority)]attribute. - You can add more rules if you inherit the
MaxLib.WebServer.Builder.Tools.RuleAttributeBaseclass. - You can add more source rules to parameter if you inherit the
MaxLib.WebServer.Builder.Tools.ParamAttributeBaseclass.