add some default types to de-duplicate similar implmentations
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Castle.Core.Logging;
|
||||
using Pilz.Extensions.Reflection;
|
||||
using Pilz.Net.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
@@ -12,8 +13,12 @@ namespace Pilz.Net.Api;
|
||||
|
||||
public class ApiServer : IApiServer
|
||||
{
|
||||
protected record struct ThreadHolder(Thread? Thread);
|
||||
public class MissingDataManagerException : Exception { }
|
||||
|
||||
protected readonly List<PrivateMessageHandler> handlers = [];
|
||||
protected readonly Dictionary<Type, IApiMessageSerializer> serializers = [];
|
||||
protected readonly Dictionary<ThreadHolder, IDataManager> managers = [];
|
||||
protected HttpListener httpListener;
|
||||
protected int restartAttempts = 0;
|
||||
protected DateTime lastRestartAttempt;
|
||||
@@ -24,6 +29,8 @@ public class ApiServer : IApiServer
|
||||
public event OnCheckAuthenticationEventHandler? OnCheckAuthentication;
|
||||
public event OnCheckContextEventHandler? OnCheckContext;
|
||||
public event OnCheckContextEventHandler? OnCheckContextCompleted;
|
||||
public event OnGetNewDataManagerEventHandler? OnGetNewDataManager;
|
||||
public event DataManagerEventHandler? OnResetDataManager;
|
||||
|
||||
protected record PrivateParameterInfo(string Name, int Index);
|
||||
|
||||
@@ -51,6 +58,10 @@ public class ApiServer : IApiServer
|
||||
|
||||
public int MaxConcurentConnections { get; set; } = 5;
|
||||
|
||||
public IDataManager Manager => GetManager();
|
||||
|
||||
public bool ThreadedDataManager { get; set; }
|
||||
|
||||
public ApiServer(string apiUrl) : this(apiUrl, null)
|
||||
{
|
||||
}
|
||||
@@ -61,16 +72,38 @@ public class ApiServer : IApiServer
|
||||
this.httpListener = httpListener ?? CreateDefaultHttpListener();
|
||||
}
|
||||
|
||||
private IDataManager GetManager()
|
||||
{
|
||||
var curThread = ThreadedDataManager ? Thread.CurrentThread : null;
|
||||
var threadHolder = new ThreadHolder(curThread);
|
||||
|
||||
if (managers.TryGetValue(threadHolder, out var mgr))
|
||||
return mgr;
|
||||
|
||||
if (OnGetNewDataManager?.Invoke(this, EventArgs.Empty) is not IDataManager manager)
|
||||
throw new MissingDataManagerException();
|
||||
|
||||
managers.Add(threadHolder, manager);
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
public virtual void ResetManager()
|
||||
{
|
||||
if (managers.Remove(new(ThreadedDataManager ? Thread.CurrentThread : null), out var manager))
|
||||
OnResetDataManager?.Invoke(this, new(manager));
|
||||
}
|
||||
|
||||
protected virtual HttpListener CreateDefaultHttpListener()
|
||||
{
|
||||
var httpListener = new HttpListener();
|
||||
|
||||
httpListener.TimeoutManager.IdleConnection = new TimeSpan(0, 10, 0);
|
||||
httpListener.TimeoutManager.IdleConnection = new TimeSpan(0, 2, 0);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
httpListener.TimeoutManager.RequestQueue = new TimeSpan(0, 10, 0);
|
||||
httpListener.TimeoutManager.HeaderWait = new TimeSpan(0, 10, 0);
|
||||
httpListener.TimeoutManager.RequestQueue = new TimeSpan(0, 2, 0);
|
||||
httpListener.TimeoutManager.HeaderWait = new TimeSpan(0, 2, 0);
|
||||
}
|
||||
|
||||
return httpListener;
|
||||
@@ -296,21 +329,27 @@ public class ApiServer : IApiServer
|
||||
if (context is not null)
|
||||
{
|
||||
Log.Info("Request retrived for " + context.Request.RawUrl);
|
||||
ResetManager();
|
||||
OnCheckContext?.Invoke(this, new(context));
|
||||
try
|
||||
{
|
||||
CheckContext(context);
|
||||
success = true;
|
||||
}
|
||||
catch (MissingDataManagerException mdmex)
|
||||
{
|
||||
Log.Error("DataManager is not supported by this server instnace!", mdmex);
|
||||
if (DebugMode) throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Error checking context", ex);
|
||||
if (DebugMode)
|
||||
throw;
|
||||
if (DebugMode) throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnCheckContextCompleted?.Invoke(this, new(context));
|
||||
ResetManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
Pilz.Net/Api/Client/BaseChildItemClient.cs
Normal file
34
Pilz.Net/Api/Client/BaseChildItemClient.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Pilz.Net.Api.Messages;
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api.Client;
|
||||
|
||||
public abstract class BaseChildItemClient<T>(IApiClient client) : BaseClient<T>(client), IBaseChildItemClient<T> where T : IDataObject
|
||||
{
|
||||
public virtual async Task<IEnumerable<T>> GetAll(int parentId)
|
||||
{
|
||||
return (await client.SendRequest<GeneralItemMessages<T>.Items>(ApiEndpoint, HttpMethod.Get, new ApiParameterCollection
|
||||
{
|
||||
["parent"] = parentId,
|
||||
})).EnsureOk().Items;
|
||||
}
|
||||
|
||||
public override async Task<T> Save(T item)
|
||||
{
|
||||
if (item.Id == 0)
|
||||
throw new NullReferenceException("Item has no parent yet!");
|
||||
return await base.Save(item);
|
||||
}
|
||||
|
||||
public virtual async Task<T> Save(T item, int parentId)
|
||||
{
|
||||
return (await client.SendRequest<GeneralItemMessages<T>.Item>(
|
||||
ApiEndpoint,
|
||||
HttpMethod.Post,
|
||||
GenerateUpdateMessage(item, true),
|
||||
new ApiParameterCollection
|
||||
{
|
||||
["parent"] = parentId,
|
||||
})).EnsureOk().Item;
|
||||
}
|
||||
}
|
||||
30
Pilz.Net/Api/Client/BaseClient.cs
Normal file
30
Pilz.Net/Api/Client/BaseClient.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Pilz.Net.Api.Messages;
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api.Client;
|
||||
|
||||
public abstract class BaseClient<T>(IApiClient client) : IBaseClient<T> where T : IDataObject
|
||||
{
|
||||
public abstract string ApiEndpoint { get; }
|
||||
|
||||
public virtual async Task<T> Get(int id)
|
||||
{
|
||||
return (await client.SendRequest<GeneralItemMessages<T>.Item>($"{ApiEndpoint}/{id}", HttpMethod.Get)).EnsureOk().Item;
|
||||
}
|
||||
|
||||
public virtual async Task Delete(int id)
|
||||
{
|
||||
(await client.SendRequest($"{ApiEndpoint}/{id}", HttpMethod.Delete)).EnsureOk();
|
||||
}
|
||||
|
||||
public virtual async Task<T> Save(T item)
|
||||
{
|
||||
var generateNew = item.Id == 0;
|
||||
return (await client.SendRequest<GeneralItemMessages<T>.Item>(
|
||||
generateNew ? ApiEndpoint : $"{ApiEndpoint}/{item.Id}",
|
||||
generateNew ? HttpMethod.Post : HttpMethod.Put,
|
||||
GenerateUpdateMessage(item, generateNew))).EnsureOk().Item;
|
||||
}
|
||||
|
||||
protected abstract ApiMessage GenerateUpdateMessage(T item, bool generateNew);
|
||||
}
|
||||
12
Pilz.Net/Api/Client/BaseItemClient.cs
Normal file
12
Pilz.Net/Api/Client/BaseItemClient.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Pilz.Net.Api.Messages;
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api.Client;
|
||||
|
||||
public abstract class BaseItemClient<T>(IApiClient client) : BaseClient<T>(client), IBaseItemClient<T> where T : IDataObject
|
||||
{
|
||||
public virtual async Task<IEnumerable<T>> GetAll()
|
||||
{
|
||||
return (await client.SendRequest<GeneralItemMessages<T>.Items>(ApiEndpoint, HttpMethod.Get)).EnsureOk().Items;
|
||||
}
|
||||
}
|
||||
9
Pilz.Net/Api/Client/IBaseChildItemClient.cs
Normal file
9
Pilz.Net/Api/Client/IBaseChildItemClient.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api.Client;
|
||||
|
||||
public interface IBaseChildItemClient<T> : IBaseClient<T> where T : IDataObject
|
||||
{
|
||||
Task<IEnumerable<T>> GetAll(int parentId);
|
||||
Task<T> Save(T item, int parentId);
|
||||
}
|
||||
10
Pilz.Net/Api/Client/IBaseClient.cs
Normal file
10
Pilz.Net/Api/Client/IBaseClient.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api.Client;
|
||||
|
||||
public interface IBaseClient<T> where T : IDataObject
|
||||
{
|
||||
Task Delete(int id);
|
||||
Task<T> Get(int id);
|
||||
Task<T> Save(T item);
|
||||
}
|
||||
8
Pilz.Net/Api/Client/IBaseItemClient.cs
Normal file
8
Pilz.Net/Api/Client/IBaseItemClient.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api.Client;
|
||||
|
||||
public interface IBaseItemClient<T> : IBaseClient<T> where T : IDataObject
|
||||
{
|
||||
Task<IEnumerable<T>> GetAll();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Castle.Core.Logging;
|
||||
using Pilz.Net.Data;
|
||||
|
||||
namespace Pilz.Net.Api;
|
||||
|
||||
@@ -6,10 +7,15 @@ public interface IApiServer
|
||||
{
|
||||
public delegate void OnCheckAuthenticationEventHandler(object sender, ApiAuthCheckEventArgs e);
|
||||
public delegate void OnCheckContextEventHandler(object sender, ApiContextEventArgs e);
|
||||
public delegate IDataManager? OnGetNewDataManagerEventHandler(object sender, EventArgs e);
|
||||
|
||||
event OnCheckAuthenticationEventHandler? OnCheckAuthentication;
|
||||
event OnCheckContextEventHandler OnCheckContext;
|
||||
event OnCheckContextEventHandler OnCheckContextCompleted;
|
||||
event OnCheckContextEventHandler? OnCheckContext;
|
||||
event OnCheckContextEventHandler? OnCheckContextCompleted;
|
||||
event OnGetNewDataManagerEventHandler? OnGetNewDataManager;
|
||||
event DataManagerEventHandler? OnResetDataManager;
|
||||
|
||||
IDataManager Manager { get; }
|
||||
|
||||
string ApiUrl { get; }
|
||||
|
||||
|
||||
21
Pilz.Net/Api/Messages/GeneralItemMessages.cs
Normal file
21
Pilz.Net/Api/Messages/GeneralItemMessages.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Pilz.Net.Api.Messages;
|
||||
|
||||
public static class GeneralItemMessages<T>
|
||||
{
|
||||
public class Item(T item) : ObjectMessage(item);
|
||||
public class Items(List<T> items) : ObjectsMessage(items);
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class ObjectMessage(T item) : ApiMessage
|
||||
{
|
||||
public T Item { get; } = item;
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class ObjectsMessage(List<T> items) : ApiMessage
|
||||
{
|
||||
public List<T> Items { get; } = items;
|
||||
}
|
||||
}
|
||||
52
Pilz.Net/Api/Server/BaseChildItemHandler.cs
Normal file
52
Pilz.Net/Api/Server/BaseChildItemHandler.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Pilz.Extensions.Reflection;
|
||||
using Pilz.Net.Data;
|
||||
using Pilz.Net.Extensions;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Pilz.Net.Api.Server;
|
||||
|
||||
public abstract class BaseChildItemHandler<TEntity, TParent, TUpdateMsg>(IApiServer server)
|
||||
: BaseHandler<TEntity, TUpdateMsg>(server)
|
||||
where TEntity : IDataObject
|
||||
where TParent : IDataObject
|
||||
where TUpdateMsg : ApiMessage
|
||||
{
|
||||
protected virtual bool RegisterGetAll => true;
|
||||
protected virtual bool RegisterPost => true;
|
||||
|
||||
public override void Initialize(IApiServer server)
|
||||
{
|
||||
base.Initialize(server);
|
||||
var t = GetType();
|
||||
if (RegisterGetAll)
|
||||
server.RegisterHandler(t.GetMethod(nameof(GetAll))!.CreateDelegate(this), new(Route, "GET"), Debugger.IsAttached);
|
||||
if (RegisterPost)
|
||||
server.RegisterHandler(t.GetMethod(nameof(Post))!.CreateDelegate(this), new(Route, "POST"), Debugger.IsAttached);
|
||||
}
|
||||
|
||||
public virtual ApiResult GetAll(int parent)
|
||||
{
|
||||
IQueryable<TEntity> list;
|
||||
|
||||
if (parent != 0 && server.Manager.Find(parent, out TParent? parentEntity))
|
||||
list = GetChilds(parentEntity).AsQueryable();
|
||||
else
|
||||
list = server.Manager.Get<TEntity>();
|
||||
|
||||
return list.ToList().Select(ToClient).ToItemsResult();
|
||||
}
|
||||
|
||||
public virtual ApiResult Post(int parent, TUpdateMsg msg)
|
||||
{
|
||||
if (!server.Manager.Find(parent, out TParent? parentEntity))
|
||||
return ApiResult.NotFound();
|
||||
var entity = CreateNewEntity();
|
||||
if (UpdateEntity(entity, msg) is ApiResult result)
|
||||
return result;
|
||||
GetChilds(parentEntity).Add(entity);
|
||||
server.Manager.Save(parentEntity, true);
|
||||
return ToClient(entity).ToItemResult();
|
||||
}
|
||||
|
||||
public abstract IList<TEntity> GetChilds(TParent parent);
|
||||
}
|
||||
63
Pilz.Net/Api/Server/BaseHandler.cs
Normal file
63
Pilz.Net/Api/Server/BaseHandler.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Pilz.Extensions.Reflection;
|
||||
using Pilz.Net.Data;
|
||||
using Pilz.Net.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Pilz.Net.Api.Server;
|
||||
|
||||
public abstract class BaseHandler<TEntity, TUpdateMsg>(IApiServer server)
|
||||
: IApiHandlerInitializer
|
||||
where TEntity : IDataObject
|
||||
where TUpdateMsg : ApiMessage
|
||||
{
|
||||
public abstract string Route { get; }
|
||||
protected virtual bool RegisterGet => true;
|
||||
protected virtual bool RegisterPut => true;
|
||||
protected virtual bool RegisterDelete => true;
|
||||
|
||||
public virtual void Initialize(IApiServer server)
|
||||
{
|
||||
var t = GetType();
|
||||
if (RegisterGet)
|
||||
server.RegisterHandler(t.GetMethod(nameof(Get))!.CreateDelegate(this), new(Route + "/{id}", "GET"), Debugger.IsAttached);
|
||||
if (RegisterPut)
|
||||
server.RegisterHandler(t.GetMethod(nameof(Put))!.CreateDelegate(this), new(Route + "/{id}", "PUT"), Debugger.IsAttached);
|
||||
if (RegisterDelete)
|
||||
server.RegisterHandler(t.GetMethod(nameof(Delete))!.CreateDelegate(this), new(Route + "/{id}", "DELETE"), Debugger.IsAttached);
|
||||
}
|
||||
|
||||
public virtual ApiResult Get(int id)
|
||||
{
|
||||
if (!server.Manager.Find(id, out TEntity? entity))
|
||||
return ApiResult.NotFound();
|
||||
return ToClient(entity).ToItemResult();
|
||||
}
|
||||
|
||||
public virtual ApiResult Put(int id, TUpdateMsg msg)
|
||||
{
|
||||
if (!server.Manager.Find(id, out TEntity? entity))
|
||||
return ApiResult.NotFound();
|
||||
if (UpdateEntity(entity, msg) is ApiResult result)
|
||||
return result;
|
||||
server.Manager.Save(entity, true);
|
||||
return ToClient(entity).ToItemResult();
|
||||
}
|
||||
|
||||
public virtual ApiResult Delete(int id)
|
||||
{
|
||||
server.Manager.Delete<TEntity>(id, true);
|
||||
return ApiResult.Ok();
|
||||
}
|
||||
|
||||
protected virtual TEntity CreateNewEntity()
|
||||
{
|
||||
return Activator.CreateInstance<TEntity>();
|
||||
}
|
||||
|
||||
protected abstract ApiResult? UpdateEntity(TEntity entity, TUpdateMsg msg);
|
||||
|
||||
protected abstract IDataObject ToClient(TEntity entity);
|
||||
}
|
||||
39
Pilz.Net/Api/Server/BaseItemHandler.cs
Normal file
39
Pilz.Net/Api/Server/BaseItemHandler.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Pilz.Extensions.Reflection;
|
||||
using Pilz.Net.Data;
|
||||
using Pilz.Net.Extensions;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Pilz.Net.Api.Server;
|
||||
|
||||
public abstract class BaseItemHandler<TEntity, TUpdateMsg>(IApiServer server)
|
||||
: BaseHandler<TEntity, TUpdateMsg>(server)
|
||||
where TEntity : IDataObject
|
||||
where TUpdateMsg : ApiMessage
|
||||
{
|
||||
protected virtual bool RegisterGetAll => true;
|
||||
protected virtual bool RegisterPost => true;
|
||||
|
||||
public override void Initialize(IApiServer server)
|
||||
{
|
||||
base.Initialize(server);
|
||||
var t = GetType();
|
||||
if (RegisterGetAll)
|
||||
server.RegisterHandler(t.GetMethod(nameof(GetAll))!.CreateDelegate(this), new(Route, "GET"), Debugger.IsAttached);
|
||||
if (RegisterPost)
|
||||
server.RegisterHandler(t.GetMethod(nameof(Put))!.CreateDelegate(this), new(Route, "POST"), Debugger.IsAttached);
|
||||
}
|
||||
|
||||
public virtual ApiResult GetAll()
|
||||
{
|
||||
return server.Manager.Get<TEntity>().ToList().Select(ToClient).ToItemsResult();
|
||||
}
|
||||
|
||||
public virtual ApiResult Post(TUpdateMsg msg)
|
||||
{
|
||||
var entity = CreateNewEntity();
|
||||
if (UpdateEntity(entity, msg) is ApiResult result)
|
||||
return result;
|
||||
server.Manager.Save(entity, true);
|
||||
return entity.ToItemResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user