Exemplo n.º 1
0
        /// <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);
                }
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Writes the API HTML document.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <param name="apiServiceType">Type of the API service.</param>
        /// <param name="apiContractOptions">The API contract options.</param>
        /// <param name="classTokenRequiredAttribute">The class token required attribute.</param>
        /// <param name="enumSets">The enum sets.</param>
        protected void WriteApiHtmlDocument(StringBuilder builder, Type apiServiceType, IApiContractOptions apiContractOptions, TokenRequiredAttribute classTokenRequiredAttribute, HashSet <Type> enumSets)
        {
            if (builder != null && apiServiceType != null && apiContractOptions != null)
            {
                foreach (MethodInfo one in apiServiceType.GetMethodInfoWithinAttribute <ApiOperationAttribute>(true, BindingFlags.Instance | BindingFlags.Public))
                {
                    // Considering in interface, can NOT tell is async or not, check return type is Task or Task<T>.
                    bool isAsync = one.IsAsync() || one.ReturnType.IsTask();

                    var apiOperationAttribute = one.GetCustomAttribute <ApiOperationAttribute>(true);
                    if (apiOperationAttribute != null)
                    {
                        StringBuilder bodyBuilder = new StringBuilder(4096);

                        #region Entity Synchronization Status

                        var entitySynchronizationAttribute = one.GetCustomAttribute <EntitySynchronizationModeAttribute>(true);
                        if (entitySynchronizationAttribute != null && !EntitySynchronizationModeAttribute.IsReturnTypeMatched(one.ReturnType))
                        {
                            entitySynchronizationAttribute = null;
                        }

                        #endregion Entity Synchronization Status

                        //Original declaration

                        bodyBuilder.Append("<h3>.NET Declaration</h3>");
                        bodyBuilder.AppendFormat(isAsync ? "<div><span style=\"color:red;font-weight:bold;\" title=\"Async\">[A] </span> {0}</div>" : "<div>{0}</div>", one.ToDeclarationCodeLook().ToHtmlEncodedText());
                        bodyBuilder.Append("<hr />");

                        //Try append description
                        var apiDescriptionAttributes = one.GetCustomAttributes <ApiDescriptionAttribute>(true);
                        if (apiDescriptionAttributes != null && apiDescriptionAttributes.Any())
                        {
                            foreach (var description in apiDescriptionAttributes)
                            {
                                if (!string.IsNullOrWhiteSpace(description.Description))
                                {
                                    bodyBuilder.AppendFormat("<div>{0}</div>", description.Description.ToHtmlEncodedText());
                                }
                            }
                        }

                        // Customized headers
                        var apiCustomizedHeaderAttributes = one.GetCustomAttributes <ApiHeaderAttribute>(true);
                        if (apiCustomizedHeaderAttributes.HasItem())
                        {
                            bodyBuilder.Append("<h3>Customized headers</h3><hr />");
                            bodyBuilder.Append("<ul>");
                            foreach (var item in apiCustomizedHeaderAttributes)
                            {
                                bodyBuilder.AppendFormat(customHeaderFormat, item.HeaderKey);
                            }

                            bodyBuilder.Append("</ul>");
                        }

                        var obsolete = one.GetCustomAttribute <ObsoleteAttribute>(true);
                        if (obsolete != null)
                        {
                            bodyBuilder.AppendFormat("<div style=\"color:red;\"> Obsoleted: {0}</div>", obsolete.Message.ToHtmlEncodedText());
                        }

                        bodyBuilder.Append("<div>Following sample shows how to use via REST API.</div>");

                        #region Request

                        bodyBuilder.Append("<h3>Request</h3><hr />");
                        bodyBuilder.Append("<url>");

                        if (string.IsNullOrWhiteSpace(apiContractOptions.Realm))
                        {
                            bodyBuilder.AppendFormat("{0} /api/{1}/{2}/", apiOperationAttribute.HttpMethod, apiContractOptions.Version, apiOperationAttribute.ResourceName);
                        }
                        else
                        {
                            bodyBuilder.AppendFormat("{0} /{1}/api/{2}/{3}/", apiContractOptions.Realm, apiOperationAttribute.HttpMethod, apiContractOptions.Version, apiOperationAttribute.ResourceName);
                        }

                        if (!string.IsNullOrWhiteSpace(apiOperationAttribute.Action))
                        {
                            bodyBuilder.AppendFormat("{0}/", apiOperationAttribute.Action);
                        }

                        var parameters         = one.GetParameters();
                        var parameterIsHandled = false;

                        if (parameters.Length == 0)
                        {
                            parameterIsHandled = true;
                        }
                        else if (parameters.Length == 1 && (apiOperationAttribute.HttpMethod.Equals(HttpConstants.HttpMethod.Get, StringComparison.OrdinalIgnoreCase) ||
                                                            apiOperationAttribute.HttpMethod.Equals(HttpConstants.HttpMethod.Delete, StringComparison.OrdinalIgnoreCase)))
                        {
                            if (parameters[0].ParameterType == typeof(string) || parameters[0].ParameterType.IsValueType)
                            {
                                bodyBuilder.AppendFormat("<span style=\"font-style:italic; font-weight:bold;color:#CC0000;\" title=\"Sample value for {0}\">", parameters[0].Name);
                                FillSampleValue(bodyBuilder, parameters[0].ParameterType, enumSets, 0, fieldName: parameters[0].Name, ignoreQuote: true);
                                bodyBuilder.Append("</span>");
                                parameterIsHandled = true;
                            }
                        }
                        else if (parameters.Length > 1 && (apiOperationAttribute.HttpMethod.Equals(HttpConstants.HttpMethod.Get, StringComparison.OrdinalIgnoreCase) ||
                                                           apiOperationAttribute.HttpMethod.Equals(HttpConstants.HttpMethod.Delete, StringComparison.OrdinalIgnoreCase)))
                        {
                            bodyBuilder.Append("?");

                            foreach (var parameterItem in parameters)
                            {
                                bodyBuilder.AppendFormat("{0}=", parameterItem.Name);
                                FillSampleValue(bodyBuilder, parameterItem.ParameterType, enumSets, 0, parameterItem.Name, true, true);
                                bodyBuilder.Append("&");
                            }

                            bodyBuilder.RemoveLastIfMatch('&', true);
                            parameterIsHandled = true;
                        }

                        bodyBuilder.Append("</url>");

                        var currentTokenRequiredAttribute = one.GetCustomAttribute <TokenRequiredAttribute>(true) ?? classTokenRequiredAttribute;
                        if (currentTokenRequiredAttribute != null && currentTokenRequiredAttribute.TokenRequired)
                        {
                            bodyBuilder.AppendLineWithFormat(requestHeaderFormat, TokenKey, "[YourTokenValue]");
                        }

                        if (entitySynchronizationAttribute != null)
                        {
                            bodyBuilder.AppendLineWithFormat(requestHeaderFormat, entitySynchronizationAttribute.IfModifiedSinceKey, DateTime.UtcNow.AddDays(-2).ToFullDateTimeString());
                        }

                        bodyBuilder.Append("<pre class=\"CodeContainer\" style=\"font-family: monospace;font-size:14px;\">");

                        if (!parameterIsHandled)
                        {
                            if (parameters.Length == 1)
                            {
                                FillSampleValue(bodyBuilder, parameters[0].ParameterType, enumSets, 0, fieldName: parameters[0].Name, followingProperty: false);
                            }
                            else
                            {
                                builder.AppendFormat(objectBrace, "{");

                                foreach (var parameterItem in parameters)
                                {
                                    FillProperty(bodyBuilder, parameterItem.Name, parameterItem.ParameterType);
                                    AppendColon(bodyBuilder);
                                    FillSampleValue(bodyBuilder, parameterItem.ParameterType, enumSets, 1, followingProperty: true);

                                    AppendComma(bodyBuilder);
                                }

                                RemoveUnnecessaryColon(bodyBuilder);
                                bodyBuilder.AppendFormat(objectBrace, "}");
                            }
                        }

                        bodyBuilder.Append("</pre>");

                        #endregion Request

                        #region Response

                        bodyBuilder.Append("<h3>Response</h3><hr />");

                        bodyBuilder.AppendLineWithFormat(requestHeaderFormat, HttpConstants.HttpHeader.ContentType, apiOperationAttribute.ContentType.SafeToString(HttpConstants.ContentType.Json));

                        if (entitySynchronizationAttribute != null)
                        {
                            bodyBuilder.AppendLineWithFormat(requestHeaderFormat, entitySynchronizationAttribute.LastModifiedKey, DateTime.UtcNow.ToFullDateTimeString());
                        }

                        bodyBuilder.Append("<pre class=\"CodeContainer\" style=\"font-family: monospace;font-size:14px;\">");

                        var returnType = one.ReturnType;
                        if (isAsync)
                        {
                            returnType = returnType.GetTaskUnderlyingType() ?? returnType;
                        }

                        if (returnType.IsVoid() ?? true)
                        {
                            bodyBuilder.Append("<span style=\"font-style:italic; font-weight: bold; color: #999999;\">void</span>");
                        }
                        else
                        {
                            FillSampleValue(bodyBuilder, returnType, enumSets, 0);
                        }

                        bodyBuilder.Append("</pre>");

                        #endregion Response

                        #region Http Status

                        // Http Status
                        bodyBuilder.Append("<h3>Http Status &amp; Exceptions</h3><hr />");
                        bodyBuilder.Append("<ul>");

                        if (one.ReturnType.IsVoid() ?? false)
                        {
                            bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.NoContent, HttpStatusCode.NoContent.ToString(), "If no error or exception.", "green");
                        }
                        else
                        {
                            bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.OK, HttpStatusCode.OK.ToString(), "If no error or exception.", "green");

                            if (entitySynchronizationAttribute != null)
                            {
                                bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.NotModified, HttpStatusCode.NotModified.ToString(), "If no modified since specific time stamp", "green");
                            }
                        }

                        bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.BadRequest, HttpStatusCode.BadRequest.ToString(), "If input value or/and format is invalid.", "red");

                        if (currentTokenRequiredAttribute != null && currentTokenRequiredAttribute.TokenRequired)
                        {
                            bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized.ToString(), "If token is invalid or not given.", "orange");
                        }

                        bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.Forbidden, HttpStatusCode.Forbidden.ToString(), "If action is forbidden.", "orange");
                        bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.NotFound, HttpStatusCode.NotFound.ToString(), "If resource is not found.", "orange");
                        bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.Conflict, HttpStatusCode.Conflict.ToString(), "If data conflicts.", "orange");
                        bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.InternalServerError, HttpStatusCode.InternalServerError.ToString(), "If server feature is not working or has defect(s).", "red");
                        bodyBuilder.AppendFormat(httpStatusFormat, (int)HttpStatusCode.NotImplemented, HttpStatusCode.NotImplemented.ToString(), "If server feature is not implemented yet.", "red");
                        bodyBuilder.Append("</ul>");

                        #endregion Http Status

                        builder.AppendFormat(panel, one.Name, bodyBuilder.ToString(), one.Name, "#");
                    }
                }
            }
        }