Migrating a WCF Service to an API App
Introduction
I recently responded to a question about moving or re-implementing an existing WCF Service running on a BizTalk cluster. The customer wanted to utilize Azure Logic Apps instead of BizTalk on premise. The WCF Service is running as a Windows Service.
I recommended the following:
Create a Swagger for a new API that will replace the WCF Service.
NOTE: When creating your Swagger, add descriptions for each operation and parameter
Use the same signature (operation names and input parameters) for the Methods in the WCF Service.
Create a new Azure API App in Visual Studio.
You can include Application Insights as an option.
Using the add REST API CLIENT, Select an existing Swagger metadata file, as shown below.
Use the Swagger that you created.
https://docdb.info/wp-content/uploads/2016/01/AddRestApi.png
The following figure is a sample showing the classes created.
https://docdb.info/wp-content/uploads/2016/01/vs-project.pngThe generated code will include Models, which represent Request and Response objects.
There are three additional classes created.
// Code generated by Microsoft (R) AutoRest Code Generator 0.9.7.0 // Changes may cause incorrect behavior and will be lost if the code is regenerated. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using DocDbStoredProcApi.Models; using Microsoft.Rest; namespace DocDbStoredProcApi { /// <summary> /// /// </summary> public interface IDocDbSpApi : IDisposable { /// <summary> /// The base URI of the service. /// </summary> Uri BaseUri { get; set; } /// <summary> /// Credentials for authenticating with the service. /// </summary> ServiceClientCredentials Credentials { get; set; } /// <summary> /// Returns the Response as a string /// </summary> /// <param name='storedProcedureId'> /// Required. Stored Procedure Id /// </param> /// <param name='queryRequest'> /// Required. The Query body /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> Task<HttpOperationResponse<StoredProcedureResponse>> ExecuteAStoredProcedureWithOperationResponseAsync( string storedProcedureId, QueryRequest queryRequest, CancellationToken cancellationToken = default(CancellationToken)); /// <summary> /// Get an array of all stored procedures. /// - Read the feed 10 items at a time until there are no more items to /// read /// </summary> /// <param name='cancellationToken'> /// Cancellation token. /// </param> Task<HttpOperationResponse<IList<StoredProcedure>>> GetListOfStoredProceduresWithOperationResponseAsync( CancellationToken cancellationToken = default(CancellationToken)); } }
DocDbSpAPI.cs
The following is a sample of the code generated.
// Code generated by Microsoft (R) AutoRest Code Generator 0.9.7.0 // Changes may cause incorrect behavior and will be lost if the code is regenerated. using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using DocDbStoredProcApi.Models; using Microsoft.Rest; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DocDbStoredProcApi { /// <summary> /// /// </summary> public class DocDbSpApi : ServiceClient<DocDbSpApi>, IDocDbSpApi { /// <summary> /// Initializes a new instance of the DocDbSpApi class. /// </summary> public DocDbSpApi() { BaseUri = new Uri("https://docdbspapi.azurewebsites.net"); } /// <summary> /// Initializes a new instance of the DocDbSpApi class. /// </summary> /// <param name='handlers'> /// Optional. The set of delegating handlers to insert in the http /// client pipeline. /// </param> public DocDbSpApi(params DelegatingHandler[] handlers) : base(handlers) { BaseUri = new Uri("https://docdbspapi.azurewebsites.net"); } /// <summary> /// Initializes a new instance of the DocDbSpApi class. /// </summary> /// <param name='rootHandler'> /// Optional. The http client handler used to handle http transport. /// </param> /// <param name='handlers'> /// Optional. The set of delegating handlers to insert in the http /// client pipeline. /// </param> public DocDbSpApi(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers) { BaseUri = new Uri("https://docdbspapi.azurewebsites.net"); } /// <summary> /// Initializes a new instance of the DocDbSpApi class. /// </summary> /// <param name='baseUri'> /// Optional. The base URI of the service. /// </param> /// <param name='handlers'> /// Optional. The set of delegating handlers to insert in the http /// client pipeline. /// </param> public DocDbSpApi(Uri baseUri, params DelegatingHandler[] handlers) : this(handlers) { if (baseUri == null) { throw new ArgumentNullException(nameof(baseUri)); } BaseUri = baseUri; } /// <summary> /// Initializes a new instance of the DocDbSpApi class. /// </summary> /// <param name='credentials'> /// Required. Credentials for authenticating with the service. /// </param> /// <param name='handlers'> /// Optional. The set of delegating handlers to insert in the http /// client pipeline. /// </param> public DocDbSpApi(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers) { if (credentials == null) { throw new ArgumentNullException(nameof(credentials)); } Credentials = credentials; Credentials?.InitializeServiceClient(this); } /// <summary> /// Initializes a new instance of the DocDbSpApi class. /// </summary> /// <param name='baseUri'> /// Optional. The base URI of the service. /// </param> /// <param name='credentials'> /// Required. Credentials for authenticating with the service. /// </param> /// <param name='handlers'> /// Optional. The set of delegating handlers to insert in the http /// client pipeline. /// </param> public DocDbSpApi(Uri baseUri, ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers) { if (baseUri == null) { throw new ArgumentNullException(nameof(baseUri)); } if (credentials == null) { throw new ArgumentNullException(nameof(credentials)); } BaseUri = baseUri; Credentials = credentials; Credentials?.InitializeServiceClient(this); } /// <summary> /// The base URI of the service. /// </summary> public Uri BaseUri { get; set; } /// <summary> /// Credentials for authenticating with the service. /// </summary> public ServiceClientCredentials Credentials { get; set; } /// <summary> /// Returns the Response as a string /// </summary> /// <param name='storedProcedureId'> /// Required. Stored Procedure Id /// </param> /// <param name='queryRequest'> /// Required. The Query body /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public Task<HttpOperationResponse<StoredProcedureResponse>> ExecuteAStoredProcedureWithOperationResponseAsync(string storedProcedureId, QueryRequest queryRequest, CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } /// <summary> /// Get an array of all stored procedures. /// - Read the feed 10 items at a time until there are no more items to /// read /// </summary> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public Task<HttpOperationResponse<IList<StoredProcedure>>> GetListOfStoredProceduresWithOperationResponseAsync(CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } /// <summary> /// Returns the Response as a string /// </summary> /// <param name='storedProcedureId'> /// Required. Stored Procedure Id /// </param> /// <param name='queryRequest'> /// Required. The Query body /// </param> /// <param name='ridDB'> /// Required. The Database Id /// </param> /// <param name='ridColl'> /// Required. The Collection Id /// </param> /// <param name='ridProc'> /// Required. The Proc Id /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public async Task<HttpOperationResponse<StoredProcedureResponse>> ExecuteAStoredProcedureWithOperationResponseAsync(string storedProcedureId, QueryRequest queryRequest, string ridDB, string ridColl, string ridProc, CancellationToken cancellationToken = default(CancellationToken)) { // Validate if (storedProcedureId == null) { throw new ArgumentNullException(nameof(storedProcedureId)); } if (queryRequest == null) { throw new ArgumentNullException(nameof(queryRequest)); } if (ridDB == null) { throw new ArgumentNullException(nameof(ridDB)); } if (ridColl == null) { throw new ArgumentNullException(nameof(ridColl)); } if (ridProc == null) { throw new ArgumentNullException(nameof(ridProc)); } // Tracing var shouldTrace = ServiceClientTracing.IsEnabled; string invocationId = null; if (shouldTrace) { invocationId = ServiceClientTracing.NextInvocationId.ToString(); var tracingParameters = new Dictionary<string, object> { {nameof(storedProcedureId), storedProcedureId}, {"queryRequest", queryRequest}, {"ridDB", ridDB}, {"ridColl", ridColl}, {"ridProc", ridProc} }; ServiceClientTracing.Enter(invocationId, this, "ExecuteAStoredProcedureAsync", tracingParameters); } // Construct URL var url = ""; url = url + "/"; url = url + Uri.EscapeDataString(ridDB); url = url + "/colls/"; url = url + Uri.EscapeDataString(ridColl); url = url + "/sprocs/"; url = url + Uri.EscapeDataString(ridProc); var queryParameters = new List<string> {"StoredProcedureId=" + Uri.EscapeDataString(storedProcedureId)}; if (queryParameters.Count > 0) { url = url + "?" + string.Join("&", queryParameters); } var baseUrl = BaseUri.AbsoluteUri; // Trim '/' character from the end of baseUrl and beginning of url. if (baseUrl[baseUrl.Length - 1] == '/') { baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); } if (url[0] == '/') { url = url.Substring(1); } url = baseUrl + "/" + url; url = url.Replace(" ", "%20"); // Create HTTP transport objects using (var httpRequest = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(url) }) { // Set Headers // Set Credentials if (Credentials != null) { cancellationToken.ThrowIfCancellationRequested(); await Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); } // Serialize Request var requestDoc = queryRequest.SerializeJson(null); var requestContent = requestDoc.ToString(Formatting.Indented); httpRequest.Content = new StringContent(requestContent, Encoding.UTF8); httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); // Send Request if (shouldTrace) { ServiceClientTracing.SendRequest(invocationId, httpRequest); } cancellationToken.ThrowIfCancellationRequested(); var httpResponse = await HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); if (shouldTrace) { ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); } var statusCode = httpResponse.StatusCode; cancellationToken.ThrowIfCancellationRequested(); var responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.BadRequest && statusCode != HttpStatusCode.InternalServerError) { var ex = new HttpOperationException { Request = httpRequest, Response = httpResponse, Body = null }; if (shouldTrace) { ServiceClientTracing.Error(invocationId, ex); } throw ex; } // Create Result var result = new HttpOperationResponse<StoredProcedureResponse> { Request = httpRequest, Response = httpResponse }; // Deserialize Response if (statusCode == HttpStatusCode.OK) { var resultModel = new StoredProcedureResponse(); JToken responseDoc = null; if (string.IsNullOrEmpty(responseContent) == false) { responseDoc = JToken.Parse(responseContent); } if (responseDoc != null) { resultModel.DeserializeJson(responseDoc); } result.Body = resultModel; } if (shouldTrace) { ServiceClientTracing.Exit(invocationId, result); } return result; } } /// <summary> /// Get an array of all stored procedures. /// - Read the feed 10 items at a time until there are no more items to /// read /// </summary> /// <param name='ridDB'> /// Required. The Database Id /// </param> /// <param name='ridColl'> /// Required. The Collection Id /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public async Task<HttpOperationResponse<IList<StoredProcedure>>> GetListOfStoredProceduresWithOperationResponseAsync(string ridDB, string ridColl, CancellationToken cancellationToken = default(CancellationToken)) { // Validate if (ridDB == null) { throw new ArgumentNullException(nameof(ridDB)); } if (ridColl == null) { throw new ArgumentNullException(nameof(ridColl)); } // Tracing var shouldTrace = ServiceClientTracing.IsEnabled; string invocationId = null; if (shouldTrace) { invocationId = ServiceClientTracing.NextInvocationId.ToString(); var tracingParameters = new Dictionary<string, object> {{"ridDB", ridDB}, {"ridColl", ridColl}}; ServiceClientTracing.Enter(invocationId, this, "GetListOfStoredProceduresAsync", tracingParameters); } // Construct URL var url = ""; url = url + "/"; url = url + Uri.EscapeDataString(ridDB); url = url + "/colls/"; url = url + Uri.EscapeDataString(ridColl); url = url + "/sprocs"; var baseUrl = BaseUri.AbsoluteUri; // Trim '/' character from the end of baseUrl and beginning of url. if (baseUrl[baseUrl.Length - 1] == '/') { baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); } if (url[0] == '/') { url = url.Substring(1); } url = baseUrl + "/" + url; url = url.Replace(" ", "%20"); // Create HTTP transport objects var httpRequest = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(url) }; // Set Credentials if (Credentials != null) { cancellationToken.ThrowIfCancellationRequested(); await Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); } // Send Request if (shouldTrace) { ServiceClientTracing.SendRequest(invocationId, httpRequest); } cancellationToken.ThrowIfCancellationRequested(); var httpResponse = await HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); if (shouldTrace) { ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); } var statusCode = httpResponse.StatusCode; cancellationToken.ThrowIfCancellationRequested(); var responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.NotFound && (int) statusCode != 429 && statusCode != HttpStatusCode.InternalServerError) { var ex = new HttpOperationException { Request = httpRequest, Response = httpResponse, Body = null }; if (shouldTrace) { ServiceClientTracing.Error(invocationId, ex); } throw ex; } // Create Result var result = new HttpOperationResponse<IList<StoredProcedure>> { Request = httpRequest, Response = httpResponse }; // Deserialize Response if (statusCode == HttpStatusCode.OK) { IList<StoredProcedure> resultModel = new List<StoredProcedure>(); JToken responseDoc = null; if (string.IsNullOrEmpty(responseContent) == false) { responseDoc = JToken.Parse(responseContent); } if (responseDoc != null) { resultModel = StoredProcedureCollection.DeserializeJson(responseDoc); } result.Body = resultModel; } if (shouldTrace) { ServiceClientTracing.Exit(invocationId, result); } return result; } } }
DocDbSpAPIExtensions.cs
This is the class that you use in your Controller.
The following is a sample of the code generated.
// Code generated by Microsoft (R) AutoRest Code Generator 0.9.7.0 // Changes may cause incorrect behavior and will be lost if the code is regenerated. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using DocDbStoredProcApi.Models; namespace DocDbStoredProcApi { /// <summary> /// /// </summary> public static class DocDbSpApiExtensions { /// <summary> /// Returns the Response as a string /// </summary> /// <param name='operations'> /// Reference to the DocDbStoredProcApi.IDocDbSpApi. /// </param> /// <param name='storedProcedureId'> /// Required. Stored Procedure Id /// </param> /// <param name='queryRequest'> /// Required. The Query body /// </param> /// <param name='ridDB'> /// Required. The Database Id /// </param> /// <param name='ridColl'> /// Required. The Collection Id /// </param> /// <param name='ridProc'> /// Required. The Proc Id /// </param> public static StoredProcedureResponse ExecuteAStoredProcedure(this IDocDbSpApi operations, string storedProcedureId, QueryRequest queryRequest, string ridDB, string ridColl, string ridProc) { return Task.Factory.StartNew( s => ((IDocDbSpApi) s).ExecuteAStoredProcedureAsync(storedProcedureId, queryRequest, ridDB, ridColl, ridProc), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) .Unwrap() .GetAwaiter() .GetResult(); } /// <summary> /// Returns the Response as a string /// </summary> /// <param name='operations'> /// Reference to the DocDbStoredProcApi.IDocDbSpApi. /// </param> /// <param name='storedProcedureId'> /// Required. Stored Procedure Id /// </param> /// <param name='queryRequest'> /// Required. The Query body /// </param> /// <param name='ridDB'> /// Required. The Database Id /// </param> /// <param name='ridColl'> /// Required. The Collection Id /// </param> /// <param name='ridProc'> /// Required. The Proc Id /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public static async Task<StoredProcedureResponse> ExecuteAStoredProcedureAsync(this IDocDbSpApi operations, string storedProcedureId, QueryRequest queryRequest, string ridDB, string ridColl, string ridProc, CancellationToken cancellationToken = default(CancellationToken)) { var result = await operations.ExecuteAStoredProcedureWithOperationResponseAsync(storedProcedureId, queryRequest, ridDB, ridColl, ridProc, cancellationToken).ConfigureAwait(false); return result.Body; } /// <summary> /// Get an array of all stored procedures. /// - Read the feed 10 items at a time until there are no more items to /// read /// </summary> /// <param name='operations'> /// Reference to the DocDbStoredProcApi.IDocDbSpApi. /// </param> /// <param name='ridDB'> /// Required. The Database Id /// </param> /// <param name='ridColl'> /// Required. The Collection Id /// </param> public static IList<StoredProcedure> GetListOfStoredProcedures(this IDocDbSpApi operations, string ridDB, string ridColl) { return Task.Factory.StartNew( s => ((IDocDbSpApi) s).GetListOfStoredProceduresAsync(ridDB, ridColl), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) .Unwrap() .GetAwaiter() .GetResult(); } /// <summary> /// Get an array of all stored procedures. /// - Read the feed 10 items at a time until there are no more items to /// read /// </summary> /// <param name='operations'> /// Reference to the DocDbStoredProcApi.IDocDbSpApi. /// </param> /// <param name='ridDB'> /// Required. The Database Id /// </param> /// <param name='ridColl'> /// Required. The Collection Id /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public static async Task<IList<StoredProcedure>> GetListOfStoredProceduresAsync(this IDocDbSpApi operations, string ridDB, string ridColl, CancellationToken cancellationToken = default(CancellationToken)) { var result = await operations.GetListOfStoredProceduresWithOperationResponseAsync(ridDB, ridColl, cancellationToken) .ConfigureAwait(false); return result.Body; } } }
The next step is to create a Controller class. This is the class where you will be adding in your existing code from the operations in the WCF Service. The following is an example of a controller class.
using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Web.Http; using DocDbStoredProcApi.Models; using Swashbuckle.Swagger.Annotations; using TRex.Metadata; namespace DocDbStoredProcApi.Controllers { /// <summary> /// DocumentDB Stored Procedure Controller /// </summary> public class StoredProcedureController : ApiController { /// <summary> /// Execute a Stored Procedure /// </summary> /// <remarks>Returns the Response as a string</remarks> /// <param name="StoredProcedureId">Stored Procedure Id</param> /// <param name="queryRequest">SQL Query</param> /// <param name="RidDb">Database Id</param> /// <param name="RidColl">Collection Id</param> /// <param name="operations">IDocDbSpApi</param> /// <response code="200">OK</response> /// <response code="400">BadRequest -The syntax of the SQL statement is incorrect</response> /// <response code="500">Internal Server Error</response> /// <returns>Result of Stored Procedure Execution</returns> [Metadata("Execute a Stored Procedure")] [Route("{DatabaseId}/colls/{CollectioId}/sprocs/{SprocId}")] [SwaggerResponse(HttpStatusCode.OK)] [SwaggerResponse(HttpStatusCode.BadRequest)] [SwaggerResponse(HttpStatusCode.InternalServerError)] [SwaggerOperation("ExecuteStoredProcedure")] public async Task<StoredProcedureResponse> ExecuteStoredProcedureAsync( [Metadata("Stored Procedure Id")] string StoredProcedureId, [Metadata("Query Request body")] [FromBody] QueryRequest queryRequest, [Metadata("Database Id")] string RidDb, [Metadata("Collection Id ")] string RidColl, [Metadata(nameof(IDocDbSpApi))] IDocDbSpApi operations) { var sprcResponse = await operations.ExecuteAStoredProcedureAsync(StoredProcedureId, queryRequest, RidDb, RidColl, StoredProcedureId); return sprcResponse; } /// <summary> /// Get Stored Procedures List /// </summary> /// <remarks>Get an array of all stored procedures</remarks> /// <param name="operations"></param> /// <param name="ridDB"></param> /// <param name="ridColl"></param> /// <response code="200">OK</response> /// <response code="404">Not Found - This means the resource feed you tried to read did not exist.</response> /// <response code="429">TooManyRequests - This means you have exceeded the number of request units per second. </response> /// <response code="500">Internal Server Error</response> /// <returns>StoredProcedures</returns> [Metadata("Get Stored Procedures List")] [Route("{DatabaseId}/colls/{CollectioId}/sprocs")] [SwaggerResponse(HttpStatusCode.OK)] [SwaggerResponse(HttpStatusCode.NotFound)] [SwaggerResponse(HttpStatusCode.InternalServerError)] [SwaggerResponse(429, "Too Many Requests")] [SwaggerOperation("GetListOfStoredProcedures")] public IList<StoredProcedure> GetListOfStoredProcedures( [Metadata(nameof(IDocDbSpApi))] IDocDbSpApi operations, [Metadata("Database Id")] string ridDB, [Metadata("Collection Id ")] string ridColl) { return Task.Factory.StartNew( s => ((IDocDbSpApi)s).GetListOfStoredProceduresAsync(ridDB, ridColl), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) .Unwrap() .GetAwaiter() .GetResult(); } } }
In the above example, the methods are decorated with attributes. It is a Best Practice to use the Swagger attributes.
[SwaggerResponse(HttpStatusCode.OK)]
[SwaggerResponse(HttpStatusCode.NotFound)]
[SwaggerResponse(HttpStatusCode.InternalServerError)]
[SwaggerOperation(“GetListOfStoredProcedures”)]Just add *using Swashbuckle.Swagger.Annotations; *