Azure function - GraphQLHttpClient with CreateSubscriptionStream - c#

I have a requirement like I want to subscribe an GraphQLHttpClient using CreateSubscriptionStream into a function app .
stream.Subscribe(
response =>
{
Console.WriteLine($"RaceUpdatesSubscription message: \"{response.Data}\" ");
SendMsgToTopic.SendMessageToTopicAsync(response.Data, _serviceBusConnectionString, _raceUpdatesTopicName).Wait();
},
exception => Console.WriteLine($"message RaceUpdatesSubscription stream failed: {exception}"),
() => Console.WriteLine($"message RaceUpdatesSubscription stream completed")
);
How can I achieve that Like is there any way to user any trigger from azure function or register this for subscription (Any changes happen then I need to listen that)
Now its working fine with console app but trying to use function app.

To CreateSubscriptionStream into a function app. Below are the details to subscribe
// To use NewtonsoftJsonSerializer, add a reference to NuGet package GraphQL.Client.Serializer.Newtonsoft
var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer());
Below is the code to use subscriptions
public class UserJoinedSubscriptionResult {
public ChatUser UserJoined { get; set; }
public class ChatUser {
public string DisplayName { get; set; }
public string Id { get; set; }
}
}
Below is the code how to create subscription
var userJoinedRequest = new GraphQLRequest {
Query = #"
subscription {
userJoined{
displayName
id
}
}"
};
IObservable<GraphQLResponse<UserJoinedSubscriptionResult>> subscriptionStream
= client.CreateSubscriptionStream<UserJoinedSubscriptionResult>(userJoinedRequest);
var subscription = subscriptionStream.Subscribe(response =>
{
Console.WriteLine($"user '{response.Data.UserJoined.DisplayName}' joined")
});
To get complete information on CreateSubscriptionStream refer this GraphQL

Related

Blazor WASM with SignalR sending objects

I have a class I am trying to send from WebAPI server w/ signalR to Blazor WASM. I used the new Blazor template with the .net core hosted option checked. It works fine sending a string or integer to the NewUser method, but when using a custom object like User seen below, I get nothing. I think there is a problem serializing/deserializing but I can't find any options. Am I missing something in the configuration?
public class User
{
public string Id { get; set; }
[Required(ErrorMessage ="You must specify a username")]
[StringLength(20,MinimumLength=1,ErrorMessage="Please enter a username no longer than 20 characters")]
public string Username { get; set; }
public string ConnectionId { get; set; }
}
Hub
public async Task Register(AppState state)
{
await _roomRepository.RegisterUser(state);
await Groups.AddToGroupAsync(state.user.ConnectionId,state.matchroom.Id);
await Clients.Group(state.matchroom.Id).SendAsync("NewUser", $"{state.user}");
}
Startup.cs (WebAPI Server) lots omitted
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
Blazor Page
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/matchhub"))
.Build();
hubConnection.On<User>("NewUser", (user) =>
{
AppState.matchroom.Users.Add(user);
StateHasChanged();
});
await hubConnection.StartAsync();
AppState.user.ConnectionId = hubConnection.ConnectionId;
await hubConnection.SendAsync("register", AppState)
}
It looks like you're writing state.user as a string and sending that, so the client side can't match the User type to the string type it receives.

MassTransit - Unit Testing a Large Payload Consumer

Using the InMemoryTestHarness, I am working on unit testing a Consumer that handles a command with a MessageData property. Using the InMemoryMessageDataRepository to populate the MessageData property works fine however, when the consumer attempts to load the payload (message.Body.Value) I am receiving the following exception: "The message data was not loaded: urn:msgdata:xxxxxxxxxxxxxxxxxxxxxxxxxx"}. What is the proper way to unit test a consumer that handles commands with a MessageData property?
I was going to use the In-memory transport, but it doesn't seem like the right approach since the unit test(s) will not receive immediate feedback from the consumer. Ultimately the end goal is for my unit tests to be able assert that specific exceptions are thrown or that the consumer completed successfully without errors.
using System;
using System.Linq;
using System.Threading.Tasks;
using MassTransit;
using MassTransit.MessageData;
using MassTransit.Testing;
using Newtonsoft.Json;
using Xunit;
namespace Test
{
public class LargePayloadConsumerTest
{
[Fact]
public async Task Consumer_Should_Deserialize_Message()
{
var harness = new InMemoryTestHarness();
var consumer = harness.Consumer<BigMessageConsumer>();
var command = new BigMessageCommand();
var dataRepo = new InMemoryMessageDataRepository();
var largePayload = new BigMessagePayload()
{
Id = 1,
FirstName = "TestFirstName",
MiddleName = "TestMiddleName",
LastName = "TestLastName"
};
var json = JsonConvert.SerializeObject(largePayload);
command.Body = await dataRepo.PutString(json);
var thisWorks = await command.Body.Value;
await harness.Start();
await harness.InputQueueSendEndpoint.Send(command);
Assert.True(harness.Consumed.Select<BigMessageCommand>().Any());
}
public class BigMessageConsumer : IConsumer<BigMessageCommand>
{
public async Task Consume(ConsumeContext<BigMessageCommand> context)
{
try
{
var json = await context.Message.Body.Value;
var deserialized = JsonConvert.DeserializeObject<BigMessagePayload>(json);
Console.WriteLine($"{deserialized.Id}: {deserialized.FirstName} {deserialized.MiddleName} {deserialized.LastName}");
}
catch (Exception e)
{
//throws {"The message data was not loaded: urn:msgdata:xxxxxxxxxxxxxxxxxxxxxxxxxx"}
Console.WriteLine(e);
throw;
}
}
}
public class BigMessageCommand
{
public MessageData<string> Body { get; set; }
}
public class BigMessagePayload
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
}
}
You need to configure the receive endpoint to handle the message data property, similar to this:
e.UseMessageData<BigMessageCommand>(dataRepo);
Since you're using the test harness, you can add it to the receive endpoint before you add the consumer test harness:
void ConfigureReceiveEndpoint(IReceiveEndpointConfigurator configurator)
{
configurator.UseMessageData<BigMessageCommand>(dataRepo);
}
harness.OnConfigureReceiveEndpoint += ConfigureReceiveEndpoint;

Real-time notification upon registration using SignalR

I want to send real-time notification in ASP.NET Boilerplate. Notification is saving successfully in Abp.NotificationSubscription table on subscription. When I publish the notification, the notification got saved in the Abp.Notification table but it is not displayed to the user in real-time.
My server-side code:
public async Task<RegisterOutput> Register(RegisterInput input)
{
public async Task<RegisterOutput> Register(RegisterInput input)
{
var user = await _userRegistrationManager.RegisterAsync(
input.Name,
input.Surname,
input.EmailAddress,
input.UserName,
input.Password,
true
);
_notificationSubscriptionManager.SubscribeToAllAvailableNotifications(user.ToUserIdentifier());
await _appNotifier.WelcomeToTheApplicationAsync(user);
var notification = _userNotificationManager.GetUserNotifications(user.ToUserIdentifier());
await _realTimeNotifier.SendNotificationsAsync(notification.ToArray());
// ...
}
}
_appNotifier implements WelcomeToTheApplicationAsync(user):
public async Task WelcomeToTheApplicationAsync(User user)
{
await _notificationPublisher.PublishAsync(
AppNotificationName.WelcomeToTheApplication,
new SendNotificationData("Naeem", "Hello I have sended this notification to you"),
severity: NotificationSeverity.Success,
userIds: new[] { user.ToUserIdentifier() }
);
}
SendNotificationData inherits from NotificationData:
public class SendNotificationData : NotificationData
{
public string name { get; set; }
public string message { get; set; }
public SendNotificationData(string _name, string _message)
{
name = _name;
message = _message;
}
}
I want to sent welcome notification to the user when he registers itself by using the register link on the login page. In the CloudbaseLine.Application project ... we have AccountAppService which contain the code for registering the user and sending notification to it after successful registration but the problem is notification go saved in Abp.NotificationSubscriptionTable and UserNotificationTable but user cannot received them.
public async Task<RegisterOutput> Register(RegisterInput input)
{
var user = await _userRegistrationManager.RegisterAsync(
input.Name,
input.Surname,
input.EmailAddress,
input.UserName,
input.Password,
true
);
_notificationSubscriptionManager.SubscribeToAllAvailableNotifications(user.ToUserIdentifier());
await _appNotifier.WelcomeToTheApplicationAsync(user);
var notification = _userNotificationManager.GetUserNotifications(user.ToUserIdentifier());
await _realTimeNotifier.SendNotificationsAsync(notification.ToArray());
// ...
}
public async Task WelcomeToTheApplicationAsync(User user)
{
await _notificationPublisher.PublishAsync(
AppNotificationName.WelcomeToTheApplication,
new SendNotificationData("Naeem", "Hello I have sended this notification to you"),
severity: NotificationSeverity.Success,
userIds: new[] { user.ToUserIdentifier() }
);
}
The user is not connected to the SignalR hub in the Register method.
One way to handle that is to enqueue a background job:
await _backgroundJobManager.EnqueueAsync<WelcomeNotificationJob, UserIdentifier>(
user.ToUserIdentifier(),
delay: TimeSpan.FromSeconds(5)
);
public class WelcomeNotificationJob : BackgroundJob<UserIdentifier>, ITransientDependency
{
private readonly IRealTimeNotifier _realTimeNotifier;
private readonly IUserNotificationManager _userNotificationManager;
public WelcomeNotificationJob(
IRealTimeNotifier realTimeNotifier,
IUserNotificationManager userNotificationManager)
{
_realTimeNotifier = realTimeNotifier;
_userNotificationManager = userNotificationManager;
}
[UnitOfWork]
public override void Execute(UserIdentifier args)
{
var notifications = _userNotificationManager.GetUserNotifications(args);
AsyncHelper.RunSync(() => _realTimeNotifier.SendNotificationsAsync(notifications.ToArray()));
}
}
Don't forget to register data formatters for custom notification data types, on the client side:
abp.notifications.messageFormatters['CloudBaseLine.Notification.SendNotificationData'] = function (userNotification) {
return userNotification.notification.data.message;
}

Twilio Rest API Helper Library, v 5.0.1, C# - MessageResource.Create function call not returning properly

I am using the Twilio REST API helper library, v 5.0.1 in my C# ASP.NET MVC Web Application. I created the following helper class and function to send out text messages:
using MyApplication.Web.Helpers;
using System;
using System.Configuration;
using Twilio;
using Twilio.Exceptions;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
namespace MyApplication.Web.Services
{
public class TwilioSmsSender : ISmsSender
{
public string AccountSid { get; set; }
public string AuthToken { get; set; }
public string FromPhoneNumber { get; set; }
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public string SmsPrefix { get; set; }
public string SmsSuffix { get; set; }
public TwilioSmsSender()
{
//get our Twilio account info from the config file
AccountSid = ConfigurationManager.AppSettings["TwilioAccountSid"];
AuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
FromPhoneNumber = ConfigurationManager.AppSettings["SmsService.FromPhoneNumber"];
SmsPrefix = ConfigurationManager.AppSettings["SmsPrefix"];
SmsSuffix = ConfigurationManager.AppSettings["SmsSuffix"];
if (FromPhoneNumber.Length == 10)
{
FromPhoneNumber = $"+1{FromPhoneNumber}";
}
TwilioClient.Init(AccountSid, AuthToken);
}
public INotificationResponse SendTextMessage(string phoneNumber, string message, bool useFormatting = true)
{
var resp = new TwilioSmsSenderResponse();
resp.Succeeded = false;
resp.AttemptDateTimeUtc = DateTime.UtcNow;
if (useFormatting)
{
message = $"{SmsPrefix}{message}{SmsSuffix}";
}
try
{
var msgResponse = MessageResource.Create(
to: new PhoneNumber($"+1{phoneNumber}"),
from: new PhoneNumber($"{FromPhoneNumber}"),
body: message);
//Previous line works (i.e, I get the text message that I'm sending out successfully).
//However, none of the following lines are running...
//As you see, this is in a try/catch block... and it doesn't go to the catch block either!
if (msgResponse.ErrorCode == null)
{
//successfully queued
resp.Succeeded = true;
resp.ReferenceId = msgResponse.Sid;
resp.AttemptDateTimeUtc = DateTime.UtcNow;
}
else
{
//Twilio sent an error back
log.Info($"Twilio sent an error back: {msgResponse}");
resp.Succeeded = false;
resp.Notes = $"ErrorCode: {msgResponse.ErrorCode}, ErrorMessage: {msgResponse.ErrorMessage}";
resp.AttemptDateTimeUtc = DateTime.UtcNow;
}
}
catch (Exception e)
{
resp.Succeeded = false;
resp.Notes = ExceptionsHelper.GetExceptionDetailsAsString(e);
resp.AttemptDateTimeUtc = DateTime.UtcNow;
log.Error($"Twilio Error: {resp.Notes}, to: {phoneNumber}, message: {message}");
}
return resp;
}
}
}
Unfortunately, my code is not behaving as I expected it would after the MessageResource.Create() call. That is, the text-message is sent out correctly and I receive the SMS on my phone. However, I expect the call to return control to my msgResponse variable and I expect the
if (msgResponse.ErrorCode == null) ...
line and subsequent lines to run but that is not happening. I can put a breakpoint on the var msgResponse line and it will run that just fine but it does not run any code lines after that. You’ll see that I have the call in a try/catch. I suspected there was an exception that was occurring but it doesn’t seem so because it doesn’t go to my catch block either! The text message is being sent successfully! All I want to do is to get an acknowledgement back so that I can properly log it and send that information back to the routines that are calling this function.
Any help would be greatly appreciated!
version 5.0.2 fixed this for me just update twilio to 5.0.2. they just added .ConfigureAwait(false); with CreateAsync

How to Enable/Disable Azure Function programmatically

Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
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; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.

Categories