public override void WriteToStream(Type type, object value, Stream writeStream, System.Text.Encoding content)
        {
            using (var writer = new StreamWriter(writeStream))
            {
                // First, create a package to hold the results
                var pkg = new ICTMediaType();

                // Then, get some information about the request
                // This depends upon the use of the default route template; in WebApiConfig.cs...
                // routeTemplate: "api/{controller}/{id}"
                // Also, we must remove any trailing slashes

                var request = HttpContext.Current.Request;

                var requestMethod     = request.HttpMethod;
                var requestUri        = request.Path.TrimEnd('/');
                var requestController = request.Url.Segments[2].TrimEnd('/');
                var requestId         = (request.Url.Segments.Count() > 3) ? request.Url.Segments[3].TrimEnd('/') : "";

                // Next, discover what is in the response by inspecting the type name; it could be
                // 1. If error - return a package that holds the error message
                // 2. If collection - return a package that holds a collection
                // 3. If object - return a package that holds an object

                // Setup for switch-case statement
                string responseDataType = "";

                if (type.Name == "HttpError")
                {
                    responseDataType = "error";
                }
                else if (type.Namespace == "System.Collections.Generic")
                {
                    responseDataType = "collection";
                }
                else
                {
                    responseDataType = "object";
                }

                // This is the package generating code...

                switch (responseDataType)
                {
                case "error":
                {
                    // Link relation for "self"
                    pkg.links.Add(new link()
                        {
                            rel = "self", href = requestUri, methods = requestMethod
                        });
                    pkg.count = 0;
                    pkg.data.Add(value);
                }
                break;

                case "collection":
                {
                    // How many items?
                    pkg.count = (value as ICollection).Count;

                    // Transform the data into an enumerable collection
                    IEnumerable collection = (IEnumerable)value;

                    foreach (var item in collection)
                    {
                        // Create an object that can expand at runtime
                        // by dynamically and programmatically adding new properties
                        IDictionary <string, object> newItem = new ExpandoObject();

                        // N O T I C E
                        // ###########

                        // We must do special processing for the Chinook database
                        // Each entity class has an identifier with a composite name,
                        // "Entity" name plus "Id" (e.g. CustomerId)
                        // We want to locate this "Entity" plus "Id" property name
                        // so that we can get the integer identifier for the item in the collection

                        // Algorithm...
                        // 1. For each property (except "Id"), add it to newItem
                        // 2. While looking at each property,
                        //    check whether its name matches the "Entity" plus "Id" pattern
                        // 3. If yes, save its value as the item's identifier

                        bool shouldContinueLooking = true;

                        // Id value as an integer
                        int idValueInt = 0;

                        // Go through the all the properties in an item
                        // and add them to the expando object
                        foreach (PropertyInfo prop in item.GetType().GetProperties())
                        {
                            // Add the property
                            newItem.Add(ConfigurePropertyName(prop), prop.GetValue(item));

                            if (prop.Name == "Id")
                            {
                                // Save/remember the Id value
                                idValueInt = (int)prop.GetValue(item);

                                // Stop looking, because we have found the identifier
                                shouldContinueLooking = false;
                            }

                            // Should we continue looking for the identifier?
                            if (shouldContinueLooking)
                            {
                                // Setup the entity name
                                var entityName = "";

                                // Get the controller name
                                // Remove the plural "s", if present
                                var possibleBaseType = requestController.TrimEnd('s');

                                // Compare the result against a number of rules/checks
                                if (prop.Name.Length > 2 &&
                                    prop.Name.EndsWith("Id") &&
                                    prop.Name.StartsWith(possibleBaseType, true, null) &&
                                    prop.GetValue(item) is Int32)
                                {
                                    // Boom, we have located the identifier
                                    entityName = possibleBaseType;
                                }

                                // Now do the comparison, if a match, add an "Id" property
                                entityName = entityName + "Id";
                                if (prop.Name.ToLower() == entityName.ToLower())
                                {
                                    // We have found the identifier
                                    idValueInt = (int)prop.GetValue(item);
                                }
                            }
                        }

                        // Add the links (below)
                        dynamic o = item;

                        // ################################################################################
                        // Get the supported resource URIs for the item...
                        var allApiDescriptionsForItem = ApiExplorerService.GetApiDescriptionsForUri(requestController, idValueInt.ToString());

                        // Setup a collection to hold the links
                        var itemLinks = new List <link>();

                        // For each supported resource URI, generate and compose the link
                        foreach (var apiDescription in allApiDescriptionsForItem)
                        {
                            // Fix the URI string
                            var itemUri = apiDescription.RelativePath.Replace("{id}", idValueInt.ToString());

                            // Create and initially configure the link
                            var relValue = "self";
                            if (apiDescription.HttpMethod.Method == "PUT" || apiDescription.HttpMethod.Method == "DELETE")
                            {
                                relValue = "edit";
                            }
                            var newLink = new link()
                            {
                                rel = relValue, href = itemUri, methods = apiDescription.HttpMethod.Method
                            };
                            newLink.title = apiDescription.Documentation;

                            // Get the ActionDescriptor property
                            var actionDescriptor = apiDescription.ActionDescriptor as System.Web.Http.Controllers.ReflectedHttpActionDescriptor;

                            // Look for a "from body" parameter - we need that to render the field list
                            var parBind = actionDescriptor.ActionBinding.ParameterBindings.SingleOrDefault(pb => pb.WillReadBody);

                            // Generate the field list, if we have a parameter binding
                            if (parBind != null)
                            {
                                // Get its data type
                                Type parType = parBind.Descriptor.ParameterType;

                                // Setup a fields collection
                                var fields = new List <field>();

                                // Generate the field list (different procedure for strings)
                                if (parType != null)
                                {
                                    if (parType.Name == "String")
                                    {
                                        // Generate our own field
                                        fields.Add(new field {
                                                name = "(none)", type = "string"
                                            });
                                    }
                                    else
                                    {
                                        fields = GenerateFields(parType);
                                    }
                                }

                                newLink.fields = fields;
                            }

                            // Add the new link to the collection of links
                            itemLinks.Add(newLink);
                        }

                        // Add the collection of links to the new item
                        newItem.Add("links", itemLinks);

                        // Add the new item to the package's "data" property
                        pkg.data.Add(newItem);
                    }

                    // ################################################################################
                    // Generate links for the collection URI

                    var allApiDescriptionsForColl = ApiExplorerService
                                                    .GetApiDescriptionsForUri(requestController, null);

                    // Setup a collection to hold the links
                    var pkgLinks = new List <link>();

                    // For each supported resource URI, generate and compose the link
                    foreach (var apiDescription in allApiDescriptionsForColl)
                    {
                        // Fix the URI string
                        var itemUri = apiDescription.RelativePath.Replace("{id}", 0.ToString());

                        // Create and initially configure the link
                        var relValue = "self";
                        if (apiDescription.HttpMethod.Method == "POST")
                        {
                            relValue = "edit";
                        }
                        var newLink = new link()
                        {
                            rel = relValue, href = itemUri, methods = apiDescription.HttpMethod.Method
                        };
                        newLink.title = apiDescription.Documentation;

                        // Get the ActionDescriptor property
                        var actionDescriptor = apiDescription.ActionDescriptor as System.Web.Http.Controllers.ReflectedHttpActionDescriptor;

                        Type parType = null;

                        // Look for a "from body" parameter - we need that to render the field list
                        var parBind = actionDescriptor.ActionBinding.ParameterBindings.SingleOrDefault(pb => pb.WillReadBody);
                        if (parBind != null)
                        {
                            parType = parBind.Descriptor.ParameterType;
                        }

                        // Alternatively, look for a "from URI" parameter, as we can use that too
                        if (apiDescription.ParameterDescriptions.Count == 1)
                        {
                            var pd = apiDescription.ParameterDescriptions[0];
                            if (pd.Source == System.Web.Http.Description.ApiParameterSource.FromUri)
                            {
                                // Yay, can use the binding and the type
                                parBind = actionDescriptor.ActionBinding.ParameterBindings[0];
                                parType = pd.ParameterDescriptor.ParameterType;
                            }
                        }

                        // Generate the field list, if we have a parameter binding
                        if (parBind != null)
                        {
                            // Get its data type
                            //Type parType = parBind.Descriptor.ParameterType;

                            // Setup a fields collection
                            var fields = new List <field>();

                            // Generate the field list (different procedure for strings)
                            if (parType != null)
                            {
                                if (parType.Name == "String")
                                {
                                    // Generate our own field
                                    fields.Add(new field {
                                            name = "(none)", type = "string"
                                        });
                                }
                                else
                                {
                                    fields = GenerateFields(parType);
                                }
                            }

                            newLink.fields = fields;
                        }

                        // Add the new link to the collection of links
                        pkgLinks.Add(newLink);
                    }

                    // Add the collection of links to the package
                    pkg.links = pkgLinks;
                }
                break;

                case "object":
                {
                    // No, NOT a collection

                    IDictionary <string, object> newItem = new ExpandoObject();

                    // Go through the all the properties in an item
                    foreach (PropertyInfo prop in value.GetType().GetProperties())
                    {
                        newItem.Add(ConfigurePropertyName(prop), prop.GetValue(value));
                    }

                    pkg.count = 1;
                    pkg.data.Add(newItem);

                    // ################################################################################
                    // Get the supported resource URIs for the item...
                    var allApiDescriptionsForItem = ApiExplorerService.GetApiDescriptionsForUri(requestController, requestId.ToString());

                    // Setup a collection to hold the links
                    var itemLinks = new List <link>();

                    // For each supported resource URI, generate and compose the link
                    foreach (var apiDescription in allApiDescriptionsForItem)
                    {
                        // Fix the URI string
                        var itemUri = apiDescription.RelativePath.Replace("{id}", requestId.ToString());

                        // Create and initially configure the link
                        var relValue = "self";
                        if (apiDescription.HttpMethod.Method == "PUT" || apiDescription.HttpMethod.Method == "DELETE")
                        {
                            relValue = "edit";
                        }
                        var newLink = new link()
                        {
                            rel = relValue, href = itemUri, methods = apiDescription.HttpMethod.Method
                        };
                        newLink.title = apiDescription.Documentation;

                        // Get the ActionDescriptor property
                        var actionDescriptor = apiDescription.ActionDescriptor as System.Web.Http.Controllers.ReflectedHttpActionDescriptor;

                        // Look for a "from body" parameter - we need that to render the field list
                        var parBind = actionDescriptor.ActionBinding.ParameterBindings.SingleOrDefault(pb => pb.WillReadBody);

                        // Generate the field list, if we have a parameter binding
                        if (parBind != null)
                        {
                            // Get its data type
                            Type parType = parBind.Descriptor.ParameterType;

                            // Setup a fields collection
                            var fields = new List <field>();

                            // Generate the field list (different procedure for strings)
                            if (parType != null)
                            {
                                if (parType.Name == "String")
                                {
                                    // Generate our own field
                                    fields.Add(new field {
                                            name = "(none)", type = "string"
                                        });
                                }
                                else
                                {
                                    fields = GenerateFields(parType);
                                }
                            }

                            newLink.fields = fields;
                        }

                        // Add the new link to the collection of links
                        itemLinks.Add(newLink);
                    }

                    pkg.links = itemLinks;

                    // ################################################################################
                    // Generate links for the collection URI

                    var allApiDescriptionsForColl = ApiExplorerService
                                                    .GetApiDescriptionsForUri(requestController, null);

                    // Setup a collection to hold the links
                    var pkgLinks = new List <link>();

                    // For each supported resource URI, generate and compose the link
                    foreach (var apiDescription in allApiDescriptionsForColl)
                    {
                        // Fix the URI string
                        //var itemUri = apiDescription.RelativePath.Replace("{id}", idValueInt.ToString());
                        var itemUri = apiDescription.RelativePath.Replace("{id}", 0.ToString());

                        // Create and initially configure the link
                        var relValue = "collection";
                        if (apiDescription.HttpMethod.Method == "POST")
                        {
                            relValue = "edit";
                        }
                        var newLink = new link()
                        {
                            rel = relValue, href = itemUri, methods = apiDescription.HttpMethod.Method
                        };
                        newLink.title = apiDescription.Documentation;

                        // Get the ActionDescriptor property
                        var actionDescriptor = apiDescription.ActionDescriptor as System.Web.Http.Controllers.ReflectedHttpActionDescriptor;

                        Type parType = null;

                        // Look for a "from body" parameter - we need that to render the field list
                        var parBind = actionDescriptor.ActionBinding.ParameterBindings.SingleOrDefault(pb => pb.WillReadBody);
                        if (parBind != null)
                        {
                            parType = parBind.Descriptor.ParameterType;
                        }

                        // Alternatively, look for a "from URI" parameter, as we can use that too
                        if (apiDescription.ParameterDescriptions.Count == 1)
                        {
                            var pd = apiDescription.ParameterDescriptions[0];
                            if (pd.Source == System.Web.Http.Description.ApiParameterSource.FromUri)
                            {
                                // Yay, can use the binding and the type
                                parBind = actionDescriptor.ActionBinding.ParameterBindings[0];
                                parType = pd.ParameterDescriptor.ParameterType;
                            }
                        }

                        // Generate the field list, if we have a parameter binding
                        if (parBind != null)
                        {
                            // Get its data type
                            //Type parType = parBind.Descriptor.ParameterType;

                            // Setup a fields collection
                            var fields = new List <field>();

                            // Generate the field list (different procedure for strings)
                            if (parType != null)
                            {
                                if (parType.Name == "String")
                                {
                                    // Generate our own field
                                    fields.Add(new field {
                                            name = "(none)", type = "string"
                                        });
                                }
                                else
                                {
                                    fields = GenerateFields(parType);
                                }
                            }

                            newLink.fields = fields;
                        }

                        // Add the new link to the collection of links
                        pkgLinks.Add(newLink);
                    }

                    // Add the collection of links to the package
                    pkg.links.AddRange(pkgLinks);
                }
                break;

                default:
                    break;
                }

                // Deliver the package...

                string json = JsonConvert.SerializeObject(pkg, new JsonSerializerSettings()
                {
                    NullValueHandling = NullValueHandling.Ignore
                });
                var buffer = Encoding.Default.GetBytes(json);
                writeStream.Write(buffer, 0, buffer.Length);
                writeStream.Flush();
                writeStream.Close();
            }
        }
Esempio n. 2
0
        public override void WriteToStream(Type type, object value, Stream writeStream, System.Text.Encoding content)
        {
            using (var writer = new StreamWriter(writeStream))
            {
                // First, create a package to hold the results
                var pkg = new ICTMediaType();

                if (value != null)
                {
                    // Determine the pattern by gathering query characteristics

                    // How many segments, after the fixed "/api/ segments
                    // ==================================================

                    // Will always have something in it
                    var segments = HttpContext.Current.Request.Url.Segments;
                    // Remove the first two segments, / and api/
                    // This will leave only the controller name and whatever follows that
                    var segmentsCount = segments.Length - 2;

                    // Do we have a query string?
                    // ==========================

                    var query = HttpContext.Current.Request.QueryString;
                    // If there's no query string, it does not blow up
                    var queryCount = query.Count;
                    // If there's no query string, this value is zero

                    // Get the route data, look for integer "id" property
                    // ==================================================

                    HttpRequestMessage hrm = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
                    var routeData          = hrm.GetRouteData();
                    // Has route template as a string
                    // Also has or shows "id" as a "parameter"

                    var rdItem = routeData.Values.SingleOrDefault(r => r.Key == "id");
                    // We'll get back a kvp with the data, or with nulls for key and value

                    var idKeyValue = Convert.ToInt32(rdItem.Value);
                    // If this is zero, then "id" is not in the route data
                    // If non-zero, "id" is in the route data, and we have the value

                    // Do we have an integer identifier?
                    var intId = (!string.IsNullOrEmpty(rdItem.Key)) ? true : false;

                    // How many items are in the response?
                    // ===================================

                    //var isCollection =
                    //    value.GetType().GetInterfaces()
                    //    .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));

                    var isCollection = type.Namespace == "System.Collections.Generic" ? true : false;

                    var itemCount = 1;

                    if (isCollection)
                    {
                        var items = (ICollection)value;
                        itemCount = items.Count;
                    }

                    // Continue...

                    var      absolutePath = HttpContext.Current.Request.Url.AbsolutePath;
                    string[] u            = absolutePath.Split(new char[] { '/' });

                    // We will use this later (soon)...
                    var pattern = 0;

                    if (isCollection)
                    {
                        // Will have zero or more items
                        // Will either be a get-all
                        // Or a get-some-filtered

                        // Come back and fix this later...
                        pattern = -1;

                        if (segmentsCount == 1 && queryCount == 0 && idKeyValue == 0)
                        {
                            // Get all
                            // Zero or more items in "value"
                            // Exactly 1 segment after "/api/", which suggests a collection
                            // Does not have a query string
                            // Does NOT have the "id" parameter in the route
                            pattern = 1;
                        }

                        if (segmentsCount == 1 && queryCount > 0 && idKeyValue == 0)
                        {
                            // Get some filtered
                            // Zero or more items in "value"
                            // Exactly 1 segment after "/api/", which suggests a collection
                            // Has a query string
                            // Does NOT have the "id" parameter in the route
                            pattern = 2;
                        }

                        IEnumerable collection = (IEnumerable)value;
                        int         count      = 0;
                        foreach (var item in collection)
                        {
                            count++;
                            IDictionary <string, object> newItem = new ExpandoObject();

                            // Name and value of the field that holds the identifier
                            var baseClassName = "";
                            var idValue       = 0;

                            // Go through the all the properties in an item
                            foreach (PropertyInfo prop in item.GetType().GetProperties())
                            {
                                // N O T I C E
                                // ###########

                                // Special processing for the Chinook database
                                // Each entity class has an identifier with a composite name
                                // Entity plus Id (e.g. CustomerId)

                                // Algorithm...
                                // 1. For each property (except "Id"), add it to newItem
                                // 2. While looking at each property,
                                //    check whether its name matches the "Entity" plus "Id" pattern
                                // 3. If yes, create a property named "Id" with the same value

                                // Safety check, which rejects any property named "Id"
                                if (!(prop.Name == "Id"))
                                {
                                    newItem.Add(prop.Name, prop.GetValue(item));
                                }

                                // New algorithm...

                                var objName = "";

                                // Get the all-but-last character of the URI segment for the "controller"
                                var possibleBaseType = u[2];

                                // Remove the plural "s", if present
                                if (u[2].EndsWith("s", false, null))
                                {
                                    possibleBaseType = u[2].TrimEnd('s');
                                }

                                // Compare the result against a number of rules/checks
                                if (prop.Name.Length > 2 &&
                                    prop.Name.EndsWith("Id") &&
                                    prop.Name.StartsWith(possibleBaseType, true, null) &&
                                    prop.GetValue(item) is Int32)
                                {
                                    // Boom, we have located the identifier
                                    objName = possibleBaseType;
                                }

                                // We now have the name of the base class
                                // (but do we need it for anything?)
                                baseClassName = objName;

                                // Now do the comparison, if a match, add an "Id" property
                                objName = objName + "Id";
                                if (prop.Name.ToLower() == objName.ToLower())
                                {
                                    newItem.Add("Id", prop.GetValue(item));
                                    // Save the value of this identifier to make the link next/below
                                    idValue = (int)prop.GetValue(item);
                                }
                            }

                            // Add the links (below)
                            dynamic o = item;

                            // Get the supported HTTP methods for the item...

                            // Setup...
                            object idValueObject;
                            int    idValueInt = 0;
                            if (newItem.TryGetValue("Id", out idValueObject))
                            {
                                idValueInt = (int)idValueObject;
                            }

                            // Get the item methods and add a link
                            var itemMethods = string.Join(",", ApiExplorerService.GetSupportedMethods(u[2], idValueInt.ToString()));
                            newItem.Add("Link", new link()
                            {
                                rel = "item", href = string.Format("{0}/{1}", absolutePath, idValueInt), methods = itemMethods
                            });

                            pkg.data.Add(newItem);
                        }

                        // Add a link relation for 'self'
                        pkg.links.Add(new link()
                        {
                            rel = "self", href = absolutePath, methods = "GET"
                        });

                        // Link relation for 'create', if supported
                        // Hard-coded for now - we want to make this discoverable

                        // TODO - make this discoverable ! ! !

                        if (ApiExplorerService.GetSupportedMethods(u[2], null).Contains("POST"))
                        {
                            var postLink = new link()
                            {
                                rel = "create", href = absolutePath, methods = "POST"
                            };
                            postLink.fields = new List <field>();
                            postLink.fields.Add(new field {
                                name = "FirstName", type = "string"
                            });
                            postLink.fields.Add(new field {
                                name = "LastName", type = "string"
                            });
                            postLink.fields.Add(new field {
                                name = "Age", type = "int"
                            });

                            pkg.links.Add(postLink);
                        }

                        pkg.count = count;
                    }
                    else
                    {
                        // No, NOT a collection

                        // Set pattern (come back to this later and fix)
                        pattern = -1;

                        IDictionary <string, object> newItem = new ExpandoObject();

                        // Go through the all the properties in an item
                        foreach (PropertyInfo prop in value.GetType().GetProperties())
                        {
                            newItem.Add(prop.Name, prop.GetValue(value));
                        }

                        var itemMethods = string.Join(",", ApiExplorerService.GetSupportedMethods(u[2], u[3]));
                        newItem.Add("Link", new link()
                        {
                            rel = "self", href = absolutePath, methods = itemMethods
                        });

                        // Link relation for 'self'
                        pkg.links.Add(new link()
                        {
                            rel = "self", href = absolutePath, methods = itemMethods
                        });

                        var controllerMethods = string.Join(",", ApiExplorerService.GetSupportedMethods(u[2], null));

                        // Link relation for 'collection'
                        pkg.links.Add(new link()
                        {
                            rel = "collection", href = string.Format("/{0}", u[1]), methods = controllerMethods
                        });

                        pkg.count = 1;
                        pkg.data.Add(newItem);
                    }
                }

                string json = JsonConvert.SerializeObject(pkg, new JsonSerializerSettings()
                {
                    NullValueHandling = NullValueHandling.Ignore
                });
                var buffer = Encoding.Default.GetBytes(json);
                writeStream.Write(buffer, 0, buffer.Length);
                writeStream.Flush();
                writeStream.Close();
            }
        }