We have a azure function where we are calling few API's. its an eventhub trigger function. we have a scenario where the api which we are calling goes into schedule maintenance very often. we want to build a re try mechanism where we are re trying for x number of times and on failure we want to disable the function on runtime. is there a way we can do that in the function app itself?
Thank you Melissa and Ian Kemp. Posting your suggestions as answer to help other community members.
Use the below code to disable the Azure Function
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
Check the SO for further information.
Related
I am using environment variables in Azure App Service in my ASP.NET Web API. I am able to read them successfully when the API is deployed to the Azure App service, however that kills my local debugging when I run the project because locally I am reading from appsettings.json (Also appsettings.json is used by the api when I push to our azure dev environment app service)
I am trying to not have the cosmos url and the cosmos key visible in appsetting.json, and I'm also trying to pull from the app service configuration environment variables because this app will be pushed to a dev repo and a pipeline will move it to an appservice in test, then prod, so I'm trying to have one set of logic that will simply read the environment variables in the app service for the particular url I hit(dev,test,prod etc...)
If you look at the image below of the configuration in app service I can read cosmos_key and cosmos_url in startup cs. However to run locally I have to comment out this code from startup.cs that uses the account and key variables that obtain the value from Environment.GetEnvironment.....
Can someone point me in the right direction? Thanks for your time.
private static async Task<LogDBService> InitializeCosmosClientInstanceAsyncLogs(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["logContainerName"];
var account = configurationSection["Account"];
//var account = Environment.GetEnvironmentVariable("cosmos_uri");
var key = configurationSection["Key"];
//var key = Environment.GetEnvironmentVariable("cosmos_key");
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/matchId");
var cosmosDbService = new LogDBService(client, databaseName, containerName);
return cosmosDbService;
}
Appsettings.json
Appsettings.Development.json
startup.cs
using ODSApiCore.Auth;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using ODSApi.BusinessServices;
using ODSDatabase.DBServices;
using ODSApi.Extensions;
using ODSApi.Middleware;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Unos.Foundation;
using System;
namespace ODSApi
{
public class Startup
{
private IConfiguration Configuration;
public Startup(IConfiguration configuration)
{
Configuration = Requires.NotNull(configuration,nameof(configuration));
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ODSApi", Version = "v1" });
});
//Add unos auth services
services.AddApigeeJwtBearerAuthentication(Configuration).AddAuthorization(options =>
{
options.AddPolicy(PredictiveAnalyticsAuthorizationPolicy.Name, PredictiveAnalyticsAuthorizationPolicy.Policy);
});
services.AddSingleton<ILogDBService>(InitializeCosmosClientInstanceAsyncLogs(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
services.AddSingleton<ITimeToNextOfferDBService>(InitializeCosmosClientInstanceAsy
ncTimeToNextOffer(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
services.AddSingleton<IMatchRunDBService>(InitializeCosmosClientInstanceAsyncMatchRun(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
services.AddSingleton<IMortalitySlopeDBService>(InitializeCosmosClientInstanceAsyncMortalitySlope(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
services.AddSingleton<IGraphParamsDBService>(InitializeCosmosClientInstanceAsyncGraphParams(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
services.AddScoped<IMatchRunBusinessService, MatchRunBusinessService>();
services.AddApplicationInsightsTelemetry();
services.ConfigureCors();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ODSApi v1"));
}
if (env.IsStaging())
{
}
else
{
app.UseHsts();
}
app.UseStaticFiles();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ODSApi v1"));
app.UseCors("CorsPolicy");
//Use this Middleware prior to app.UseEndpoints....
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseMiddleware(typeof(ExceptionHandlingMiddleware));
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
/*********************************
* These are the database services
* that are injected into the ioc
* container. There are 5 Services
* Based on the 5 collections in ODS
* The partition key in Cosmos is /matchId
* except for the graph params table. The
* partition key in ODS must be a field in
* database.
* ******************************/
private static async Task<LogDBService> InitializeCosmosClientInstanceAsyncLogs(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["logContainerName"];
var account = configurationSection["Account"];
//var account = Environment.GetEnvironmentVariable("cosmos_uri");
var key = configurationSection["Key"];
//var key = Environment.GetEnvironmentVariable("cosmos_key");
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/matchId");
var cosmosDbService = new LogDBService(client, databaseName, containerName);
return cosmosDbService;
}
private static async Task<TimeToNextOfferDBService> InitializeCosmosClientInstanceAsyncTimeToNextOffer(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["ttnoContainerName"];
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/matchId");
var cosmosDbService = new TimeToNextOfferDBService(client, databaseName, containerName);
return cosmosDbService;
}
private static async Task<IMatchRunDBService> InitializeCosmosClientInstanceAsyncMatchRun(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["passThroughContainerName"];
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/matchId");
var cosmosDBService = new MatchRunDBService(client, databaseName, containerName);
return cosmosDBService;
}
private static async Task<IMortalitySlopeDBService> InitializeCosmosClientInstanceAsyncMortalitySlope(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["mortalitySlopeContainerName"];
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/matchId");
var cosmosDbService = new MortalitySlopeDBService(client, databaseName, containerName);
return cosmosDbService;
}
private static async Task<IGraphParamsDBService> InitializeCosmosClientInstanceAsyncGraphParams(IConfigurationSection configurationSection)
{
var databaseName = configurationSection["DatabaseName"];
var containerName = configurationSection["graphParamsContainerName"];
var account = configurationSection["Account"];
var key = configurationSection["Key"];
var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
var cosmosDbService = new GraphParamsDBService(client, databaseName, containerName);
return cosmosDbService;
}
}
}
I want to call the api and at the function decides what level of info to show/return based on user's roles.
Can someone give a sample on how to get logged user's roles in Azure Function on Azure Static Web App?
When deploying Azure Function via "Function App", I can get the roles and current username, but with "Static Web App" I haven't figured it out yet.
namespace Function1
{
public class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ClaimsPrincipal principal)
{
IEnumerable<string> roles = principal.Claims.Where(e => e.Type.Equals("roles")).Select(e => e.Value);
string name = principal.Identity.Name;
string responseMessage = $"Hello, {name}. This HTTP triggered function executed successfully. {string.Join(',', roles)}";
return new OkObjectResult(responseMessage);
}
}
}
You can access like this,
public static ClaimsPrincipal Parse(HttpRequest req)
{
var header = req.Headers["x-ms-client-principal"];
var data = header.FirstOrDefault();
if(data == null) {
return null;
}
var decoded = System.Convert.FromBase64String(data);
var json = System.Text.ASCIIEncoding.ASCII.GetString(decoded);
var principal = JsonSerializer.Deserialize<ClientPrincipal>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
principal.UserRoles = principal.UserRoles.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase);
if (!principal.UserRoles.Any())
{
return new ClaimsPrincipal();
}
var identity = new ClaimsIdentity(principal.IdentityProvider);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, principal.UserId));
identity.AddClaim(new Claim(ClaimTypes.Name, principal.UserDetails));
identity.AddClaims(principal.UserRoles.Select(r => new Claim(ClaimTypes.Role, r)));
return new ClaimsPrincipal(identity);
}
Here is a sample
I've been trying to convert the functions in this https://blog.jeremylikness.com/build-a-serverless-link-shortener-with-analytics-faster-than-finishing-your-latte-8c094bb1df2c to a WebAPI equivalent. This is my webapi call:
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] ShortRequest shortRequest)
{
_logger.LogInformation($"ShrinkUrl api called with req: {shortRequest}");
if(!Request.IsHttps && !Request.Host.Host.Contains("localhost"))
return StatusCode(StatusCodes.Status400BadRequest);
if (string.IsNullOrEmpty(shortRequest.Input))
return StatusCode(StatusCodes.Status404NotFound);
try
{
var result = new List<ShortResponse>();
var analytics = new Analytics();
// determine whether or not to process analytics tags
bool tagMediums = analytics.Validate(shortRequest);
var campaign = string.IsNullOrWhiteSpace(shortRequest.Campaign) ? DefaultCampaign : shortRequest.Campaign;
var url = shortRequest.Input.Trim();
var utm = analytics.TagUtm(shortRequest);
var wt = analytics.TagWt(shortRequest);
_logger.LogInformation($"URL: {url} Tag UTM? {utm} Tag WebTrends? {wt}");
// get host for building short URL
var host = Request.Scheme + "://" + Request.Host;
await _tableOut.CreateIfNotExistsAsync();
if (_keyTable == null)
{
_logger.LogInformation($"Keytable is null, creating initial partition key of 1");
_keyTable = new NextId
{
PartitionKey = "1",
RowKey = "KEY",
Id = 1024
};
var keyAdd = TableOperation.Insert(_keyTable);
await _tableOut.ExecuteAsync(keyAdd);
}
// strategy for getting a new code
string getCode() => Utility.Encode(_keyTable.Id++);
// strategy for logging
void logFn(string msg) => _logger.LogInformation(msg);
// strategy to save the key
async Task saveKeyAsync()
{
var operation = TableOperation.Replace(_keyTable);
await _tableOut.ExecuteAsync(operation);
}
// strategy to insert the new short url entry
async Task saveEntryAsync(TableEntity entry)
{
var operation = TableOperation.Insert(entry);
await _tableOut.ExecuteAsync(operation);
}
// strategy to create a new URL and track the dependencies
async Task saveWithTelemetryAsync(TableEntity entry)
{
await TrackDependencyAsync(
"AzureTableStorageInsert",
"Insert",
async () => await saveEntryAsync(entry),
() => true);
await TrackDependencyAsync(
"AzureTableStorageUpdate",
"Update",
async () => await saveKeyAsync(),
() => true);
}
if (tagMediums)
{
// this will result in multiple entries depending on the number of
// mediums passed in
result.AddRange(await analytics.BuildAsync(
shortRequest,
Source,
host,
getCode,
saveWithTelemetryAsync,
logFn,
HttpUtility.ParseQueryString));
}
else
{
// no tagging, just pass-through the URL
result.Add(await Utility.SaveUrlAsync(
url,
null,
host,
getCode,
logFn,
saveWithTelemetryAsync));
}
_logger.LogInformation($"Done.");
//return req.CreateResponse(HttpStatusCode.OK, result);
}
catch (Exception ex)
{
_logger.LogError("An unexpected error was encountered.", ex);
//return req.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
return null;
}
And this is the function params:
[FunctionName("ShortenUrl")]
public static async Task<HttpResponseMessage>([HttpTrigger(AuthorizationLevel.Function, "post")],HttpRequestMessage req,
[Table(Utility.TABLE, "1", Utility.KEY, Take = 1)]NextId keyTable,
[Table(Utility.TABLE)]CloudTable, TraceWriter log)
The azure function takes care of ensuring that the keyTable contains the next Id in the counter but I cannot figure out how to do the same in the webapi call.
Any ideas?
a have UWP app, that working correctly. Now i working on .Net Standard Library providing same function and i do not now, what I'm doing wrong.
UWP code (working correctly)
private async void btnRVM_ClickR(object sender, RoutedEventArgs e)
{
using (HttpClient httpClient = new HttpClient())
{
Uri uri = new Uri("http://examplecom/api/Account/Register");
HttpContent content = new StringContent(JsonConvert.SerializeObject(new RegistrationObject(tbxUsername.Text, tbxPassword.Text, tbxConfirm.Text)), Encoding.UTF8, "application/json");
try
{
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
}
catch (Exception e1)
{
string excMessage = e1.ToString();
}
}
But almost same code in .Net standard library do not working
public static async Task<object> RegisterAsync(string username, string password, string confirmPassword)
{
using (HttpClient httpClient = new HttpClient())
{
Uri uri = new Uri("http://example/api/Account/Register");
HttpContent content = new StringContent(JsonConvert.SerializeObject(new RegistrationObject(username, password, confirmPassword)), Encoding.UTF8, "application/json");
try
{
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
}
catch (Exception e1)
{
string excMessage = e1.ToString();
}
return Task.FromResult<object>(null);
}
}
Is it possible to block all calls to my web api that are not coming from the web-site itself?
I mean if my MVC app runs at : http://www.domain.com and the web api at http://www.domain.com/api/service, I want the web api to accept calls only from current application only. No external calls allowed.
I will guess maybe a message handler will be the best in this case?
Create a Controller for error page and catch all garbage requests like this:
config.Routes.MapHttpRoute("block", "{*something}", new { controller = "Error", action = "Get" });
You should implement token authorization using a delegating handler.
public class AuthorizationHeaderHandler : DelegatingHandler
{
public AuthorizationHeaderHandler(HttpConfiguration httpConfiguration)
{
//set the inner handler
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> apiKeyHeaderValues = null;
if (request.Headers.TryGetValues("X-ApiKey", out apiKeyHeaderValues))
{
var apiKeyHeaderValue = apiKeyHeaderValues.First();
//based on the api-key get username whose session is stil valid.
var username = //code to get user based on apiKeyHeaderValue;
if (!string.IsNullOrEmpty(username))
{
var usernameClaim = new Claim(ClaimTypes.Name, username);
var identity = new ClaimsIdentity(new[] {usernameClaim}, "ApiKey");
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
}
}
else
{
//You don't have an ApiKey from the request... can't proceed
var response = request.CreateResponse(HttpStatusCode.Forbidden,
new {Message = "You are not Authorized to access that resource"}); //new HttpResponseMessage(HttpStatusCode.Forbidden);
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
return base.SendAsync(request, cancellationToken);
}
}
You can then register the handler in the WebApiConfig
public class WebApiConfig
{
public static void Init(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints:null,
handler: new AuthorizationHeaderHandler(GlobalConfiguration.Configuration)
);
}
}
you can then setup your login controller to authorize a user and assign then a token
public class UserController : ApiController
{
public async Task<HttpResponseMessage> Login([FromBody] UserDTO userDTO)
{
// first perform user authentication.
// clear all existing tokens for this authorized user
//create security token and save token of current user
//You can store this in a database and use a repository to create these.
// Tokens can be guids.
// await token creation
return Request.CreateResponse(HttpStatusCode.OK, new {LogingResult = result, token = token});
}
}
Once that user has the token, it can be used for Api requests by adding to the request header. In Angularjs it can take the following form.
'use strict';
(function () {
angular.module('App', ['ngRoute', 'ngCookies']);
//interceptor for server calls
var httpInterceptor = function ($q, $window, $location) {
return function(promise) {
var success = function(response) {
return response;
};
var error = function(response) {
if (response.status === 403) {
$location.url('/login');
}
return $q.reject(response);
};
return promise.then(success, error);
};
}
httpInterceptor['$inject'] = ['$q', '$window', '$location'];
angular.module('App').factory('httpInterceptor', httpInterceptor);
var api = function ($http, $cookies) {
return {
init: function (token) {
$http.defaults.headers.common['X-ApiKey'] = token || $cookies.token;
}
};
}
api['$inject'] = ['$http', '$cookies'];
angular.module('App').factory('api', api);
})();
Yes this is definitely possible. You have to create a custom handler and filter for the RemoteIpAddress found in the request. Here's an implementation using Owin Self-Host:
public class CustomerHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request?.GetClientIpAddress() != "127.0.0.1")
{
return await Task.FromResult(request.CreateResponse(HttpStatusCode.Unauthorized));
}
return await base.SendAsync(request, cancellationToken);
}
}
public static class HttpReqestMessageExtension
{
public static string GetClientIpAddress(this HttpRequestMessage request)
{
if (!request.Properties.ContainsKey("MS_OwinContext")) return null;
dynamic owinContext = request.Properties["MS_OwinContext"];
return owinContext.Request.RemoteIpAddress;
}
}
If you where using ASP.Net then you would use the appropriate key => MS_HttpContext
Now you simply add this to the startup of your Api:
var config = new HttpConfiguration();
config.MessageHandlers.Add(new CustomerHandler());