-
-
Save BobPusateri/84b03f9c8877fdefd546edd29ae3a8a8 to your computer and use it in GitHub Desktop.
Sample code for redirection service using Azure Functions & Cosmos DB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Net; | |
using Microsoft.Azure.Cosmos; | |
using Microsoft.Azure.Functions.Worker; | |
using Microsoft.Azure.Functions.Worker.Http; | |
using Microsoft.Extensions.Configuration; | |
namespace URLRedirect | |
{ | |
public class Redirect | |
{ | |
private static Lazy<CosmosClient> _lazyClient = new Lazy<CosmosClient>(InitializeCosmosClient); | |
private CosmosClient _cosmosClient => _lazyClient.Value; | |
private static CosmosClient InitializeCosmosClient() { | |
// initialize configuration object from Function App environment variables | |
IConfigurationRoot config = new ConfigurationBuilder() | |
.AddEnvironmentVariables() | |
.Build(); | |
return new CosmosClient( | |
// get endpoint and token from configuration object | |
accountEndpoint: config["COSMOS_ENDPOINT"], | |
authKeyOrResourceToken: config["COSMOS_KEY"] | |
); | |
} | |
// Define Azure Function "DoRedirect" as an anonymous GET function with one parameter, "redirectKey" | |
[Function("DoRedirect")] | |
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route ="{redirectKey}")] HttpRequestData req, | |
string redirectKey) | |
{ | |
// default target URL. If a result can't be found or an error otherwise occurs, redirect to here. | |
string targetUrl = "https://www.bobpusateri.com"; | |
ItemResponse<RedirectObj> itemResult = null; | |
// Cosmos DB lookups are case-sensitive, so do a lowercase comparison. | |
string redirectKeyLower = redirectKey.ToLower(); | |
PartitionKey partitionKey = new PartitionKey(redirectKeyLower); | |
bool recordHit = false; | |
Container container = _cosmosClient.GetContainer("<db_name>", "<container-name>"); | |
// query Cosmos DB for a record matching the redirection key | |
Task<ItemResponse<RedirectObj>> ItemReadTask = container.ReadItemAsync<RedirectObj>(redirectKeyLower, partitionKey); | |
// create response object & add some caching settings to it | |
HttpResponseData response = req.CreateResponse(HttpStatusCode.Found); | |
response.Headers.Add("cache-control", "max-age=0, public, stale-if-error=86400"); | |
try{ | |
itemResult = await ItemReadTask; | |
RedirectObj i = itemResult.Resource; | |
// if a match is found and it's enabled, save the target URL and note a hit occurred | |
if(i.enabled) { | |
targetUrl = i.targetUrl; | |
recordHit = true; | |
} | |
} catch { | |
// I'm just absorbing errors at this time - planning to add logging later | |
} | |
// update hit data | |
try { | |
// if a hit occurred, update the hit counter and date for this record | |
if(recordHit) { | |
List<PatchOperation> updateOperations = new() { | |
// increment use count by 1, create if it doesn't exist | |
PatchOperation.Increment("/use_count", 1) | |
}; | |
if(itemResult!.Resource.last_used_utc < DateTime.UtcNow) { | |
// add/update last used date (date only) if it's greater than the last entry | |
updateOperations.Add(PatchOperation.Add("/last_used_utc", DateTime.UtcNow.Date)); | |
} | |
// update the document | |
_ = container.PatchItemAsync<RedirectObj>( | |
id: redirectKeyLower, | |
partitionKey: partitionKey, | |
patchOperations: updateOperations | |
); | |
} | |
} catch { | |
// intentionally blank | |
} | |
// add redirect header to response object & return to client | |
response.Headers.Add("location", targetUrl); | |
return response; | |
} // Run() | |
} // class Redirect | |
} // EOF |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace URLRedirect { | |
// object used for serializing to/from Cosmos DB | |
public class RedirectObj { | |
// Lookup ID for the redirect. This is the value shown in the URL | |
public string id {get; set;} | |
// is this redirect enabled? | |
// if not, function will pretend it doesn't exist at all | |
public bool enabled {get; set;} | |
// url to redirect users to | |
public string targetUrl {get; set;} | |
// number of times the redirect has been used | |
public int use_count {get; set;} | |
// last time the redirect has been used | |
public DateTime last_used_utc {get; set;} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// example JSON document for a redirect entry in Cosmos DB | |
{ | |
"id": "zombocom", | |
"targetUrl": "https://zombo.com/", | |
"enabled": 1 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment