示例#1
0
        public HttpApi(ITypeConverter typeConverter = null)
        {
            TypeConverter = TypeConverterAttribute.Combine(typeof(T), typeConverter ?? DefaultTypeConverter.Create());
            Headers       = HeaderAttribute.GetHeaders(typeof(T));

            // Create endpoints
            var endpoints = new Dictionary <MethodInfo, HttpApiEndpoint>();

            foreach (var method in typeof(T).GetMethods())
            {
                var httpMethod = method.GetHttpMethodAttribute();
                if (httpMethod != null)
                {
                    var endpoint = CreateEndpoint(method, httpMethod);
                    endpoints[method] = endpoint;
                }
            }
            Endpoints = endpoints;
        }
示例#2
0
        protected HttpApiEndpoint CreateEndpoint(MethodInfo method, IHttpMethodAttribute httpMethod)
        {
            // Store a variable to store the type converter to be used for this endpoint
            var endpointTypeConverter = TypeConverterAttribute.Combine(method, TypeConverter);

            // Store the additional custom headers (if any) defined on the method itself
            var headers = Headers.Concat(HeaderAttribute.GetHeaders(method));

            // Store a dictionary to store the argument handlers
            var argumentHandlers = new Dictionary <string, IHttpArgumentHandler>();

            // Get the path associated with the API as a whole
            var pathPrefix = typeof(T).GetCustomAttribute <PathAttribute>()?.Path;

            // Combine it with the path for the endpoint
            var path = httpMethod.Path;

            if (pathPrefix != null)
            {
                path = HttpPath.Combine(pathPrefix, path);
            }

            // Store the url to where we intend to call
            var url = HttpUrlParser.Parse(path);

            // Create a hash set so we can quickly deterine which parameters appy to the path
            var pathParameters = new HashSet <string>(url.PathParts.OfType <VariableHttpPathPart>().Select(x => x.Key));

            // Create a hash set so we can quickly determine which parameters apply to the query string
            var queryParameters = new HashSet <string>(url.QueryParts.Select(x => x.Value).OfType <VariableHttpPathPart>().Select(x => x.Key));

            // Store a list that aren't for anything else and thus must be consigned to the body
            var bodyParameters = new List <ParameterInfo>();

            // Iterate through the parameters and figure out which argument handler to use for each
            foreach (var parameter in method.GetParameters())
            {
                // If the parameter overrides the type converter, we'll overwrite the typeConverter variable
                var typeConverter = TypeConverterAttribute.Combine(parameter, endpointTypeConverter);

                // Store headerAttribute for later
                var headerAttribute = parameter.GetCustomAttribute <HeaderAttribute>();

                // If this is a path parameter, then create a respective argument handler
                if (pathParameters.Contains(parameter.Name))
                {
                    argumentHandlers[parameter.Name] = new PathArgumentHandler(typeConverter);
                }
                // Otherwise, if it's a query string parameter, create an argument handler for that
                else if (queryParameters.Contains(parameter.Name))
                {
                    argumentHandlers[parameter.Name] = new QueryArgumentHandler(typeConverter);
                }
                // See if the parameter represents a header.  If it does, create the respective argument handler.
                else if (headerAttribute != null)
                {
                    argumentHandlers[parameter.Name] = new HttpHeaderArgumentHandler(typeConverter, headerAttribute.Name, headerAttribute.Values);
                }
                // If the parameter is Func<Stream, Task> then it should be invoked with the response stream for custom handling.
                else if (parameter.ParameterType == typeof(Func <Stream, Task>))
                {
                    argumentHandlers[parameter.Name] = new StreamResponseArgumentHandler(typeConverter);
                }
                // If the parameter type is HttpApiRequest, we assume you want to interact with the request directly
                else if (parameter.ParameterType == typeof(Action <HttpApiRequest>))
                {
                    argumentHandlers[parameter.Name] = new HttpApiRequestArgumentHandler(typeConverter);
                }
                // If the parameter type is HttpBody, then we assume you want to interact with the HttpBody directly
                else if (parameter.ParameterType == typeof(HttpBody))
                {
                    argumentHandlers[parameter.Name] = new HttpBodyArgumentHandler(typeConverter);
                }
                // If we get to this point, we assume the method parameter is provided in the body of the request
                else
                {
                    bodyParameters.Add(parameter);
                }
            }

            // You can override the name associated with the parameter by using either JsonPropertyAttribute or
            // NameAttribute.  We provide a NameAttribute to avoid forcing you to use JsonPropertyAttribute when
            // not dealing with JSON.
            string GetName(ParameterInfo parameter)
            {
                var jsonPropertyAttribute = parameter.GetCustomAttribute <JsonPropertyAttribute>(true);
                var nameAttribute         = parameter.GetCustomAttribute <NameAttribute>(true);

                return(nameAttribute?.Value ?? jsonPropertyAttribute?.PropertyName ?? parameter.Name);
            }

            // If there is data for a body, then create a handler to provide a body
            if (bodyParameters.Any())
            {
                var isMultipart = method.GetCustomAttribute <MultipartAttribute>() != null;
                var isForm      = method.GetCustomAttribute <FormAttribute>() != null;
                var isText      = method.GetCustomAttribute <TextAttribute>() != null;

                Action <Func <ParameterInfo, ITypeConverter, IHttpArgumentHandler> > setBodyArgumentHandlers = factory =>
                {
                    foreach (var parameter in bodyParameters)
                    {
                        var typeConverter = TypeConverterAttribute.Combine(parameter, endpointTypeConverter);
                        argumentHandlers[parameter.Name] = factory(parameter, typeConverter);
                    }
                };

                // If the argument represents an input stream, use the respective argument handler
                if (bodyParameters.First().ParameterType == typeof(Stream) && bodyParameters.Count == 1 && !isMultipart)
                {
                    var parameter     = bodyParameters.Single();
                    var typeConverter = TypeConverterAttribute.Combine(parameter, endpointTypeConverter);
                    argumentHandlers[parameter.Name] = new StreamArgumentHandler(typeConverter);
                }
                // If the argument represents an input stream, use the respective argument handler
                else if (bodyParameters.First().ParameterType == typeof(byte[]) && bodyParameters.Count == 1 && !isMultipart)
                {
                    var parameter     = bodyParameters.Single();
                    var typeConverter = TypeConverterAttribute.Combine(parameter, endpointTypeConverter);
                    argumentHandlers[parameter.Name] = new ByteArrayArgumentHandler(typeConverter);
                }
                // If it's explicitly multipart or any parameter is a stream, then each parameter represent a multipart section that encapsulates the value
                else if (isMultipart || bodyParameters.Any(x => x.ParameterType == typeof(Stream)))
                {
                    setBodyArgumentHandlers((parameter, typeConverter) => new MultipartArgumentHandler(typeConverter));
                }
                else if (isForm)
                {
                    setBodyArgumentHandlers((parameter, typeConverter) => new FormArgumentHandler(typeConverter, GetName(parameter)));
                }
                else if (isText)
                {
                    setBodyArgumentHandlers((parameter, typeConverter) => new StringArgumentHandler(typeConverter));
                }
                // Otherwise, we're going to serialize the request as JSON
                else
                {
                    // If there's only one body parameter and its not annotated with [Object], then we're going to serialize
                    // the argument as a raw JSON value.
                    if (bodyParameters.Count == 1 && bodyParameters.Single().GetCustomAttribute <ObjectAttribute>() == null)
                    {
                        var parameter     = bodyParameters.Single();
                        var typeConverter = TypeConverterAttribute.Combine(parameter, endpointTypeConverter);
                        argumentHandlers[parameter.Name] = new DirectJsonArgumentHandler(typeConverter);
                    }
                    // Otherwise we're going to create a dynamically composed JSON object where each parameter represents a
                    // property of the composed JSON object.
                    else
                    {
                        // Foreach body parameter, create a json argument handler
                        setBodyArgumentHandlers((parameter, typeConverter) => new ComposedJsonArgumentHandler(typeConverter, GetName(parameter)));
                    }
                }
            }

            var  returnType = method.ReturnType;
            bool isAsync    = false;

            if (typeof(Task).IsAssignableFrom(returnType))
            {
                isAsync    = true;
                returnType = returnType.GetTaskType() ?? typeof(void);
            }

            var responseTypeConverter = TypeConverterAttribute.Combine(method.ReturnTypeCustomAttributes, endpointTypeConverter);

            IHttpResponseHandler responseHandler;

            // If the return type of the method is void, then we will simply return null from the response
            if (returnType == typeof(void))
            {
                responseHandler = new NullResponseHandler();
            }
            // If the return type is byte[], we provide the raw contents of the response body as a byte array
            else if (returnType == typeof(byte[]))
            {
                responseHandler = new ByteArrayResponseHandler();
            }
            // If the return type is HttpApiResponse, we return the raw response from the method
            else if (returnType == typeof(HttpApiResponse))
            {
                responseHandler = new HttpApiResponseResponseHandler();
            }
            // If the return type is HttpBody, we return the articulated HttpBody (one of its subclasses) from the method
            else if (returnType == typeof(HttpBody))
            {
                responseHandler = new HttpBodyResponseHandler();
            }
            // Otherwise, we return a response that is based the returned Content-Type and converted into the desired C#
            // type accordingly.
            else
            {
                responseHandler = new BodyBasedResponseHandler();
            }

            responseHandler.TypeConverter = responseTypeConverter;
            responseHandler.ResponseType  = returnType;

            var endpoint = new HttpApiEndpoint(method, url, httpMethod.Method, argumentHandlers, responseHandler, headers, isAsync);

            return(endpoint);
        }