Click here to Skip to main content
Click here to Skip to main content
Go to top

Web API Data Structure Controller

, 10 Mar 2013
Rate this:
Please Sign up or sign in to vote.
Generate class definition for Web API Service Models.

Introduction

This project is to illustrate how to make use of the ASP.NET Web API wisely to simulate the traditional Web Services Description Language (WSDL) service binding. This project uses a single Web API Data Structure Controller that monitors the Model namespace to generate a data structure class file upon request. It provides developers with up to-date class definitions. Developers are able to plug and play the generated file with their needs without rewriting the same class definitions.

Background

The intended users for this document are users with familiarity of ASP.NET MVC, Web API, and WPF technologies. And users who want faster development on Web API services.

Benefits of using the Data Structure Controller

Eliminate the need for rewriting the data structure classes.

Eliminate the need for implementing WebClient to access Web API services:

Generated sample file

Enhancements

Implemented user defined sample method interface for programmers to override or inject more useful sample methods for the Web API consuming programmers to use.

Sample method's code.txt file sample:

Generated class file with injected sample method code:

Using the code

Host this DataStructureController as a Web API Service, call it through:

  • url/Api/DataStructure/
  • url/Api/DataStructure/?Observable=true
  • url/Api/DataStructure/?IncludeSample=true
  • url/Api/DataStructure/?Observable=true&IncludeSample=true

After downloading the class file, download the JsonConvert library referred on top of the generated file, put the downloaded file in your app project, and start using it immediately. Be sure to run the TestDB.edmx.sql file under the Models folder to create the predefined database. MVC 4.0 installation is required to run the solution.

Data Structure Controller Class Definition

using System;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using TestProject.Models;
using System.Text;
using System.Data.Objects.DataClasses;
using TestProject.Properties;
using System.Reflection;
using System.Data;
using System.Net.Http.Headers;
using System.IO;
using System.Web;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace TestProject.Controllers.Api
{
    public class DataStructureController : ApiController
    {
        // GET api/datastructure
        public HttpResponseMessage Get(bool observable=false)
        {
            var text = GenerateClass(observable: observable);
            var response = CreateFileResponse(text);
            return response;
        }

        // GET api/datastructure/user
        public HttpResponseMessage Get(string name, bool observable = false)
        {
            var text = GenerateClass(name, observable);
            var response = CreateFileResponse(text);
            return response;
        }

        private static HttpResponseMessage CreateFileResponse(string text)
        {
            var data = Encoding.UTF8.GetBytes(text);
            var response = new HttpResponseMessage();
            var ms = new MemoryStream(data);
            response.Content = new StreamContent(ms);
            response.Content.Headers.ContentType = 
              new MediaTypeHeaderValue("application/octet-stream");
            response.Content.Headers.ContentDisposition = 
              new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = 
              string.Format("{0}.cs", Settings.Default.ModelNamespace);
            return response;
        }

        public static string GenerateClass(string className = null, bool observable = false)
        {
            var types = typeof(TestDBEntities).Assembly
                                              .GetTypes()
                                              .Where(x => x.Namespace == Settings.Default.ModelNamespace &&
                                                          x.BaseType == typeof(EntityObject));
            
            if (!string.IsNullOrWhiteSpace(className))
                types = types.Where(x => x.Name == className);

            var builder = new StringBuilder();
            var localJsonLibrary = string.Format("{0}/{1}", 
                DataStructureHelper.MachineHostUrl, Settings.Default.LocalJsonLibrary);
            builder.AppendFormat("//{1} or download it from this server: {2}{0}", 
                Environment.NewLine, Settings.Default.JsonConverterLibrary, localJsonLibrary);
            builder.AppendLine(Settings.Default.UsingNamespace);
            builder.AppendFormat("namespace {0}{1}{{{1}", 
                Settings.Default.ModelNamespace, Environment.NewLine);

            var classExtend = string.Empty;
            var classNotifier = string.Empty;
            if (observable)
            {
                classExtend = ": INotifyPropertyChanged";
                classNotifier = Settings.Default.PropertyChangeNotifier;
            }

            builder.AppendFormat("{0}public static class ApiServiceUrlHelper {1}{0}{{{1}", 
                    GetTabs(1), Environment.NewLine);
            builder.AppendFormat("{0}public const string ApiHostUrl = \"{2}\";{1}", 
                    GetTabs(2), Environment.NewLine, DataStructureHelper.MachineHostUrl);
            builder.AppendFormat("{0}public const string ApiServiceUrl = 
               ApiHostUrl + \"/Api/\";{1}", GetTabs(2), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(1), Environment.NewLine);

            foreach (var type in types)
            {
                builder.AppendFormat("{0}public partial class {2}{3}{1}\t{{{1}", 
                  GetTabs(1), Environment.NewLine, type.Name, classExtend);
                builder.AppendFormat("{0}public const string ApiServiceUrl = 
                  ApiServiceUrlHelper.ApiServiceUrl + \"{2}/\";{1}", 
                  GetTabs(2), Environment.NewLine, type.Name);
                var properties = type.GetProperties();
                GetClassDefinition(observable, builder, properties);
                builder.AppendLine(classNotifier);
                builder.AppendLine(GetClassUsage(type, observable));
                builder.AppendFormat("{0}}}{1}", GetTabs(1), Environment.NewLine);
            }

            builder.Append("}");

            return builder.ToString();
        }

        private static void GetClassDefinition(bool observable, 
                StringBuilder builder, PropertyInfo[] properties)
        {
            foreach (var property in properties)
            {
                if (IsBuiltInType(property.PropertyType))
                    continue;
                builder.Append(GetProperty(property, observable));
            }
        }

        private static string GetClassUsage(Type type, bool observable)
        {
            var className = type.Name;
            var builder = new StringBuilder();
            var helper = new DataStructureHelper(observable, 2, className);
            var generatedGetUsage = false;
            var generatedPosUsage = false;
            var generatedPutUsage = false;
            var generatedDeleteUsage = false;

            if (type.GetInterface(typeof(IDataStructure).Name) != null)
            {
                var obj = (IDataStructure)Activator.CreateInstance(type);
                generatedGetUsage = GenerateMethodSample(obj.GenerateGetMethodSample, helper, builder);
                generatedPosUsage = GenerateMethodSample(obj.GeneratePosMethodSample, helper, builder);
                generatedPutUsage = GenerateMethodSample(obj.GeneratePutMethodSample, helper, builder);
                generatedDeleteUsage = GenerateMethodSample(obj.GenerateDeleteMethodSample, helper, builder);
                var fileName = obj.GenerateMethodSamplesFromFile();
                GenerateOtherMethodSamples(fileName, helper, builder);
            }
           
            if(!generatedGetUsage)
                builder.Append(GetClassGetUsage(className, observable));
            if(!generatedPosUsage)
                builder.Append(GetClassPostUsage(className));
            if(!generatedPutUsage)
                builder.Append(GetClassPutUsage(className));
            if (!generatedDeleteUsage)
                builder.Append(GetClassDeleteUsage(className));

            return builder.ToString();
        }

        private static void GenerateOtherMethodSamples(string fileName, 
                DataStructureHelper helper, StringBuilder builder)
        {
            var regex = new Regex(@"@(\w+)", RegexOptions.Compiled);
            var args = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) 
            {
                {"@Observable", helper.Observable.ToString()},
                {"@HostUrl", "ApiServiceUrlHelper.ApiServiceUrl"},
                {"@CurrentServiceUrl", "ApiServiceUrl"},
                {"@CurrentService", helper.CurrentService},
                {"@Tabs", helper.Tabs.ToString()},
             };
            using (var reader = File.OpenText(fileName))
            {
                var text = reader.ReadToEnd();
                string output = regex.Replace(text, match => 
                                                { if (args.Any(x => x.Key == match.Value)) 
                                                        return args[match.Value]; 
                                                  return match.Value; 
                                                });
                builder.AppendLine(output);
            }
        }

        private static bool GenerateMethodSample(Func<DataStructureHelper, 
                string> func, DataStructureHelper helper, StringBuilder builder)
        {
            var text = func(helper);
            var result = text != null;
            if (result)
                builder.Append(text);
            return result;
        }

        private static string GetClassGetUsage(string className, bool observable)
        {
            var builder = new StringBuilder();
            var type = observable ? "ObservableCollection" : "Collection";
            var returnType = string.Format("{0}<{1}>", type, className);
            builder.AppendFormat("{0}public static {2} GetAll(){1}{0}{{{1}", 
                    GetTabs(2), Environment.NewLine, returnType);
            builder.AppendFormat("{0}using (WebClient client = 
              new WebClient()){1}{0}{{{1}", GetTabs(3), Environment.NewLine, className);
            builder.AppendFormat("{0}var dataString = 
              client.DownloadString(ApiServiceUrl);{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}var data = 
              JsonConvert.DeserializeObject<{2}>(dataString);{1}", 
              GetTabs(4), Environment.NewLine, returnType);
            builder.AppendFormat("{0}return data;{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(2), Environment.NewLine);
            return builder.ToString();
        }

        private static string GetClassPostUsage(string className)
        {
            var builder = new StringBuilder();
            builder.AppendFormat("{0}public static {2} Add({2} obj){1}{0}{{{1}", 
              GetTabs(2), Environment.NewLine, className);
            builder.AppendFormat("{0}using (WebClient client = 
              new WebClient()){1}{0}{{{1}", GetTabs(3), Environment.NewLine, className);
            builder.AppendFormat("{0}client.Headers[HttpRequestHeader.ContentType] = 
              \"application/json\";{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}var jsonObj = 
              JsonConvert.SerializeObject(obj);{1}", GetTabs(4), Environment.NewLine, className);
            builder.AppendFormat("{0}var dataString = 
              client.UploadString(ApiServiceUrl, jsonObj);{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}var data = 
              JsonConvert.DeserializeObject<{2}>(dataString);{1}", GetTabs(4), Environment.NewLine, className);
            builder.AppendFormat("{0}return data;{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(2), Environment.NewLine);
            return builder.ToString();
        }

        private static string GetClassPutUsage(string className)
        {
            var builder = new StringBuilder();
            builder.AppendFormat("{0}public void Update(){1}{0}{{{1}", 
              GetTabs(2), Environment.NewLine, className);
            builder.AppendFormat("{0}using (WebClient client = 
              new WebClient()){1}{0}{{{1}", GetTabs(3), Environment.NewLine, className);
            builder.AppendFormat("{0}client.Headers[HttpRequestHeader.ContentType] = 
              \"application/json\";{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}var jsonObj = 
              JsonConvert.SerializeObject(this);{1}", GetTabs(4), Environment.NewLine, className);
            builder.AppendFormat("{0}var dataString = 
              client.UploadString(ApiServiceUrl+this.ID, \"put\", 
              jsonObj);{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(2), Environment.NewLine);
            return builder.ToString();
        }

        private static string GetClassDeleteUsage(string className)
        {
            var builder = new StringBuilder();
            builder.AppendFormat("{0}public void Delete(){1}{0}{{{1}", 
              GetTabs(2), Environment.NewLine, className);
            builder.AppendFormat("{0}using (WebClient client = 
              new WebClient()){1}{0}{{{1}", GetTabs(3), Environment.NewLine, className);
            builder.AppendFormat("{0}var dataString = client.UploadString(
              ApiServiceUrl+this.ID, \"delete\", \"\");{1}", GetTabs(4), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(2), Environment.NewLine);
            return builder.ToString();
        }

        private static string GetProperty(PropertyInfo property, bool observable)
        {
            var type = GetTypeName(property.PropertyType, observable);
            if (!observable)
                return string.Format("{0}public {2} {3} {{ get; set; }}{1}", 
                GetTabs(2), Environment.NewLine, type, property.Name);
            var builder = new StringBuilder();
            var privateField = string.Format("_{0}", property.Name);
            builder.AppendFormat("{0}private {2} {3};{1}", GetTabs(2), 
              Environment.NewLine, type, privateField);
            builder.AppendFormat("{0}public {2} {3} {1}{0}{{{1}", 
              GetTabs(2), Environment.NewLine, type, property.Name);
            builder.AppendFormat("{0}get{1}{0}{{{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}return {2};{1}", GetTabs(4), Environment.NewLine, privateField);
            builder.AppendFormat("{0}}}{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}set{1}{0}{{{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}{2} = value;{1}{0}RaisePropertyChangedEvent(\"{3}\");{1}", 
              GetTabs(4), Environment.NewLine, privateField, property.Name);
            builder.AppendFormat("{0}}}{1}", GetTabs(3), Environment.NewLine);
            builder.AppendFormat("{0}}}{1}", GetTabs(2), Environment.NewLine);
            return builder.ToString();
        }

        private static string GetTabs(int num)
        {
            var builder = new StringBuilder();
            for (var i = 0; i < num; i++)
                builder.Append("\t");
            return builder.ToString();
        }

        private static string GetTypeName(Type type, bool observable)
        {
            if (!type.IsGenericType)
                return type.Name;
            var genericType = type.GetGenericArguments()[0].Name;
            if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                return string.Format("{0}?", genericType);
            if (type.GetGenericTypeDefinition() == typeof(EntityCollection<>))
            {
                var collection = observable ? "ObservableCollection" : "Collection";
                return string.Format("{0}<{1}>", collection, genericType);
            }
            return type.Name;
        }

        private static bool IsBuiltInType(Type type)
        {
            return type == typeof(EntityState) ||
                    type == typeof(EntityKey) ||
                    type == typeof(EntityReference) ||
                     type.BaseType == typeof(EntityReference);
        }
    }
}

Points of Interest

I learned that we don't have to wait for Microsoft to do all the things for us when we can do them ourselves. The handler will definitely save 50% or more of your development time; feel free to enhance the features you want.

History

  • Version 1.0. Created the basic functionality of the Data Structure Handler.
  • Version 1.1:
    • Enhanced the class generation of Web Service consumption to use a centralized URL string.
    • Implemented user defined class sample method interface to allow us to inject some useful sample methods or properties to the generated class file.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

xibao
Software Developer
United States United States
I code for fun, my code are free.
I code with skill, not with bible.
I hate php$ when $ is not mine.
I hate C++ because of -> pointer.
I like Java because it doesn't use -> pointer.
I like Assembly because it makes me closer to machine.
I love C#'s efficiency and power.
I love jQuery's lazy coding ways.

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberxibao11-Mar-13 18:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 10 Mar 2013
Article Copyright 2013 by xibao
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid