Purpose
The purpose of this tutorial is to show how to use a C# .Net 4.7 (a simple ASMX web service) to send and receive JSON from an external web service using SSL certificate authentication. The ultimate "authentication" pass/fail is determined by the external web service you are connecting to and may contain a variety of ways with which to authenticate the SSL certificate (from you providing it in advance). Those mechanisms are beyond the scope of this tutorial; rather this tutorial focuses on setting up "your end" with the SSL certificate (pfx format) and sending/receiving JSON. While you may notice an ASMX web service is being used (meaning its return data will be in JSON encapsulated in simple XML, because the JSON is a string it would be easy to parse into JSON using Newtonsoft or another library).
With traditional XML-based data, you typically need a WSDL. However, as I will demonstrate here, you do not actually need any WSDL for the ASMX web service which will be communicating with an external web service.
While traditionally unorthadox in nature, you can have an ASMX web service communicate in non-XML syntax. What I will show here is having it communicate using JSON using a series of standard C# classes that you define which, in essence, form the schema.
Let's begin.
DEFINE THE C# CLASSES THAT WILL REPRESENT THE JSON DATA:
using System;
using System.Web.Services;
using System.Threading.Tasks;
using System.IO;
using System.Text;
using Newtonsoft.Json; /* A nuget package which serializes and deserializes JSON data */
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
using System.Text.Json;
using System.Net.Http; /* Depending on proceessing method to use, you may need to add System.Net.Http.WebRequest via References -> Add References of project. NOTE: An Azure environment is unlikely to recognize System.Net.Http.WebRequest. */
using System.Security.Cryptography.X509Certificates;
/* Class to contain the data to make a web service request with */
public class SvcPostObject {
public r_Resources Resources { get; set; }
public r_Filters Filters { get; set; }
}
public class r_Resources {
public bool IncludeDemographicData { get; set; }
}
public class r_Filters {
public string[] Ids { get; set; }
public string SamAccountName { get; set; }
}
/* Class to hold the response from the web service */
public class r_WebSvcObject {
public int? TotalCount { get; set; }
public int? PageCount { get; set; }
public int? StartIndex { get; set; }
public string Message { get; set; }
public r_Response_Result[] Result { get; set; }
}
public class r_Response_Result {
public string Id { get; set; }
public string SamAccountName { get; set; }
public string Title { get; set; }
}
/* Class to contain problems or errors with request or response */
public class ProblemResponseObject {
public int? TotalCount { get; set; }
public int? PageCount { get; set; }
public int? StartIndex { get; set; }
public string Message { get; set; }
public r_Response_Result[] Result { get; set; }
}
DEFINE THE C# METHOD THAT WILL INTERACT WITH THE EXTERNAL WEBSERVICE AND HANDLE THE SSL CERTIFICATE:
/* Call Worker that uses an SSL certificate to authenticate and complete the request */
/* Defined as Task<> in the event Define logic to use is not set to 1 */
private async Task<string> getWebServiceData(string searchid = "") {
string Url = "https://www.somesite.com";
string Uri = "/api/somewebservice";
string CertName = "somecertificate.pfx";
string CertPwd = "somepassword";
string CertIssuerName = "Issuer Name of the certificate as a single line";
string CertPath = Server.MapPath("~"); /* resolves to root of website, like wwwroot */
string CertFilePath = CertPath + CertName;
/* Define a class for problems or errors */
var probError = new ProblemResponseObject();
/* Define class for POST data that will be sent to the web service */
var postDataRequestResources = new r_Resources();
postDataRequestResources.IncludeDemographicData = false;
/* Define class for POST data that will be sent to the web service */
var postDataRequestFilters = new r_Filters();
postDataRequestFilters.Ids = new string[] { "12345" };
/* Determine if SSL certificate is present */
if(File.Exists(CertFilePath)) {
/* Load and validate the SSL certificate */
X509Certificate2 certificate = new X509Certificate2(CertFilePath, CertPwd);
if (certificate != null && certificate.IssuerName.Name == CertIssuerName) {
/* Build the JSON request from the classes */
var postDataRequestSvcPostObject = new SvcPostObject();
postDataRequestSvcPostObject.Resources = postDataRequestResources;
postDataRequestSvcPostObject.Filters = postDataRequestFilters;
try {
/* Define logic to use. 1 = traditional method, 2 = trapping asynchronous activity as synchronous */
var logicUse = 1;
/* In an Azure environment, this traditional method is not likely operable given the usage of System.Net.Http.WebRequest via References -> Add References of project. */
if (logicUse == 1) {
string sUrl = Url + Uri;
HttpWebRequest httpWReq = (HttpWebRequest)WebRequest.Create(sUrl);
/* Newtonsoft.Json method */
string postData = JsonConvert.SerializeObject(postDataRequestSvcPostObject);
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//string postData = JsonSerializer.Serialize<SvcPostObject>(postDataRequestSvcPostObject, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
Encoding encoding = new UTF8Encoding();
byte[] data = encoding.GetBytes(postData);
httpWReq.ContentLength = data.Length;
httpWReq.ProtocolVersion = HttpVersion.Version11;
httpWReq.Method = "POST";
httpWReq.ContentType = "application/json";
httpWReq.ClientCertificates.Add(certificate);
Stream stream = httpWReq.GetRequestStream();
stream.Write(data, 0, data.Length);
stream.Close();
HttpWebResponse responseAlt = (HttpWebResponse)httpWReq.GetResponse();
using (StreamReader reader = new StreamReader(responseAlt.GetResponseStream())) {
string responseContent = reader.ReadToEnd();
responseData = responseContent;
}
}
else {
/* Newtonsoft.Json method */
var postJsonData = new StringContent(JsonConvert.SerializeObject(postDataRequestSvcPostObject), Encoding.UTF8, "application/json");
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//var postJsonData = new StringContent(JsonSerializer.Serialize<SvcPostObject>(postDataRequestSvcPostObject, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true }), Encoding.UTF8, "application/json");
var handler = new WebRequestHandler();
handler.ClientCertificates.Add(certificate);
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri(Url);
HttpResponseMessage response = client.PostAsync(Url + Uri, postJsonData).Result;
var responseStatusCode = response.StatusCode.ToString();
if (responseStatusCode == "OK") {
Stream responseStream = await response.Content.ReadAsStreamAsync();
using (StreamReader responseReader = new StreamReader(responseStream)) {
responseData = responseReader.ReadToEnd();
}
if (responseData.Length > 0) {
/* Response recorded */
}
else {
probError.Message = "No expected response data.";
/* Newtonsoft.Json method */
responseData = JsonConvert.SerializeObject(probError);
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//responseData = JsonSerializer.Serialize<ProblemResponseObject>(probError, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
}
else {
probError.Message = String.Format("Response Error {0}.", responseStatusCode);
/* Newtonsoft.Json method */
responseData = JsonConvert.SerializeObject(probError);
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//responseData = JsonSerializer.Serialize<ProblemResponseObject>(probError, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
}
}
catch (Exception error) {
probError.Message = error.ToString();
/* Newtonsoft.Json method */
responseData = JsonConvert.SerializeObject(probError);
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//responseData = JsonSerializer.Serialize<ProblemResponseObject>(probError, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
}
else {
probError.Message = "Certificate not valid.";
/* Newtonsoft.Json method */
responseData = JsonConvert.SerializeObject(probError);
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//responseData = JsonSerializer.Serialize<ProblemResponseObject>(probError, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
}
else {
probError.Message = "Certificate not found.";
/* Newtonsoft.Json method */
responseData = JsonConvert.SerializeObject(probError);
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//responseData = JsonSerializer.Serialize<ProblemResponseObject>(probError, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
return responseData.ToString();
}
/* Method to handle web service response data by placing it in classes and then creating a string with that class data */
private string Response_Handler(string responseJsonString) {
var relevantData = "";
var responseData = new r_WebSvcObject();
/* Newtonsoft.Json method */
responseData = (r_WebSvcObject)JsonConvert.DeserializeObject(responseJsonString, typeof(r_WebSvcObject));
/* NOTE: If Newtonsoft.Json cannot be used, use the following instead */
//responseData = JsonSerializer.Deserialize<r_WebSvcObject>(responseJsonString, new JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
/* Get data from classes */
int TotalCount = 0;
int PageCount = 0;
int StartIndex = 0;
string Message = string.Empty;
if (responseData.TotalCount != null) { TotalCount = (int)responseData.TotalCount; }
if (responseData.PageCount != null) { PageCount = (int)responseData.PageCount; }
if (responseData.StartIndex != null) { StartIndex = (int)responseData.StartIndex; }
if (responseData.Message != null) { Message = responseData.Message; }
/* Loop through array of results */
foreach (var item in responseData.Result) {
relevantData += "Id = " + item.Id + ",";
relevantData += "SamAccountName = " + item.SamAccountName + ",";
relevantData += "Title = " + item.Title + ". ";
}
return relevantData;
}
DEFINE A STANDARD ASMX METHOD THAT YOU WILL CALL:
/* Example ASMX web service (this is what you call). Returned data will be json encapsulated by xml */
[WebMethod]
public string HelloWorld(string id = "") {
var callResponse = "";
/* Assume id is validated */
/* Make web service call and wait for the results */
var responseData = Task.Run(() => getWebServiceData(id).Result;
/* Transform data and return simple string representation */
callResponse = Response_Handler(responseData);
return callResponse;
}