protected override Task <HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Method == HttpMethod.Options) { // Get the controller and id values var controllerRequested = request.GetRouteData().Values["controller"] as string; var idRequested = request.GetRouteData().Values["id"] as string; // Collector for the supported methods IEnumerable <string> supportedMethods = ApiExplorerService.GetSupportedMethods(controllerRequested, idRequested); // The controllerRequested does not exist, so return HTTP 404 if (!supportedMethods.Any()) { return(Task.Factory.StartNew(() => request.CreateResponse(HttpStatusCode.NotFound))); } return(Task.Factory.StartNew(() => { var resp = new HttpResponseMessage(HttpStatusCode.OK); string methods = string.Join(",", supportedMethods); // For standard requests, add the 'Allow' header resp.Content = new StringContent(""); resp.Content.Headers.Add("Allow", methods); // For Ajax requests resp.Headers.Add("Access-Control-Allow-Origin", "*"); resp.Headers.Add("Access-Control-Allow-Methods", methods); return resp; })); } return(base.SendAsync(request, cancellationToken)); }
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(); } }