diff --git a/sms-url-shortener/.gitignore b/sms-url-shortener/.gitignore new file mode 100644 index 00000000..ff5b00c5 --- /dev/null +++ b/sms-url-shortener/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/sms-url-shortener/.vscode/extensions.json b/sms-url-shortener/.vscode/extensions.json new file mode 100644 index 00000000..bb763007 --- /dev/null +++ b/sms-url-shortener/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions", + "ms-dotnettools.csharp" + ] +} \ No newline at end of file diff --git a/sms-url-shortener/.vscode/launch.json b/sms-url-shortener/.vscode/launch.json new file mode 100644 index 00000000..894cbe6a --- /dev/null +++ b/sms-url-shortener/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to .NET Functions", + "type": "coreclr", + "request": "attach", + "processId": "${command:azureFunctions.pickProcess}" + } + ] +} \ No newline at end of file diff --git a/sms-url-shortener/.vscode/settings.json b/sms-url-shortener/.vscode/settings.json new file mode 100644 index 00000000..8b7e7367 --- /dev/null +++ b/sms-url-shortener/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "azureFunctions.deploySubpath": "bin/Release/net6.0/publish", + "azureFunctions.projectLanguage": "C#", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.preDeployTask": "publish (functions)" +} \ No newline at end of file diff --git a/sms-url-shortener/.vscode/tasks.json b/sms-url-shortener/.vscode/tasks.json new file mode 100644 index 00000000..be13430d --- /dev/null +++ b/sms-url-shortener/.vscode/tasks.json @@ -0,0 +1,69 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clean (functions)", + "command": "dotnet", + "args": [ + "clean", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile" + }, + { + "label": "build (functions)", + "command": "dotnet", + "args": [ + "build", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean (functions)", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile" + }, + { + "label": "clean release (functions)", + "command": "dotnet", + "args": [ + "clean", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile" + }, + { + "label": "publish (functions)", + "command": "dotnet", + "args": [ + "publish", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean release (functions)", + "problemMatcher": "$msCompile" + }, + { + "type": "func", + "dependsOn": "build (functions)", + "options": { + "cwd": "${workspaceFolder}/bin/Debug/net6.0" + }, + "command": "host start", + "isBackground": true, + "problemMatcher": "$func-dotnet-watch" + } + ] +} \ No newline at end of file diff --git a/sms-url-shortener/Properties/launchSettings.json b/sms-url-shortener/Properties/launchSettings.json new file mode 100644 index 00000000..e74a001a --- /dev/null +++ b/sms-url-shortener/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "sms_url_shortener": { + "commandName": "Project", + "commandLineArgs": "--port 7106", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/sms-url-shortener/README.md b/sms-url-shortener/README.md new file mode 100644 index 00000000..8225b217 --- /dev/null +++ b/sms-url-shortener/README.md @@ -0,0 +1,57 @@ +--- +page_type: sample +languages: +- dotnet +products: +- azure +- azure-functions +- azure-communication-services +--- + +# Send a shortened URL link using Azure Communication Services SMS + +SMS messages are limited to 160 characters. This can pose limitations when sending links to customers and truncate the message for many reasons; the link can exceed 160 characters and/or contain query parameters for the user profile and cookie information, etc. Leverage the Azure URL shortener to help stay within the 160 character limit. + +## Pre-requisites + +- An active Azure subscription. [Create an account for free](https://azure.microsoft.com/free/?ref=microsoft.com&utm_source=microsoft.com&utm_medium=docs&utm_campaign=visualstudio). +- An active Azure Communication Services resource. For more information, see [Create an Azure Communication Services resource](https://learn.microsoft.com/azure/communication-services/quickstarts/create-communication-resource?tabs=windows&pivots=platform-net). +- An Azure Communication Services phone number. [Get a phone number](https://learn.microsoft.com/azure/communication-services/quickstarts/telephony/get-phone-number?tabs=windows&pivots=programming-language-csharp). You will need to [verify your phone number](https://learn.microsoft.com/azure/communication-services/quickstarts/sms/apply-for-toll-free-verification) so it can send messages with URLs. +- Deployed [AzUrlShortener](https://github.com/microsoft/AzUrlShortener). Click [Deploy to Azure](https://github.com/microsoft/AzUrlShortener/wiki/How-to-deploy-your-AzUrlShortener) button for quick deploy. + - [*Optional*] Deploy the [Admin web app](https://github.com/microsoft/AzUrlShortener/blob/main/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md) to manage and monitor links in UI. +- For this tutorial, we will be leveraging an Azure Function serve as an endpoint we can call to request SMS to be sent with a shortened URL. You could always use an existing service, different framework like express or just run this as a Node.JS console app. To follow this instructions to set up an [Azure Function for C#](https://learn.microsoft.com/azure/azure-functions/create-first-function-vs-code-csharp). + +## Run locally + +You will need to [verify your phone number](https://learn.microsoft.com/azure/communication-services/quickstarts/sms/apply-for-toll-free-verification) to send SMS messages with URLs. Once you have submitted your verification application, it might take a couple days for the phone number to be enabled to send URLs before it gets full verified (full verification takes 5-6 weeks). For more information on toll-free number verification, see [Apply for toll-free verification](https://learn.microsoft.com/azure/communication-services/quickstarts/sms/apply-for-toll-free-verification). + +1. Ensure to have the Azure Function Extension on Visual Studio. Click into the tab on the left side menu and initialize the project +2. Open a terminal and navigate to the repository directory +3. Run `cd sms-url-shortener` to get in the same directory as the function +4. Run `dotnet restore` which will install the dependencies for the sample +5. Update the values in the code to add your Azure Communication Services connection string, phone number and the endpoint for the URL shortener deployed as a pre-requisite. The sample is configured to take this values from environment variables within the `local.settings.json` file. If the file is not automatically created, you can create it and add the following values: + +```json + +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "", + "FUNCTIONS_WORKER_RUNTIME": "dotnet", + "ACS_CONNECTIONSTRING": "", + "ACS_PHONE_NUMBER": "", // Ex. +15555555555 + "URL_SHORTENER": "" // Ex. https:///api/UrlCreate + } +} + +``` + +6. In Visual Studio Code, click into the `SendSMSUrlShortener.cs` file and press `F5`. (Alternatively run `func host start` within the functions directory) + +Then using a tool like [Postman](https://www.postman.com/), you can test your function by making a `POST` request to the endpoint of your Azure Function. You will need to provide the phone number and URL as query parameters. For example, if your Azure Function is running locally, you can make a request to `http://localhost:7071/api/?phoneNumber=%2B15555555555&url=https://www.microsoft.com`. You should receive a response with the shortened URL and a status of `Success`. + +## Deploy to Azure + +To deploy your Azure Function, you can follow [step by step instructions](https://learn.microsoft.com/azure/azure-functions/create-first-function-vs-code-csharp?pivots=programming-language-dotnet#sign-in-to-azure). + +Once deployed, you can access the function through a similar method as you did when testing locally. You will need to provide the phone number and URL as query parameters. For example, if your Azure Function is deployed to Azure, you can make a request to `https://.azurewebsites.net/api/?phoneNumber=%2B15555555555&url=https://www.microsoft.com`. You should receive a response with the shortened URL and a status of `Success`. \ No newline at end of file diff --git a/sms-url-shortener/SendSMSUrlShortener.cs b/sms-url-shortener/SendSMSUrlShortener.cs new file mode 100644 index 00000000..23007cd5 --- /dev/null +++ b/sms-url-shortener/SendSMSUrlShortener.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Net.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System.Text.Json; +using Azure; +using Azure.Communication; +using Azure.Communication.Sms; +using System.Text; + +namespace Company.Function +{ + public class ShortenedUrl + { + public string ShortUrl { get; set; } + } + + public static class SendSMSUrlShortener + { + [FunctionName("SendSMSUrlShortener")] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request."); + + //Parse Query Parameters + string phoneNumberTo = req.Query["phoneNumber"]; + string urlToShorten = req.Query["url"]; + + //Get short URL from Azure URL Shortener + using var client = new HttpClient(); + var requestData = new + { + Url = urlToShorten + }; + var requestBody = JsonSerializer.Serialize(requestData); + var httpContent = new StringContent(requestBody, Encoding.UTF8, "application/json"); + string urlShortener = Environment.GetEnvironmentVariable("URL_SHORTENER", EnvironmentVariableTarget.Process); // Ex. https:///api/UrlCreate + var response = await client.PostAsync(urlShortener, httpContent); + var content = await response.Content.ReadAsStringAsync(); + var data = System.Text.Json.JsonSerializer.Deserialize(content); + var url = data.ShortUrl; + + //Send SMS with Azure Communication Services + string connectionString = Environment.GetEnvironmentVariable("ACS_CONNECTIONSTRING", EnvironmentVariableTarget.Process); + string phoneNumberFrom = Environment.GetEnvironmentVariable("ACS_PHONE_NUMBER", EnvironmentVariableTarget.Process); // Ex. +15555555555 + SmsClient smsClient = new SmsClient(connectionString); + SmsSendResult sendResult = smsClient.Send( + from: phoneNumberFrom, + to: phoneNumberTo, + message: "Here is your shortened URL: " + url + ); + + return new OkObjectResult(sendResult); + } + } +} diff --git a/sms-url-shortener/host.json b/sms-url-shortener/host.json new file mode 100644 index 00000000..beb2e402 --- /dev/null +++ b/sms-url-shortener/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + } +} \ No newline at end of file diff --git a/sms-url-shortener/sms-url-shortener.csproj b/sms-url-shortener/sms-url-shortener.csproj new file mode 100644 index 00000000..0915521b --- /dev/null +++ b/sms-url-shortener/sms-url-shortener.csproj @@ -0,0 +1,20 @@ + + + net6.0 + v4 + sms_url_shortener + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + +