-1

I have an Azure Function in C# which I have almost built. It is setup to receive data from SendGrid, verify the data and then write the data to Cosmos. Here is the code:

namespace SendGrid.CosmosLogging
{
    public static class SendGrid_Logging
    {
        [FunctionName("SendGrid_Logging")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
            HttpRequest req,
            [CosmosDB(
                databaseName: "portal-dev",
                collectionName: "sendgrid",
                ConnectionStringSetting = "CosmosDbConnectionString")]
            IAsyncCollector<dynamic> documentsOut,
            ILogger log)
        {

        try
        {
            log.LogInformation("SendGrid C# HTTP trigger function started processing a request.");
            
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var headers = req.Headers;
            var sgbody = req.Body;

            //Check validity
            CheckValidity(log, documentsOut, sgbody);   //req.Body - has to be Stream object

        }
        catch (Exception e)
        {
            log.LogInformation("Error validating SendGrid event, error is: "+ e.ToString());
            throw;
        }
        
    }

    private static async void CheckValidity(ILogger log, IAsyncCollector<dynamic> documentsOut, Stream sgBody)
    {
        // Add a JSON document to the output container.
        await documentsOut.AddAsync( new
        {
            //Write the sendgrid body only to the Cosmos container
            sgBody
            //Body = "This is something"
            //name = name
        });
        responseMessage =
            "The SendGrid HTTP triggered function executed successfully processing Id: ";//${doc_id}
        log.LogInformation(responseMessage);
    }
    
}

}

The problem is the line writing the sgBody during the CheckValidity method. This never comes through to the Cosmos container. However if it is replaced with something like:

            // Add a JSON document to the output container.
            await documentsOut.AddAsync( new
            {
                //Write the sendgrid body only to the Cosmos container
                //sgBody
                Body = "This is something"
                //name = name
            });

Then I get an output that looks like this in Cosmos:

{
    "Body": "This is something",
    "id": "e65c2efe-79d2-4997-b1b2-33833dbf14ce",
    "_rid": "2WwUAKT2MsvdKgAAAAAAAA==",
    "_self": "dbs/2WwUAA==/colls/2WwUAKT2Mss=/docs/2WwUAKT2MsvdKgAAAAAAAA==/",
    "_etag": "\"12015ff1-0000-1a00-0000-61679d8c0000\"",
    "_attachments": "attachments/",
    "_ts": 1634180492
}

So my question is, how do I get the entire contents of the sgBody variable to write? There is something I am clearly missing here (I aren't a C# developer, but am trying my best!).

EDIT:

The format I am wanting to get as output to the Cosmos container is a properly formatted JSON body, something like:

{
   "body":{
      "timestamp":1631141801,
      "sg_template_name":"Receiver - NonProd",
      "ip":"149.72.246.163",
      "tls":1,
      "smtp-id":"<2MhvBnobRN-KQNdFF9cO4w@geopod-ismtpd-5-0>",
      "email":"somewhere@something.com",
      "response":"250 2.6.0 <2MhvBnobRN-KQNdFF9cO4w@geopod-ismtpd-5-0> [InternalId=300647724419, Hostname=SY4P282MB3809.AUSP282.PROD.OUTLOOK.COM] 289934 bytes in 0.131, 2154.683 KB/sec Queued mail for delivery",
      "sg_message_id":"2MhvBnobRN-KQNdFF9cO4w.filterdrecv-75ff7b5ffb-vm78l-1-61393FA4-43.0",
      "event":"delivered",
      "sg_event_id":"ZGVsaXZlcmVkLTAtMjI4MDI0MTAtMk1odkJub2JSTi1LUU5kRkY5Y080dy0w",
      "sg_template_id":"d-04096fb423674bdf8870dfc92eec944f",
      "category":[
         "develop",
         "Something"
      ]
   },
   "id":"0c4143fa-d5a2-43e8-864f-82f333ace3cd",
   "_rid":"SL81APS-4Q21KQAAAAAAAA==",
   "_self":"dbs/SL81AA==/colls/SL81APS-4Q0=/docs/SL81APS-4Q21KQAAAAAAAA==/",
   "_etag":"\"7700726c-0000-1a00-0000-61393fb20000\"",
   "_attachments":"attachments/",
   "_ts":1631141810
}
blobbles
  • 251
  • 2
  • 10
  • 1
    Change `Stream sgBody` to `string sgBody` in and do `Body = sgBody` in `CheckValidity` method. Call `CheckValidity` method with `CheckValidity(sgEventValid, log, documentsOut, requestBody)` – Chetan Oct 14 '21 at 03:39
  • Ahhh, thanks @Chetan! However, now my output looks something like: { "sgBody": "[{\"category\":[\"develop\",\"...tls\":1}]\r\n", "id": "6807b2bc-7d45-4bef-aaec-e2f19c568503", "_rid": "2WwUAKT2MsthKwAAAAAAAA==", "_self": "dbs/2WwUAA==/colls/2WwUAKT2Mss=/docs/2WwUAKT2MsthKwAAAAAAAA==/", "_etag": "\"1301cdda-0000-1a00-0000-6167a7b30000\"", "_attachments": "attachments/", "_ts": 1634183091 } So the whole thing comes out as one long string, instead proper CR/LF's it has \r\n's. – blobbles Oct 14 '21 at 03:47
  • 2
    Far too much code for this site. Please see [mre]. You've posted far too much code in comparison to the content of your question. – Ken White Oct 14 '21 at 04:23
  • Ahhh OK @KenWhite - have reduced the code to only indicate what I am doing. Sorry! – blobbles Oct 14 '21 at 04:31
  • To fix this `instead proper CR/LF's it has \r\n's`. You can refer to [What is a quick way to force CRLF in C# / .NET?](https://stackoverflow.com/questions/841396/what-is-a-quick-way-to-force-crlf-in-c-sharp-net), [String.Replace CRLF with '\n'](https://stackoverflow.com/questions/9400959/string-replace-crlf-with-n) and [how to replace characters as newline break](https://github.com/Microsoft/vscode/issues/13459#issuecomment-252908869) – Ecstasy Oct 14 '21 at 06:41

1 Answers1

1

OK, I managed to do this myself. This will be particularly helpful to anyone using an Azure function to consume SendGrid events, which are then written to a Cosmos database, but obviously useful in other situations too. I have also added in headers, which come through as an iHeaderDictionary type, which is a bit hard to work with when you want standard JSON.

namespace SendGrid.CosmosLogging
{
    public static class SendGrid_Logging
    {
        [FunctionName("SendGrid_Logging")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
            HttpRequest req,
            [CosmosDB(
                databaseName: "portal-dev",
                collectionName: "sendgrid",
                ConnectionStringSetting = "CosmosDbConnectionString")]
            IAsyncCollector<dynamic> documentsOut,
            ILogger log)
        {

        try
        {
            log.LogInformation("SendGrid C# HTTP trigger function started processing a request.");
            
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var headers = req.Headers;
            var sgbody = req.Body;

            //Create a new dictionary and fill it with the items in the existing headers. This is done due to needing to
            //convert the headers to a dynamic object for the Cosmos write. Can only easily stringify a Dictionary object,
            //but not the specific HttpRequest header object, so have to convert.
            Dictionary<string,string> ihHeaders = new Dictionary<string,string>
            {};
            foreach (var item in req.Headers)
            {
                ihHeaders.Add(item.Key,item.Value);
            }
            string jsonHeaders = JsonConvert.SerializeObject(ihHeaders,Formatting.Indented);

            //Use these dynamic objects for writing to Cosmos
            dynamic sendgriddata = JsonConvert.DeserializeObject(requestBody);
            dynamic sendgridheaders = JsonConvert.DeserializeObject(jsonHeaders);

            //Check validity
            CheckValidity(log, documentsOut, sendgriddata, sendgridheaders);   //req.Body - has to be Stream object

        }
        catch (Exception e)
        {
            log.LogInformation("Error validating SendGrid event, error is: "+ e.ToString());
            throw;
        }
        
    }

    private static async void CheckValidity(ILogger log, IAsyncCollector<dynamic> documentsOut, dynamic sgBody, dynamic sgHeaders)
    {
        // Add a JSON document to the output container.
        await documentsOut.AddAsync( new
        {
            //Write the sendgrid body only to the Cosmos container
            headers = sgHeaders
            body = sgBody
        });
        responseMessage =
            "The SendGrid HTTP triggered function executed successfully processing Id: ";//${doc_id}
        log.LogInformation(responseMessage);
    }
    
}

This gives a nicely formatted JSON output in Cosmos:

JSON output

blobbles
  • 251
  • 2
  • 10