Skip to content

Going from Schema to Code Using C# (RESTful Web Service)

Daniel Lowe edited this page Nov 17, 2021 · 1 revision

Overview

The software artifacts in the ES-CIM repository are meant for use in either SOAP or RESTful web services. Although the majority of web services utilize REST, SOAP is still a major part of the CIM world, and can be challenging for new developers unfamiliar with the technology to use. This page will go into detail on creating a RESTful CIM Web Service using the ES-CIM artifacts.

Generating Code from the XSDs

Code can be generated from either XSDs or JSON Schema to develop code representing the structure of a message. For this project, use xsd.exe to generate code directly from an XSD. Although this is described in XML, the service can still be set up to send/receive either JSON or XML. The snippet below show code built from the XSD.

Because of the nature of CIM messages potentially being in either XML or JSON format, content-type must be parsed and serialization and validation handled properly. The content types should be application/json or application/xml. While System.Xml libraries can be used to handle XML payloads, Newtonsoft.Json works best for handling JSON.

Sample code for parsing JSON/XML for PWRWaterChemistry:

Models.PWRWaterChemistryDataSetsRequestMessageType dataSetRequest = null;
Stream content = await Request.Content.ReadAsStreamAsync();
content.Seek(0, SeekOrigin.Begin);

// If validation passes, statusCode should be Created (201). NoContent (204) or OK (200) are alternatives.
switch (contentType.ToLower())
{
 case "application/json":
  string jsonSchemaFileName = HostingEnvironment.MapPath("~/Schema/PWRWaterChemistryDataSets/PWRWaterChemistryDataSets.draft-07.schema.json")
  //string jsonSchemaFileName = HostingEnvironment.MapPath("~/Schema/PWRWaterChemistryDataSets/ConvertedFrom_PWRWaterChemistry.xsd.json");

  try
  {
   using (StreamReader file = File.OpenText(jsonSchemaFileName))
   using (JsonTextReader schemaReader = new JsonTextReader(file))
   using (StreamReader contentReader = new StreamReader(content))
   using (JSchemaValidatingReader validatingReader = new JSchemaValidatingReader(new JsonTextReader(contentReader)))
   {
    validatingReader.Schema = JSchema.Load(schemaReader);
    validatingReader.ValidationEventHandler += ValidatingReader_ValidationEventHandler;

    JsonSerializer serializer = new JsonSerializer();
    dataSetRequest = serializer.Deserialize<Models.PWRWaterChemistryDataSetsRequestMessageType>(validatingReader);
   }
  }
  catch (Exception exc)
  {
   if (!errors.Any())
   {
    if (exc.InnerException == null)
    {
     errors.Add(new Models.ErrorType(10.0, exc.Message));
    }
    else
    {
     errors.Add(new Models.ErrorType(10.1, exc.InnerException.ToString()));
    }
   }
  }
  if (errors.Any())
  {
   //result = BadRequest($"PWRWaterChemistry - Message validation failed - " + string.Join(" - ", messages));
   responseMessage.Reply.Result = Models.ReplyTypeResult.FAILED;
   responseMessage.Reply.Error = errors.ToArray();
   result = ResponseMessage(Request.CreateResponse(HttpStatusCode.BadRequest, responseMessage));
  }
  else
  {
   responseMessage.Reply.Result = Models.ReplyTypeResult.OK;
   responseMessage.Payload = dataSetRequest.Payload;
   result = Created(Request.RequestUri, responseMessage);
  }
  break;

 case "application/xml":
  string mainSchemaFileName = HostingEnvironment.MapPath("~/Schema/PWRWaterChemistryDataSets/PWRWaterChemistryDataSetsMessage.xsd");
  string messageSchemaFileName = HostingEnvironment.MapPath("~/Schema/Message.xsd");
  string dataSetSchemaFileName = HostingEnvironment.MapPath("~/Schema/PWRWaterChemistryDataSets/PWRWaterChemistryDataSets.xsd");

  using (StreamReader mainSchemaFile = File.OpenText(mainSchemaFileName))
  using (StreamReader messageFile = File.OpenText(messageSchemaFileName))
  using (StreamReader dataSetFile = File.OpenText(dataSetSchemaFileName))
  {
   XmlSchemaSet schemaSet = new XmlSchemaSet();
   schemaSet.Add(XmlSchema.Read(messageFile, ValidationMessageCallBack));
   schemaSet.Add(XmlSchema.Read(dataSetFile, ValidationMessageCallBack));
   schemaSet.Add(XmlSchema.Read(mainSchemaFile, ValidationMessageCallBack));

   // Set the validation settings.
   XmlReaderSettings settings = new XmlReaderSettings
   {
    ValidationType = ValidationType.Schema,
    Schemas = schemaSet,
    ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings,
    IgnoreComments = true
   };
   settings.ValidationEventHandler += ValidationMessageCallBack;

   try
   {
    // Create the XmlReader object to process the content stream.
    using (XmlReader reader = XmlReader.Create(content, settings))
    {
     XmlRootAttribute dataSetsRoot = new XmlRootAttribute
     {
      ElementName = "PWRWaterChemistryDataSetsRequestMessage",
      IsNullable = true,
      DataType = "tns:PWRWaterChemistryDataSetsRequestMessageType",
      Namespace = "http://epri.com/powergeneration/2020/PWRWaterChemistryDataSetsMessage"
     };

     XmlSerializer serializer = new XmlSerializer(typeof(Models.PWRWaterChemistryDataSetsRequestMessageType), dataSetsRoot);
     dataSetRequest = serializer.Deserialize(reader) as Models.PWRWaterChemistryDataSetsRequestMessageType;
    }
   }
   catch (Exception exc)
   {
    if (!errors.Any())
    {
     if (exc.InnerException == null)
     {
      errors.Add(new Models.ErrorType(10.0, exc.Message));
     }
     else
     {
      errors.Add(new Models.ErrorType(10.1, exc.InnerException.ToString()));
     }
    }
   }

  }

  XmlSerializer serializeResponse = new XmlSerializer(responseMessage.GetType());
  using (Utf8StringWriter sw = new Utf8StringWriter())
  {
   if (errors.Any())
   {
    responseMessage.Reply.Result = Models.ReplyTypeResult.FAILED;
    responseMessage.Reply.Error = errors.ToArray();
    serializeResponse.Serialize(sw, responseMessage);
    result = ResponseMessage(new HttpResponseMessage(HttpStatusCode.BadRequest)
    {
     Content = new StringContent(sw.ToString(), Encoding.UTF8, contentType)
    });
   }
   else
   {
    responseMessage.Reply.Result = Models.ReplyTypeResult.OK;
    responseMessage.Header.CorrelationID = dataSetRequest.Header.CorrelationID;
    responseMessage.Payload = dataSetRequest.Payload;
    serializeResponse.Serialize(sw, responseMessage);

    result = ResponseMessage(new HttpResponseMessage(HttpStatusCode.Created)
    {
     Content = new StringContent(sw.ToString(), Encoding.UTF8, contentType)
    });
   }
  }
  break;

 default:
  result = ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, $"Content-Type '{contentType}' is not supported"));
  break;
}