Showing posts with label Azure. Show all posts
Showing posts with label Azure. Show all posts

Monday, March 6, 2023

How I Used ChatGPT to Respond to my Emails in 5 Minutes



What is ChatGPT?

ChatGPT is an advanced AI chatbot created by the folks at https://openai.com that can accurately reproduce human responses without prior training on the subject. The GPT stands for generative pre-trained transformer, which means the model is already trained. This is different than a traditional chat bot, see my example here https://www.fiveminutecoder.com/2020/12/create-faq-bot-using-microsoft-bot.html, that needs prior knowledge on a subject to create accurate response to the subject. ChatGPT can also be tuned for your business similar to a traditional chat bot system by training the system with additional information. 

What makes ChatGPT so impressive is the confident responses made by the bot. You ask it a question and it will respond with an in depth answer. It also allows for follow up questions giving a feeling of a natural conversation with a human. Many people, including developers, are seeing the power of this and questioning if their job is in danger. While the tool is impressive, it does not replace the extensive knowledge gained by troubleshooting an issue for hours. Also, while the chat bot is confident in its answers this does not mean it is right. 

To test out ChatGPT I decided to make an email response app to respond to all the junk mail I get. This will be an Azure function that runs in 5 minute intervals. It will use the Graph API to check my email for new emails then send the subject/body of the email to the ChatGPT API. I found that the subject helps with a better response. Once I get a response I will reply to the email and set it to read. I wanted to see how the API and bot worked. So I asked it how to create an integration to the API while the system got me started it's response were either incomplete or outdated. 

Let's use ChatGPT to setup Chat GPT

Since Chat GPT is known for giving detailed responses to questions including code, lets just ask the chatbot how to setup ChatGPT in C#. 




Great! this looks like it will work. During setup however, this was wrong. It looks like the NuGet Package was updated to support Open API GPT-3 which changed the code. The updated calls can be found on the GitHub site here: https://github.com/OkGoDoIt/OpenAI-API-dotnet.

Before writing the app, I wanted to test out the API using PostMan. I wanted to get a feel that the chatbot could respond so again, I asked the Chat GPT chatbot for how to use Postman.



Again this looked promising. I setup Postman as the instructions showed and got an error. There was no model parameter passed in the JSON file. The model is quite important the model is what chat bot to use.... I guess that is an 0/2 using Chat GPT to code. 



 In my tests I used two different models "Davinci", which is the most sophisticated, but the slowest, and "Curie" which is a faster model. Out of the two, Davinci came across angrier in it's responses so I decided to use Curie for this example. Here are some of the responses I got from Postman using a junk email, basically they are replies with false information, hilarious!

"I look forward to hearing from you.Please share this with your team and I would be happy to provide details on our past projects.Regards,Shailesh Srinivasan"

" If you could send me your skype ID that would be great. Thank you."

 " I will discuss project portfolio, your team's strengths and skill gaps, the job description and requirements, and how you will benefit from working with us. All of our consultants are seasoned professionals who have worked for fortune 500 companies and top-tier consulting firms. We are typically able to leverage our existing resources to find the right talent for you.I look forward to working with you.Regards,TedFor a free consultation please contact me at ted@TECHstaffing.com. I am happy to help you with your project staffing needs. Please visit our website at www.TECHstaffing.com for more information. Ted KolodziejskyPhone: 1-972-200-1791Email: ted@TECHstaff"
 
 " I would love to chat about the following topics:1. What is the best way to build a strong engineering team?2. What are your hiring challenges?3. What is your IT roadmap for the next 3-5 years?4. What",


From this experience, I dont see Chat GPT taking my job anytime soon, but still to complete the exercise, the auto response Azure Function can be found below.

Creating an Email Auto Responder in 5 minute

To begin, an Azure function must be created, the details to create an Azure function can be found in a previous post here, https://www.fiveminutecoder.com/2021/05/create-email-tracking-campaign-using.html. Also, a Graph API application must be created. Again, details about how to do this can be found in a previous post here, https://www.fiveminutecoder.com/2021/03/creating-azure-document-queue-for.html. For the app permissions, application permissions are necessary. Under the Graph API section, find the mail section. The app will need read/write permissions and send as permissions.




Next the following Nuget packages must be installed.

 Azure.Identity, Microsoft.Graph, Microsoft.Graph.Core, OpenAI


At the top of the function I added my using statements for the installed nuget packages.


using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Azure.Identity;

Next I defined my IDs necessary to access all the apps. This includes the Chat GPT API and Graph API.


//openAISecretKey
private string openAIKey = "";
//ID of the mailbox you want to auto reply from
private string userId = "user id of mailbox";
//ID of the tenant used
private string tenantId = "azure tenant"; 
//App id from created azure app
private string clientId = "registered app client id"; 
//Secret created for the app
private string clientSecret = "registered app secret"; 
//hold our graph context here for our calls
private GraphServiceClient graphService;

Inside the Run function, i setup the calls to get the unread emails then loop through the emails and respond to the email.


[FunctionName("CheckNewEmail")]
public async Task Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer,  ILogger log)
{
	try
	{
		log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
		graphService = GetGraphAPIClient();
		List newMessages = await GetNewEmails();
		log.LogInformation("found " + newMessages.Count);
		foreach(Message message in newMessages)
		{
			log.LogInformation("replying to " + message.Subject);
			string response = await GetChatGPTResponse(message.Subject, message.Body.Content);
			await SendEmail(message.Id, message.From, response);
			await UpdateToRead(message.Id);
			log.LogInformation("Reply successful");

		}
	}
	catch(Exception ex)
	{
		log.LogError(ex, ex.Message);
	}
}  


To instantiate the graph service I used the new Azure.Identity to create an authentication scope and then return the created service to be used throughout the application.


//Create the graph service client  that will be used to get and respond to emails
private GraphServiceClient GetGraphAPIClient()
{
	
	string[] scopes = new string[] {"https://graph.microsoft.com/.default" };
	// using Azure.Identity;
	var options = new TokenCredentialOptions
	{
		AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
	};

	ClientSecretCredential clientSecretCredential = new ClientSecretCredential(
		tenantId, clientId, clientSecret, options);

	GraphServiceClient graphClient = new GraphServiceClient(clientSecretCredential, scopes);
	return graphClient;
}


Using the newly created client, a call is made to the Graph API to get all the unread emails using the isRead filter.


//Graph API call to get all unread emails
private async Task> GetNewEmails()
{
	MessageCollectionResponse messages = await graphService.Users[userId].Messages.GetAsync((requestConfiguration) =>{
		requestConfiguration.QueryParameters.Filter = "isRead eq false";
	});
	
	return messages.Value;
}

Once all the emails are fetched, the subject and body are combined into one string and then sent to the Chat GPT API.


//The call to the Chat GPT end point
private async Task GetChatGPTResponse(string Subject, string Body)
{
	OpenAI_API.OpenAIAPI openai = new OpenAI_API.OpenAIAPI(openAIKey);

	//Create a request suitable for the Chat GPT API. It will remove an non readable characters that the API cannot read
	OpenAI_API.Completions.CompletionRequest completionRequest = new OpenAI_API.Completions.CompletionRequest(Subject + "." + Body, OpenAI_API.Models.Model.CurieText,150);

	// Send a request to the ChatGPT model
	OpenAI_API.Completions.CompletionResult response = await openai.Completions.CreateCompletionAsync(completionRequest);

	return response.Completions[0].Text;
}


With an AI generated response, I send an email using the Graph API to the original sender.


//Graph API call to send reply to email
private async Task SendEmail(string MessageId, Recipient RecipientEmail, string Response)
{
	Microsoft.Graph.Users.Item.Messages.Item.Reply.ReplyPostRequestBody reply = new Microsoft.Graph.Users.Item.Messages.Item.Reply.ReplyPostRequestBody
	{
		Message = new Message
		{
			ToRecipients = new List
			{
				new Recipient()
				{
					EmailAddress = new EmailAddress()
					{
						Address = RecipientEmail.EmailAddress.Address,
						Name = !String.IsNullOrEmpty(RecipientEmail.EmailAddress.Name) ? RecipientEmail.EmailAddress.Name : RecipientEmail.EmailAddress.Address
					}
				}
			},
		},
		Comment = Response,
		
	};

	await graphService.Users[userId].Messages[MessageId].Reply.PostAsync(reply);
}


Finally, I set the email to read so it is not picked up by the next call.


//Graph API call to update email to read
private async Task UpdateToRead(string MessageId)
{
	
	//only update the properties we want to update
	Message msg = new Message()
	{
		IsRead = true
	};
	await graphService.Users[userId].Messages[MessageId].PatchAsync(msg);
}


 
That's it! A function for responding to emails has been created and let the spammers be enthralled by the witty comebacks of the AI. To view the code, please visit my GitHub page here: https://github.com/fiveminutecoder/blogs/tree/master/ChatGPTEmail

UPDATE!!!!

With the general release of ChatGPT 3.5 the responses have changed significantly. We can give the bot a persona to respond to the emails which greatly changes the usefulness of the application. While I miss the snarky response of Davinci using ChatGPT 3.5 is the way to go.

To test this in Postman, all that needs to be done is update the body to include messages instead of prompt. You will see the messages section is an array. This is to help with persistence in responses. Also notice system and user role. System role allows me to tell the chat bot how to act, while the user role is the content to respond to.


{
    "messages":[
        {"role": "system", "content": "You are the assistant to the Director of IT. He does not want any meetings"},
        {"role": "user", "content": "email body here!!"}
    ],
    "temperature": 0.7,
    "max_tokens": 3250,
    "top_p": 1,
    "frequency_penalty": 0,
    "presence_penalty": 0,
    "model": "gpt-3.5-turbo-0301"
}


For the C# application instead of the completion endpoint, the ChatCompletion endpoint will be used, this is a quick change to handle the new message array.




var result = await api.Chat.CreateChatCompletionAsync(new ChatRequest()
{
	Model = Model.ChatGPTTurbo,
	Temperature = 0.7,
	MaxTokens = 50,
	Messages = new ChatMessage[] {
	new ChatMessage(ChatMessageRole.System, "You are the assistant to the Director of IT. He does not want any meetings")
		new ChatMessage(ChatMessageRole.User, "email body here!!")
	}
});

Tuesday, December 27, 2022

Auto Tagging Invoices Using Azure AI Cognitive Services in 5 Minutes


 


In a previous blog post we covered SharePoint Syntex for auto tagging invoices by using a content type, which can be found here SharePoint Syntex in 5 Minutes. Sometimes there needs to be more processing outside of SharePoint before the document can be uploaded or external systems must be accessed for metadata properties. This kind of functionality can become very complex when trying to use a Power App or Flow to accomplish this. Microsoft provides AI services for reading invoices that can be read and then used for the business logic that goes beyond what Syntex can do. These services are a consumption based API in Azure that allows uploading invoices for processing to return the same metadata results that can be found in SharePoint Syntex.


Setting up Azure

1) Create a Cognitive Services Plan






2) Once the cognitive services is created, there is a list of several services including form services. Selecting this will open up the form studio which allows for uploading and reviewing the forms the service will be used for training.




3) Since this is a 5 minute tutorial, I will be using the prebuilt invoice recognizer.


4) Since this is a prebuilt model it comes with several examples already loaded. By clicking the "Analyze" button, the invoice will highlight all the points of interest and assign it metadata. This screen is verry similar to the SharePoint Syntex screen seen in my previous blog SharePoint Syntex in 5 Minutes


5) To make sure this predefined model works for your invoices, select the upload in the top left corner and then upload a sample invoice.




6) Finally, a storage account for the invoice service to access documents must be created. Our invoice service must be able to access the files they must be made available. For this demo, I will be making my blob storage available to the internet. For security reasons DO NOT DO THIS IN PRODUCTION. For a production environment you will want to setup a network for your AI service that is connected to your blob storage for secure access. For details on how to create a storage account, see my pervious blog post Create an Azure Document Queue for Loading and Tagging SharePoint Documents - Part 1


Consuming the service

For this example, I created a WPF app to display our uploaded invoice and its associated properties. To begin, 4 NuGet packages must be installed.

These 2 are needed for the form recognition service, the form recognizer API reading the invoice and the Azure storage blob API for exposing the invoice.


Azure.AI.FormRecognizer
Azure.Storage.Blobs

The other 2 NuGet packages needed are for drawing our invoice. PdfLibCore will be used to convert the PDF into an Image and System.Drawing.Common will be used for drawing the image. It is important to note that this example was done on Windows. System.Drawing may not be Linux/Mac compatible.


System.Drawing.Common
PdfLibCore

The app layout is a simple grid system made up of 3 rows. One for uploading an invoice, the other for displaying it's properties, and then the bottom row for any errors while uploading.


<window height="1000" mc:ignorable="d" title="Five Minute Invoice Tagger" width="1600" x:class="FiveMintueInvoiceTagger.MainWindow" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:FiveMintueInvoiceTagger" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <grid>
        <grid.columndefinitions="">
            <columndefinition width="500"></columndefinition>
            <columndefinition width="1100"></columndefinition>
        </grid>
        <grid.rowdefinitions="">
            <rowdefinition height="50"gt;</rowdefinition>
            <rowdefinition height="750"gt;</rowdefinition>
            <rowdefinition height="750"gt;</rowdefinition>
        </grid>
        <stackpanel grid.column="0" grid.row="0">
        <label content="Select an invoice...">
        <button click="UploadFile_Click" content="Select Invoice">
        </button></label></stackpanel>
        <image grid.column="0" grid.row="1" height="800" name="InvoiceImage" width="450">
    <datagrid grid.column="1" grid.row="1" height="800" name="DocumentProperties" width="1050">
    <label grid.column="0" grid.row="2" name="ErrorMsg">
    </label></datagrid></image></grid> 
</window>

Next, an object is needed to hold our invoice properties for displaying the results. This class has 3 items, the Field's name, the Field's Value, and the confidence score that the API grabbed the right information.


public class InvoiceProperty
{
	//Field name found on invoice
	public string Field {get;set;}
	//Field value
	public string Value {get;set;}
	//How confident AI is that field value is correct
	public string Score {get;set;}
}

References to the NuGet packages must be added to the project, along with some other using statements for displaying the invoice image.


using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using Azure;
using Azure.AI.FormRecognizer.DocumentAnalysis;
using Azure.Storage.Blobs;
using PdfLibCore;
using PdfLibCore.Enums;

A click event is added to the upload button to grab the invoice and process the request. This method is async so a loading screen should be added. Since this a 5 minute application it has been omitted.


private async void UploadFile_Click(object sender, RoutedEventArgs e)  
{  
	try
	{
		ErrorMsg.Content = "";

		//we only want pdf invoices
		Microsoft.Win32.OpenFileDialog openFileDlg = new Microsoft.Win32.OpenFileDialog(); 
		openFileDlg.Filter = "Pdf Files|*.pdf";
		// Launch OpenFileDialog by calling ShowDialog method
		Nullable result = openFileDlg.ShowDialog();
		// Get the selected file name and display in a TextBox.
		// Load content of file in a TextBlock
		if (result == true)
		{
			//Upload to azure blob so Azure AI can access file
			string invoicePath = await UploadInvoiceForProcessing(openFileDlg.FileName);

			//perform Invoice tagging
			Task> invoicePropertiesTask = GetDocumentProperties(invoicePath);

			//Convert PDF to image so we can view it next to properties
			UpdateInvoiceImage(openFileDlg.FileName);

			//Wait for Azure to return results, set it to our data grid
			DocumentProperties.ItemsSource = await invoicePropertiesTask;

		}
	}
	catch(Exception ex)
	{
		ErrorMsg.Content = ex.Message;
	}
}

In our button event, there are 3 functions called One for uploading the invoice to Azure, one for processing the invoice, and one for converting the image. Our upload function will upload the invoice to Azure Blob Storage to make the invoice available to the Azure Form Recognizer Service. Again, in a production environment make sure your blob storage is not publicly available. 


private async Task UploadInvoiceForProcessing(string FilePath)
{
	string cs = "";
	string fileName = System.IO.Path.GetFileName(FilePath);
	Console.WriteLine("File name {0}", fileName);
	//customer is the name of our blob container where we can view documents in Azure
	//blobs require us to create a connection each time we want to upload a file
	BlobClient blob  = new BlobClient(cs, "invoice", fileName); 

	//Gets a file stream to upload to Azure
	using(FileStream stream = File.Open(FilePath, FileMode.Open))
	{
		var blobInfo = await blob.UploadAsync(stream);
		
	}
	
	return "blob base storage url" + fileName;
}

Next the invoice URL is passed to the Form Recognizer Service for processing


private async Task> GetDocumentProperties(string InvoicePath)
{
	
	List invoiceProperties = new List();

	//Endpoint and key found in Azure AI service
	string endpoint = "ai service url";
	string key = "ai service key";
	AzureKeyCredential credential = new AzureKeyCredential(key);
	DocumentAnalysisClient client = new DocumentAnalysisClient(new Uri(endpoint), credential);

	//create Uri for the invoice
	Uri invoiceUri = new Uri(InvoicePath);

	//Analyzes the invoice
	AnalyzeDocumentOperation operation = await client.AnalyzeDocumentFromUriAsync(WaitUntil.Completed, "prebuilt-invoice", invoiceUri);
	AnalyzeResult result = operation.Value;

	//iterate the results and populates list of field values
	for (int i = 0; i < result.Documents.Count; i++)
	{
		AnalyzedDocument document = result.Documents[i];
		foreach(string field in document.Fields.Keys)
		{
			DocumentField documentField = document.Fields[field];
			InvoiceProperty invoiceProperty = new InvoiceProperty()
				{
				  Field = field,
				  Value = documentField.Content,
				  Score = documentField.Confidence?.ToString()
				};

				invoiceProperties.Add(invoiceProperty);
			}
	}

	return invoiceProperties;
}


While the invoice is being processed, the application will convert the PDF to an image to be displayed in the application. The form recognizer service returns references for the PDF to draw the bounding boxes of the data found which could be used to draw onto the image.


 private void UpdateInvoiceImage(string FilePath)
{
	using(var pdf = new PdfDocument(File.Open(FilePath, FileMode.Open)))
	{
		//for this example we only want to show the first page
		if(pdf.Pages.Count > 0)
		{
			var pdfPage = pdf.Pages[0];

			var dpiX= 600D;
			var dpiY = 600D;
			var pageWidth = (int) (dpiX * pdfPage.Size.Width / 72);
			var pageHeight = (int) (dpiY * pdfPage.Size.Height / 72);
		
			var bitmap = new PdfiumBitmap(pageWidth, pageHeight, true);                                

			pdfPage.Render(bitmap, PageOrientations.Normal, RenderingFlags.LcdText);
			BitmapImage image = new BitmapImage();
			image.BeginInit();
			image.StreamSource = bitmap.AsBmpStream(dpiX,dpiY);
			image.EndInit();
			InvoiceImage.Source = image;
		}
		
	}
}


Once this is completed your application will display the invoice with the properties found with an application created in 5 minutes.




To view the full code, please visit the Five Minute Coder GitHub here: Five Minute Invoice Tagger


Thursday, May 26, 2022

How to Use CI/CD for Azure App Services Using Azure Dev Ops

 What is Dev Ops?

Before looking at how to configure Azure Dev Ops it is important to understand what DevOps is. Like the word, DevOps is combing development with operations. The goal is to get updates and features to the end user faster and in a more automated fashion. In a water fall method, gathering requirements, development, testing, and deployment are handled in an IT bubble. The end user isn't part of the process until the end. How is the team supposed to know if they are on the right track? What if something was interpreted wrong? These types of issues can cause lengthy hold ups and serious budget issues. Agile tries to rectify this by working with the business owners more regularly by deploying smaller features at a faster pace. The goal is to get feedback quicker and pivot to to that feedback. 




This is where Azure DevOps comes in. In order to get code out quickly, automating processes is necessary. We can use Azure to build our code, run test cases, package our code, and deploy it our web server all without human intervention. Additional workflows can be added to alert approvers schedule deployments and setup different environments can also be utilized


Continuous Integration
Continuous integration is the first step of our DevOps automation. Usually a team is made up of more than one developer. Each developer will be working on a feature or part of a feature. When they are done, they must integrate their code with the rest of the code. Using the source control called Git developers can request the code be integrated into the rest of the code with a pull request. This can alert a manager or other developers to review the code then approve the merge. This will merge the code back into the larger code set and the developer can pull a new feature. To make sure the code integrates correctly, a developer will create a series of unit tests to validate the code passes the requirements. If the tests do not pass the merged updates will not deploy the updates.


Continuous Delivery
Continuous delivery is the automation of deploying the code to an environment. Once the code is accepted into the branch a release is created and a user is alerted to initiate the deployment of code. This is especially useful for production ready code, it allows for a person to intervene and review the code before it is deployed to a production site. Typically once the review is over the user will approve and the code is deployed.


Continuous Deployment

Continuous deployment removes the human interaction from the delivery side. This is useful for you dev and staging sites and will allow for developers to freely deploy their code into a site seamlessly to see changes immediately once all tests pass from the integration side. It is assumed here that test cases and unit tests have been thoroughly designed and tested otherwise it is very easy to introduce a bug without someone knowing.



Setting up branches

When setting up branches, I prefer the Gitflow strategy that can be referenced here https://www.gitkraken.com/learn/git/git-flow

What makes this setup different from other git strategies is the fact that there are 3 main branches and then branches are created from these. Other styles will create a new feature branch for new changes and create releases from this instead of merging into one main branch for production. 

3 main branches

1) Main branch - production code 
2) dev branch - development of new features
3) Hotfix branch - fixes for production.

From the dev branch you would create your feature or release branches, from the hotfix branch you would create branches for fixing production bugs, and additional branches should be made from dev branch or hotfix branch then merged into these branches. Having a dev branch, we are able to create an integration site for  testing and approved before deployment. Because multiple features and releases can be merged into the dev branch before making it to production you must make sure your dev branch is production ready before the merge, which will require a code freeze before promoting to production. This is the main difference between continuous deployment and continuous delivery.




Creating a CI/CD solution in 5 minutes

To begin using Azure Dev ops we need something to deploy and interact with our site. To begin, we will create a new code repository for source control, I called mine "ci_cd blog". When creating your repository make sure to choose GIT and not TFVC. 





With our repository created we can clone it to our local machine.

I will then create a simple web application along with a test project.

dotnet new mvc -o BasicApp


Before I commit my code, I will want to to setup my continuous integration pipeline. This is the pipeline section



From within the pipeline section, I will select Pipelines. The pipeline is where I will create the build/test/deploy for my application. Before an application is deployed the pipeline will run several commands to ensure our code is ready to deploy. If it does not the pipeline fails and our code will not be deployed to the application. To create a pipeline, click "Pipelines" and then select "Create Pipeline"






Next we must select where our code is currently stored. Our code for this example will be stored within Azure Dev Ops, so I will select "Azure Repos Git". Azure Dev Ops also integrates with GitHub or other git repositories if those are being used. Notice the bubble that says YAML, this is the language used to develop the pipeline.



Once the Azure Repos Git is selected, the pipeline must be tied to a repo. The next step is to select the repo created earlier.



Finally, we will select how we will configure the pipeline. There are several options to start from that come preconfigured for different applications and languages. For this post, I will be using the Starter Pipeline.




The pipeline should now look like this.
From the image above, we see a drop down with the branch this pipeline is saved to, the trigger to run the pipeline, and the steps the pipeline will take. Since we are deploying a web app we can delete the current steps. We will leave the VM image as ubuntu for the blog but if you app is running windows, change this to windows-latest to build for a Windows machine. 


//TODO: pool code


A basic pipeline should consist of at least 5 steps. Our project should build, test, publish, copy published files, then publish those files to the pipeline. In the menu to the right, we see several tasks, here we can select the tasks above to implement in our pipeline. Simply search for what you want to accomplish and the task will help you build the basics for each task. Intellisense in the pipeline will also give you clues to advanced settings the GUI doesn't offer. In the tasks we will use the .NET Core task to build, test, and publish our files. 






Once you click Add at the bottom, our YAML file will fill in with the appropriate syntax. 

//TODO build code

Our test and publish are the same, steps. Publish offers more options, we will use the defaults.

//TODO: test code


//TODO publish code


Test will run our test projects and if it fails will stop the pipeline and publishing. This keeps the published site clean and free from any mistakes.


Now that the project is built and tested, the published files get copied to the staging directory. We do this to keep folder clean, an advanced setting is "CleanTargetFolder" this way the code is copied to an empty directory and old/bad DLLs, files, or zips are not published. We will use the built in variables for the directories.




//TODO: copy file Yaml


For the fifth step we can do one of two things. First, we could just deploy directly to Azure from the pipeline. I only recommend this solution for dev environments, the reason being is it does not give you control over what is deployed to your environment. If the pipeline builds it will deploy automatically. This removes any approval control or deferral of deployment. Also, it does not give you the option to roll back to a previous build. You would need to run the entire pipeline again to revert your deployment which can be costly.



I prefer to use releases to deploy my code. These can still be continuous and without intervention, but it gives more control over what happens when our code deploys. To create a release, a pipeline container must be created. To do this, search "publish" to find the "Publish build artifacts" in the task pane. For the task details, the default values will work.




//TODO: publish artifacts YAML


With the pipeline setup, all the builds can be viewed along with test results, status, and deployment times. Each build can be drilled into to view branch changes that kicked the build off.






Setup deployment Releases

1) Create new release pipeline

2) Select app service deployment

3) Name deployment stage

4) Click job/task in stage

5) select step.

6) connect to subscription

Adding Code to the repository

1) open a project in VSCode

Before connecting to the project, a git repository must be created locally, open the termianal in VSCode and type the following commands:

2) run "Git Init" to create empty git repository

3) run "git add ." to add all items to git repository

4) run "git commit -m "Initial Commit" "  this will commit all items to be ready to push to repository

Now, open the devops repository to find the clone button. This will give the repository URL to push our project too

5) In devops go to your repository and find the clone button and copy URL







Using the Clone button can result in lost code we do not want to pull the empty project, we want to push what we have to the empty project. To do this type the following commands in the VSCode terminal.

6) "git remote add origin <url>" 

*Be sure to replace <url> with the url copied from DevOps

Once connected to the remote origin run "git push" to push your committed files. You should receive a prompt to login using your email/password. If not, you can create an account credentials by selecting the little man in the corner and going to alternate credentials.

7) "git push"

 






When the project is pushed, our repository will automatically kick off and start publishing our website. With that, a successful DevOps pipeline is created.


C#, Azure, Azure DevOps, DevOps, Continuous Development, Continuous Integration, CI/CD, Branch Management, GIT

Thursday, January 27, 2022

Creating a CosmosDB Azure Function in 5 Minutes

What is an Azure Function?

When talking about cloud solutions, there are many choices. A web application can be deployed using a typical virtual machine or the PaaS (Platform as a Service) solution of an Azure web app. Sometimes a quick function is all you need, and a large application is overkill. This is where an Azure Function comes into play.   With an Azure Function, we can implement a web end point that can be called by other Azure services or create a typical http endpoint. These functions can can be coded directly in the browser for a quick way to roll up an endpoint. In addition to this, there are integration features where you can setup triggers, integrate data sources, and choose the output option for the function all without code. 

Azure Functions work well with a microservice architecture. Functions are well suited for keeping your code to one domain and avoiding a sprawling application into other domains. They are meant to fit a very specific action and it makes it difficult to go beyond that specific action. Also, with the ability to trigger the function from various methods setting up an event bus that will react and trigger the function when necessary.

Example of a function setup.

Create an Azure Function in 5 minutes

Setup CosmosDB

In this example, we are going to create a function that returns some data from a CosmosDB. For our function to be configured using integrations, we need to make sure we are using a SQL backed Cosmos DB. We can still use Azure Functions to connect to other NoSQL databases, but it will be the traditional connection, such as BSON.
 


In the newly created Cosmos DB, we need to create a new container. For this example, a simple Product database will be used. Our container will use the following model, and we will use Product ID for our partition key.


public class GetTestItem
{
    public string id {get;set;}
    public string ProductId {get;set;}
    public string manufacturer {get;set;}
    public string description {get;set;}
}





Here is an example of some data inserted into our newly created database.


Setup Azure Function

With the data source created, the next step is to setup the Azure function. 


With our app created, next we need to setup a trigger. For this example, I am going to call it GETALL for demonstration purposes. Proper REST Url would be "Products". When creating the function, we are also going to select "Development in Portal", and "Anonymous" for authorization. Finally, this will be an HTTP trigger.

In our new function, click "Integration". This will bring up our menu to configure the function.



In our integration screen, a new input needs to be created. This will bring up a message for creating a connection to the Cosmos DB database. Just follow the steps to select your Cosmos DB connection. The "Document Parameter Name" is the name we will use to reference in our function.








Going back to our integration screen, select the function. Now pull up the run.csx. This is where we will write our code to manipulate the Cosmos DB data. The biggest changes will be in the constructor of our function.



public static async Task Run(HttpRequest req, ILogger log)

In the constuctor, we will add a collection that will represent our SQL collection. You will notice in the code below, that the connection string now has an IEnumerable name Products to represent the input we declared in in the integration screen. Once this is added our function will connect to the database and pull all items from our container with no code. The example below will just write back all the data as a response to the request.


#r "Newtonsoft.Json"

using System.Net;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;



public class Product
{
    public string id {get;set;}
    public string ProductId {get;set;}
    public string manufacturer {get;set;}
    public string description {get;set;}
}

public static async Task Run(HttpRequest req, 
                IEnumerable Products,
                ILogger log)
{
    try
    {
    log.LogInformation("C# HTTP trigger function processed a request.");

    return new OkObjectResult(Products);
    }
    catch (Exception ex)
    {
                log.LogError($"Couldn't insert item. Exception thrown: {ex.Message}");
                return new StatusCodeResult(StatusCodes.Status500InternalServerError);
    }
}


That's it. Our function app is ready and will show all items for our Cosmos DB with very little coding.

Bonus! Setup an additional call to get item by id

It is pretty impractical to display all items from our database. So how can we add a query to our integration to pull the data we need. If you notice, on the bottom of our input integration screen there is a SQL query section. To setup a query function, let's go back and create a new function GetById.  An additional function can be added from the Azure portal in the function settings. We will use the same settings from our GET app We will have it be anonymous and use the designer. Once the new function is created, go to the integration screen and create the input. fill out the database name and collection name from the previous step. This time, a SQL query will be added at the bottom. The query will select the top item where id = id


SELECT TOP 1 * FROM d where d.id = {id}



In our SQL query you will find the parameter {id} which will represent the data we are wanting to pass to the query. So to get our id parameter we will want to update the request's route to include the id. To do this, open up the Trigger in our integration menu to get the edit screen for adding a route.




In the route template we will add our parameter {id} to the route. For this example I will have GetByID/{id} to show what I am actually doing, in a production environment this route would be something like Products/{id}.





Now in our new function app, we will open the function and go to our run file. I will add the new parameters for ID and my SQL database to the constructor. Since i have a query setup, my SQL collection will now be filtered, in this case contain the one item with the id passed to the function.



#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public class Product
{
    public string id {get;set;}
    public string ProductId {get;set;}
    public string manufacturer {get;set;}
    public string description {get;set;}
}

public static async Task Run(HttpRequest req, 
string id,
 IEnumerable Products,
  ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");
    log.LogInformation(id.ToString());
    log.LogInformation(Products.First().ProductId);
}

That's it, we have setup two Azure functions for pulling data from Cosmos DB without ever opening Visual Studio.

Tuesday, July 27, 2021

Data Caching in 5 minutes using Redis in Azure

What is caching?

Before I get into how to use Redis to cache data, it is important to understand what is caching and why we need it. To describe caching simply it is a way to store data in a more accessible way to increase performance. Probably the most common example is how a browser caches a site's images, CSS, and JavaScript files. It does this by storing the data locally to speed up web pages by not having to go out to the server and download them on every call.

Storing large files like images makes sense, but how can that thought process be applied to our back end systems? As business logic gets more complex, systems are making more calls to databases and sub systems than ever before. These calls and data manipulations take time, and by caching the results the applications performance will dramatically increase as the application doesn't have to waste time fetching data and manipulating the results every time someone needs it.

When to use caching.

So when should I use caching? If the data is not live how can we trust it? Why not just call the database every time if SQL is fast? These are all good questions, and caching is not a one size fit all answer. Usually caching is done in memory since it is faster than disk i/o , but this means storage is limited and more expensive.  We need to ask ourselves if it makes sense to cache this item. Below are a few questions that we need to ask ourselves before adding the complexity of caching.

  1. Will caching speed up the call? Why add an extra fail point if we do not see any improvements.

  2. Does the data change often? If the data is constantly changing our cache will become outdated very quickly and that would remove the point of the cache.

  3. Is the data accessed often? Memory is expensive, why store something that no one sees.

  4. Do I have more than one server? This is more of a how do I cache my data question. Having more than 1 server adds complexity to the system and if you cache directly to the server it means caching of the data will be out of sync and could cause issues for users between calls.
If the above questions are a yes, then caching is a good choice to lessen the burden on our other systems. 

How do I cache my data?

It has been decided caching is necessary to increase performance, how should the data be cached? Below is a diagram of how a cache flow should work.



If a small service is needing to cache data, then using MemoryCache is an easy way to start. Memory Cache will not scale out with your system and it will become quickly unusable. Our in memory cache's will not be synced so things like session data will be lost when a user hits a different server.

Using a separate service for caching will allow us to scale our system outwards and continue to keep a stateless web site. What is a stateless website? This means our backend does not have knowledge of previous actions. So if we are trying to keep track of a user's session we would use a session Id  with all calls to track the user' session independent of each transaction. This becomes extremely useful when dealing with microservices. Each service is developed independently usually with it's own database, so by using a session id our service can call the session service to pull user's session information and validate the call.

This is where Redis comes into play. Redis is a caching database that can be deployed and scaled independently of  the application. It is important to remember caching should not replace your persistent database storage, it should be treated as a temporary repository. 

Creating a Redis Caching database in 5 minutes.

In this blog, we will be using an instance of Redis deployed to Azure. Setup is easy search for "Azure Cache for Redis" and select your instance size.

Azure Setup



Using Redis Cache in 5 minutes

For this example we have two use cases to use Redis Cache. One to track our session and one to optimize a "complex" database call and cache it in Redis to improve performance.


To start, a web application is needed for pulling information. For this example, I will be reusing the database and tables creating in my previous blog post for email tracking which can be found here:  https://www.fiveminutecoder.com/2021/05/create-email-tracking-campaign-using.html. Once the databases are setup, the next step is to create the web application.


dotnet new mvc --n "RedisCacheExample"


For the site I have two pages, the Home page and the page to view the summary of email campaigns. These pages are pretty basic, so for brevity of the blog they will be omitted. If you would like to see the code please visit the repository at the end of the blog. The home page has a pseudo login page to create our session. I am not authenticating to anything just collecting the session data before moving to the campaign screen. The campaign screen requires a valid session id otherwise it will redirect to the home page to create a session.




For the session, I am using Redis only. Sessions are temporary and once a user leaves a website or is inactive the session needs to expire.  I have it configured for 10 minutes. If there is 10 minutes of inactivity a new session is required to continue. For a more secure site, long polling JavaScript can auto sign out a user by checking session status every minute or so. Below you will find the postback that create our session data in Redis.
 


[HttpPost, ValidateAntiForgeryToken]
public async Task Index(SessionModel Model)
{
	//creates a unique session id
	Guid sessionId = Guid.NewGuid();

	//This is a 5 minute project so we are going to code in controller
	//Create connection to Redis
	using(ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(""))
	{

		//Get database, this returns default database
		var db = redis.GetDatabase();

		//add session information to Redis with a 10 minute expiration time
		await db.StringSetAsync(sessionId.ToString(), JsonConvert.SerializeObject(Model),TimeSpan.FromMinutes(10));

	}

	//Session created, now go to campaigns
	return RedirectToAction("Index", "Campaigns", new { SessionId=sessionId.ToString()});
}


With our user information collected and our session created we will now move to the campaigns page. This is where I make my SQL call to pull in the campaigns. This example might not be the most performance hungry SQL call but it is complex enough that caching helps with performance.



async Task> GetCampaigns()
{
	//Replace with your sql connection string
	string cs = "";

	//List to hold our campaigns
	List types = new List();

	//connct to sql
	using(SqlConnection connection = new SqlConnection(cs))
	{
		//Open our SQL connection
		connection.Open();

		//complex SQL query worthy of being cached
		using(SqlCommand cmd = new SqlCommand(@"select Count(dbo.campaign_tracking.Campaign) EmailsOpened, dbo.campaigns.CampaignId, dbo.campaigns.Subject from dbo.campaign_tracking
												right join  dbo.campaigns on dbo.campaign_tracking.Campaign = dbo.campaigns.CampaignId
												Group By  dbo.campaign_tracking.Campaign, dbo.campaigns.CampaignId, dbo.campaigns.Subject", connection))
		{

			//execute query
			SqlDataReader reader = await cmd.ExecuteReaderAsync();
			while(await reader.ReadAsync())
			{
				//object for storing campaign information
				CampaignTypes type = new CampaignTypes()
				{
					CampaignId = reader["CampaignId"]!= null ? reader["CampaignId"].ToString() : "invalid id",
					Subject = reader["Subject"] != null ? reader["Subject"].ToString() : "Subject not found",
					EmailCount = reader["EmailsOpened"] != null ? Convert.ToInt32(reader["EmailsOpened"]) : 0
				};

				types.Add(type);
			}
		}

		//close connection
		connection.Close();
	}

	//return our list of campaigns
	return types;
}

In our Campaigns controller, I have setup a basic Cache-Aside pattern for pulling our data. What you see in our action is check if the data exists in our Redis Cache Database, if it doesn't get the data from SQL and update Redis. 



public async Task Index(string Sessionid)
{
	//Create a connection to Redis
	using(ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(""))
	{
		//Get Redis Database
		var db = redis.GetDatabase();

		//set viewmodel so they it is not null for our view
		CampaignsModel campaigns = new CampaignsModel()
		{
		  CampaignTypes = new List(),
		  Session = new SessionModel()  
		};

		//Check is session id exists in redis
		if(await db.KeyExistsAsync(Sessionid))
		{                   
			//session id exists in Redis check for campaign cache in redis
			if(await db.KeyExistsAsync("CampaignTypes"))
			{
				//campaigns are cached, get data from redis
				var campaignCache  = await db.StringGetAsync("CampaignTypes");
				campaigns.CampaignTypes = JsonConvert.DeserializeObject>(campaignCache);
			}
			else
			{
				//campaigns are not cached, get campaigns from SQL
				campaigns.CampaignTypes = await GetCampaigns();

				//save campaigns to Redis for future use
				await db.StringSetAsync("CampaignTypes", JsonConvert.SerializeObject(campaigns.CampaignTypes), TimeSpan.FromMinutes(5));
			}


			//pull session information from Redis
			var session = await db.StringGetAsync(Sessionid);
			campaigns.Session = JsonConvert.DeserializeObject(session);
			campaigns.Session.Id = Sessionid;

			//A refresh of the page should extend our session open by 10 minutes
			await db.KeyExpireAsync(Sessionid, TimeSpan.FromMinutes(10));

			return View(campaigns);
		}
		else{
			//session expired
			return RedirectToAction("Index", "Home");
		}
	}
}

The result is a basic table that shows our list campaigns and the number of emails opened from the campaign.


Clone the project


You can find the full project on my GitHub site here https://github.com/fiveminutecoder/blogs/tree/master/RedisCacheExample


C#, dotnet, dotnet core, .NET, MVC, Razor, Redis, Cache, Caching, Cache Database, Microsoft, .Net Core, .Net 5, .Net Framework, SQL
C#, C sharp, machine learning, ML.NET, dotnet core, dotnet, O365, Office 365, developer, development, Azure, Supervised Learning, Unsupervised Learning, NLP, Natural Language Programming, Microsoft, SharePoint, Teams, custom software development, sharepoint specialist, chat GPT,artificial intelligence, AI

Cookie Alert

This blog was created and hosted using Google's platform Blogspot (blogger.com). In accordance to privacy policy and GDPR please note the following: Third party vendors, including Google, use cookies to serve ads based on a user's prior visits to your website or other websites. Google's use of advertising cookies enables it and its partners to serve ads to your users based on their visit to your sites and/or other sites on the Internet. Users may opt out of personalized advertising by visiting Ads Settings. (Alternatively, you can opt out of a third-party vendor's use of cookies for personalized advertising by visiting www.aboutads.info.) Google analytics is also used, for more details please refer to Google Analytics privacy policy here: Google Analytics Privacy Policy Any information collected or given during sign up or sign is through Google's blogger platform and is stored by Google. The only Information collected outside of Google's platform is consent that the site uses cookies.