Receiving custom WebHooks in a Windows Form Application - c#

I am working to make an internal application available from an external location. To accomplish this I’m looking into different options like the use of a rest API or using a socket server which can be placed between the old application and the database on the server. One of the concerns of the end product is that if multiple users alter the same data and sent this to the server that there is the possibility that they overwrite each other’s changes. The planned approach for this is have a server sided log for data changes, and notify the user that relevant data has been altered to give them the choice to reload their form. The latter part is the issue from which my question has come forth. Unlike sockets web protocols do not work bidirectional. Potential fixes that I have found are polling, framing and WebHook of which the latter seem to be the most efficient.
However looking at the different available WebHook receiver samples they seem to have something in common. They use either MVC or WebAPI and all have an altered WebConfig in which the method config.InitializeCustomWebHooks(); has been added.
I’ve been trying to find a way to add this in by altering the Program.cs without success.
Also I’ve tried to find a method that has to do with Hooks using the HttpClient (which I’ve used for the rest of my demo application so far), WebClient and HttpResponse. However this search also did not bear any fruits. Also the only post of someone who tried to accomplish something similar (have a windows forms application listen to GitHub) that I found got a [response][1] that made me believe that webhooks and windows forms just don’t go together.
This search has ended with me wondering or:
Is it a possibility to consume an API 2 services WebHooks with a windows form application?
And if possible I would like to be informed how to do this?
For now my API 2 server has been pretty much altered according the example provided by [Asp.Net][2]
And my client has implemented the CustomWebHookHandler from the CustomReceiver from the same.
Edit for next issue:
note: both client and server contain the same packages as used in the repo as shown by Peter Bons.
The client contains a secret key within the app.config, matching the key within the subscribe method and the new Startup.cs has been called (I am able to sent echo's to the client sided hosted server) so that part is working. The registration class also matches up with the example repo
Client initiates the webhook with the following method:
public void SubscribeTo(String subscribeString)
{
HttpResponseMessage result;
// Create a webhook registration
Registration registration = new Registration
{
WebHookUri = $"{adressReceiver}/api/webhooks/incoming/custom",
Description = "A message is posted.",
Secret = "12345678901234567890123456789012",
Filters = new List<string> { "EmployeeChanged" }
};
result = client.PostAsJsonAsync(#"webhooks/registrations", registration).Result;
string resultString = result.ToString();
}
Where adress receiver is the same string that was used to start the echoable webservice.
Upon debugging the method the following actions server/client occur:
on the line containing result = the server sided debug point in CustomFilterProvider.GetFiltersAsync get's triggered.
At the return method it shows that 1 filer was returned with the Name: "EmployeeChanged" back on the client the resultString contains the StatusCode: Created
During the method the client has the system output:
ClientApp.exe Information: 0 : Registered 'IWebHookReceiver' instances with the following names: custom.
ClientApp.exe Information: 0 : Processing incoming WebHook request with receiver 'custom' and id ''.
ClientApp.exe Information: 0 : Registered configuration setting 'Custom' for ID '''.
And the server:
Application Insights Telemetry (unconfigured):
{
"name": "Microsoft.ApplicationInsights.Dev.RemoteDependency",
"time": "2017-09-12T10:16:25.8947601Z",
"tags": {
"ai.internal.nodeName": "PC-78.vericon.nl",
"ai.operation.id": "1348c1c9-4b7a5ac6dd5f1940",
"ai.cloud.roleInstance": "PC-78.vericon.nl",
"ai.internal.sdkVersion": "rdddsd:2.4.1-1362"
},
"data": {
"baseType": "RemoteDependencyData",
"baseData": {
"ver": 2,
"name": "GET /api/webhooks/incoming/custom",
"id": "|1348c1c9-4b7a5ac6dd5f1940.",
"data":
"http://localhost:55999/api/webhooks/incoming/custom?echo=e9f7b03ca8aa4226a7a9dfc99dacff16",
"duration": "00:00:00.2046329",
"resultCode": "200",
"success": true,
"type": "Http",
"target": "localhost:55999",
"properties": { "DeveloperMode": "true" }
}
}
}
Application Insights Telemetry (unconfigured):
{
"name": "Microsoft.ApplicationInsights.Dev.Request",
"time": "2017-09-12T10:14:50.0204275Z",
"tags": {
"ai.internal.nodeName": "PC-78.vericon.nl",
"ai.operation.name": "POST /api/webhooks/registrations",
"ai.operation.id": "lXKbEdh4GOU=",
"ai.location.ip": "::1",
"ai.cloud.roleInstance": "PC-78.vericon.nl",
"ai.internal.sdkVersion": "web:2.4.1-1362"
},
"data": {
"baseType": "RequestData",
"baseData": {
"ver": 2,
"id": "|lXKbEdh4GOU=.1348c1c8_",
"name": "POST /api/webhooks/registrations",
"duration": "00:01:36.1228603",
"success": true,
"responseCode": "201",
"url": "http://localhost:55997/api/webhooks/registrations",
"properties": { "DeveloperMode": "true" }
}
}
}
After a bit of research my reasoning for this output is that the server sends an echo request to the ip given by the client to see or it is a reachable destination. The code created also implies that the server found this adress because otherwise a code BadRequest would occur
So I think that the webhook get's correctly established futher on in the application I have got a method that makes a post request to the server. I put a debug point in the server sided method to see what happens (there's also is a debug point in my client handler).
after doing the client sided action that should get me in my controller the debug point within indeed get's triggered in my method:
public async Task<IHttpActionResult> Post(Employee e)
{
await this.NotifyAsync(CustomFilterProvider.EmployeeChanged, new { Employee = e });
if (EmployeeModifier.updateEmployee(e))
{
log.Info("User: " + User.Identity.Name + " Updated an employee with the following new value " + Newtonsoft.Json.JsonConvert.SerializeObject(e));
return Ok();
}
else
{
log.Error("User: " + User.Identity.Name + " failed to update an employee");
return BadRequest();
}
}
when stepping through it it follows the route towards to return Ok()
the client receives the system output:
ClientApp.exe Information: 0 : Processing incoming WebHook request with receiver 'custom' and id ''.
However the debug point within the clients handler never get's triggered.
the handler for now looks like:
class CustomWebHookHandler : WebHookHandler
{
public CustomWebHookHandler()
{
}
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
System.Windows.Forms.MessageBox.Show("In handler");
return Task.FromResult(true);
}
}
My client startup.cs Configuration method looks like:
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
var controllerType = typeof(WebHookReceiversController);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional }
);
config.InitializeReceiveCustomWebHooks();
var traceWriter = config.EnableSystemDiagnosticsTracing();
traceWriter.IsVerbose = true;
traceWriter.MinimumLevel = TraceLevel.Error;
appBuilder.UseWebApi(config);
}
So that contains the var controllerType
I hope this is enough relevant code for you, my fear is that my server is only sending echo's/ping requests to my client.
But I don't know how to change that since I've tried both NotifyAsync and NotifyAllAsync, with and without Filter( in client Registration class instance). The clients OWIN hosts keeps up for sure since it is echo able and the client output shows that it actually does receive a message from the server but that's where I'm crashed.

Related

CosmosDB Azure function Trigger with SignalR output binding

I'm trying to create a Azure function with a Cosmos Trigger (in c#) that monitors changes to a CosmosDB, this seems relatively simple and I have managed to do this without trouble, I have an Azure function that logs changes to the DB to the log console. I am trying to write an output binding to send the changes to Azure SignalR but when I try this, I am met with isolated process doesn't support the assemblies required to do this. Does anyone have a very simple example of a c# Azure Cosmos Trigger function that sends the changes detected to a Cosmos DB to an Azure SignalR servicee so I can subscribe to this and report these to a client app. Any help would be greatly appreciated.
The code I found on the web for what I want to do (this is just a test function) is below:
[Function("CosmosTriggerSigR")]
public void Run([CosmosDBTrigger(
databaseName: "test",
collectionName: "testCollection",
ConnectionStringSetting = "cosmos_DOCUMENTDB",
LeaseCollectionName = "leases")]
IReadOnlyList<MyDocument> input,
[SignalR(HubName = "events", ConnectionStringSetting = "SignalRConnectionString")]
IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
if (input != null && input.Count > 0)
{
_logger.LogInformation("Documents modified: " + input.Count);
_logger.LogInformation("First document Id: " + input[0].Id);
}
}
and the when trying to deploy it, it shows this error:
C:\Users\CosmosSigr1204\CosmosTriggerSigR.cs(29,14): error AZFW0001: The attribute 'SignalRAttribute' is a WebJobs attribute and not supported in the .NET Worker (Isolated Process). [C:\Users\CosmosSigr1204\CosmosSigr1204.csproj]
The terminal process "C:\Program Files\dotnet\dotnet.exe 'publish', '--configuration', 'Release', '/property:GenerateFullPaths=true', '/consoleloggerparameters:NoSummary'" terminated with exit code: 1.
I know pretty much nothing about Azure SignalR and I'm trying to muddle through so apologies if the code above isn't what it should be for what I'm trying to do.
Here is a full solution that uses the combination of services you specify: https://github.com/ealsur/serverlessazurefriday
Particularly this Function: https://github.com/ealsur/serverlessazurefriday/blob/master/src/DistributedDashboard/NotificationsTrigger.cs which ties the CosmosDBTrigger and SignalR output like so:
[FunctionName("Notifications")]
public static async Task Run(
[CosmosDBTrigger(
databaseName: "eventsDb",
collectionName: "events",
LeaseCollectionPrefix = "Notifications",
ConnectionStringSetting = "CosmosDBAzureFriday",
PreferredLocations = "%REGION%",
LeaseCollectionName = "leases")]
IReadOnlyList<Document> events,
[SignalR(HubName = "events", ConnectionStringSetting = "SignalRAzureFriday")]
IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
await signalRMessages.AddAsync(new SignalRMessage()
{
Target = "console",
Arguments = new[] {
events.Select(singleEvent => JsonConvert.DeserializeObject<ConsoleLog>(singleEvent.ToString()))
}
});
}
In this case, the code will send 1 SignalR message containing all the documents that were received on the Trigger, you could opt to send 1 SignalR message per Trigger document, that is up to your application design.
In the case of this solution, the client application (browser) connects to the SignalR hub using the JS library and receives the SignalR message that contain all the Cosmos DB documents and consumes the array (reference https://github.com/ealsur/serverlessazurefriday/blob/master/src/ClientApp/scripts/site.js):
// Assuming connection is a SignalR connection created by SignalR library
// https://learn.microsoft.com/aspnet/core/signalr/javascript-client?#connect-to-a-hub
connection.on('console', function(messages){
angular.forEach(messages, function(message){
term.writeln('>> Device '+message.deviceId +' in ' + message.region + ' reports value of ' + message.value);
});
});
Where console is just the name that matches the Target on the SignalR output message.

Azure Custom Vision Api POST Request error, even correct ID's

I'm trying to publish my Custom Vision Iteration after I trained it. But I always get a "Bad Request" Error.
I'm trying with the following line of code:
trainingApi.PublishIteration(ProjectID, iteration.Id, "Model", predictionResourceId);
It should Publish my Iteration but I just get an error.
I re-checked all my ID's but everything looks fine. Has the model name be something specific (start with lower letter or something)?
Edit:
I tried it now with a POST Request in Postman but now I receive:
{
"code": "BadRequestInvalidPublishTarget",
"message": "Invalid prediction resource id"
}
But I re-checked my Prediction Resource ID and it's correct.
Edit 2:
I thin I put the wrong thing into predictionId in the POST request, I just put in a ID but I think it should have been the /subscriptions/... part like described by microsoft. The problem now is:
{
"code": "BadRequestInvalidPublishTarget",
"message": "Invalid prediction id, please pass a prediction resource id."
}
For me this means it doesn't receive a prediction resource id, but I'm lost so I'm out of ideas what the problem could be.
Edit 3:
I forgot to add my POST Request:
https://xxx.cognitiveservices.azure.com/customvision/v3.0/training/projects/xxx/
iterations/xxx/publish?publishName=Model&predictionId=/subscriptions/xxx/
resourceGroups/CustomVision/providers/Microsoft.CognitiveServices/accounts/xxx
Publish using POST request
If you want to ensure that you have the right syntax, you can check using Custom vision portal by doing the same steps.
For example when I try to publish an iteration of a project, I can see the following call in the console:
https://westeurope.api.cognitive.microsoft.com/customvision/v3.3/Training/projects/ID_OF_MY_PROJECT/iterations/ID_OF_MY_ITERATION/publish?predictionId=%2Fsubscriptions%2FID_OF_MY_AZURE_SUBSCRIPTION%2FresourceGroups%2FNAME_OF_MY_RESOURCE_GROUP%2Fproviders%2FMicrosoft.CognitiveServices%2Faccounts%2FNAME_OF_MY_CUSTOM_VISION_PREDICTION_RESOURCE&publishName=NAME_OF_MY_ITERATION
Demo:
So yes, the "publicationId" value is looking like the one you mentioned but you have to encode the value of this string.
So change this:
predictionId=/subscriptions/xxx/
resourceGroups/CustomVision/providers/Microsoft.CognitiveServices/accounts/xxx
to
predictionId=%2Fsubscriptions%2Fxxx%2F
resourceGroups%2FCustomVision%2Fproviders%2FMicrosoft.CognitiveServices%2Faccounts%2Fxxx
in your call.
And be careful to use a prediction resource, not a training one.
Publish using C#
Here is a demo using C# and the official Custom Vision package hosted on Nuget (here)
using System;
using Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training;
namespace so65714960
{
class Program
{
private static CustomVisionTrainingClient _trainingClient;
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
_trainingClient = new CustomVisionTrainingClient(new ApiKeyServiceClientCredentials("PUT_YOUR_TRAINING_KEY_HERE"));
// I'm specifying my endpoint here as I'm working on West Europe region
_trainingClient.Endpoint = "https://westeurope.api.cognitive.microsoft.com/";
var projectId = new Guid("4b...a5"); // Put your Project Id here
var iterationId = new Guid("9d...e"); // Put your iteration Id here
// Get iteration information
var targetIteration = _trainingClient.GetIteration(projectId, iterationId);
Console.WriteLine($"Iteration publish resource Id: '{targetIteration.OriginalPublishResourceId}'");
// If originalPublishResourceId is not null, it is already published
// For this demo purpose, we unpublish first to publish again after if it is already published
if (!string.IsNullOrWhiteSpace(targetIteration.OriginalPublishResourceId))
{
_trainingClient.UnpublishIteration(projectId, iterationId);
// Force status refresh
targetIteration = _trainingClient.GetIteration(projectId, iterationId);
Console.WriteLine($"Iteration publish resource Id after unpublish: '{targetIteration.OriginalPublishResourceId}'");
}
// Publish
var publicationResourceId = "/subscriptions/7c...e8/resourceGroups/Cognitive_Demo/providers/Microsoft.CognitiveServices/accounts/NRO-Cognitive-CustomVision-WestEurope-Prediction-S0";
var publication = _trainingClient.PublishIteration(projectId, iterationId, "Publication1", publicationResourceId);
// Force status refresh
targetIteration = _trainingClient.GetIteration(projectId, iterationId);
Console.WriteLine($"Iteration publish resource Id after publish: '{targetIteration.OriginalPublishResourceId}'");
}
}
}
See my Azure resource used:

PayPalAPIInterfaceServiceService and SetExpressCheckout config / credential information

I created an app in my business account on this page:
https://developer.paypal.com/developer/applications/
When I click on the app I created I see the following:
Sandbox account, Client ID, and Secret.
I am trying to call SetExpressCheckout… but the documentation is unclear and examples are all over the map.
Basically I’m seeing things like:
var request = new SetExpressCheckoutReq() { … };
var config = new Dictionary<string, string>()
{
{ "mode", "sandbox" }, // some variations of these values
{ "clientId", "fromAbovePage" },
{ "clientSecret", "fromAbovePage" },
{ "sandboxAccount", "fromAbovePage" },
{ "apiUsername", "IDontKnow" },
{ "apiPassword", "IDontKnow" },
{ "apiSignature", "IDontKnow" }
};
var service = new PayPalAPIInterfaceServiceService(config);
var response = service.SetExpressCheckout(request, new SignatureCredential(config["apiUsername"], config["apiPassword"], config["apiSignature"]));
Also, kind of weird that credentials go into both the PayPalAPIInterfaceServiceService and the actual SetExpressCheckout call.
What are (and where do I get) the correct values for the above config? (the request itself I have pretty much figured out)
Note: PayPal support told me that I need to use Reference Transactons in order to charge varying amounts over potentially varying times without subsequent user interaction, if that is relevant.
I would love to see examples of this with the most recent API's if anyone has that information as well.
Thank you.
SetExpressCheckout is a legacy NVP API
For sandbox, it uses credentials from the "Profile" of a sandbox account in https://www.paypal.com/signin?intent=developer&returnUri=https%3A%2F%2Fdeveloper.paypal.com%2Fdeveloper%2Faccounts%2F
For live, https://www.paypal.com/api
ClientID/Secret credentials are used for the current v2/checkout/orders REST API, which at the moment does not have any public documentation for vaulting or reference transactions; it is for one time payments. You can find information for a server-side integration at https://developer.paypal.com/docs/checkout/reference/server-integration/
If you are using this REST API integration, create two routes --one for 'Set Up Transaction' and one for 'Create Transaction'. Then pair them with this approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server

Why I get Internal Server Error after publishing Azure Function?

I've made an API in Azure Functions that uses an Azure Sql database.
I have this route:
[FunctionName("GetAllClassesForTeacher")]
public IActionResult GetAllClassesForTeacher(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequestMessage req,
ILogger log)
{
var headers = req.Headers;
if (headers.TryGetValues("TeacherId", out var Teacher))
{
int TeacherId = Convert.ToInt32(Teacher.First());
var result = _classDistributionServices.GetAllClassesForTeacher(TeacherId);
if (result != null)
{
return new OkObjectResult(result);
}
return new NotFoundResult();
}
return new NotFoundResult();
}
When tested localy, it works everytime. But after publishing it on Azure Portal, I get back from postman:
{
"statusCode": 500,
"message": "Internal server error",
"activityId": "f98c4112-0c31-4841-99a5-c79dffa41d86"
What I've tried/did until now:
to take the IP from Azure Function and register it in Firewalls and
virtual networks from Azure Sql Server.
I've made sure that the ConnectionString from local.settings.json it
is uploaded on portal.
I've declared this route in API Management.
But still the error persists.
I have a feeling that is still because of database firewall even if I've registered the right IP?
Can you please help me?
There a few way for you to figure out what is the problem:
1-Enable Application Insights and use the Live Metrics to capture the error
2-Use Kudu and check for more details about the error (e.g. https://myfunctionapp.scm.azurewebsites.net/azurejobs/#/functions)
3-You can also wrap your code with a try/catch and throw details of the exception in the response output.

Azure application insights drops some custom events

I have a Web Application and want to track user logins in AppInsights with the following code:
[HttpPost]
public async Task<ActionResult> Login(AccountViewModel model, string returnUrl)
{
var success = await _userManager.CheckPasswordAsync(model);
_telemetryClient.TrackEvent("Login",
new Dictionary<string, string>
{
{ "UserName", model.UserName },
{ "UsingPin", model.UsingPin.ToString()},
{ "ReturnUrl", returnUrl},
{ "LastUsedUrl", model.LastUsedUrl },
{ "Success", success.ToString() }
});
return View(model);
}
The code works, I get events in analytics portal... but only a subset of events! In the sessions table on the database side I have about 1000 unique user logins, but AI reports around 100...
What I have already checked:
_telemetryClient is a singleton configured through Unity (<lifetime type="singleton" />)
there is no quota configured in Azure portal
I have more than 1000 POST requests to the Login method, this looks just right (I mean every request comes to this exact server, returns 200 status, etc.)
So it looks like Azure just drops some of custom events... or they are not send to azure for some reason. Is there any configuration part I am missing?
As far as I know, if your application sends a lot of data and you are using the Application Insights SDK for ASP.NET version 2.0.0-beta3 or later, the adaptive sampling feature may operate and send only a percentage of your telemetry.
So I guess this the reason why you just see 100 records in the AI.
If you want to send all the records to the AI, I suggest you could disable the adaptive sampling. More details about how to disable the adaptive sampling, you could refer to this article.
Notice: This is not recommended. Sampling is designed so that related telemetry is correctly transmitted, for diagnostic purposes.

Categories