/// <summary> /// Initializes a new instance of the <see cref="RuntimeRoute" /> class. /// </summary> /// <param name="routeIdentifier">The route key.</param> /// <param name="methodInfo">The method information.</param> /// <param name="instanceType">Type of the instance.</param> /// <param name="instance">The instance.</param> /// <param name="isActionUsed">if set to <c>true</c> [is action used].</param> /// <param name="isTokenRequired">if set to <c>true</c> [is token required].</param> /// <param name="moduleName">Name of the module.</param> /// <param name="contentType">Type of the content.</param> /// <param name="setting">The setting.</param> /// <param name="apiCacheAttribute">The API cache attribute.</param> /// <param name="omitApiTracking">The omit API tracking.</param> /// <param name="permissions">The permissions.</param> /// <param name="headerKeys">The header keys.</param> public RuntimeRoute(ApiRouteIdentifier routeIdentifier, MethodInfo methodInfo, Type instanceType, object instance, bool isActionUsed, bool isTokenRequired, string moduleName, string contentType, RestApiSettings setting, ApiCacheAttribute apiCacheAttribute, OmitApiTrackingAttribute omitApiTracking, IDictionary <string, ApiPermissionAttribute> permissions = null, List <string> headerKeys = null) : this() { ApiMethod = methodInfo; ApiInstance = instance; IsActionUsed = isActionUsed; InstanceType = instanceType; Setting = setting; OmitApiTracking = omitApiTracking; OperationParameters = new RuntimeApiOperationParameters { ContentType = contentType, IsTokenRequired = isTokenRequired, CustomizedHeaderKeys = headerKeys, Permissions = permissions, ModuleName = moduleName }; ApiRouteIdentifier = routeIdentifier; IsVoid = ApiMethod?.ReturnType?.IsVoid(); if (apiCacheAttribute != null) { ApiCacheAttribute = apiCacheAttribute; ApiCacheContainer = apiCacheAttribute.CacheContainer ?? new ApiCacheContainer(routeIdentifier.ToString(), apiCacheAttribute.CacheParameter); } }
/// <summary> /// Initializes the routes. /// </summary> /// <param name="instance">The instance.</param> /// <param name="settings">The settings.</param> /// <exception cref="DataConflictException">Route</exception> private static void InitializeRoute(object instance, RestApiSettings settings = null) { lock (routeOperationLocker) { if (instance != null) { var typeName = instance.GetType().FullName; if (!initializedTypes.Contains(typeName)) { #region Initialize routes var doneInterfaceTypes = new List <string>(); foreach (var interfaceType in instance.GetType().GetInterfaces()) { InitializeApiType(doneInterfaceTypes, routes, interfaceType, instance, settings); } #endregion Initialize routes initializedTypes.Add(typeName); } } } }
/// <summary> /// Initializes a new instance of the <see cref="RestApiRouter" /> class. /// </summary> /// <param name="defaultApiSettings">The default API settings.</param> /// <param name="allowOptions">if set to <c>true</c> [allow options].</param> public RestApiRouter(RestApiSettings defaultApiSettings, bool allowOptions = false) : base(defaultApiSettings, allowOptions) { if (_firstInstance == null) { _firstInstance = this; } }
/// <summary> /// Adds the handler (instance and settings) into route. /// </summary> /// <param name="instance">The instance.</param> /// <param name="settings">The settings.</param> public static void Add(object instance, RestApiSettings settings = null) { InitializeRoute(instance, settings); }
/// <summary> /// Initializes the type of the API. /// </summary> /// <param name="doneInterfaceTypes">The done interface types.</param> /// <param name="routes">The routes.</param> /// <param name="interfaceType">Type of the interface.</param> /// <param name="instance">The instance.</param> /// <param name="settings">The settings.</param> /// <param name="parentApiContractAttribute">The parent API class attribute.</param> /// <param name="parentApiModuleAttribute">The parent API module attribute.</param> /// <param name="omitApiTrackingAttribute">The omit API tracking attribute.</param> /// <param name="parentTokenRequiredAttribute">The parent token required attribute.</param> /// <exception cref="DataConflictException">routeKey</exception> private static void InitializeApiType(List <string> doneInterfaceTypes, Dictionary <ApiRouteIdentifier, RuntimeRoute> routes, Type interfaceType, object instance, RestApiSettings settings = null, ApiContractAttribute parentApiContractAttribute = null, ApiModuleAttribute parentApiModuleAttribute = null, OmitApiTrackingAttribute omitApiTrackingAttribute = null, TokenRequiredAttribute parentTokenRequiredAttribute = null) { if (routes != null && interfaceType != null && doneInterfaceTypes != null) { if (doneInterfaceTypes.Contains(interfaceType.FullName)) { return; } var apiContract = parentApiContractAttribute ?? interfaceType.GetCustomAttribute <ApiContractAttribute>(true); var omitApiTracking = omitApiTrackingAttribute ?? interfaceType.GetCustomAttribute <OmitApiTrackingAttribute>(true); var apiModule = parentApiModuleAttribute ?? interfaceType.GetCustomAttribute <ApiModuleAttribute>(true); var tokenRequiredAttribute = parentTokenRequiredAttribute ?? interfaceType.GetCustomAttribute <TokenRequiredAttribute>(true); var moduleName = apiModule?.ToString(); if (apiContract != null && !string.IsNullOrWhiteSpace(apiContract.Version)) { if (apiContract.Version.SafeEquals(ApiConstants.BuiltInFeatureVersionKeyword, StringComparison.OrdinalIgnoreCase)) { throw ExceptionFactory.CreateInvalidObjectException(nameof(apiContract.Version), reason: "<builtin> cannot be used as version due to it is used internally."); } foreach (var method in interfaceType.GetMethods()) { var apiOperationAttribute = method.GetCustomAttribute <ApiOperationAttribute>(true); #region Initialize based on ApiOperation if (apiOperationAttribute != null) { var permissions = new Dictionary <string, ApiPermissionAttribute>(); var additionalHeaderKeys = new HashSet <string>(); var apiPermissionAttributes = method.GetCustomAttributes <ApiPermissionAttribute>(true); var apiCacheAttribute = method.GetCustomAttribute <ApiCacheAttribute>(true); if (apiPermissionAttributes != null) { foreach (var one in apiPermissionAttributes) { permissions.Merge(one.PermissionIdentifier, one); } } var headerKeyAttributes = method.GetCustomAttributes <ApiHeaderAttribute>(true); if (headerKeyAttributes != null) { foreach (var one in headerKeyAttributes) { additionalHeaderKeys.Add(one.HeaderKey); } } var routeKey = ApiRouteIdentifier.FromApiObjects(apiContract, apiOperationAttribute); var tokenRequired = method.GetCustomAttribute <TokenRequiredAttribute>(true) ?? tokenRequiredAttribute; // If method can not support API cache, consider as no api cache. if (apiCacheAttribute != null && (!apiOperationAttribute.HttpMethod.Equals(HttpConstants.HttpMethod.Get, StringComparison.OrdinalIgnoreCase) || !apiCacheAttribute.InitializeParameterNames(method))) { apiCacheAttribute = null; } var runtimeRoute = new RuntimeRoute(routeKey, method, interfaceType, instance, !string.IsNullOrWhiteSpace(apiOperationAttribute.Action), tokenRequired != null && tokenRequired.TokenRequired, moduleName, apiOperationAttribute.ContentType, settings, apiCacheAttribute, omitApiTracking ?? method.GetCustomAttribute <OmitApiTrackingAttribute>(true), permissions, additionalHeaderKeys.ToList()); if (routes.ContainsKey(routeKey)) { throw new DataConflictException(nameof(routeKey), objectIdentity: routeKey?.ToString(), data: new { existed = routes[routeKey].SafeToString(), newMethod = method.GetFullName(), newInterface = interfaceType.FullName }); } // EntitySynchronizationModeAttribute var entitySynchronizationModeAttribute = method.GetCustomAttribute <EntitySynchronizationModeAttribute>(true); if (entitySynchronizationModeAttribute != null) { if (EntitySynchronizationModeAttribute.IsReturnTypeMatched(method.ReturnType)) { runtimeRoute.OperationParameters.EntitySynchronizationMode = entitySynchronizationModeAttribute; } } routes.Add(routeKey, runtimeRoute); } #endregion Initialize based on ApiOperation } foreach (var one in interfaceType.GetInterfaces()) { InitializeApiType(doneInterfaceTypes, routes, one, instance, settings, apiContract, apiModule, omitApiTracking, tokenRequiredAttribute); } //Special NOTE: // Move this add action in scope of if apiContract is valid. // Reason: in complicated cases, when [A:Interface1] without ApiContract, but [Interface2: Interface] with defining ApiContract, and [B: A, Interface2], then correct contract definition might be missed. doneInterfaceTypes.Add(interfaceType.FullName); } } }
/// <summary> /// Initializes a new instance of the <see cref="ApiHandlerBase" /> class. /// </summary> /// <param name="defaultApiSettings">The default API settings.</param> /// <param name="allowOptions">if set to <c>true</c> [allow options].</param> protected ApiHandlerBase(RestApiSettings defaultApiSettings, bool allowOptions = false) { RestApiSettingPool.AddSetting(defaultApiSettings); AllowOptions = allowOptions; }
/// <summary> /// Packages the response. /// </summary> /// <param name="context">The context.</param> /// <param name="data">The data.</param> /// <param name="operationParameters">The operation parameters.</param> /// <param name="ex">The ex.</param> /// <param name="acceptEncoding">The accept encoding.</param> /// <param name="noBody">if set to <c>true</c> [no body].</param> /// <param name="settings">The settings.</param> public static void PackageResponse(HttpContextContainer <TRequest, TResponse> context, object data, RuntimeApiOperationParameters operationParameters = null, BaseException ex = null, string acceptEncoding = null, bool noBody = false, RestApiSettings settings = null) { if (context != null) { if (settings == null) { settings = RestApiSettingPool.DefaultRestApiSettings; } var objectToReturn = ex != null ? (settings.OmitExceptionDetail ? ex.ToSimpleExceptionInfo() : ex.ToExceptionInfo()) : data; context.SetResponseHeader(HttpConstants.HttpHeader.SERVERNAME, EnvironmentCore.MachineName); context.SetResponseHeader(HttpConstants.HttpHeader.TRACEID, ApiTraceContext.TraceId); context.SetResponseHeader(HttpConstants.HttpHeader.POWEREDBY, HttpConstants.HttpValues.PoweredByBeyova); context.SetResponseHeader(HttpConstants.HttpHeader.BEYOVAAPIVERSION, BeyovaVersion.CommonVesion); int httpStatusCode = (int)(ex == null ? (noBody ? HttpStatusCode.NoContent : HttpStatusCode.OK) : ex.Code.ToHttpStatusCode()); if (ex == null && operationParameters?.EntitySynchronizationMode != null) { DateTime?lastModifiedStamp = null; data = operationParameters.EntitySynchronizationMode.RebuildOutputObject(ContextHelper.ApiContext.LastSynchronizedStamp, data, ref httpStatusCode, ref noBody, out lastModifiedStamp); if (lastModifiedStamp.HasValue) { context.SetResponseHeader(operationParameters.EntitySynchronizationMode.LastModifiedKey, lastModifiedStamp.Value.ToFullDateTimeTzString()); } } context.ResponseStatusCode = (HttpStatusCode)httpStatusCode; if (!noBody) { var contentType = HttpConstants.ContentType.Json; var contentWritten = false; byte[] responseBytes = null; if (ex == null) { if (!string.IsNullOrWhiteSpace(operationParameters?.ContentType)) { contentType = operationParameters.ContentType; if (objectToReturn != null && contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase) && objectToReturn.GetType() == typeof(byte[])) { responseBytes = (byte[])objectToReturn; } } } if (responseBytes == null) { responseBytes = Framework.DefaultTextEncoding.GetBytes(contentType.Equals(HttpConstants.ContentType.Json, StringComparison.OrdinalIgnoreCase) ? objectToReturn.ToJson(true, JsonConverters) : objectToReturn.SafeToString()); } context.SetResponseHeader(HttpConstants.HttpHeader.SERVEREXITTIME, DateTime.UtcNow.ToFullDateTimeTzString()); if (settings.EnableContentCompression) { acceptEncoding = acceptEncoding.SafeToString().ToLowerInvariant(); if (acceptEncoding.Contains(HttpConstants.HttpValues.GZip)) { context.WriteResponseGzipBody(responseBytes, contentType); contentWritten = true; } else if (acceptEncoding.Contains(HttpConstants.HttpValues.Deflate)) { context.WriteResponseDeflateBody(responseBytes, contentType); contentWritten = true; } } if (!contentWritten) { //return as string; context.WriteResponseBody(responseBytes, contentType); } } else { context.SetResponseHeader(HttpConstants.HttpHeader.SERVEREXITTIME, DateTime.UtcNow.ToFullDateTimeTzString()); } } }
/// <summary> /// Initializes a new instance of the <see cref="ApiHandlerBase" /> class. /// </summary> /// <param name="defaultApiSettings">The default API settings.</param> /// <param name="allowOptions">if set to <c>true</c> [allow options].</param> protected ApiHandlerBase(RestApiSettings defaultApiSettings, bool allowOptions = false) : base(defaultApiSettings, allowOptions) { }
/// <summary> /// Initializes a new instance of the <see cref="RestApiRouter" /> class. /// </summary> /// <param name="defaultApiSettings">The default API settings.</param> /// <param name="allowOptions">if set to <c>true</c> [allow options].</param> public RestApiRouter(RestApiSettings defaultApiSettings, bool allowOptions = false) : base(defaultApiSettings, allowOptions) { }
/// <summary> /// Adds the setting. /// </summary> /// <param name="setting">The setting.</param> /// <param name="overrideIfExists">if set to <c>true</c> [override if exists].</param> /// <returns></returns> public static bool AddSetting(RestApiSettings setting, bool overrideIfExists = false) { return((setting != null) ? settingsContainer.Merge(setting.Name.SafeToString(), setting, overrideIfExists) : false); }
/// <summary> /// Adds the handler (instance and settings) into route. /// </summary> /// <param name="instance">The instance.</param> /// <param name="settings">The settings.</param> public void Add(object instance, RestApiSettings settings = null) { RestApiRoutePool.Add(instance, settings); }