using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using EmbedIO.Routing;
using EmbedIO.Utilities;
using Swan;
namespace EmbedIO.WebApi
{
///
/// A module using objects derived from
/// as collections of handler methods.
///
public abstract class WebApiModuleBase : RoutingModuleBase
{
private const string GetRequestDataAsyncMethodName = nameof(IRequestDataAttribute.GetRequestDataAsync);
private static readonly MethodInfo PreProcessRequestMethod = typeof(WebApiController).GetMethod(nameof(WebApiController.PreProcessRequest));
private static readonly MethodInfo HttpContextSetter = typeof(WebApiController).GetProperty(nameof(WebApiController.HttpContext)).GetSetMethod(true);
private static readonly MethodInfo RouteSetter = typeof(WebApiController).GetProperty(nameof(WebApiController.Route)).GetSetMethod(true);
private static readonly MethodInfo AwaitResultMethod = typeof(WebApiModuleBase).GetMethod(nameof(AwaitResult), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo AwaitAndCastResultMethod = typeof(WebApiModuleBase).GetMethod(nameof(AwaitAndCastResult), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo DisposeMethod = typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose));
private static readonly MethodInfo SerializeResultAsyncMethod = typeof(WebApiModuleBase).GetMethod(nameof(SerializeResultAsync), BindingFlags.Instance | BindingFlags.NonPublic);
private readonly HashSet _controllerTypes = new HashSet();
///
/// Initializes a new instance of the class,
/// using the default response serializer.
///
/// The base route served by this module.
///
///
protected WebApiModuleBase(string baseRoute)
: this(baseRoute, ResponseSerializer.Default)
{
}
///
/// Initializes a new instance of the class,
/// using the specified response serializer.
///
/// The base route served by this module.
/// A used to serialize
/// the result of controller methods returning
/// or Task<object>.
/// is .
///
///
protected WebApiModuleBase(string baseRoute, ResponseSerializerCallback serializer)
: base(baseRoute)
{
Serializer = Validate.NotNull(nameof(serializer), serializer);
}
///
/// A used to serialize
/// the result of controller methods returning values.
///
public ResponseSerializerCallback Serializer { get; }
///
/// Gets the number of controller types registered in this module.
///
public int ControllerCount => _controllerTypes.Count;
///
/// Registers a controller type using a constructor.
/// In order for registration to be successful, the specified controller type:
///
/// must be a subclass of ;
/// must not be an abstract class;
/// must not be a generic type definition;
/// must have a public parameterless constructor.
///
///
/// The type of the controller.
/// The module's configuration is locked.
///
/// is already registered in this module.
/// does not satisfy the prerequisites
/// listed in the Summary section.
///
///
/// A new instance of will be created
/// for each request to handle, and dereferenced immediately afterwards,
/// to be collected during next garbage collection cycle.
/// is not required to be thread-safe,
/// as it will be constructed and used in the same synchronization context.
/// However, since request handling is asynchronous, the actual execution thread
/// may vary during execution. Care must be exercised when using thread-sensitive
/// resources or thread-static data.
/// If implements ,
/// its Dispose method will be called when it has
/// finished handling a request.
///
///
///
protected void RegisterControllerType()
where TController : WebApiController, new()
=> RegisterControllerType(typeof(TController));
///
/// Registers a controller type using a factory method.
/// In order for registration to be successful:
///
/// must be a subclass of ;
/// must not be a generic type definition;
/// 's return type must be either
/// or a subclass of .
///
///
/// The type of the controller.
/// The factory method used to construct instances of .
/// The module's configuration is locked.
/// is .
///
/// is already registered in this module.
/// - or -
/// does not satisfy the prerequisites listed in the Summary section.
///
///
/// will be called once for each request to handle
/// in order to obtain an instance of .
/// The returned instance will be dereferenced immediately after handling the request.
/// is not required to be thread-safe,
/// as it will be constructed and used in the same synchronization context.
/// However, since request handling is asynchronous, the actual execution thread
/// may vary during execution. Care must be exercised when using thread-sensitive
/// resources or thread-static data.
/// If implements ,
/// its Dispose method will be called when it has
/// finished handling a request. In this case it is recommended that
/// return a newly-constructed instance of
/// at each invocation.
/// If does not implement ,
/// may employ techniques such as instance pooling to avoid
/// the overhead of constructing a new instance of
/// at each invocation. If so, resources such as file handles, database connections, etc.
/// should be freed before returning from each handler method to avoid
/// starvation.
///
///
///
protected void RegisterControllerType(Func factory)
where TController : WebApiController
=> RegisterControllerType(typeof(TController), factory);
///
/// Registers a controller type using a constructor.
/// In order for registration to be successful, the specified :
///
/// must be a subclass of ;
/// must not be an abstract class;
/// must not be a generic type definition;
/// must have a public parameterless constructor.
///
///
/// The type of the controller.
/// The module's configuration is locked.
/// is .
///
/// is already registered in this module.
/// - or -
/// does not satisfy the prerequisites
/// listed in the Summary section.
///
///
/// A new instance of will be created
/// for each request to handle, and dereferenced immediately afterwards,
/// to be collected during next garbage collection cycle.
/// is not required to be thread-safe,
/// as it will be constructed and used in the same synchronization context.
/// However, since request handling is asynchronous, the actual execution thread
/// may vary during execution. Care must be exercised when using thread-sensitive
/// resources or thread-static data.
/// If implements ,
/// its Dispose method will be called when it has
/// finished handling a request.
///
///
///
protected void RegisterControllerType(Type controllerType)
{
EnsureConfigurationNotLocked();
controllerType = ValidateControllerType(nameof(controllerType), controllerType, false);
var constructor = controllerType.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0);
if (constructor == null)
{
throw new ArgumentException(
"Controller type must have a public parameterless constructor.",
nameof(controllerType));
}
if (!TryRegisterControllerTypeCore(controllerType, Expression.New(constructor)))
throw new ArgumentException($"Type {controllerType.Name} contains no controller methods.");
}
///
/// Registers a controller type using a factory method.
/// In order for registration to be successful:
///
/// must be a subclass of ;
/// must not be a generic type definition;
/// 's return type must be either
/// or a subclass of .
///
///
/// The type of the controller.
/// The factory method used to construct instances of .
/// The module's configuration is locked.
///
/// is .
/// - or -
/// is .
///
///
/// is already registered in this module.
/// - or -
/// One or more parameters do not satisfy the prerequisites listed in the Summary section.
///
///
/// will be called once for each request to handle
/// in order to obtain an instance of .
/// The returned instance will be dereferenced immediately after handling the request.
/// is not required to be thread-safe,
/// as it will be constructed and used in the same synchronization context.
/// However, since request handling is asynchronous, the actual execution thread
/// may vary during execution. Care must be exercised when using thread-sensitive
/// resources or thread-static data.
/// If implements ,
/// its Dispose method will be called when it has
/// finished handling a request. In this case it is recommended that
/// return a newly-constructed instance of
/// at each invocation.
/// If does not implement ,
/// may employ techniques such as instance pooling to avoid
/// the overhead of constructing a new instance of
/// at each invocation. If so, resources such as file handles, database connections, etc.
/// should be freed before returning from each handler method to avoid
/// starvation.
///
///
///
protected void RegisterControllerType(Type controllerType, Func factory)
{
EnsureConfigurationNotLocked();
controllerType = ValidateControllerType(nameof(controllerType), controllerType, true);
factory = Validate.NotNull(nameof(factory), factory);
if (!controllerType.IsAssignableFrom(factory.Method.ReturnType))
throw new ArgumentException("Factory method has an incorrect return type.", nameof(factory));
var expression = Expression.Call(
factory.Target == null ? null : Expression.Constant(factory.Target),
factory.Method);
if (!TryRegisterControllerTypeCore(controllerType, expression))
throw new ArgumentException($"Type {controllerType.Name} contains no controller methods.");
}
private static int IndexOfRouteParameter(RouteMatcher matcher, string name)
{
var names = matcher.ParameterNames;
for (var i = 0; i < names.Count; i++)
{
if (names[i] == name)
return i;
}
return -1;
}
private static T AwaitResult(Task task) => task.ConfigureAwait(false).GetAwaiter().GetResult();
private static T AwaitAndCastResult(string parameterName, Task