C# Web API to consume Azure SQL Server Data - c#

I would really appreciate it if you can point me to the right place or right idea. I have not done C# in ages and I am not sure what I am doing anymore.
Basically I have an Azure based MSSQL database that I need to read data from and return that data in JSON format for my iPhone app. Azure APIs do not allow you to read directly from the DB but you can build a .Net web API and use that - so that is where I went to.
I have gotten the first part to work. In that I can make a http request and Azure responds fine. I have also built the DB and tables and I have tested all that and it works. I have also tested the query and it runs well formatted JSON.
However, I need to pass the data in the data reader as an http response and I cannot figure that part out. Attached are the two vanilla files..one that makes the dummy http request and the other is the db connector file.
So in short, in the Function1.cs file I need to go from
:req.CreateResponse(HttpStatusCode.OK, "Hello" + name); to
:req.CreateResponse(HttpStatusCode.OK, );
First File: Function1.cs
namespace getOutageAlerts
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
if (name == null)
{
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
name = data?.name;
}
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
**:req.CreateResponse(HttpStatusCode.OK, "Hello" + name);**
}
}
}
Second File: DBConnector.cs
using System;
using System.Data.SqlClient;
public class DBConnector
{
public DBConnector()
{
SqlConnection getalertsConnection = new SqlConnection("Server=tcp:xxxxxxxxx.database.windows.net,1433;Initial Catalog=mckMobileAppsDB;Persist Security Info=False;User ID=xxxxxxx;Password=xxxxxxxx;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
SqlCommand cmd = new SqlCommand();
SqlDataReader reader;
cmd.CommandText = "SELECT Date_Created As Date, Incident_Number AS Incident, Flash_Summary AS Summary, Service, Business_Impact AS Impact, Incident_Status AS Status from OutagesMobileAppNotifications FOR JSON PATH, Root('Alerts')";
cmd.CommandType = CommandType.Text;
cmd.Connection = getalertsConnection;
getalertsConnection.Open();
reader = cmd.ExecuteReader();
// Data is accessible through the DataReader object here.
getalertsConnection.Close();
}
}

You're question is phrased a little awkwardly, plus the bad formatting doesn't help. However, it looks as though you want to return JSON from an Azure Serverless Function. Based on this assumption I'll try to answer.
To return JSON you will want to add the Newtonsoft.Json package. This is simple, at the top of your function add the following:
#r "Newtonsoft.Json"
using System.Net;
using System.Text;
using Newtonsoft.Json;
using System.Linq;
(Edit: Added using statement for System.Linq so the Select is available for the data reader.)
In the DBConnector you wrote I would convert the reader to an array of objects and then serialize that result to JSON. (I have truncated your list of fields to jsut 2 for brevity... you'll obviously need to add the whole list.)
var incidents = dataReader.Select(m => new { m.Date, m.Incident }).ToList();
var jsonResult = JsonConvert.SerializeObject(incidents, Formatting.Indented);
Then return the JSON.
return new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(jsonResult, Encoding.UTF8, "application/json")
};
(I am trying to do this from memory... and I haven't done Azure Functions in about a year... so the exact code may differ.)
Additionally... since you haven't worked with C# in a while and maybe never with Azure Functions, it might be more simple to keep everything in the same function. By this I mean you don't have to have the second class/file... you can just put all the SQL work in your main method. I would normally consider this a bad practice, but in the short term it might ease your efforts.

Related

RestRequest Parameters adding logic

I am having some issues finding information about adding some logic field in my RestRequest using V 107. I am trying to add a filter to my GET query
dl_document_indexed_date gt '2020-12-07T08:30:42.483Z'
There are a few other queries in the call which i am using Dictionary<string, string> to store them, and it works great however it only works if i am looking for something equal to, as adding it to the parameters it seems by default its equal to and i am not finding any way to add any other logic, gt/ge/lt/le etc. using the older version i would just append the url adding the logic i need, but i am not seeing a way to append the url either. Looking over their documentation i either missed it, cant find it, or its not there. Any help would be greatly appreciated! My method looks like this
public static async Task<string> GET_API(String RequestUrl, string RequestObject, Dictionary<string, string> parameters)
{
var request = new RestRequest(RequestObject);
var options = new RestClientOptions(RequestUrl)
{
ThrowOnAnyError = true,
Timeout = -1
};
var client = new RestClient(options);
client.Authenticator = new OAuth2AuthorizationRequestHeaderAuthenticator("Bearer " + TokenManager.GetAccessTokenString("TRN"));
foreach (var parameter in parameters)
{
request.AddQueryParameter(parameter.Key, parameter.Value);
}
var response = await client.GetAsync(request);
return response.Content.ToString();
}
I send the BaseURL , the RequestObject would be table i am calling in the base URL, and my dictionary item contains the Field name, and the field values that i am dynamically generating on another method that would append the string. and example would be
parameters.Add("dl_document_name", "TableA");
which would append the URL with dl_document_name eq 'TableA'
it would call the API after i add the OAuth Token i create and return the data i need and send it back. or another option i guess could be appending the string with the logic i need to return the data
You should use OData, it's easy to implement and it has different kind of filters, you also can set which filters are usable and which aren't.
https://www.odata.org/
I figured out a work around, if i only have one i can add it to the first parameter and adding the filter as the first key, which will work unless i have multiple conditions that are not eq
parameters.Add("filter","dl_document_indexed_date gt '2020-12-07T08:30:42.483Z'");

How I can access all fields of a http-request in an azure function (parse JSON in C#)?

The microsoft azure is for me complete new programming topic.
Programming base language is C#.
I have to use Azure Funtion Http trigger from logic App (when new e-mail arrive).
I give all possible data from the incoming e-mail in the log app to
to the azure function call.
Then I had first the problem nothing (no data) was requestable from the HttpRequest req.
I found out that C# .NetCore3.1 has problem with JSON-Object and get null-object back.
Then I build the project with .NetCore2.1 and it worked generally.
I get correct informations like req.Host and i see a vaild JSON-object in data after such a call
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
But I don't know how I an parse the data like (from, to, subject, content, attachment,..) from the JSON-object.
I have tried many samples i found , but i fond nothing what worked, e.g. get null, exception,..
Can please someone here post a small code snippet that works for azure function compiled with VS2017/2019 .NetCore2.1..
And other question is there eventually a way get the MimeMessage-obj direct from the http-request?
then i can get the informations direct from that object, but helping me with one of this both solution
where fine.
Otherwise I spilit the the JSoN Structure on myself to get the the informations on a very awkward way.
So I hope someone can help me.
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation($" host : {req.Host}");
string name = req.Query["name"];
// read the contents of the posted data into a string
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
// use Json.NET to deserialize the posted JSON into a C# dynamic object
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
For Example i had such a Json-content (is in dtaa after thes 2 lines of code):
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
Json-Content for a short email to parse (other request could differn in more ore lesser paramters(fields))
{ {
"Conversion Id": "AAQkADRmZDAwMTMwLTI3MTMtNGI0Ny1iMzFiLTQzYWJiZDY0YWI1ZQAQAE2OZM06t0uQqpd9MuONKNQ=",
"From": "test1#testxyz.com",
"Has Attachment": false,
"Internet Message Id": "<AM0PR07MB44178000A0B37882D6BC01D99ACC0#AM0PR07MB4417.eurprd07.prod.outlook.com>",
"Is Html": false,
"Is rRad": false,
"Message Id": "AAMkADRmZDAwMTMwLTI3MTMtNGI0Ny1iMzFiLTQzYWJiZDY0YWI1ZQBGAAAAAAAvlJZPJxnGS4f6YWxh7zGsBwBnLziSnuG8R6h5C2SVmlYlAAHViSWpAACAWl3JfUo4SI7D5g-MgfEiAAJiPJQeAAA=",
"Received Time": "2020-12-09T14:05:06+00:00",
"Subject": "test1",
"To": "test2#testxyz.com",
"emailBody": "1234\r\n",
"importance": "normal"
}
}
For this requirement, you need to create an inner class in your function code to parse the request body to an object. Please refer to my code below:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace FunctionApp2
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
//dynamic data = JsonConvert.DeserializeObject(requestBody);
EmailItem data = JsonConvert.DeserializeObject<EmailItem>(requestBody);
log.LogInformation(data.From);
log.LogInformation(data.Has_Attachment.ToString());
return new OkObjectResult("completed");
}
}
public class EmailItem
{
public string From { get; set; }
public Boolean Has_Attachment { get; set; }
//Add other fields which you want, please note add all of the fields which your request may contains in this inner class.
}
}
As you mentioned other request could differn in more ore lesser paramters(fields), so please add all of the fields which your request may contains in the EmailItem class.
By the way, the code I provided above was test with .net core 2.1 as you required. But I think the code can also work in .net core 3.1

How to send data to Service Bus Topic with Azure Functions?

I have default C# based HTTP Trigger here and I wish to send data "Hello Name" to Service Bus Topic (already created). I'm coding at portal.
How to do it Service Bus output binding?
This is not working. Any help available?
-Reference missing for handling Service Bus?
-How to define Connection of service bus? Where is Functions.json
-How to send a message to service bus?
//This FunctionApp get triggered by HTTP and send message to Azure Service Bus
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Company.Function
{
public static class HttpTriggerCSharp1
{
[FunctionName("HttpTriggerCSharp1")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")] // I added this for SB Output. Where to define.
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
// I added this for SB Output
return responseMessage;
}
}
}
Firstly, there are two bindings to send data to service bus. Firstly is what you show, using the return binding, after install two packages Microsoft.Azure.WebJobs.Extensions.ServiceBus and WindowsAzure.ServiceBus, then you will be able to send data. And you could not do it cause your function type is IActionResult and you are trying to return string(responseMessage).
So if you want to send the whole responseMessage, just return new OkObjectResult(responseMessage);, it will work. And the result would be like below pic.
And if you want to use return responseMessage; should change your method type to string, it will be public static async Task<string> RunAsync and result will be below.
Another binding you could refer to below code or this sample.
[FunctionName("Function1")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")]
public static async Task RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[ServiceBus("myqueue", Connection = "ServiceBusConnection")] MessageSender messagesQueue,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
byte[] bytes = Encoding.ASCII.GetBytes(responseMessage);
Message m1 = new Message(bytes);
await messagesQueue.SendAsync(m1);
}
How to define Connection of service bus? Where is Functions.json
In the local you should define the connection in the local.settings.jon, you could use any name with the connection, then in the binding Connection value should be the name you set in the json file. And cause you are using c#, so you could not modify the function.json file, there will be a function.json file in the debug folder. So you could only change the binding in the code.
Hope this could help you, if you still have other problem , please feel free to let me know.
Make sure you first install Microsoft.Azure.WebJobs.Extensions.ServiceBus NuGet package. Then make sure you are using it in your project:
using Microsoft.Azure.WebJobs.Extensions.ServiceBus;
Make sure you clean and build the project to make sure you have no errors.
Then you need to make sure you have a "ServiceBusConnection" connection string inside your local.settings.json file:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"ServiceBusConnection": "Endpoint=sb://...",
}
}
Which you can get if you go to Azure portal -> Service bus namespace -> Shared access policies -> RootManageSharedAccessKey -> Primary Connection String. Copy and paste this connection string inside "ServiceBusConnection". You can also use the Secondary Connection String as well.
Note: Service bus queues/topics have shared access policies as well. So if you don't want to use the Service bus namespace level access policies, you can create one at queue/topic level, so you your function app only has access to the queue/topic defined in your namespace.
Also if you decide to publish your function app, you will need to make sure you create a configuration application setting for "ServiceBusConnection", since local.settings.json is only used for local testing.

How to register Redirect URL to webhook

Hi i've been strugling to register redirect url to Kaizal webhook (its chat app from microsoft like a whatsapp), I've create asp.net API Controller to register it in my Kaizala webhook. What i want to do is whenever there is a messege or Job created on Kaizala Group, my redirect url will capture the data and save the data to my database. I think i already meet all the requirement from here https://learn.microsoft.com/en-us/kaizala/connectors/webhookvalidaton, my Get method is already returning validationToken from Header. But it always return this error when i try to register my redirect url "message": "Callback URL couldn't be validated. StatusCode = InternalServerError",.
https://kaizala007.blog/2017/12/30/exploring-kaizala-webhooks/comment-page-1/#comment-3776
From this documentation he said i need support both get and post method, already add both but my post method still doesn't do anything just return statuscode.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
namespace KaizalaTestApi.Controllers
{
public class KaizalaCallbackController : ApiController
{
// GET: KaizalaCallback
[HttpGet]
public string Get()
{
string x = Request.Headers.GetValues("validationToken").FirstOrDefault().ToString();
return x;
}
[HttpPost]
public HttpResponseMessage Post()
{
//string validationToken = Request.Headers.GetValues("validationToken").FirstOrDefault().ToString();
//string data = new StreamReader(Request.Content).ReadToEnd();
string str = Request.Content.ReadAsStringAsync().Result;
return Request.CreateResponse(HttpStatusCode.OK);
}
}
}
Why im getting that error ? do i need to do any specific thing in my post method to able make it works ?
It is not the fact that you are returning int that it worked.
The first attempt failed because of an error when invoking
Request.Headers.GetValues("validationToken").FirstOrDefault().ToString();
Most likely a null reference exception, because you tried to access the token in the header, while linked documentation states
Kaizala will generate a validation token and send a GET request to your webhook with a query parameter “validationToken”.
emphasis mine
which was avoided in the updated used of
HttpContext.Request.Query["validationToken"]
I would suggest updating the syntax to
[HttpGet]
public IActionResult Get() {
var validationToken = Request.Query["validationToken"];
return Content(validationToken);
}
Of course you can refactor to add validation code for the expected query string parameter.
I suggest the above because there is no guarantee that the token will be a valid int based on the documentation. And even if it is, this approach would be more flexible if they ever do change away from using integers as tokens.
The requirements simply states
Your service is supposed to return the validation token (received in request) in the response body as a plain-text
With that, only return exactly what was sent. Do not try to make any changes to it.
Already found how to make it works, it seem we need to return int in our get method that we get from query paramstring instead of string, i using asp net core 2.2 to make my callback url. Here take a look at my sample code to save in the database too
[HttpGet]
public int Get()
{
int validationToken = Convert.ToInt32(HttpContext.Request.Query["validationToken"]);
return validationToken;
}
[HttpPost]
public void Post([FromBody]JObject rawBody)
{
using (TheoDBContext db = new TheoDBContext())
{
var jsonData = rawBody["data"];
string name = rawBody["fromUserName"].ToString();
string title = jsonData["title"].ToString();
Kaizala newKaizala = new Kaizala();
newKaizala.Name = name;
newKaizala.Question = title;
newKaizala.Answer = "yes";
db.Kaizala.Add(newKaizala);
db.SaveChanges();
}
}

Shopify and Private Applications

I have created a private app for use on my website (The idea is the website will act as the front end using shopify's api to connect to the store).
When I create the application, if I edit it there is an example url which looks like this:
https://926b44aa1af222f2089ffa4988bc146b:49a28c7ccfa3b29b2a9af8019b2723cc#kudos-6.myshopify.com/admin/products.json
f you click that link, you can see the products I have set up.
Now, if I take that URL and put it into my website using jQuery $.get, I get an error stating Invalid API key or access token (unrecognized login or wrong password which is really infuriating as you can imaging.
I tried to add a header using the ApiPassword which looks like this:
"X-Shopify-Access-Token": "49a28c7ccfa3b29b2a9af8019b2723cc"
But I get the same error.
Now this is probably due to not allowing cross origin headers, etc. So I created a function in c#:
public string Get()
{
using (var wc = new WebClient())
{
return wc.DownloadString("https://926b44aa1af222f2089ffa4988bc146b:49a28c7ccfa3b29b2a9af8019b2723cc#kudos-6.myshopify.com/admin/products.json");
}
}
Which I expected to work, but it doesn't. I get the same error.
So I tried:
public string Get()
{
using (var wc = new WebClient())
{
wc.Headers.Add("X-Shopify-Access-Token", "926b44aa1af222f2089ffa4988bc146b");
return wc.DownloadString("https://926b44aa1af222f2089ffa4988bc146b:49a28c7ccfa3b29b2a9af8019b2723cc#kudos-6.myshopify.com/admin/products.json");
}
}
and guess what, I get the same error.
So I tried using the secret:
public string Get()
{
using (var wc = new WebClient())
{
wc.Headers.Add("X-Shopify-Access-Token", "e63081c23cd05b64205dbdb670d60241");
return wc.DownloadString("https://926b44aa1af222f2089ffa4988bc146b:49a28c7ccfa3b29b2a9af8019b2723cc#kudos-6.myshopify.com/admin/products.json");
}
}
Same error.
Can anyone tell me why?
So, for anyone else having this problem, here is the solution:
First, add these lines to your WebApiConfig:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.Remove(config.Formatters.XmlFormatter);
Then, use your Api password as the access token like this:
public string Get()
{
using (var wc = new WebClient())
{
wc.Headers.Add("X-Shopify-Access-Token", "49a28c7ccfa3b29b2a9af8019b2723cc");
return wc.DownloadString("https://kudos-6.myshopify.com/admin/orders.json");
}
}
and that is it. If you do it like that, it will work.
I had a same issue and fixed it by adding a header information.. I am accessing API through windows service..
So I have created a custom app in Shopify and
I was getting authentication error as I was only passing in API Key and APi password.
You need to attach Admin API token to the header and also use API key and API password.
var restSharpClient = new RestClient(_shopifyBaseStoreURL);
restSharpClient.Authenticator = new HttpBasicAuthenticator(_privateAppAPIKey, _privateAppPassword);
restSharpClient.AddDefaultHeader("X-Shopify-Access-Token", "shpXXXX");
I also added in.when making a GET / PUT request..
restSharpRequest.Method = Method.GET;
restSharpRequest.AddHeader("content-type", "application/json");
restSharpRequest.AddHeader("Accept", "application/json");
restSharpRequest.AddHeader("X - Shopify - Access - Token", "shxxxxxxxxxxxxxxxx");
You can retrieve products using jQuery like this:
$.ajax({
type: 'GET',
url: 'http://yourshop.myshopify.com/products.json',
dataType: 'jsonp',
success: function(data) {
console.log(data);
}
});
You should STOP and quit this pattern while you are ahead. Do you not see the folly of putting your API token with access to your shop in a public website? There is nothing stopping any Tom, Dick or Jane from molesting your shop and ruining your e-commerce day.
Instead, make yourself a front-end that uses an App Proxy to access your backend goods. At least that is secure, and ensures no one can mess with your shop.

Categories

Resources