This commit is contained in:
19
server/AyaNova/ControllerHelpers/ApiCreatedResponse.cs
Normal file
19
server/AyaNova/ControllerHelpers/ApiCreatedResponse.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiCreatedResponse
|
||||
{
|
||||
|
||||
public object Result { get; }
|
||||
|
||||
public ApiCreatedResponse(object result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
99
server/AyaNova/ControllerHelpers/ApiCustomExceptionFilter.cs
Normal file
99
server/AyaNova/ControllerHelpers/ApiCustomExceptionFilter.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using App.Metrics;
|
||||
using AyaNova.Util;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is essentially an unhandled exception handler
|
||||
/// </summary>
|
||||
public class ApiCustomExceptionFilter : IExceptionFilter
|
||||
{
|
||||
private readonly ILogger log;
|
||||
|
||||
public ApiCustomExceptionFilter(ILoggerFactory logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
this.log = logger.CreateLogger("Server Exception");
|
||||
}
|
||||
|
||||
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
HttpStatusCode status = HttpStatusCode.InternalServerError;
|
||||
String message = String.Empty;
|
||||
#region If need to refine this further and deal with specific types
|
||||
// var exceptionType = context.Exception.GetType();
|
||||
// if (exceptionType == typeof(UnauthorizedAccessException))
|
||||
// {
|
||||
// message = "Unauthorized Access";
|
||||
// status = HttpStatusCode.Unauthorized;
|
||||
// }
|
||||
// else if (exceptionType == typeof(NotImplementedException))
|
||||
// {
|
||||
// message = "A server error occurred.";
|
||||
// status = HttpStatusCode.NotImplemented;
|
||||
// }
|
||||
// // else if (exceptionType == typeof(MyAppException))
|
||||
// // {
|
||||
// // message = context.Exception.ToString();
|
||||
// // status = HttpStatusCode.InternalServerError;
|
||||
// // }
|
||||
// else
|
||||
// {
|
||||
#endregion
|
||||
message = context.Exception.Message;
|
||||
status = HttpStatusCode.InternalServerError;
|
||||
//}
|
||||
|
||||
//No need to log test exceptions to check and filter out
|
||||
bool loggableError = true;
|
||||
|
||||
if (message.StartsWith("Test exception"))
|
||||
loggableError = false;
|
||||
|
||||
//LOG IT
|
||||
if (loggableError)
|
||||
log.LogError(context.Exception, "Error");
|
||||
|
||||
|
||||
//Track this exception
|
||||
IMetrics metrics = (IMetrics)ServiceProviderProvider.Provider.GetService(typeof(IMetrics));
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.UnhandledExceptionsMeter,context.Exception.GetType().ToString());
|
||||
|
||||
|
||||
HttpResponse response = context.HttpContext.Response;
|
||||
response.StatusCode = (int)status;
|
||||
response.ContentType = "application/json; charset=utf-8";
|
||||
|
||||
//This line is critical, without it the response is not proper and fails in various clients (postman, xunit tests with httpclient)
|
||||
context.ExceptionHandled = true;
|
||||
//context.Result
|
||||
|
||||
response.WriteAsync(JsonConvert.SerializeObject(
|
||||
new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, "Server internal error", "See server log for details"),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
29
server/AyaNova/ControllerHelpers/ApiDetailError.cs
Normal file
29
server/AyaNova/ControllerHelpers/ApiDetailError.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Newtonsoft.Json;
|
||||
using AyaNova.Biz;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Detail error for inner part of error response
|
||||
/// </summary>
|
||||
public class ApiDetailError
|
||||
{
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Code { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Message { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Target { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Error { get; internal set; }
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
36
server/AyaNova/ControllerHelpers/ApiError.cs
Normal file
36
server/AyaNova/ControllerHelpers/ApiError.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ApiError
|
||||
{
|
||||
public string Code { get; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<ApiDetailError> Details { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Message { get; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Target { get; }
|
||||
|
||||
public ApiError(ApiErrorCode apiCode, string message = null, string target = null)
|
||||
{
|
||||
Code = ((int)apiCode).ToString();
|
||||
Target = target;
|
||||
Message=message;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
37
server/AyaNova/ControllerHelpers/ApiErrorCode.cs
Normal file
37
server/AyaNova/ControllerHelpers/ApiErrorCode.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
public enum ApiErrorCode : int
|
||||
{
|
||||
/*
|
||||
DON'T FORGET TO UPDATE THE API-ERROR-CODES.MD DOCUMENTATION
|
||||
AND UPDATE THE ApiErrorCodeStockMessage.cs
|
||||
*/
|
||||
|
||||
API_CLOSED = 2000,
|
||||
API_OPS_ONLY = 2001,
|
||||
API_SERVER_ERROR = 2002,
|
||||
AUTHENTICATION_FAILED = 2003,
|
||||
NOT_AUTHORIZED = 2004,
|
||||
CONCURRENCY_CONFLICT=2005,
|
||||
NOT_FOUND = 2010,
|
||||
PUT_ID_MISMATCH = 2020,
|
||||
INVALID_OPERATION = 2030,
|
||||
VALIDATION_FAILED = 2200,
|
||||
VALIDATION_REQUIRED = 2201,
|
||||
VALIDATION_LENGTH_EXCEEDED = 2202,
|
||||
VALIDATION_INVALID_VALUE = 2203
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
58
server/AyaNova/ControllerHelpers/ApiErrorCodeStockMessage.cs
Normal file
58
server/AyaNova/ControllerHelpers/ApiErrorCodeStockMessage.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class ApiErrorCodeStockMessage
|
||||
{
|
||||
internal static string GetMessage(ApiErrorCode code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case ApiErrorCode.API_CLOSED:
|
||||
return "API Closed";
|
||||
case ApiErrorCode.API_OPS_ONLY:
|
||||
return "API Closed to non operations routes";
|
||||
case ApiErrorCode.CONCURRENCY_CONFLICT:
|
||||
return "Object was changed by another user since retrieval (concurrency token mismatch)";
|
||||
case ApiErrorCode.NOT_FOUND:
|
||||
return "Object not found";
|
||||
case ApiErrorCode.VALIDATION_FAILED:
|
||||
return "Object did not pass validation";
|
||||
case ApiErrorCode.VALIDATION_REQUIRED:
|
||||
return "Required field empty";
|
||||
case ApiErrorCode.VALIDATION_LENGTH_EXCEEDED:
|
||||
return "Field too long";
|
||||
case ApiErrorCode.VALIDATION_INVALID_VALUE:
|
||||
return "Field is set to a non allowed value";
|
||||
case ApiErrorCode.AUTHENTICATION_FAILED:
|
||||
return "Authentication failed";
|
||||
case ApiErrorCode.PUT_ID_MISMATCH:
|
||||
return "Update failed: ID mismatch - route ID doesn't match object id";
|
||||
case ApiErrorCode.INVALID_OPERATION:
|
||||
return "An attempt was made to perform an invalid operation";
|
||||
case ApiErrorCode.NOT_AUTHORIZED:
|
||||
return "User not authorized for this resource operation (insufficient rights)";
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/*
|
||||
VALIDATION_FAILED = 2200,
|
||||
VALIDATION_REQUIRED = 2201,
|
||||
VALIDATION_LENGTH_EXCEEDED = 2202,
|
||||
VALIDATION_INVALID_VALUE = 2203
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
104
server/AyaNova/ControllerHelpers/ApiErrorResponse.cs
Normal file
104
server/AyaNova/ControllerHelpers/ApiErrorResponse.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using AyaNova.Biz;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiErrorResponse
|
||||
{
|
||||
|
||||
[JsonIgnore]
|
||||
private ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger<ApiErrorResponse>();
|
||||
|
||||
//Mandatory properties
|
||||
public ApiError Error { get; }
|
||||
|
||||
|
||||
|
||||
|
||||
//Generic error
|
||||
public ApiErrorResponse(ApiErrorCode apiCode, string target = null, string message = null)
|
||||
{
|
||||
|
||||
//try to get a stock message if nothing specified
|
||||
if (message == null)
|
||||
{
|
||||
message = ApiErrorCodeStockMessage.GetMessage(apiCode);
|
||||
}
|
||||
|
||||
Error = new ApiError(apiCode, message, target);
|
||||
|
||||
log.LogDebug("apiCode={0}, target={1}, message={2}", apiCode, target, message);
|
||||
}
|
||||
|
||||
|
||||
//Bad request error response handling
|
||||
|
||||
public ApiErrorResponse(ModelStateDictionary modelState)
|
||||
{
|
||||
|
||||
if (modelState.IsValid)
|
||||
{
|
||||
throw new ArgumentException("ModelState must be invalid", nameof(modelState));
|
||||
}
|
||||
|
||||
|
||||
//Set outer error and then put validation in details
|
||||
|
||||
Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetMessage(ApiErrorCode.VALIDATION_FAILED));
|
||||
|
||||
|
||||
//https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/
|
||||
//Message = "Validation Failed";
|
||||
Error.Details = new List<ApiDetailError>();
|
||||
Error.Details.AddRange(modelState.Keys
|
||||
.SelectMany(key => modelState[key].Errors
|
||||
.Select(x => new ApiDetailError() { Code = ((int)ApiErrorCode.VALIDATION_FAILED).ToString(), Target = key, Message = x.ErrorMessage, Error=ApiErrorCode.VALIDATION_FAILED.ToString() })));
|
||||
|
||||
|
||||
log.LogDebug("BadRequest - Validation error");
|
||||
}
|
||||
|
||||
|
||||
//Business rule validation error response
|
||||
public ApiErrorResponse(List<ValidationError> errors)
|
||||
{
|
||||
Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetMessage(ApiErrorCode.VALIDATION_FAILED));
|
||||
Error.Details = new List<ApiDetailError>();
|
||||
foreach (ValidationError v in errors)
|
||||
{
|
||||
Error.Details.Add(new ApiDetailError() { Target = v.Target, Message = v.Message, Error = v.ErrorType.ToString() });
|
||||
}
|
||||
log.LogDebug("BadRequest - Validation error");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void AddDetailError(ApiErrorCode apiCode, string target = null, string message = null)
|
||||
{
|
||||
if (Error.Details == null)
|
||||
{
|
||||
Error.Details = new List<ApiDetailError>();
|
||||
}
|
||||
|
||||
//try to get a stock message if nothing specified
|
||||
if (message == null)
|
||||
{
|
||||
message = ApiErrorCodeStockMessage.GetMessage(apiCode);
|
||||
}
|
||||
|
||||
Error.Details.Add(new ApiDetailError() { Code = ((int)apiCode).ToString(), Target = target, Message = message });
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
33
server/AyaNova/ControllerHelpers/ApiNotAuthorizedResponse.cs
Normal file
33
server/AyaNova/ControllerHelpers/ApiNotAuthorizedResponse.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiNotAuthorizedResponse
|
||||
{
|
||||
|
||||
[JsonIgnore]
|
||||
private ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger<ApiNotAuthorizedResponse>();
|
||||
|
||||
//Mandatory properties
|
||||
public ApiError Error { get; }
|
||||
|
||||
//Generic error
|
||||
public ApiNotAuthorizedResponse()
|
||||
{
|
||||
Error = new ApiError(ApiErrorCode.NOT_AUTHORIZED, ApiErrorCodeStockMessage.GetMessage(ApiErrorCode.NOT_AUTHORIZED));
|
||||
|
||||
log.LogDebug("ApiErrorCode={0}, message={1}", (int)ApiErrorCode.NOT_AUTHORIZED, Error.Message);
|
||||
}
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
19
server/AyaNova/ControllerHelpers/ApiOkResponse.cs
Normal file
19
server/AyaNova/ControllerHelpers/ApiOkResponse.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiOkResponse
|
||||
{
|
||||
|
||||
public object Result { get; }
|
||||
|
||||
public ApiOkResponse(object result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
30
server/AyaNova/ControllerHelpers/ApiOkWithPagingResponse.cs
Normal file
30
server/AyaNova/ControllerHelpers/ApiOkWithPagingResponse.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiOkWithPagingResponse<T>
|
||||
{
|
||||
|
||||
public object Result { get; }
|
||||
public object Paging { get; }
|
||||
|
||||
public ApiOkWithPagingResponse(ApiPagedResponse<T> pr)
|
||||
{
|
||||
Result = pr.items;
|
||||
Paging = pr.PageLinks;
|
||||
|
||||
}
|
||||
|
||||
// public ApiOkWithPagingResponse(object result, AyaNova.Models.PaginationLinkBuilder lb)
|
||||
// {
|
||||
// Result = result;
|
||||
// Paging = lb.PagingData();
|
||||
|
||||
// }
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
24
server/AyaNova/ControllerHelpers/ApiPagedResponse.cs
Normal file
24
server/AyaNova/ControllerHelpers/ApiPagedResponse.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using AyaNova.Models;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiPagedResponse<T>
|
||||
{
|
||||
|
||||
public T[] items { get; }
|
||||
public object PageLinks { get; }
|
||||
|
||||
public ApiPagedResponse(T[] returnItems, object pageLinks)
|
||||
{
|
||||
items = returnItems;
|
||||
PageLinks = pageLinks;
|
||||
|
||||
}
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
186
server/AyaNova/ControllerHelpers/ApiServerState.cs
Normal file
186
server/AyaNova/ControllerHelpers/ApiServerState.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains the current status of the server
|
||||
/// is injected everywhere for routes and others to check
|
||||
/// </summary>
|
||||
public class ApiServerState
|
||||
{
|
||||
|
||||
//private ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger<ApiServerState>();
|
||||
public enum ServerState
|
||||
{
|
||||
///<summary>Unknown state, used for parsing</summary>
|
||||
UNKNOWN = 0,
|
||||
///<summary>No access for anyone API completely locked down</summary>
|
||||
Closed = 1,
|
||||
///<summary>Access only to API Operations routes</summary>
|
||||
OpsOnly = 2,
|
||||
///<summary>Open for all users (default)</summary>
|
||||
Open = 3
|
||||
}
|
||||
|
||||
private ServerState _currentState = ServerState.Closed;
|
||||
private ServerState _priorState = ServerState.Closed;
|
||||
private string _reason = string.Empty;
|
||||
private string _priorReason = string.Empty;
|
||||
private bool SYSTEM_LOCK = false;//really this is a license lock but not called that
|
||||
|
||||
public ApiServerState()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal void SetSystemLock(string reason)
|
||||
{
|
||||
//Lock down the server for license related issue
|
||||
//Still allows ops routes, treats as if server was set to closed even if they change it to open
|
||||
//only way to reset it is to fetch a valid license
|
||||
SetState(ServerState.OpsOnly, reason);
|
||||
SYSTEM_LOCK = true;
|
||||
}
|
||||
|
||||
|
||||
//WARNING: if in future this is used for anything other than a license then it will need to see if locked for another reason before unlocking
|
||||
//recommend putting a code number in the reason then looking to see if it has the matching code
|
||||
internal void ClearSystemLock()
|
||||
{
|
||||
SYSTEM_LOCK = false;
|
||||
SetState(ServerState.Open, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the server state
|
||||
/// </summary>
|
||||
/// <param name="newState"></param>
|
||||
/// <param name="reason"></param>
|
||||
public void SetState(ServerState newState, string reason)
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
|
||||
_reason = reason;//keep the reason even if the state doesn't change
|
||||
if (newState == _currentState) return;
|
||||
|
||||
//Here we will likely need to trigger a notification to users if the state is going to be shutting down or is shut down
|
||||
_priorState = _currentState;//keep the prior state so it can be resumed easily
|
||||
_priorReason=_reason;//keep the original reason
|
||||
|
||||
_currentState = newState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of the server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ServerState GetState()
|
||||
{
|
||||
return _currentState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current reason for the state of the server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string Reason
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentState == ServerState.Open)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Server state is: {_currentState.ToString()}, Reason: {_reason}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetOpsOnly(string reason)
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(ServerState.OpsOnly, reason);
|
||||
}
|
||||
|
||||
|
||||
public void SetClosed(string reason)
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(ServerState.Closed, reason);
|
||||
}
|
||||
|
||||
public void SetOpen()
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(ServerState.Open, string.Empty);
|
||||
}
|
||||
|
||||
public void ResumePriorState()
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(_priorState, _priorReason);
|
||||
}
|
||||
|
||||
|
||||
public bool IsOpsOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentState == ServerState.OpsOnly;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentState == ServerState.Open && !SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentState == ServerState.Closed || SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpenOrOpsOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsOpen || IsOpsOnly;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSystemLocked
|
||||
{
|
||||
get
|
||||
{
|
||||
return SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
209
server/AyaNova/ControllerHelpers/ApiUploadProcessor.cs
Normal file
209
server/AyaNova/ControllerHelpers/ApiUploadProcessor.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
//Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
//See AttachmentController at bottom of class for example of form that works with this code
|
||||
|
||||
/// <summary>
|
||||
/// Handle processing uplod form with potentially huge files being uploaded (which means can't use simplest built in upload handler method)
|
||||
/// </summary>
|
||||
internal static class ApiUploadProcessor
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Process uploaded attachment file
|
||||
/// Will be treated as a temporary file for further processing into database
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
internal static async Task<ApiUploadedFilesResult> ProcessAttachmentUpload(Microsoft.AspNetCore.Http.HttpContext httpContext)
|
||||
{
|
||||
return await ProcessUpload(httpContext, true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Process uploaded utility file (backup, import etc)
|
||||
/// Anything that will be stored in the backup folder as is
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
internal static async Task<ApiUploadedFilesResult> ProcessUtilityFileUpload(Microsoft.AspNetCore.Http.HttpContext httpContext)
|
||||
{
|
||||
return await ProcessUpload(httpContext, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// handle upload
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="processAsAttachment"></param>
|
||||
/// <returns><see cref="ApiUploadedFilesResult"/> list of files and form field data (if present)</returns>
|
||||
private static async Task<ApiUploadedFilesResult> ProcessUpload(Microsoft.AspNetCore.Http.HttpContext httpContext, bool processAsAttachment)
|
||||
{
|
||||
|
||||
ApiUploadedFilesResult result = new ApiUploadedFilesResult();
|
||||
FormOptions _defaultFormOptions = new FormOptions();
|
||||
|
||||
// Used to accumulate all the form url encoded key value pairs in the
|
||||
// request.
|
||||
var formAccumulator = new KeyValueAccumulator();
|
||||
|
||||
var boundary = MultipartRequestHelper.GetBoundary(
|
||||
MediaTypeHeaderValue.Parse(httpContext.Request.ContentType),
|
||||
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
||||
var reader = new MultipartReader(boundary, httpContext.Request.Body);
|
||||
|
||||
var section = await reader.ReadNextSectionAsync();
|
||||
|
||||
|
||||
while (section != null)
|
||||
{
|
||||
ContentDispositionHeaderValue contentDisposition;
|
||||
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
|
||||
|
||||
if (hasContentDispositionHeader)
|
||||
{
|
||||
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
|
||||
{
|
||||
|
||||
string filePathAndName = string.Empty;
|
||||
var CleanedUploadFileName = contentDisposition.FileName.Value.Replace("\"", "");
|
||||
|
||||
if (processAsAttachment)
|
||||
{
|
||||
//get temp file path and temp file name
|
||||
filePathAndName = FileUtil.NewRandomAttachmentFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
//store directly into the backup file folder
|
||||
//NOTE: all utility files are always stored as lowercase to avoid recognition issues down the road
|
||||
CleanedUploadFileName = CleanedUploadFileName.ToLowerInvariant();
|
||||
filePathAndName = FileUtil.GetFullPathForUtilityFile(CleanedUploadFileName);
|
||||
}
|
||||
|
||||
|
||||
//save to disk
|
||||
using (var stream = new FileStream(filePathAndName, FileMode.Create))
|
||||
{
|
||||
section.Body.CopyTo(stream);
|
||||
}
|
||||
result.UploadedFiles.Add(new UploadedFileInfo()
|
||||
{
|
||||
InitialUploadedPathName = filePathAndName,
|
||||
OriginalFileName = CleanedUploadFileName,
|
||||
MimeType = section.ContentType
|
||||
|
||||
});
|
||||
}
|
||||
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
|
||||
{
|
||||
// Content-Disposition: form-data; name="key"
|
||||
//
|
||||
// value
|
||||
|
||||
// Do not limit the key name length here because the
|
||||
// multipart headers length limit is already in effect.
|
||||
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
|
||||
var encoding = GetEncoding(section);
|
||||
using (var streamReader = new StreamReader(
|
||||
section.Body,
|
||||
encoding,
|
||||
detectEncodingFromByteOrderMarks: true,
|
||||
bufferSize: 1024,
|
||||
leaveOpen: true))
|
||||
{
|
||||
// The value length limit is enforced by MultipartBodyLengthLimit
|
||||
var value = await streamReader.ReadToEndAsync();
|
||||
if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = String.Empty;
|
||||
}
|
||||
formAccumulator.Append(key.Value, value);
|
||||
|
||||
if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
|
||||
{
|
||||
throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drains any remaining section body that has not been consumed and
|
||||
// reads the headers for the next section.
|
||||
section = await reader.ReadNextSectionAsync();
|
||||
}
|
||||
|
||||
//Get any extra form fields and return them
|
||||
result.FormFieldData = formAccumulator.GetResults();
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static Encoding GetEncoding(MultipartSection section)
|
||||
{
|
||||
MediaTypeHeaderValue mediaType;
|
||||
var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
|
||||
// UTF-7 is insecure and should not be honored. UTF-8 will succeed in
|
||||
// most cases.
|
||||
if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
return mediaType.Encoding;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains result of upload form processor
|
||||
/// </summary>
|
||||
public class ApiUploadedFilesResult
|
||||
{
|
||||
public Dictionary<string, Microsoft.Extensions.Primitives.StringValues> FormFieldData { get; set; }
|
||||
public List<UploadedFileInfo> UploadedFiles { get; set; }
|
||||
|
||||
public ApiUploadedFilesResult()
|
||||
{
|
||||
FormFieldData = new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
|
||||
UploadedFiles = new List<UploadedFileInfo>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
121
server/AyaNova/ControllerHelpers/Authorized.cs
Normal file
121
server/AyaNova/ControllerHelpers/Authorized.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using EnumsNET;
|
||||
using System.Collections.Generic;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class Authorized
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// User has any ops role limited or full
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="CheckRoles"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasAnyRole(IDictionary<object, object> HttpContextItems, AuthorizationRoles CheckRoles)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
if (currentUserRoles.HasAnyFlags(CheckRoles))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// READ / GENERAL ACCESS
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="objectType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsAuthorizedToRead(IDictionary<object, object> HttpContextItems, AyaType objectType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
|
||||
//NOTE: this assumes that if you can change you can read
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).Change))
|
||||
return true;
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).Read))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CREATE
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="objectType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsAuthorizedToCreate(IDictionary<object, object> HttpContextItems, AyaType objectType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).Change))
|
||||
return true;
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).EditOwn))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MODIFY
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="objectType"></param>
|
||||
/// <param name="ownerId"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsAuthorizedToModify(IDictionary<object, object> HttpContextItems, AyaType objectType, long ownerId = -1)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
long currentUserId = UserIdFromContext.Id(HttpContextItems);
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).Change))
|
||||
return true;
|
||||
if (ownerId != -1)
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).EditOwn) && ownerId == currentUserId)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DELETE
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="objectType"></param>
|
||||
/// <param name="ownerId"></param>
|
||||
/// <returns></returns>
|
||||
//For now just going to treat as a modify, but for maximum flexibility keeping this as a separate method in case we change our minds in future
|
||||
internal static bool IsAuthorizedToDelete(IDictionary<object, object> HttpContextItems, AyaType objectType, long ownerId = 1)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
long currentUserId = UserIdFromContext.Id(HttpContextItems);
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).Change))
|
||||
return true;
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(BizRoles.GetRoleSet(objectType).EditOwn) && ownerId == currentUserId)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
//FROM DOCS HERE:
|
||||
//https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
//https://github.com/aspnet/Docs/tree/74a44669d5e7039e2d4d2cb3f8b0c4ed742d1124/aspnetcore/mvc/models/file-uploads/sample/FileUploadSample
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
var formValueProviderFactory = context.ValueProviderFactories
|
||||
.OfType<FormValueProviderFactory>()
|
||||
.FirstOrDefault();
|
||||
if (formValueProviderFactory != null)
|
||||
{
|
||||
context.ValueProviderFactories.Remove(formValueProviderFactory);
|
||||
}
|
||||
|
||||
var jqueryFormValueProviderFactory = context.ValueProviderFactories
|
||||
.OfType<JQueryFormValueProviderFactory>()
|
||||
.FirstOrDefault();
|
||||
if (jqueryFormValueProviderFactory != null)
|
||||
{
|
||||
context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
77
server/AyaNova/ControllerHelpers/MultipartRequestHelper.cs
Normal file
77
server/AyaNova/ControllerHelpers/MultipartRequestHelper.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class MultipartRequestHelper
|
||||
{
|
||||
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
|
||||
// The spec says 70 characters is a reasonable limit.
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="lengthLimit"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
|
||||
{
|
||||
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
|
||||
if (string.IsNullOrWhiteSpace(boundary))
|
||||
{
|
||||
throw new InvalidDataException("Missing content-type boundary.");
|
||||
}
|
||||
|
||||
if (boundary.Length > lengthLimit)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Multipart boundary length limit {lengthLimit} exceeded.");
|
||||
}
|
||||
|
||||
return boundary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsMultipartContentType(string contentType)
|
||||
{
|
||||
return !string.IsNullOrEmpty(contentType)
|
||||
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentDisposition"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
// Content-Disposition: form-data; name="key";
|
||||
return contentDisposition != null
|
||||
&& contentDisposition.DispositionType.Equals("form-data")
|
||||
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
|
||||
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentDisposition"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
|
||||
return contentDisposition != null
|
||||
&& contentDisposition.DispositionType.Equals("form-data")
|
||||
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|
||||
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
89
server/AyaNova/ControllerHelpers/PaginationLinkBuilder.cs
Normal file
89
server/AyaNova/ControllerHelpers/PaginationLinkBuilder.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
public class PaginationLinkBuilder
|
||||
{ //adapted from //https://www.jerriepelser.com/blog/paging-in-aspnet-webapi-pagination-links/
|
||||
public Uri FirstPage { get; private set; }
|
||||
public Uri LastPage { get; private set; }
|
||||
public Uri NextPage { get; private set; }
|
||||
public Uri PreviousPage { get; private set; }
|
||||
public PagingOptions PagingOptions { get; }
|
||||
public long TotalRecordCount { get; }
|
||||
|
||||
public PaginationLinkBuilder(IUrlHelper urlHelper, string routeName, object routeValues, PagingOptions pagingOptions, long totalRecordCount)
|
||||
{
|
||||
PagingOptions = pagingOptions;
|
||||
TotalRecordCount = totalRecordCount;
|
||||
|
||||
// Determine total number of pages
|
||||
var pageCount = totalRecordCount > 0
|
||||
? (int)Math.Ceiling(totalRecordCount / (double)pagingOptions.Limit)
|
||||
: 0;
|
||||
|
||||
// Create page links
|
||||
|
||||
FirstPage = new Uri(urlHelper.Link(routeName, new RouteValueDictionary(routeValues)
|
||||
{
|
||||
{"pageNo", 1},
|
||||
{"pageSize", pagingOptions.Limit}
|
||||
}));
|
||||
|
||||
|
||||
LastPage = new Uri(urlHelper.Link(routeName, new RouteValueDictionary(routeValues)
|
||||
{
|
||||
{"pageNo", pageCount},
|
||||
{"pageSize", pagingOptions.Limit}
|
||||
}));
|
||||
|
||||
if (pagingOptions.Offset > 1)
|
||||
{
|
||||
PreviousPage = new Uri(urlHelper.Link(routeName, new RouteValueDictionary(routeValues)
|
||||
{
|
||||
{"pageNo", pagingOptions.Offset - 1},
|
||||
{"pageSize", pagingOptions.Limit}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (pagingOptions.Offset < pageCount)
|
||||
{
|
||||
NextPage = new Uri(urlHelper.Link(routeName, new RouteValueDictionary(routeValues)
|
||||
{
|
||||
{"pageNo", pagingOptions.Offset + 1},
|
||||
{"pageSize", pagingOptions.Limit}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Return paging data suitable for API return
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Object PagingLinksObject()
|
||||
{
|
||||
return new
|
||||
{
|
||||
Count = TotalRecordCount,
|
||||
Offset = PagingOptions.Offset,
|
||||
Limit = PagingOptions.Limit,
|
||||
First = FirstPage,
|
||||
Previous = PreviousPage,
|
||||
Next = NextPage,
|
||||
Last = LastPage
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
23
server/AyaNova/ControllerHelpers/PagingOptions.cs
Normal file
23
server/AyaNova/ControllerHelpers/PagingOptions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
public sealed class PagingOptions
|
||||
{
|
||||
public const int MaxPageSize = 100;
|
||||
public const int DefaultOffset = 0;
|
||||
public const int DefaultLimit = 25;
|
||||
|
||||
[FromQuery]
|
||||
[Range(0, int.MaxValue)]
|
||||
public int? Offset { get; set; }
|
||||
|
||||
[FromQuery]
|
||||
[Range(1, MaxPageSize, ErrorMessage = "Limit must be greater than 0 and less than 100.")]
|
||||
public int? Limit { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
21
server/AyaNova/ControllerHelpers/UserIdFromContext.cs
Normal file
21
server/AyaNova/ControllerHelpers/UserIdFromContext.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EnumsNET;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class UserIdFromContext
|
||||
{
|
||||
internal static long Id(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
|
||||
long? l = (long?)HttpContextItems["AY_USER_ID"];
|
||||
if (l==null)
|
||||
return 0L;
|
||||
return (long)l;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
20
server/AyaNova/ControllerHelpers/UserNameFromContext.cs
Normal file
20
server/AyaNova/ControllerHelpers/UserNameFromContext.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EnumsNET;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class UserNameFromContext
|
||||
{
|
||||
internal static string Name(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
string s = (string)HttpContextItems["AY_USERNAME"];
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return "UNKNOWN USER NAME";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
18
server/AyaNova/ControllerHelpers/UserRolesFromContext.cs
Normal file
18
server/AyaNova/ControllerHelpers/UserRolesFromContext.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EnumsNET;
|
||||
using System.Collections.Generic;
|
||||
using AyaNova.Biz;
|
||||
|
||||
namespace AyaNova.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class UserRolesFromContext
|
||||
{
|
||||
internal static AuthorizationRoles Roles(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
return (AuthorizationRoles)HttpContextItems["AY_ROLES"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
Reference in New Issue
Block a user