.net core monitor nats request with prometheus - c#

I have a net core 3.1 web-API application that integrated with nats
and I need to capture all my application's traffic and send them to Prometheus for monitor application's activity
in this case, I've used this article but it described a way of capturing Http requests not nats
is anyone have any experience in such a problem?

I finally figure it out!
If anyone has the same question Here is my solution:
{
private readonly ITracer _tracer;
public TraceMiddleware(ITracer tracer)
{
_tracer = tracer;
}
public async Task ExecuteAsync(NatsContext context, PipelineDelegate next)
{
var operationName = $"{context.Request.Address.Service}:{context.Request.Address.Method}";
var builder = _tracer.BuildSpan(operationName);
#region [Context TraceId]
builder.WithTag("internalRequestId", context["requestId"].ToString());
context.Tracer.AddTracer(context.Request.Address.ToNatsSubject(), context["requestId"].ToString());
builder.WithTag("TraceId", context.Tracer.TraceId);
#endregion
using (var scope = builder.StartActive(false))
{
var span = scope.Span;
span.SetTag("flow", context.Tracer.ToString());
span.SetTag("request-route", context.Request.Address.ToNatsSubject());
span.Log(DateTimeOffset.UtcNow, "request-started");
#region [Prometheus]
var counter = Metrics.CreateCounter("app_request_total", "Nats Requests Total",
new CounterConfiguration
{
LabelNames = new[] {"path", "method", "statusCode"}
});
var path = context.Request.Address.Service;
var method = context.Request.Address.Method;
int statusCode;
try
{
await next();
}
catch (Exception)
{
statusCode = 500;
counter.Labels(path, method, statusCode.ToString()).Inc();
throw;
}
statusCode = (int) context.Response.Status;
counter.Labels(path, method, statusCode.ToString()).Inc();
#endregion
span.Log(DateTimeOffset.UtcNow, "request-finished");
var logData = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("status-code", context.Response.Status),
new KeyValuePair<string, object>("has-exception", context.Response.Error != null)
};
span.Log(logData);
span.Finish();
}
}
}```

Related

LSP for VS Code - client/server, need some tips

I struggle with understanding how does LSP client-side works. I mean I think I understand the theory of communication (JSON-RPC/LSP Protocol basics) but I struggle with existing libraries that are used for this for VS Code and I think trying to rewrite it is kinda pointless, especially client-side where I do not feel proficient at all
All examples I see provide a path to the server, so the LSP client can start it
it makes sense, but I'd rather avoid it during development, I'd want to have the server aopen in debugging mode and just start VS Code
I tried to start with basic of basic server implementation (C#)
public class Server
{
private JsonRpc RPC { get; set; }
public async Task Start()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
var pipeName = "LSP_Pipe";
var writerPipe = new NamedPipeClientStream(pipeName);
var readerPipe = new NamedPipeClientStream(pipeName);
await writerPipe.ConnectAsync(10_000);
await readerPipe.ConnectAsync(10_000);
Log.Information("RPC Listening");
RPC = new JsonRpc(writerPipe, readerPipe, this);
RPC.StartListening();
this.RPC.Disconnected += RPC_Disconnected;
await Task.Delay(-1);
}
private void RPC_Disconnected(object sender, JsonRpcDisconnectedEventArgs e)
{
Log.Information("Disconnected");
}
[JsonRpcMethod(RPCMethods.InitializeName)]
public object Initialize(JToken arg)
{
Log.Information("Initialization");
var serializer = new JsonSerializer()
{
ContractResolver = new ResourceOperationKindContractResolver()
};
var param = arg.ToObject<InitializeParams>();
var clientCapabilities = param?.Capabilities;
var capabilities = new ServerCapabilities
{
TextDocumentSync = new TextDocumentSyncOptions(),
CompletionProvider = new CompletionOptions(),
SignatureHelpProvider = new SignatureHelpOptions(),
ExecuteCommandProvider = new ExecuteCommandOptions(),
DocumentRangeFormattingProvider = false,
};
capabilities.TextDocumentSync.Change = TextDocumentSyncKind.Incremental;
capabilities.TextDocumentSync.OpenClose = true;
capabilities.TextDocumentSync.Save = new SaveOptions { IncludeText = true };
capabilities.CodeActionProvider = clientCapabilities?.Workspace?.ApplyEdit ?? true;
capabilities.DefinitionProvider = true;
capabilities.ReferencesProvider = true;
capabilities.DocumentSymbolProvider = true;
capabilities.WorkspaceSymbolProvider = false;
capabilities.RenameProvider = true;
capabilities.HoverProvider = true;
capabilities.DocumentHighlightProvider = true;
return new InitializeResult { Capabilities = capabilities };
}
}
but I'm unable to setup client with those vscode-languageclient/node libraries even to get Log.Information("Initialization"); part
How can I provide the way they communicate - e.g name of named pipe? or just HTTP posts?
I'm not proficent in js / node development at all, so sorry for every foolish question
I've seen mature/production grade C# Language Server implementations but I'm overwhelmed just by their builders, there's sooo much stuff happening, sop that's why I'd want to write server from scratch, but for client use existing libs
var server = await LanguageServer.From(
options =>
options
.WithInput(Console.OpenStandardInput())
.WithOutput(Console.OpenStandardOutput())
.ConfigureLogging(
x => x
.AddSerilog(Log.Logger)
.AddLanguageProtocolLogging()
.SetMinimumLevel(LogLevel.Debug)
)
.WithHandler<TextDocumentHandler>()
.WithHandler<DidChangeWatchedFilesHandler>()
.WithHandler<FoldingRangeHandler>()
.WithHandler<MyWorkspaceSymbolsHandler>()
.WithHandler<MyDocumentSymbolHandler>()
.WithHandler<SemanticTokensHandler>()
.WithServices(x => x.AddLogging(b => b.SetMinimumLevel(LogLevel.Trace)))
.WithServices(
services => {
services.AddSingleton(
provider => {
var loggerFactory = provider.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<Foo>();
logger.LogInformation("Configuring");
return new Foo(logger);
}
);
services.AddSingleton(
new ConfigurationItem {
Section = "typescript",
}
).AddSingleton(
new ConfigurationItem {
Section = "terminal",
}
);
}
)
.OnInitialize(
async (server, request, token) => {
var manager = server.WorkDoneManager.For(
request, new WorkDoneProgressBegin {
Title = "Server is starting...",
Percentage = 10,
}
);
workDone = manager;
await Task.Delay(2000);
manager.OnNext(
new WorkDoneProgressReport {
Percentage = 20,
Message = "loading in progress"
}
);
}
)
.OnInitialized(
async (server, request, response, token) => {
workDone.OnNext(
new WorkDoneProgressReport {
Percentage = 40,
Message = "loading almost done",
}
);
await Task.Delay(2000);
workDone.OnNext(
new WorkDoneProgressReport {
Message = "loading done",
Percentage = 100,
}
);
workDone.OnCompleted();
}
)
.OnStarted(
async (languageServer, token) => {
using var manager = await languageServer.WorkDoneManager.Create(new WorkDoneProgressBegin { Title = "Doing some work..." });
manager.OnNext(new WorkDoneProgressReport { Message = "doing things..." });
await Task.Delay(10000);
manager.OnNext(new WorkDoneProgressReport { Message = "doing things... 1234" });
await Task.Delay(10000);
manager.OnNext(new WorkDoneProgressReport { Message = "doing things... 56789" });
var logger = languageServer.Services.GetService<ILogger<Foo>>();
var configuration = await languageServer.Configuration.GetConfiguration(
new ConfigurationItem {
Section = "typescript",
}, new ConfigurationItem {
Section = "terminal",
}
);
var baseConfig = new JObject();
foreach (var config in languageServer.Configuration.AsEnumerable())
{
baseConfig.Add(config.Key, config.Value);
}
logger.LogInformation("Base Config: {Config}", baseConfig);
var scopedConfig = new JObject();
foreach (var config in configuration.AsEnumerable())
{
scopedConfig.Add(config.Key, config.Value);
}
logger.LogInformation("Scoped Config: {Config}", scopedConfig);
}
)
);
Thanks in advance

WaterfallDialog choiceprompt working in emulator but not working in directline webchannel in Bot framework v4

I have recently migrated my project from bot framework v3 to bot V4.
I have FeedbackDialog file in this i have implemented waterfallsteps, choiceprompts.
Everything and the flow is working correctly in Emulator.
But it is not working properly means the flow is not working fine in Webchannel
Please provide the solution to this problem.
Here is my code.
public FeedbackDialog() : base(nameof(FeedbackDialog))
{
AddDialog(new ChoicePrompt("ShowChoicePrompt") { Style = ListStyle.HeroCard });
AddDialog(new ChoicePrompt(nameof(ConfirmPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
StartAsync,
MessageReceivedAsync,
ResumeAfterPositiveFeedbackSelectionClarification
}));
InitialDialogId = nameof(WaterfallDialog);
}
public async Task<DialogTurnResult> StartAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var reply = ((Activity)stepContext.Context.Activity).CreateReply(BotConstants.feedbackRequestText);
reply.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "👍", Type=ActionTypes.PostBack, Value=BotConstants.positiveFeedbackValue },
new CardAction(){ Title = "👎", Type=ActionTypes.PostBack, Value=BotConstants.negativeFeedbackValue }
}
};
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
// return await stepContext.PromptAsync(nameof(ConfirmPrompt), promptOptions, cancellationToken);
}
public async Task<DialogTurnResult> MessageReceivedAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var data=stepContext.Result;
var res = stepContext.Result;
// string res= Convert.ToString(data.GetType().GetProperty("Synonym").GetValue(data, null));
var feedbackDetails = (FeedbackData)stepContext.Options;
var userFeedback =Convert.ToString(res);
// var userFeedback = feedbackDetails.userFeedback;
string userQuestion = feedbackDetails.userQuestion;
string intent = feedbackDetails.intent;
int Id = feedbackDetails.Id;
if (userFeedback.Contains(BotConstants.positiveFeedbackValue) || userFeedback.Contains(BotConstants.negativeFeedbackValue))
{
// create telemetry client to post to Application Insights
TelemetryClient telemetry = new TelemetryClient();
telemetry.InstrumentationKey = Configuration.CDPOSConfigurationManager.GetAppSetting("InstrumentationKey");
RequestRepository requestRepository = new RequestRepository();
if (userFeedback.Contains(BotConstants.positiveFeedbackValue))
{
// post feedback to App Insights
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"LuisIntent", intent },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("Yes-Vote", properties);
var chatFeedback = true;
//requestRepository.AddChatbotEntryToDB(userQuestion, intent, chatFeedback);
requestRepository.updateChatFeedbackStatus(Id, chatFeedback);
var options = BotConstants.positiveFeedbackOption;
var descriptions = BotConstants.positiveFeedbackOptionDesc;
Activity textPrompt = stepContext.Context.Activity.CreateReply(BotConstants.positiveFeedbackResponse);
List<Microsoft.Bot.Builder.Dialogs.Choices.Choice> choices = new List<Microsoft.Bot.Builder.Dialogs.Choices.Choice>()
{
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.YeshaveAnotherQuestionOption, Synonyms = new List<string> { "Yes" } },
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.NothankyouOption, Synonyms = new List<string> { "No" }}
};
try
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions {Prompt= MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices= choices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
catch (Exception ex)
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices = choices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
else if (userFeedback.Contains(BotConstants.negativeFeedbackValue))
{
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"LuisIntent", intent },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("No-Vote", properties);
var chatFeedback = false;
//requestRepository.AddChatbotEntryToDB(userQuestion, intent, chatFeedback);
requestRepository.updateChatFeedbackStatus(Id, chatFeedback);
await stepContext.Context.SendActivityAsync(BotConstants.negativeFeedbackResponse);
await stepContext.Context.SendActivityAsync(BotConstants.logTicketResponse);
var options = BotConstants.negativeFeedbackOption;
var descriptions = BotConstants.negativeFeedbackDesc;
List<Microsoft.Bot.Builder.Dialogs.Choices.Choice> NegativeChoices = new List<Microsoft.Bot.Builder.Dialogs.Choices.Choice>()
{
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.logTicket, Synonyms = new List<string> { BotConstants.logTicketOption } },
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.rephraseQuestionOption, Synonyms = new List<string> { BotConstants.rephraseQuestionOption }}
};
try
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices = NegativeChoices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
catch (Exception ex)
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.question), Choices = NegativeChoices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
}
else
{
return await stepContext.BeginDialogAsync(nameof(RootDialog), null, cancellationToken);
// no feedback, return to main dialog
// return await stepContext.EndDialogAsync(userFeedback, cancellationToken);
//return stepContext.BeginDialogAsync(userFeedback, cancellationToken);
}
return await stepContext.EndDialogAsync(userFeedback, cancellationToken);
}
private async Task<DialogTurnResult> ResumeAfterPositiveFeedbackSelectionClarification(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var data = stepContext.Result;
if (stepContext.Result != null)
{
string selection = Convert.ToString(data.GetType().GetProperty("Synonym").GetValue(data, null));
// var selection = stepContext.Result;
if (selection == BotConstants.YeshaveAnotherQuestionOption || selection == BotConstants.haveAnotherQuestionOption || selection == BotConstants.rephraseQuestionOption)
{
await stepContext.Context.SendActivityAsync(BotConstants.askAnotherQuestion);
}
else
{
await stepContext.Context.SendActivityAsync(BotConstants.logaTicketResponse);
}
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
public static void Register(HttpConfiguration config)
{
var builder = new ContainerBuilder();
// builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<MessagesController>().InstancePerRequest();
// The ConfigurationCredentialProvider will retrieve the MicrosoftAppId and
// MicrosoftAppPassword from Web.config
builder.RegisterType<ConfigurationCredentialProvider>().As<ICredentialProvider>().SingleInstance();
// Create the Bot Framework Adapter with error handling enabled.
builder.RegisterType<AdapterWithErrorHandler>().As<IBotFrameworkHttpAdapter>().SingleInstance();
// The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, everything stored in memory will be gone.
IStorage dataStore = new MemoryStorage();
// Create Conversation State object.
// The Conversation State object is where we persist anything at the conversation-scope.
var conversationState = new ConversationState(dataStore);
builder.RegisterInstance(conversationState).As<ConversationState>().SingleInstance();
// Register the main dialog, which is injected into the DialogBot class
builder.RegisterType<RootDialog>().SingleInstance();
// Register the DialogBot with RootDialog as the IBot interface
builder.RegisterType<DialogBot<RootDialog>>().As<IBot>();
var container = builder.Build();
var resolver = new AutofacWebApiDependencyResolver(container);
config.DependencyResolver = resolver;
}
When a bot only works when it's running locally, memory storage is very often the issue. When a bot is deployed to an app service, there may be multiple instances of the service running simultaneously to provide high availability, and each instance will have its own memory. Like all REST API's, your bot needs to not rely on memory for the state it accesses across multiple turns. You need external storage so that all instances of the service have access to the same data, and so the data isn't lost when the service restarts. Please refer to the documentation for more information.

Read metadata in grpc on the server side c#

I am sending token in metadata from the client side
Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
ItemQuery item = new ItemQuery() { Id = "abc" };
var client = new MyService.MyServiceClient(channel);
Metadata data = new Metadata
{
{ "token", "Bearer xhrttt" }
};
var reply = client.GetItem(item, data);
But not able to find a way to fetch it in server side, Any help is appreciated
below is an example of how my server-side code looks(i tried certain other ways also)
public override Task<ItemResponse> GetItem(ItemQuery request , ServerCallContext context)
{
try
{
var a = context.RequestHeaders["token"]; // not working
ItemResponse itmRes = new ItemResponse();
if (request.Id == "foo")
{
itmRes.Items.Add(new Item() { Id = "foo", Name = "foobar" });
}
return Task.FromResult(itmRes);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
return null;
}
Below is the code to fetch metadata in c#
Metadata.Entry metadataEntry = context.RequestHeaders.FirstOrDefault(m =>
String.Equals(m.Key, "token", StringComparison.Ordinal));
if (metadataEntry.Equals(default(Metadata.Entry)) || metadataEntry.Value == null)
{
return null;
}
Console.WriteLine("Token value is {0}", metadataEntry.Value);
for more details refer https://csharp.hotexamples.com/examples/Grpc.Core/ServerCallContext/-/php-servercallcontext-class-examples.html
Based on this tutorial, this and this, getting and setting metadata can be summarized:
GreeterService.cs (GrpcGreeter.csproj)
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
context.WriteResponseHeadersAsync(
new Metadata() { { "server_header", "nice to see you too" } });
context.ResponseTrailers.Add(
new Metadata.Entry("server_trailer", "see you later") { });
string? client_header = context.RequestHeaders.GetValue("client_header");
return Task.FromResult(new HelloReply
{
Message = $"i got your header, {request.Name}. it reads: {client_header}"
});
}
Program.cs (GrpcGreeterClient.csproj)
// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7143");
Greeter.GreeterClient client = new Greeter.GreeterClient(channel);
var call = client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" },
new Metadata() { { "client_header", "hey there" } });
Metadata headers = await call.ResponseHeadersAsync;
Console.WriteLine($"Server Header: {headers.GetValue("server_header")}");
HelloReply rsp = await call.ResponseAsync;
Console.WriteLine($"Server Response: {rsp.Message}");
Metadata trailers = call.GetTrailers();
string? myTrailer = trailers.GetValue("server_trailer");
Console.WriteLine($"Server Trailers: {myTrailer}");
Output:
Server Header: nice to see you too
Server Response: i got your header, GreeterClient. it reads: hey there
Server Trailers: see you later

Redirect to URL with POST method in Asp.Net Core

I have simple url rewriter:
private static void RedirectToAPI(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Path.Value.StartsWith("/apiendpoint", StringComparison.OrdinalIgnoreCase))
{
var json = JsonConvert.SerializeObject(request.Path.Value
.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1));
var response = context.HttpContext.Response;
response.Headers[HeaderNames.Location] = $"/custom";
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
using (var bodyWriter = new StreamWriter(response.Body))
{
bodyWriter.Write(json);
bodyWriter.Flush();
}
}
}
The problem is, when it redirects to /custom url, request has method GET, while this method require POST.
For example, send GET request to url /apiendpoint/first/second/third, then rewriter responds to redirect, accordingly, the following request must be with method POST, but now, it is GET.
How can I change method of request, which is after url rewriter response?
EDIT: Ah, just now checked the comments. If the initial request is a GET, then this won't work either and you can't tell the browser to POST. Not without returning a view that auto-executes a form with JavaScript.
You need to return a 308, not a 301.
Here is the changed code:
private static void RedirectToAPI(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Path.Value.StartsWith("/apiendpoint", StringComparison.OrdinalIgnoreCase))
{
var json = JsonConvert.SerializeObject(request.Path.Value
.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1));
var response = context.HttpContext.Response;
response.Headers[HeaderNames.Location] = $"/custom";
response.StatusCode = 308;
context.Result = RuleResult.EndResponse;
using (var bodyWriter = new StreamWriter(response.Body))
{
bodyWriter.Write(json);
bodyWriter.Flush();
}
}
}
308 is a permanent redirect that requires the browser to preserve the method. https://httpstatuses.com/308
The temporary version is 307.
Convenience methods for these redirects are available in MVC Core 2.0.0-preview1.
If using ASP.NET Core 2.0 there is RedirectPermanentPreserveMethod, you can read about it here. If this method is not supported you can try doing it manually.
This is a sample code
public class ConvertGetToPostHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add your logic here to decide if the request Method needs to be changed
// Caution: This works only if you're redirecting internally
request.Method = HttpMethod.Post;
return await base.SendAsync(request, cancellationToken);
}
}
You will have to add the Handler to Handler Pipeline as
config.MessageHandlers.Add(new ConvertGetToPostHandler());
Also, read over this documentation to get some insight into it's purpose and usage.
Can you set #juunas answer as a correct answer, his answer solved it for me as you can see in my example code.
internal class RedirectCultureRule : IRule
{
private const string CultureKey = "culture";
public void ApplyRule(RewriteContext context)
{
HttpRequest httpRequest = context.HttpContext.Request;
httpRequest.Query.TryGetValue(CultureKey, out StringValues cultureValues);
string culture = cultureValues;
if (cultureValues.Count > 0 && culture.IsCultureMatch())
{
context.Result = RuleResult.ContinueRules;
return;
}
Dictionary<string, string> queryParts = new Dictionary<string, string>();
NameValueCollection queryString = HttpUtility.ParseQueryString(httpRequest.QueryString.ToString());
foreach (string key in queryString)
{
queryParts[key.Trim()] = queryString[key].Trim();
}
if (!queryParts.ContainsKey(CultureKey))
{
queryParts[CultureKey] = CultureInfo.CurrentCulture.Name;
}
string query = $"?{string.Join("&", queryParts.Select(qp => $"{qp.Key}={qp.Value}"))}";
if (query.Length > 1)
{
httpRequest.QueryString = new QueryString(query);
}
string url = UriHelper.GetDisplayUrl(httpRequest);
HttpResponse httpResponse = context.HttpContext.Response;
httpResponse.StatusCode = 308;
httpResponse.Headers[HeaderNames.Location] = url;
using (StreamReader requestReader = new StreamReader(httpRequest.Body))
{
using (StreamWriter responseWriter = new StreamWriter(httpResponse.Body))
{
string body = requestReader.ReadToEnd();
responseWriter.Write(body);
responseWriter.Flush();
}
}
context.Result = RuleResult.EndResponse;
}
}

Google Calendar API for UWP Windows 10 Application One or more errors occurred

I'm trying to use Google Calendar API v3, but i have problems while running the codes, it always gives me that error :
An exception of type 'System.AggregateException' occurred in mscorlib.ni.dll but was not handled in user code
Additional information: One or more errors occurred.
I don't know why it does, also It should work as well. Here is a screenshot for it :
Also my codes are :
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/client_secrets.json"),
Scopes,
"user",
CancellationToken.None).Result;
// Create Google Calendar API service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
var calendarService = new CalendarService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Windows 10 Calendar sample"
});
var calendarListResource = await calendarService.CalendarList.List().ExecuteAsync();
If you can at least help with calling it through REST API, that would be great too, but you must consider that it's UWP, so it has another way to get it work as well.
As i already tried through REST API, but i always get "Request error code 400".
Thanks for your attention.
The Google API Client Library for .NET does not support UWP by now. So we can't use Google.Apis.Calendar.v3 Client Library in UWP apps now. For more info, please see the similar question: Universal Windows Platform App with google calendar.
To use Google Calendar API in UWP, we can call it through REST API. To use the REST API, we need to authorize requests first. For how to authorize requests, please see Authorizing Requests to the Google Calendar API and Using OAuth 2.0 for Mobile and Desktop Applications.
After we have the access token, we can call Calendar API like following:
var clientId = "{Your Client Id}";
var redirectURI = "pw.oauth2:/oauth2redirect";
var scope = "https://www.googleapis.com/auth/calendar.readonly";
var SpotifyUrl = $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={Uri.EscapeDataString(redirectURI)}&response_type=code&scope={Uri.EscapeDataString(scope)}";
var StartUri = new Uri(SpotifyUrl);
var EndUri = new Uri(redirectURI);
// Get Authorization code
WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, StartUri, EndUri);
if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
var decoder = new WwwFormUrlDecoder(new Uri(WebAuthenticationResult.ResponseData).Query);
if (decoder[0].Name != "code")
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {decoder.GetFirstValueByName("error")}.");
return;
}
var autorizationCode = decoder.GetFirstValueByName("code");
//Get Access Token
var pairs = new Dictionary<string, string>();
pairs.Add("code", autorizationCode);
pairs.Add("client_id", clientId);
pairs.Add("redirect_uri", redirectURI);
pairs.Add("grant_type", "authorization_code");
var formContent = new Windows.Web.Http.HttpFormUrlEncodedContent(pairs);
var client = new Windows.Web.Http.HttpClient();
var httpResponseMessage = await client.PostAsync(new Uri("https://www.googleapis.com/oauth2/v4/token"), formContent);
if (!httpResponseMessage.IsSuccessStatusCode)
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {httpResponseMessage.StatusCode}.");
return;
}
string jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
var jsonObject = Windows.Data.Json.JsonObject.Parse(jsonString);
var accessToken = jsonObject["access_token"].GetString();
//Call Google Calendar API
using (var httpRequest = new Windows.Web.Http.HttpRequestMessage())
{
string calendarAPI = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
httpRequest.Method = Windows.Web.Http.HttpMethod.Get;
httpRequest.RequestUri = new Uri(calendarAPI);
httpRequest.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", accessToken);
var response = await client.SendRequestAsync(httpRequest);
if (response.IsSuccessStatusCode)
{
var listString = await response.Content.ReadAsStringAsync();
//TODO
}
}
}
I have the Google .NET Client working in my UWP app. The trick is that you have to put it in a .NET Standard 2.0 Class Library, expose the API services you need, and then reference that library from your UWP app.
Also, you have to handle the getting the auth token yourself. It's not that much work and the Drive APIs and Calendar APIs work just fine (the only ones I've tried). You can see that I pass in a simple class that contains the auth token and other auth details to a method called Initialize.
Here is the single class I used in the .NET Standard 2.0 class library:
namespace GoogleProxy
{
public class GoogleService
{
public CalendarService calendarService { get; private set; }
public DriveService driveService { get; private set; }
public GoogleService()
{
}
public void Initialize(AuthResult authResult)
{
var credential = GetCredentialForApi(authResult);
var baseInitializer = new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "{your app name here}" };
calendarService = new Google.Apis.Calendar.v3.CalendarService(baseInitializer);
driveService = new Google.Apis.Drive.v3.DriveService(baseInitializer);
}
private UserCredential GetCredentialForApi(AuthResult authResult)
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{your app client id here}",
ClientSecret = "",
},
Scopes = new string[] { "openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly" },
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
var token = new TokenResponse()
{
AccessToken = authResult.AccessToken,
RefreshToken = authResult.RefreshToken,
ExpiresInSeconds = authResult.ExpirationInSeconds,
IdToken = authResult.IdToken,
IssuedUtc = authResult.IssueDateTime,
Scope = "openid email profile https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly https://www.googleapis.com/auth/drive.readonly",
TokenType = "bearer" };
return new UserCredential(flow, authResult.Id, token);
}
}
}
In order to get the Auth token from google, you have to use custom schemes. Register your app as an 'iOS' app on the google services console and put in a URI scheme (something unique). Then add this scheme to your UWP manifest under Declarations->Protocol. Handle it in your App.xaml.cs:
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)args;
Uri uri = protocolArgs.Uri;
Debug.WriteLine("Authorization Response: " + uri.AbsoluteUri);
locator.AccountsService.GoogleExternalAuthWait.Set(uri.Query);
}
}
That GoogleExternalAuthWait comes from some magical code I found about how to create an asynchronous ManualResetEvent. https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/ It looks like this (I only converted it to generic).
public class AsyncManualResetEvent<T>
{
private volatile TaskCompletionSource<T> m_tcs = new TaskCompletionSource<T>();
public Task<T> WaitAsync() { return m_tcs.Task; }
public void Set(T TResult) { m_tcs.TrySetResult(TResult); }
public bool IsReset => !m_tcs.Task.IsCompleted;
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<T>(), tcs) == tcs)
return;
}
}
}
This is how you start the Google Authorization. What happens is it launches an external browser to begin the google signing process and then wait (that's what the AsyncManualResetEvent does). When you're done, Google will launch a URI using your custom scheme. You should get a message dialog saying the browser is trying to open an app... click ok and the AsyncManualResetEvent continues and finishes the auth process. You'll need to make a class that contains all the auth info to pass to your class library.
private async Task<AuthResult> AuthenticateGoogleAsync()
{
try
{
var stateGuid = Guid.NewGuid().ToString();
var expiration = DateTimeOffset.Now;
var url = $"{GoogleAuthorizationEndpoint}?client_id={WebUtility.UrlEncode(GoogleAccountClientId)}&redirect_uri={WebUtility.UrlEncode(GoogleRedirectURI)}&state={stateGuid}&scope={WebUtility.UrlEncode(GoogleScopes)}&display=popup&response_type=code";
var success = Windows.System.Launcher.LaunchUriAsync(new Uri(url));
GoogleExternalAuthWait = new AsyncManualResetEvent<string>();
var query = await GoogleExternalAuthWait.WaitAsync();
var dictionary = query.Substring(1).Split('&').ToDictionary(x => x.Split('=')[0], x => Uri.UnescapeDataString(x.Split('=')[1]));
if (dictionary.ContainsKey("error"))
{
return null;
}
if (!dictionary.ContainsKey("code") || !dictionary.ContainsKey("state"))
{
return null;
}
if (dictionary["state"] != stateGuid)
return null;
string tokenRequestBody = $"code={dictionary["code"]}&redirect_uri={Uri.EscapeDataString(GoogleRedirectURI)}&client_id={GoogleAccountClientId}&access_type=offline&scope=&grant_type=authorization_code";
StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
// Performs the authorization code exchange.
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.AllowAutoRedirect = true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(GoogleTokenEndpoint, content);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(stringResponse);
var id = DecodeIdFromJWT((string)json["id_token"]);
var oauthToken = new AuthResult()
{
Provider = AccountType.Google,
AccessToken = (string)json["access_token"],
Expiration = DateTimeOffset.Now + TimeSpan.FromSeconds(int.Parse((string)json["expires_in"])),
Id = id,
IdToken = (string)json["id_token"],
ExpirationInSeconds = long.Parse((string)json["expires_in"]),
IssueDateTime = DateTime.Now,
RefreshToken = (string)json["refresh_token"]
};
return oauthToken;
}
else
{
return null;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return null;
}
}

Categories