Click here to Skip to main content
13,150,867 members (40,110 online)
Click here to Skip to main content
Add your own
alternative version

Stats

76.3K views
95 bookmarked
Posted 24 Jan 2016

Generate C# Client API for ASP.NET Web API

, 27 Aug 2017
Rate this:
Please Sign up or sign in to vote.
Generate strongly typed client API in C# for ASP.NET Web API supporting desktop, Universal Windows, Android and iOS

Introduction

Strongly Typed Client API Generators generate strongly typed client API in C# codes and TypeScript codes for minimizing repetitive tasks and improving productivity and quality when developing client programs. You may then provide or publish either the generated source codes or the compiled client API libraries to yourself and other developers.

This project provides these products:

  1. Code generator for strongly typed client API in C# supporting desktop, Universal Windows, Android and iOS.
  2. Code generator for strongly typed client API in TypeScript for jQuery and Angular 2.
  3. TypeScript CodeDOM, a CodeDOM component for TypeScript, derived from CodeDOM of .NET Framework.
  4. POCO2TS.exe, a command line program that generates TypsScript interfaces from POCO classes.
  5. Fonlow.Poco2Ts, a component that generates TypsScript interfaces from POCO classes

This article is focused on generating C# Client API libraries. For client API libraries in TypeScript, please check the other article.

Key Features

  1. Client API codes generated are directly mapped from the Web API controller methods, .NET primitive types and POCO classes. This is similar to what svcutil.exe in WCF has offered.
  2. Doc comments of controller methods and POCO classes are copied.

Key Benefits

  1. WebApiClientGen is seamlessly integrated with ASP.NET Web API with very little steps/overheads to setup, maintain and synchronize between Web API and client APIs, during RAD or Agile Software Development.
  2. Support all .NET primitive types including decimal.
  3. Support DataTime, DataTimeOffset, Array, Tuple, Dynamic Object, Dictionary and KeyValuePair
  4. Strongly typed generated codes are subject to design time type checking and compile time type checking.
  5. Provide high level of abstraction, shielding application developers from repetitive technical details of RESTful practices and traditional AJAX calls.
  6. Rich meta info including doc comments make IDE intellisense more helpful, so application developers have less need of reading separated API documents.

Background

If you have ever developed SOAP base Web services using WCF, you might have enjoyed using the client API codes generated by SvcUtil.exe or Service References of Visual Studio IDE. When moving to Web API, I felt that I had got back to the stone age, since I had to do a lot of data type checking at design time using my precious brain power while computes should have done the job.

I had developed some RESTful Web services on top of IHttpHandler/IHttpModule back in 2010, which did not need strongly typed data but arbitrary data like documents and streams. However, I have been developing more Web projects that need highly abstraction and semantic data types.

I see that ASP.NET Web API does support highly abstraction and strongly typed function prototypes through class ApiController, and ASP.NET MVC framework optionally provides nicely generated Help Page describing the API functions. However, after developing the Web API, I have to hand-craft some very primitive and repetitive client codes to consume the Web services. If the Web API was developed by others, I will have to read the online help pages.

I miss my good old days with WCF. Smile | :)

Therefore, I had searched and tried to find some solutions that could release me from crafting primitive and repetitive codes so I could focus on building business logic at the client sides. Here's a list of open source projects assisting the development of client programs:

  1. WADL
  2. RAML with .NET
  3. WebApiProxy
  4. Swashbuckle
  5. AutoRest
  6. OData

While these solutions could generate strongly typed client codes and reduce repetitive tasks at some degree, however, I found that none of them could give me all the fluent and efficient programming experiences that I would expect:

  1. Strongly typed client data models mapping to the data models of the service.
  2. Strongly typed function prototypes mapping to the functions of derived classes of ApiController.
  3. Code generations in the wholesale style like the way of WCF.
  4. Cherry-picking data models through data annotations using popular attributes like DataContractAttribute and JsonObjectAttribute, etc.
  5. Type checking at design time and compile time.
  6. Intellisense for client data models, function prototypes and doc comments.

Here comes WebApiClientGen.

Presumptions

  1. You are developing ASP.NET Web API 2.x applications, and will be developing applications running on Windows desktop, Universal Windows, Android or iOS using C# as the primary programming language.
  2. You and fellow developers prefer highly abstraction through strongly typed functions in both the server side and the client side.
  3. The POCO classes are used by both Web API and Entity Framework Code First, and you may not want to publish all data classes and members to client programs.

For following up this new way of developing client programs, it is better for you to have an ASP.NET Web API project, or a MVC project which contains Web API. You may use an existing project, or create a demo one.

Using the Code

Step 0: Install NuGet package WebApiClientGen to the Web MVC/API Project

The installation will also install dependent NuGet packages Fonlow.TypeScriptCodeDOM and Fonlow.Poco2Ts to the project references.

The NuGet package will add 2 TS files to the ~/Scripts/ClientApi folder of the project, one is HttpClient.ts, and the other is WebApiClientAuto.ts which will be replaced every time the CodeGen is executed.

Additionally, CodeGenController.cs for triggering the CodeGen is added to the project's Controllers folder.
The CodeGenController should be available only during development in the debug build, since the client API should be generated only once for each version of the Web API.

#if DEBUG  //This controller is not needed in production release,
    // since the client API should be generated during development of the Web Api.
...

namespace Fonlow.WebApiClientGen
{
    [System.Web.Http.Description.ApiExplorerSettings(IgnoreApi = true)]//this controller is a
            //dev backdoor during development, no need to be visible in ApiExplorer.
    public class CodeGenController : ApiController
    {
        /// <summary>
        /// Trigger the API to generate WebApiClientAuto.cs for an established client API project.
        /// POST to  http://localhost:10965/api/CodeGen with json object CodeGenParameters
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns>OK if OK</returns>
        [HttpPost]
        public string TriggerCodeGen(CodeGenParameters parameters)
        {
...
        }
    }

 

 

Remarks

CodeGenController is installed in YourMvcOrWebApiProject/Controllers, even though the scaffolding of a MVC project has folder API for derived classes of ApiController.

Step 1: Create the .NET client API project

Ensure the following packages are referenced:

  1. Microsoft ASP.NET Web API 2.2 Client Libraries
  2. Newtonsoft Json.NET.
  3. System.Runtime.Serialization
  4. System.ServiceModel
  5. System.ComponentModel.DataAnnotations

As illustrated in this screenshot:

Step 2: Prepare JSON Config Data

Your Web API project may have POCO classes and API functions like the ones blow.

namespace DemoWebApi.DemoData
{
    public sealed class Constants
    {
        public const string DataNamespace = "http://fonlow.com/DemoData/2014/02";
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public enum AddressType
    {
        [EnumMember]
        Postal,
        [EnumMember]
        Residential,
    };

    [DataContract(Namespace = Constants.DataNamespace)]
    public enum Days
    {
        [EnumMember]
        Sat = 1,
        [EnumMember]
        Sun,
        [EnumMember]
        Mon,
        [EnumMember]
        Tue,
        [EnumMember]
        Wed,
        [EnumMember]
        Thu,
        [EnumMember]
        Fri
    };

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Address
    {
        [DataMember]
        public Guid Id { get; set; }

        public Entity Entity { get; set; }

        /// <summary>
        /// Foreign key to Entity
        /// </summary>
        public Guid EntityId { get; set; }

        [DataMember]
        public string Street1 { get; set; }

        [DataMember]
        public string Street2 { get; set; }

        [DataMember]
        public string City { get; set; }

        [DataMember]
        public string State { get; set; }

        [DataMember]
        public string PostalCode { get; set; }

        [DataMember]
        public string Country { get; set; }

        [DataMember]
        public AddressType Type { get; set; }

        [DataMember]
        public DemoWebApi.DemoData.Another.MyPoint Location;
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Entity
    {
        public Entity()
        {
            Addresses = new List<Address>();
        }

        [DataMember]
        public Guid Id { get; set; }

        
        [DataMember(IsRequired =true)]//MVC and Web API does not care
        [System.ComponentModel.DataAnnotations.Required]//MVC and Web API care about only this
        public string Name { get; set; }

        [DataMember]
        public IList<Address> Addresses { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Person : Entity
    {
        [DataMember]
        public string Surname { get; set; }
        [DataMember]
        public string GivenName { get; set; }
        [DataMember]
        public DateTime? BirthDate { get; set; }

        public override string ToString()
        {
            return Surname + ", " + GivenName;
        }

    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Company : Entity
    {
        [DataMember]
        public string BusinessNumber { get; set; }

        [DataMember]
        public string BusinessNumberType { get; set; }

        [DataMember]
        public string[][] TextMatrix
        { get; set; }

        [DataMember]
        public int[][] Int2DJagged;

        [DataMember]
        public int[,] Int2D;

        [DataMember]
        public IEnumerable<string> Lines;
    }

...
...

namespace DemoWebApi.Controllers
{
    [RoutePrefix("api/SuperDemo")]
    public class EntitiesController : ApiController
    {
        /// <summary>
        /// Get a person
        /// </summary>
        /// <param name="id">unique id of that guy</param>
        /// <returns>person in db</returns>
        [HttpGet]
        public Person GetPerson(long id)
        {
            return new Person()
            {
                Surname = "Huang",
                GivenName = "Z",
                Name = "Z Huang",
                BirthDate = DateTime.Now.AddYears(-20),
            };
        }

        [HttpPost]
        public long CreatePerson(Person p)
        {
            Debug.WriteLine("CreatePerson: " + p.Name);

            if (p.Name == "Exception")
                throw new InvalidOperationException("It is exception");

            Debug.WriteLine("Create " + p);
            return 1000;
        }

        [HttpPut]
        public void UpdatePerson(Person person)
        {
            Debug.WriteLine("Update " + person);
        }

        [HttpPut]
        [Route("link")]
        public bool LinkPerson(long id, string relationship, [FromBody] Person person)
        {
            return person != null && !String.IsNullOrEmpty(relationship);
        }

        [HttpDelete]
        public void Delete(long id)
        {
            Debug.WriteLine("Delete " + id);
        }

        [Route("Company")]
        [HttpGet]
        public Company GetCompany(long id)
        {

The JSON config data in v1.9.0 is like this:

{
    "ApiSelections": {
        "ExcludedControllerNames": [
            "DemoWebApi.Controllers.Account"
        ],

        "DataModelAssemblyNames": [
            "DemoWebApi.DemoData",
            "DemoWebApi"
        ],
        "CherryPickingMethods": 1
    },

    "ClientApiOutputs": {
        "ClientLibraryProjectFolderName": "DemoWebApi.ClientApi",
        "GenerateBothAsyncAndSync": true,

    }
}

The config data sample in v1.8.0 and earlier versions is available here.

It is recommended to save the JSON config data into a file like this one.

If you have all POCO classes defined in the Web API project, you should put the assembly name of the Web API project to the array of "DataModelAssemblyNames". If you have some dedicated data model assemblies for good separation of concerns, you should put respective assembly names to the array. You have options of generating TypeScript client API codes, or C# client API codes, or both.

The CodeGen generates C# client proxy classes from POCO classes according to "CherryPickingMethods" which is described in the doc comment below:

/// <summary>
/// Flagged options for cherry picking in various development processes.
/// </summary>
[Flags]
public enum CherryPickingMethods
{
    /// <summary>
    /// Include all public classes, properties and properties.
    /// </summary>
    All = 0,

    /// <summary>
    /// Include all public classes decorated by DataContractAttribute,
    /// and public properties or fields decorated by DataMemberAttribute.
    /// And use DataMemberAttribute.IsRequired
    /// </summary>
    DataContract =1,

    /// <summary>
    /// Include all public classes decorated by JsonObjectAttribute,
    /// and public properties or fields decorated by JsonPropertyAttribute.
    /// And use JsonPropertyAttribute.Required
    /// </summary>
    NewtonsoftJson = 2,

    /// <summary>
    /// Include all public classes decorated by SerializableAttribute,
    /// and all public properties or fields
    /// but excluding those decorated by NonSerializedAttribute.
    /// And use System.ComponentModel.DataAnnotations.RequiredAttribute.
    /// </summary>
    Serializable = 4,

    /// <summary>
    /// Include all public classes, properties and properties.
    /// And use System.ComponentModel.DataAnnotations.RequiredAttribute.
    /// </summary>
    AspNet = 8,
}

The default one is DataContract for opt-in. And you may use any or combinations of methods.

Step 3: Run the DEBUG Build of the Web API Project and POST JSON Config data to Trigger the Generation of Client API Codes

Run the Web project in IDE on IIS Express.

You then use Curl or Poster or any of your favorite client tools to POST to http://localhost:10965/api/CodeGen, with content-type=application/json.

Hints:

So basically you just need one step to generate the client API, since you don't need to install the NuGet package every time.

It shouldn't be hard for you to write some batch scripts to launch the Web API and POST the JSON config data. And I have actually drafted one for your reference: a Powershell script file that launch the Web (API) project on IIS Express then post the JSON config file. So basically most of the time for continuously updating / synchronizing Web API and client API, you just need step 3 through running a Powershell script. This reduces a lot overheads for Continuous Integration.

 

Publish Client API Libraries

After these steps, now you have the client API in C# generated, similar to this example:

namespace DemoWebApi.DemoData.Client
{
    
    
    public enum AddressType
    {
        
        Postal,
        
        Residential,
    }
    
    public enum Days
    {
        
        Sat = 1,
        
        Sun = 2,
        
        Mon = 3,
        
        Tue = 4,
        
        Wed = 5,
        
        Thu = 6,
        
        Fri = 7,
    }
    
    public class Address : object
    {
        
        private System.Guid _Id;
        
        private string _Street1;
        
        private string _Street2;
        
        private string _City;
        
        private string _State;
        
        private string _PostalCode;
        
        private string _Country;
        
        private DemoWebApi.DemoData.Client.AddressType _Type;
        
        private DemoWebApi.DemoData.Another.Client.MyPoint _Location;
        
        public System.Guid Id
        {
            get
            {
                return _Id;
            }
            set
            {
                _Id = value;
            }
        }
        
        public string Street1
        {
            get
            {
                return _Street1;
            }
            set
            {
                _Street1 = value;
            }
        }
        
        public string Street2
        {
            get
            {
                return _Street2;
            }
            set
            {
                _Street2 = value;
            }
        }
        
        public string City
        {
            get
            {
                return _City;
            }
            set
            {
                _City = value;
            }
        }
        
        public string State
        {
            get
            {
                return _State;
            }
            set
            {
                _State = value;
            }
        }
        
        public string PostalCode
        {
            get
            {
                return _PostalCode;
            }
            set
            {
                _PostalCode = value;
            }
        }
        
        public string Country
        {
            get
            {
                return _Country;
            }
            set
            {
                _Country = value;
            }
        }
        
        public DemoWebApi.DemoData.Client.AddressType Type
        {
            get
            {
                return _Type;
            }
            set
            {
                _Type = value;
            }
        }
        
        public DemoWebApi.DemoData.Another.Client.MyPoint Location
        {
            get
            {
                return _Location;
            }
            set
            {
                _Location = value;
            }
        }
    }
    
    public class Entity : object
    {
        
        private System.Guid _Id;
        
        private string _Name;
        
        private DemoWebApi.DemoData.Client.Address[] _Addresses;
        
        public System.Guid Id
        {
            get
            {
                return _Id;
            }
            set
            {
                _Id = value;
            }
        }
        
        [System.ComponentModel.DataAnnotations.RequiredAttribute()]
        public string Name
        {
            get
            {
                return _Name;
            }
            set
            {
                _Name = value;
            }
        }
        
        public DemoWebApi.DemoData.Client.Address[] Addresses
        {
            get
            {
                return _Addresses;
            }
            set
            {
                _Addresses = value;
            }
        }
    }
    
    public class Person : DemoWebApi.DemoData.Client.Entity
    {
        
        private string _Surname;
        
        private string _GivenName;
        
        private System.Nullable<System.DateTime> _BirthDate;
        
        public string Surname
        {
            get
            {
                return _Surname;
            }
            set
            {
                _Surname = value;
            }
        }
        
        public string GivenName
        {
            get
            {
                return _GivenName;
            }
            set
            {
                _GivenName = value;
            }
        }
        
        public System.Nullable<System.DateTime> BirthDate
        {
            get
            {
                return _BirthDate;
            }
            set
            {
                _BirthDate = value;
            }
        }
    }
    
    public class Company : DemoWebApi.DemoData.Client.Entity
    {
        
        private string _BusinessNumber;
        
        private string _BusinessNumberType;
        
        private string[][] _TextMatrix;
        
        private int[][] _Int2DJagged;
        
        private int[,] _Int2D;
        
        private string[] _Lines;
        
        public string BusinessNumber
        {
            get
            {
                return _BusinessNumber;
            }
            set
            {
                _BusinessNumber = value;
            }
        }
        
        public string BusinessNumberType
        {
            get
            {
                return _BusinessNumberType;
            }
            set
            {
                _BusinessNumberType = value;
            }
        }
        
        public string[][] TextMatrix
        {
            get
            {
                return _TextMatrix;
            }
            set
            {
                _TextMatrix = value;
            }
        }
        
        public int[][] Int2DJagged
        {
            get
            {
                return _Int2DJagged;
            }
            set
            {
                _Int2DJagged = value;
            }
        }
        
        public int[,] Int2D
        {
            get
            {
                return _Int2D;
            }
            set
            {
                _Int2D = value;
            }
        }
        
        public string[] Lines
        {
            get
            {
                return _Lines;
            }
            set
            {
                _Lines = value;
            }
        }
    }
    
    public class MyPeopleDic : object
    {
        
        private System.Collections.Generic.Dictionary<string, DemoWebApi.DemoData.Client.Person> _Dic;
        
        private System.Collections.Generic.Dictionary<string, string> _AnotherDic;
        
        private System.Collections.Generic.Dictionary<int, string> _IntDic;
        
        public System.Collections.Generic.Dictionary<string, DemoWebApi.DemoData.Client.Person> Dic
        {
            get
            {
                return _Dic;
            }
            set
            {
                _Dic = value;
            }
        }
        
        public System.Collections.Generic.Dictionary<string, string> AnotherDic
        {
            get
            {
                return _AnotherDic;
            }
            set
            {
                _AnotherDic = value;
            }
        }
        
        public System.Collections.Generic.Dictionary<int, string> IntDic
        {
            get
            {
                return _IntDic;
            }
            set
            {
                _IntDic = value;
            }
        }
    }
}
namespace DemoWebApi.DemoData.Another.Client
{
    
    
    public struct MyPoint
    {
        
        public double X;
        
        public double Y;
    }
}

    public partial class Entities
    {
        
        private System.Net.Http.HttpClient client;
        
        private System.Uri baseUri;
        
        public Entities(System.Net.Http.HttpClient client, System.Uri baseUri)
        {
            if (client == null)
                throw new ArgumentNullException("client", "Null HttpClient.");

            if (baseUri == null)
                throw new ArgumentNullException("baseUri", "Null baseUri");

            this.client = client;
            this.baseUri = baseUri;
        }
        
        /// <summary>
        ///
        /// PUT api/SuperDemo/link?id={id}&relationship={relationship}
        /// </summary>
        public async Task<bool> LinkPersonAsync(long id, string relationship, DemoWebApi.DemoData.Client.Person person)
        {
            var requestUri = this.baseUri + "api/SuperDemo/link?id="+id+"&relationship="+relationship;
            using (var requestWriter = new System.IO.StringWriter())
            {
            var requestSerializer = JsonSerializer.Create();
            requestSerializer.Serialize(requestWriter, person);
            var content = new StringContent(requestWriter.ToString(), System.Text.Encoding.UTF8, "application/json");
            var responseMessage = await client.PutAsync(requestUri, content);
            responseMessage.EnsureSuccessStatusCode();
            var stream = await responseMessage.Content.ReadAsStreamAsync();
            using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
            {
            var serializer = new JsonSerializer();
            return System.Boolean.Parse(jsonReader.ReadAsString());
            }
            }
        }
        
        /// <summary>
        ///
        /// PUT api/SuperDemo/link?id={id}&relationship={relationship}
        /// </summary>
        public bool LinkPerson(long id, string relationship, DemoWebApi.DemoData.Client.Person person)
        {
            var requestUri = this.baseUri + "api/SuperDemo/link?id="+id+"&relationship="+relationship;
            using (var requestWriter = new System.IO.StringWriter())
            {
            var requestSerializer = JsonSerializer.Create();
            requestSerializer.Serialize(requestWriter, person);
            var content = new StringContent(requestWriter.ToString(), System.Text.Encoding.UTF8, "application/json");
            var responseMessage = this.client.PutAsync(requestUri, content).Result;
            responseMessage.EnsureSuccessStatusCode();
            var stream = responseMessage.Content.ReadAsStreamAsync().Result;
            using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
            {
            var serializer = new JsonSerializer();
            return System.Boolean.Parse(jsonReader.ReadAsString());
            }
            }
        }
        
        /// <summary>
        ///
        /// GET api/SuperDemo/Company?id={id}
        /// </summary>
        public async Task<DemoWebApi.DemoData.Client.Company> GetCompanyAsync(long id)
        {
            var requestUri = this.baseUri + "api/SuperDemo/Company?id="+id;
            var responseMessage = await client.GetAsync(Uri.EscapeUriString(requestUri));
            responseMessage.EnsureSuccessStatusCode();
            var stream = await responseMessage.Content.ReadAsStreamAsync();
            using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
            {
            var serializer = new JsonSerializer();
            return serializer.Deserialize<DemoWebApi.DemoData.Client.Company>(jsonReader);
            }
        }
        
        /// <summary>
        ///
        /// GET api/SuperDemo/Company?id={id}
        /// </summary>
        public DemoWebApi.DemoData.Client.Company GetCompany(long id)
        {
            var requestUri = this.baseUri + "api/SuperDemo/Company?id="+id;
            var responseMessage = this.client.GetAsync(Uri.EscapeUriString(requestUri)).Result;
            responseMessage.EnsureSuccessStatusCode();
            var stream = responseMessage.Content.ReadAsStreamAsync().Result;
            using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
            {
            var serializer = new JsonSerializer();
            return serializer.Deserialize<DemoWebApi.DemoData.Client.Company>(jsonReader);
            }
        }

If you would expect some external developers to use your Web API, you may publish the generated C# client API codes or compiled libraries targeting various platforms along with the help pages generated by the ASP.NET MVC framework.

Consume the API Codes Generated

Here's a simple example:

var httpclient = new system.net.http.httpclient();
var api = new demowebapi.controllers.client.entities(httpclient, baseuri);
person person = new person()
{
    name = "some one",
    surname = "one",
    givenname = "some",
    birthdate = datetime.now.addyears(-20),
    addresses = new address[]{new address(){
        city="brisbane",
        state="qld",
        street1="somewhere",
        street2="over the rainbow",
        postalcode="4000",
        country="australia",
        type= addresstype.postal,
        location = new demowebapi.demodata.another.client.mypoint() {x=4, y=9 },
    }},
};

var id = api.createperson(person);

 

When writing client codes in some decent text editors like Visual Studio, you may get nice intellisense, so you rarely need to read Web API Help Page.

Support Universal Windows Apps, Android Apps, and iOS Apps

For Universal Windows apps, you may create a client API library like this:

For Android apps, you may have a client API project like this with Mono.Android:

For iOS apps, you may create a client API project like this with Xamarin.iOS:

Hints:

If you would provide compiled libraries for various platforms with the same codebase, you may create a symbolic link to file WebApiClientAuto.cs generated in folder DemoWebApi.ClientApi.

As illustrated in the screenshot, click on "Add As Link" you will create a symbolic link to the cs file in project DemoWebApi.iOSClientApi.

 

Summary of Benefits

  1. Seamlessly integrated with ASP.NET Web API with very little steps/overheads to setup, maintain and synchronize between Web API and client APIs
  2. Support all built-in types including decimal
  3. Support DataTime, DataTimeOffset, Array, Tuple, Dynamic Object, Dictionary and KeyValuePair
  4. Strongly typed generated codes to be subject to design time type checking and compile time type checking
  5. High abstraction
  6. Intellisense

 

Points of Interests

While ASP.NET MVC and Web API use NewtonSoft.Json for JSON applications, NewtonSoft.Json can handle well POCO classes decorated by DataContractAttribute.

The CLR namespaces will be translated to client namespaces through adding "Client" as suffix. For example, namespace My.Name.space will be translated to My.Name.space.Client.

From certain point of view, the one to one mapping between the service namespaces/function names and the client namespaces/function names is exposing the implementation details of the service, which generally is not recommended. However, traditional RESTful client programming requires programmers to be aware of the URL query templates of service functions, and the query templates are of implementation details of the service. So both approaches expose the implementation details of the service at some degree.

To client developers, classic function prototype like

ReturnType DoSomething(Type1 t1, Type2 t2 ...)

is the API function, and the rest is the technical implementation details of transportation: TCP/IP, HTTP, SOAP, resource-oriented, CRUD-based URIs, RESTful, XML and JSON etc. The function prototype and a piece of API document should be good enough for calling the API functions. Client developers should not have to care about those implementation details of transportation, at least when the operation is successful. Only when errors kick in, developers will have to care about the technical details of handling errors. For example, in SOAP base web services, you have to know about SOAP faults; and in RESTful Web services, you may have to deal with HTTP status codes.

And the query templates give little sense of semantic meaning of the API functions. In contrast, WebApiClientGen names the client functions after the service functions, just as SvcUtil.exe in WCF will do by default, so the client functions generated have good semantic meaning as long as you as the service developers had named the service functions after good semantic names.

In the big picture of SDLC covering both the service development and the client development, the service developers have the knowledge of semantic meaning of service functions, and it is generally a good programming practice to name functions after functional descriptions. Resource-oriented CRUD may have semantic meaning or just become a technical translation from functional descriptions.

WebApiClientGen copies the doc comments in the generated C# codes, thus you have little need of reading the generated Help Pages generated by MVC, and your client programming with the service will become more seamless.

Hints

And it shouldn't be hard to write scripts to automate some steps altogether for Continuous Integration.

Swashbuckle +AutoRest VS. WebApiClientGen

Swashbukle generates Swagger meta through reading ApiExplorer of Web API, so client programmers of different platforms may generate client API in respective languages.

AutoRest reads Swagger meta and generates client API in C# and javascript, and surely it then can generate client API for RESTful Web services coded in any programming language as long as the services could provide Swagger meta.

Thus Swashbukle + AutoRest could provide what WebApiClientGen has offered, almost.

WebApiClientGen generates client API codes in C# and TypeScript through reading ApiExplorer but without involving Swagger meta. It is much simpler and more efficient to use and covering more data types such as .NET decimal type for monetary calculations and Tuple types etc. And Swagger does not support the decimal type of .NET.

At high level, Swagger is for "Model First" approach, while WebApiClient is for "Code First" approach. Swashbuckle+AutoRest is trying to support "Code First" approach but with some extra trips.

WebApiClientGen is not to replace Swashbukle and AutoRest entirely. If you are developing Web API and you would provide client API libraries in C# and TypeScript generated to yourself, your team or external teams, WebApiClientGen is more seamless, straightforward and comprehensive for such scenarios.

If you are to generate client APIs in PHP, Java, JavaScript, C# and C++ etc., you may find Swagger and its accessories could be more viable.

References:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Zijian
Software Developer
Australia Australia
I started my IT career in programming on different embedded devices since 1992, such as credit card readers, smart card readers and Palm Pilot.

Since 2000, I have mostly been developing business applications on Windows platforms while also developing some tools for myself and developers around the world, so we developers could focus more on delivering business values rather than repetitive tasks of handling technical details.

Beside technical works, I enjoy reading literatures, playing balls, cooking and gardening.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
PraiseThanks Pin
Member 1067628121-Apr-17 17:39
memberMember 1067628121-Apr-17 17:39 
QuestionRelationship to Swagger Pin
Member 1087658421-Apr-17 9:21
memberMember 1087658421-Apr-17 9:21 
AnswerRe: Relationship to Swagger Pin
Zijian25-Apr-17 1:51
memberZijian25-Apr-17 1:51 
PraiseLooks promising Pin
mldisibio21-Apr-17 8:59
membermldisibio21-Apr-17 8:59 
QuestionWCF is alive... Pin
Lucian Gadau21-Apr-17 1:47
professionalLucian Gadau21-Apr-17 1:47 
"the good, old days of WCF"??? Last time I looked, which is a minute ago, WCF is still available as a Visual C# set of templates in Visual Studio 2017. So it't pretty much alive and doing well.
GeneralMy vote of 1 Pin
Jeff Bowman17-Apr-17 12:08
professionalJeff Bowman17-Apr-17 12:08 
GeneralRe: My vote of 1 Pin
Zijian17-Apr-17 15:21
memberZijian17-Apr-17 15:21 
GeneralRe: My vote of 1 Pin
Jeff Bowman17-Apr-17 15:23
professionalJeff Bowman17-Apr-17 15:23 
GeneralRe: My vote of 1 Pin
Dewey27-Aug-17 12:17
memberDewey27-Aug-17 12:17 
GeneralRe: My vote of 1 Pin
Jeff Bowman27-Aug-17 17:17
professionalJeff Bowman27-Aug-17 17:17 
GeneralRe: My vote of 1 Pin
maxoptimus28-Aug-17 0:03
membermaxoptimus28-Aug-17 0:03 
QuestionSeparate data project Pin
Ian Klek10-Apr-17 22:15
memberIan Klek10-Apr-17 22:15 
AnswerRe: Separate data project Pin
Zijian17-Apr-17 0:01
memberZijian17-Apr-17 0:01 
GeneralMy vote of 5 Pin
Ankush Bansal10-Apr-17 2:37
memberAnkush Bansal10-Apr-17 2:37 
QuestionVB Support Pin
Jeff Bowman26-Jan-17 11:39
professionalJeff Bowman26-Jan-17 11:39 
AnswerRe: VB Support Pin
Zijian8-Apr-17 19:32
memberZijian8-Apr-17 19:32 
GeneralRe: VB Support Pin
Jeff Bowman9-Apr-17 6:55
professionalJeff Bowman9-Apr-17 6:55 
GeneralRe: VB Support Pin
Zijian10-Apr-17 3:56
memberZijian10-Apr-17 3:56 
GeneralRe: VB Support Pin
Jeff Bowman10-Apr-17 15:58
professionalJeff Bowman10-Apr-17 15:58 
GeneralRe: VB Support Pin
Zijian17-Apr-17 0:22
memberZijian17-Apr-17 0:22 
GeneralRe: VB Support Pin
Jeff Bowman17-Apr-17 8:44
professionalJeff Bowman17-Apr-17 8:44 
GeneralRe: VB Support Pin
Zijian17-Apr-17 11:55
memberZijian17-Apr-17 11:55 
QuestionQuestion Pin
Member 1048548710-Sep-16 16:05
memberMember 1048548710-Sep-16 16:05 
AnswerRe: Question Pin
Zijian8-Apr-17 19:35
memberZijian8-Apr-17 19:35 
GeneralMy vote of 5 Pin
Touristas9-Sep-16 3:09
memberTouristas9-Sep-16 3:09 
Questionsupport Web API of ASP.NET 5 vNext Pin
anotheroneaa8-Mar-16 21:09
memberanotheroneaa8-Mar-16 21:09 
QuestionFormat Pin
Nelek25-Jan-16 0:15
protectorNelek25-Jan-16 0:15 
AnswerRe: Format Pin
Zijian25-Jan-16 9:37
memberZijian25-Jan-16 9:37 

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

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

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170924.2 | Last Updated 27 Aug 2017
Article Copyright 2016 by Zijian
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid