Implementing a generic API caller - c#

I am trying to implement a generic caller that uses OpenWeatherMap's different weather API's, but I got stuck in regards to how I would put in the right identifier for the link.
.../weather?q=... returns JSON data for the current weather;
.../forecast?q=... returns JSON data for a five day forecast.
I am looking for the textbook way to maybe retrieve the API type of each class through accessing GetAPIType(), cast that to an int and put it in the index, so that I would be able to use identifiers[index]. Or perhaps there is an easier way to do it.
Checking for the typeof(T) also crossed my mind, and I would assign the index depending on the if(typeof(T).Equals(typeof(...))) construct, but that seems very messy and if OpenWeatherMap had 100 API's in theory, I would need 100 different if constructs. With this in mind, wouldn't creating those checks beat the purpose of Client being generic?
A third solution I thought of would be passing APIType type as a parameter for the Client constructor,
e.g. var client = new Client<CurrentWeatherDTO>(APIType.CurrentWeather, location, apiKey),
but given the fact that Client is generic and I already provide a type when I instantiate it, it would seem awfully redundant.
Client.cs
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Reflection;
namespace Rainy.OpenWeatherMapAPI
{
public class Client<T>
{
private readonly string location;
private readonly string apiKey;
private readonly string requestUri;
private readonly string[] identifiers = { "weather", "forecast" };
private readonly int index;
public Client(string location, string apiKey)
{
// Get the type of API used in order to get the right identifier for the link.
// ??? Maybe use Reflection, somehow.
this.location = location;
this.apiKey = apiKey;
requestUri = $"api.openweathermap.org/data/2.5/{}?q={location}&appid={apiKey}";
}
public async Task<T> GetWeather(CancellationToken cancellationToken)
{
using (var client = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
var stream = await response.Content.ReadAsStreamAsync();
if (response.IsSuccessStatusCode)
return DeserializeJsonFromStream<T>(stream);
var content = await StreamToStringAsync(stream);
throw new APIException
{
StatusCode = (int)response.StatusCode,
Content = content
};
}
}
private U DeserializeJsonFromStream<U>(Stream stream)
{
if (stream == null || stream.CanRead == false)
return default(U);
using (var sr = new StreamReader(stream))
using (var jtr = new JsonTextReader(sr))
{
var js = new JsonSerializer();
var searchResult = js.Deserialize<U>(jtr);
return searchResult;
}
}
private async Task<string> StreamToStringAsync(Stream stream)
{
string content = null;
if (stream != null)
using (var sr = new StreamReader(stream))
content = await sr.ReadToEndAsync();
return content;
}
}
}
APIType.cs
namespace Rainy.OpenWeatherMapAPI
{
public enum APIType
{
CurrentWeather = 0,
FiveDayForecast = 1
}
}
IWeather.cs
namespace Rainy.OpenWeatherMapAPI
{
public interface IWeather
{
APIType GetAPIType();
}
}
CurrentWeatherDTO.cs
namespace Rainy.OpenWeatherMapAPI.CurrentWeatherData
{
class CurrentWeatherDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.CurrentWeather;
}
}
}
FiveDayForecastDTO.cs
namespace Rainy.OpenWeatherMapAPI.WeatherForecastData
{
class FiveDayForecastDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}

I would not use an enum to drive the index of an array.
I would directly return the string in a static way.
This solution can also work with the index of the array if you want.
Here is the code and the dotnetfiddle:
using System;
public class Program
{
public static void Main()
{
var client1 = new Client<CurrentWeatherDTO>(null);
Console.WriteLine("Client CurrentWeather type: " + client1.Type);
var client2 = new Client<FiveDayForecastDTO>(null);
Console.WriteLine("Client FiveDay type: " + client2.Type);
}
public class Client<T> where T : IWeather, new()
{
public string Type { get; set; }
public Client(string apiKey)
{
var dto = (IWeather)new T();
this.Type = dto.GetAPIType();
}
}
public static class APIType
{
public static string CurrentWeather = "weather";
public static string FiveDayForecast = "forecast";
}
public interface IWeather
{
string GetAPIType();
}
class CurrentWeatherDTO : IWeather
{
public string GetAPIType()
{
return APIType.CurrentWeather;
}
}
class FiveDayForecastDTO : IWeather
{
public string GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}

I would probably use a solution like this, but maybe a bit more error handling.
There's a couple of references for how to use HttpClient.
I don't really understand the part in the requestUri with {}, maybe that's part of your problem, I changed it to {???} in my sample code.
class Client
{
// Problems using HttpClient and look into using IHttpClientFactory...
// http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
// https://www.hanselman.com/blog/HttpClientFactoryForTypedHttpClientInstancesInASPNETCore21.aspx
static HttpClient _httpClient = new HttpClient();
readonly string WeatherUri = $"api.openweathermap.org/data/2.5/{???}?q={0}&appid={1}";
public async Task<T> GetWeather<T>(string location, CancellationToken cancellationToken)
{
var apiKey = ApiKeyAttribute.GetApiKey<T>();
if (apiKey == null) throw new Exception("ApiKeyAttirbute missing");
var requestUri = string.Format(WeatherUri, location, apiKey);
return await GetItem<T>(requestUri, cancellationToken);
}
public async Task<T> GetItem<T>(string requestUri, CancellationToken cancellationToken)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken);
if (!response.IsSuccessStatusCode) throw new Exception("Error requesting data");
if (response.Content == null) return default(T);
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
[ApiKeyAttribute("weather")]
class CurrentWeatherDTO { /* add appropriat properties */ }
[ApiKeyAttribute("forecast")]
class FiveDayForecastDTO { /* add appropriat properties */ }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
class ApiKeyAttribute : Attribute
{
public string Name { get; private set; }
public ApiKeyAttribute(string name)
{
Name = name;
}
public static string GetApiKey<T>()
{
var attribute = typeof(T).GetCustomAttribute<ApiKeyAttribute>();
return attribute?.Name;
}
}

Related

Get all classes that implement an interface and call a function in .NET Core

How can I get all classes that implements a specific interface then call a function of that class if a string member of the specific class matches a given one?
Basically what I have is a ICommandHandler interface:
interface ICommandHandler
{
string Command { get; }
Task ExecuteAsync();
}
and a class that implements it:
public class StartCommand : ICommandHandler
{
public string Command { get => "start"; }
public async Task ExecuteAsync()
{
// do something
}
}
What I want to do is to get all the classes that implements the ICommandHandler interface, then verify if the class.Command equals a specific string and if it does then call the ExecuteAsync method.
I've tried using this answer here: https://stackoverflow.com/a/45382386/15306888 but the class.Command is always null
Edit: The answer I got bellow does what I wanted to do:
What I was looking for was a way to use ICommandHandler to allow me to easily gather all the classes inheriting from it and call the ExecuteAsync function instead of having to manually add the methods in the part of the code handling TdLib message events.
So now my project directory looks something like this:
TelegramClient.cs - The place where the classes inheriting from the ICommandHandler are loaded, and where the ExecuteAsync method is called if the Command member matches the Command parsed from the message returned by telegram.
Handlers/ - Base directory for my handlers
CommandHandlers/ - Folder with the ICommandHandler interface and the classes inheriting from it
MessageHandlers/ - Folder with another interface IMessageHandler and the classes inheriting from it
Anyway, in the meantime I've found another answer on a stackoverflow question (Had to scroll a few times) that made it way easier and faster to get multiple handlers without having to repeat the same code over and over again. I've ended by combining the answer linked above with this one: https://stackoverflow.com/a/41650057/15306888
So I ended with a simple method:
public static IEnumerable<T> GetAll<T>()
{
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(T).IsAssignableFrom(type))
.Where(type =>
!type.IsAbstract &&
!type.IsGenericType &&
type.GetConstructor(new Type[0]) != null)
.Select(type => (T)Activator.CreateInstance(type))
.ToList();
}
That can be easily used like:
private async Task ProcessMessage(TdApi.Update.UpdateNewMessage message)
{
var command = GetCommand(message.Message);
var textMessage = GetMessageText(message.Message);
if (!String.IsNullOrWhiteSpace(command))
{
var commandHandlers = GetAll<ICommandHandler>();
foreach (var handler in commandHandlers)
{
if (command == handler.Command)
await handler.ExecuteAsync(_client, message.Message);
}
}
else if (!String.IsNullOrWhiteSpace(textMessage))
{
var messageHandlers = GetAll<IMessageHandler>();
foreach (var handler in messageHandlers)
{
var outgoing = handler.Outgoing && message.Message.IsOutgoing;
var incoming = handler.Incoming && !message.Message.IsOutgoing;
if (outgoing || incoming)
{
if (!String.IsNullOrEmpty(handler.Pattern))
{
var match = Regex.Match(textMessage, handler.Pattern);
if (match.Success)
await handler.ExecuteAsync(_client, message.Message);
}
}
}
}
}
How the Interface is actually implemented:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TdLib;
namespace YoutubeDl_Bot.Handlers.CommandHandlers
{
public class StartCommand : ICommandHandler
{
public string Command { get => "/start"; }
public async Task ExecuteAsync(TdClient client, TdApi.Message message)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Hi! I'm a bot that downloads and sends video and audio files from youtube links and many other supported services");
stringBuilder.AppendLine(String.Empty);
stringBuilder.AppendLine("**Usage:**");
stringBuilder.AppendLine("• Send or forward a text message containing links and I will:");
stringBuilder.AppendLine("• Download the best audio quality available for the video in the speecified link");
stringBuilder.AppendLine("• Download the best video quality available for the video in the speecified link");
stringBuilder.AppendLine("• Send the direct download URL for every link specified in the message");
stringBuilder.AppendLine("• Supported links are available here: https://ytdl-org.github.io/youtube-dl/supportedsites.html");
var formattedText = await client.ExecuteAsync(new TdLib.TdApi.ParseTextEntities { Text = stringBuilder.ToString(), ParseMode = new TdLib.TdApi.TextParseMode.TextParseModeMarkdown() });
await client.ExecuteAsync(new TdLib.TdApi.SendMessage { ChatId = message.ChatId, InputMessageContent = new TdLib.TdApi.InputMessageContent.InputMessageText { Text = formattedText } });
}
}
}
Full TelegramClient class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using TdLib;
using YoutubeDl_Bot.Handlers.CallbackHandlers;
using YoutubeDl_Bot.Handlers.CommandHandlers;
using YoutubeDl_Bot.Handlers.MessageHandlers;
using YoutubeDl_Bot.Settings;
using YoutubeDl_Bot.Utils;
namespace YoutubeDl_Bot
{
class TelegramBotClient
{
private static TdClient _client;
private static TdLib.TdApi.User _me;
private static int _apiId;
private static string _apiHash;
private static string _token;
#if DEBUG
private static readonly int _verbosityLevel = 4;
#else
private static readonly int _verbosityLevel = 0;
#endif
public static bool AuthCompleted = false;
public TelegramBotClient(int apiId, string apiHash)
{
_apiId = apiId;
_apiHash = apiHash;
}
public async Task<TdClient> CreateClient()
{
_client = new TdClient();
await _client.ExecuteAsync(new TdApi.SetLogVerbosityLevel { NewVerbosityLevel = _verbosityLevel });
return _client;
}
public void StartListening(string botToken)
{
_token = botToken;
_client.UpdateReceived += _client_UpdateReceived;
}
private async void _client_UpdateReceived(object sender, TdApi.Update update)
{
switch (update)
{
case TdApi.Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(TdApi.AuthorizationState.AuthorizationStateWaitTdlibParameters):
await _client.ExecuteAsync(new TdApi.SetTdlibParameters
{
Parameters = new TdApi.TdlibParameters
{
ApiId = _apiId,
ApiHash = _apiHash,
ApplicationVersion = "0.0.1",
DeviceModel = "Bot",
SystemLanguageCode = "en",
SystemVersion = "Unknown"
}
});
break;
case TdApi.Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(TdLib.TdApi.AuthorizationState.AuthorizationStateWaitEncryptionKey):
await _client.ExecuteAsync(new TdLib.TdApi.CheckDatabaseEncryptionKey());
break;
case TdLib.TdApi.Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(TdLib.TdApi.AuthorizationState.AuthorizationStateWaitPhoneNumber):
await _client.ExecuteAsync(new TdLib.TdApi.CheckAuthenticationBotToken { Token = _token });
break;
case TdLib.TdApi.Update.UpdateConnectionState updateConnectionState when updateConnectionState.State.GetType() == typeof(TdLib.TdApi.ConnectionState.ConnectionStateReady):
// To Do Settings
var botSettings = new BotSettings(_apiId, _apiHash, _token);
_me = await _client.ExecuteAsync(new TdLib.TdApi.GetMe());
Helpers.Print($"Logged in as: {_me.FirstName}");
SettingsManager.Set<BotSettings>("BotSettings.data", botSettings);
break;
case TdLib.TdApi.Update.UpdateNewMessage message:
if (!message.Message.IsOutgoing)
await ProcessMessage(message);
break;
case TdApi.Update.UpdateNewCallbackQuery callbackQuery:
await ProcessCallbackQuery(callbackQuery);
break;
default:
break;
}
}
#region PROCESS_MESSAGE
private async Task ProcessMessage(TdApi.Update.UpdateNewMessage message)
{
var command = GetCommand(message.Message);
var textMessage = GetMessageText(message.Message);
#region COMMAND_HANDLERS
if (!String.IsNullOrWhiteSpace(command))
{
var commandHandlers = GetAll<ICommandHandler>();
foreach (var handler in commandHandlers)
{
if (command == handler.Command)
await handler.ExecuteAsync(_client, message.Message);
}
}
#endregion
#region MESSAGE_HANDLERS
else if (!String.IsNullOrWhiteSpace(textMessage))
{
var messageHandlers = GetAll<IMessageHandler>();
foreach (var handler in messageHandlers)
{
var outgoing = handler.Outgoing && message.Message.IsOutgoing;
var incoming = handler.Incoming && !message.Message.IsOutgoing;
if (outgoing || incoming)
{
if (!String.IsNullOrEmpty(handler.Pattern))
{
var match = Regex.Match(textMessage, handler.Pattern);
if (match.Success)
await handler.ExecuteAsync(_client, message.Message);
}
}
}
}
#endregion
}
#endregion
#region PROCESS_CALLACK
private async Task ProcessCallbackQuery(TdApi.Update.UpdateNewCallbackQuery callbackQuery)
{
if (callbackQuery.Payload.GetType() == typeof(TdApi.CallbackQueryPayload.CallbackQueryPayloadData))
{
var payload = callbackQuery.Payload as TdApi.CallbackQueryPayload.CallbackQueryPayloadData;
var callbackHandlers = GetAll<ICallbackHandler>();
foreach (var handler in callbackHandlers)
{
if (handler.DataIsRegex)
if (Regex.Match(System.Text.Encoding.UTF8.GetString(payload.Data), handler.Data).Success)
await handler.ExecuteAsync(_client, callbackQuery);
else if (handler.Data == System.Text.Encoding.UTF8.GetString(payload.Data))
await handler.ExecuteAsync(_client, callbackQuery);
}
}
}
#endregion
#region COMMAND_PARSER
public string GetCommand(TdApi.Message message)
{
string command = null;
TdLib.TdApi.FormattedText formattedText = new TdLib.TdApi.FormattedText();
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageText))
{
var messageText = message.Content as TdLib.TdApi.MessageContent.MessageText;
formattedText = messageText.Text;
}
else
{
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessagePhoto))
{
var messagePhoto = message.Content as TdLib.TdApi.MessageContent.MessagePhoto;
formattedText = messagePhoto.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageDocument))
{
var messageDocument = message.Content as TdLib.TdApi.MessageContent.MessageDocument;
formattedText = messageDocument.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageVideo))
{
var messageVideo = message.Content as TdLib.TdApi.MessageContent.MessageVideo;
formattedText = messageVideo.Caption;
}
}
foreach (var entity in formattedText.Entities)
{
if (entity.Type.GetType() == typeof(TdLib.TdApi.TextEntityType.TextEntityTypeBotCommand) && String.IsNullOrWhiteSpace(command))
{
if (entity.Offset == 0)
{
var splitCommand = formattedText.Text.Split();
if (splitCommand[0].EndsWith($"#{_me.Username}"))
{
command = splitCommand[0].Split('#')[0];
}
else
{
command = splitCommand[0];
}
}
}
}
return command;
}
#endregion
#region MESSAGE_PARSER
public string GetMessageText(TdApi.Message message)
{
TdLib.TdApi.FormattedText formattedText = new TdLib.TdApi.FormattedText();
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageText))
{
var messageText = message.Content as TdLib.TdApi.MessageContent.MessageText;
formattedText = messageText.Text;
}
else
{
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessagePhoto))
{
var messagePhoto = message.Content as TdLib.TdApi.MessageContent.MessagePhoto;
formattedText = messagePhoto.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageDocument))
{
var messageDocument = message.Content as TdLib.TdApi.MessageContent.MessageDocument;
formattedText = messageDocument.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageVideo))
{
var messageVideo = message.Content as TdLib.TdApi.MessageContent.MessageVideo;
formattedText = messageVideo.Caption;
}
}
return formattedText.Text;
}
#endregion
#region REFLECTION
// https://stackoverflow.com/a/41650057/15306888
public static IEnumerable<T> GetAll<T>()
{
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(T).IsAssignableFrom(type))
.Where(type =>
!type.IsAbstract &&
!type.IsGenericType &&
type.GetConstructor(new Type[0]) != null)
.Select(type => (T)Activator.CreateInstance(type))
.ToList();
}
#endregion
}
}
Since it's always null I think that the problem is that you're not creating an instance of your handler. I prepared a demo for you where I did that and it works.
public interface ICommandHandler
{
string Command { get; }
Task ExecuteAsync();
}
public class FirstCommandHandler : ICommandHandler
{
public string Command => "First";
public async Task ExecuteAsync()
{
Console.WriteLine("Hello from first.");
await Task.Delay(10);
}
}
public class SecondCommandHandler : ICommandHandler
{
public string Command => "Second";
public async Task ExecuteAsync()
{
Console.WriteLine("Hello from second.");
await Task.Delay(10);
}
}
public class Program
{
static async Task Main(string[] args)
{
var handlers = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(ICommandHandler).IsAssignableFrom(p) && p.IsClass);
foreach (var handler in handlers)
{
var handlerInstance = (ICommandHandler)Activator.CreateInstance(handler);
if (handlerInstance.Command == "First")
{
await handlerInstance.ExecuteAsync();
}
}
}
}
If it's not the case, could you show some more code? Are you trying to check Command value by reflection?

Why Task.Run not processing all lines on Task?

Im using WebApi to Deserialize Object on client side, witch contains some lightweight images, the code reads:
private void Button_Click(object sender, object e)
{
LoadApi();
}
private async void LoadApi()
{
using (var client = new HttpClient())
{
var responseMessage = await client.GetAsync("http://" +
TxtIP.Text + "/api/prod");
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
List<ClsProd> lstData = new List<ClsProd>();
var jsonResponse = await
responseMessage.Content.ReadAsStringAsync();
if (jsonResponse != null)
{
lstData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ClsProd>>(jsonResponse);
}
ListView1.ItemsSource = lstData;
}
}
}
my ClsProd looks witch get all data from Web Api is:
public class ClsProd : System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg { get; set; }
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
ClsImgBase64 CImg = new ClsImgBase64();
CImg.EvtResult += CImg_EvtResult;
CImg.Start(imax);
}
private void CImg_EvtResult(ImageSource e)
{
ImgPg = e;
NotifyPropertyChanged("ImgPg");
}
}
All data is properly fetch and displayed on list, including string SImax witch is image encoded as Base64 string. The only problem is image conversion from base64 string to image is not happening.
Here is my class it does not pass the 1st statment on Task.Run, please help me find what is wrong. Also same funcition works when called from async void.
public class ClsImgBase64
{
public event Action<ImageSource> EvtResult;
public ClsImgBase64()
{
}
public void Start(string s)
{
System.Threading.Tasks.Task.Run(async () =>
{
//read stream
byte[] bytes = Convert.FromBase64String(s);
var image = bytes.AsBuffer().AsStream().AsRandomAccessStream();
//decode image
//var decoder = await BitmapDecoder.CreateAsync(image);
image.Seek(0);
//create bitmap
var output = new WriteableBitmap(1, 1);
await output.SetSourceAsync(image);
if (EvtResult != null)
{
EvtResult(output);
}
});
}
}
As per async void there's probably an Exception thrown which was lost and not displayed bacause the executing code is not awaited. Let's fix it.
Web part
avoid async void in methods that's aren't event handlers, also handle all possible exceptions in async void method
HttpClient is intended to be instantiated once per app rather than per use
HttpResponseMessage is IDisposable
private async void Button_Click(object sender, object e)
{
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
// show ex.Message here in UI or log it
}
}
private static readonly HttpClient _client = new HttpClient();
private async Task LoadDataAsync()
{
using var response = await _client.GetAsync($"http://{TxtIP.Text}/api/prod");
string json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
List<ClsProd> data = JsonConvert.DeserializeObject<List<ClsProd>>(json);
ListView1.ItemsSource = data;
await DecodeAllImagesAsync(data);
}
// decoding all at once asynchronously, see implementation below
private Task DecodeAllImagesAsync(List<ClsProd> data)
{
return Task.WhenAll(data.Select(item => item.DecodeImageAsync()).ToArray());
}
Consider using System.Text.Json to deserealize instead of old Newtonsoft.Json. It would allow to deserealize response.Content as Stream, faster with less memory consumption e.g:
using var stream = await response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync();
List<ClsProd> data = await JsonSerializer.DeserealizeAsync<List<ClsProd>>(stream);
Data part
Use using directives at the beggining of the code to attach namespaces that will help not to repeat namespaces in the code explicitly
using System.ComponentModel;
It makes possible to write INotifyPropertyChanged instead of System.ComponentModel.INotifyPropertyChanged. I'll remove inlined namespaces below.
don't start long-running job from a constructor, it's unpredictable behavior because costructor must be always successful. Start loading images later. Also constructor cannot await asynchronous tasks. Separate method can.
public class ClsProd : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ImageSource _imgPg;
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg
{
get => _imgPg;
set
{
_imgPg = value;
NotifyPropertyChanged();
}
}
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
}
public async Task DecodeImageAsync()
{
ImgPg = await ClsImgBase64.DecodeAsync(SImax);
}
}
Decoder
As now it's awaitable and doesn't need a callback, decoding method doesn't interact with the instance data. So, it can be static.
public static class ClsImgBase64
{
public static async Task<ImageSource> DecodeAsync(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
using var stream = bytes.AsBuffer().AsStream().AsRandomAccessStream();
// stream.Seek(0); // not sure if it needed
var decoder = await BitmapDecoder.CreateAsync(stream);
var pixelData = await decoder.GetPixelDataAsync();
var pixelArray = pixelData.DetachPixelData();
var bitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
await bitmap.PixelBuffer.AsStream().WriteAsync(pixelArray, 0, pixelArray.Length);
return bitmap;
}
}
Decoder's code based on this answer.
If it will be laggy, try to wrap 2 Decoder's lines with Task.Run. Only if it will be laggy.
using var stream = await Task.Run(() =>
{
byte[] bytes = Convert.FromBase64String(base64);
return bytes.AsBuffer().AsStream().AsRandomAccessStream();
});
Finally: give classes, methods and other things more clear names, that would make the code maintainable.

C# Swagger generated client how to authenticate and use autogenerated code

I have sucessfully generated classes from Swagger Definition (Openapi 3.0.3) using nSwag Studio, but I have no idea how to properly use this as a client.
Manually RestSharp code works fine, but I'd like to use autogenerated code to consume webservice methods and can't do it properly
This is working fine:
string clientId = "dev";
string clientSecret = #"pass";
var client = new RestClient("http://192.168.1.10/xyz/api/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddHeader("grant_type", "client_credentials");
var credentials = string.Format("{0}:{1}", clientId, clientSecret);
var headerValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));
request.AddHeader("Authorization", $"Basic {headerValue}");
request.AddParameter($"application/x-www-form-urlencoded", $"grant_type=client_credentials&client_id={clientId}&client_secret{clientSecret}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request, Method.POST);
My try with autogenerated code. My apoligizes if its wrong, but can't find any example.
There's just few examples but looks like client is generated in a different way (client initialization with base url - but still no idea about basic authorization in this context)
Googling returns mostly "How to create server side" or add swagger to asp/mvc/webapi project.
string URL = #"http://192.168.1.10/xyz/api";
string clientId = "dev";
string clientSecret = #"pass";
IO.Swagger.Client.ApiClient client = new IO.Swagger.Client.ApiClient(URL);
IO.Swagger.Client.Configuration.DefaultApiClient = client; //<<this throws error
IO.Swagger.Client.Configuration.Username = clientId;
IO.Swagger.Client.Configuration.Password = clientSecret;
client.AddDefaultHeader("Authorization", "bearer TOKEN");
IO.Swagger.Api.AuthApi authApi = new IO.Swagger.Api.AuthApi(client);
Error in ApiClient class
public ApiClient(String basePath="/xyz/api")
{
BasePath = basePath;
RestClient = new RestClient(BasePath); //System.UriFormatException: 'Invalid URI: The format of the URI could not be determined.'
}
I have tried many string forms of url (class doesn't accept Uri explictly)
Configuration
using System;
using System.Reflection;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace IO.Swagger.Client
{
public class Configuration
{
public const string Version = "1.0.0";
public static ApiClient DefaultApiClient = new ApiClient();
public static String Username { get; set; }
public static String Password { get; set; }
public static Dictionary<String, String> ApiKey = new Dictionary<String, String>();
public static Dictionary<String, String> ApiKeyPrefix = new Dictionary<String, String>();
private static string _tempFolderPath = Path.GetTempPath();
public static String TempFolderPath
{
get { return _tempFolderPath; }
set
{
if (String.IsNullOrEmpty(value))
{
_tempFolderPath = value;
return;
}
// create the directory if it does not exist
if (!Directory.Exists(value))
Directory.CreateDirectory(value);
// check if the path contains directory separator at the end
if (value[value.Length - 1] == Path.DirectorySeparatorChar)
_tempFolderPath = value;
else
_tempFolderPath = value + Path.DirectorySeparatorChar;
}
}
private const string ISO8601_DATETIME_FORMAT = "o";
private static string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
public static String DateTimeFormat
{
get
{
return _dateTimeFormat;
}
set
{
if (string.IsNullOrEmpty(value))
{
// Never allow a blank or null string, go back to the default
_dateTimeFormat = ISO8601_DATETIME_FORMAT;
return;
}
// Caution, no validation when you choose date time format other than ISO 8601
// Take a look at the above links
_dateTimeFormat = value;
}
}
public static String ToDebugReport()
{
String report = "C# SDK (IO.Swagger) Debug Report:\n";
report += " OS: " + Environment.OSVersion + "\n";
report += " .NET Framework Version: " + Assembly
.GetExecutingAssembly()
.GetReferencedAssemblies()
.Where(x => x.Name == "System.Core").First().Version.ToString() + "\n";
report += " Version of the API: 2.0.1\n";
report += " SDK Package Version: 1.0.0\n";
return report;
}
}
}
Client
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.IO;
using System.Web;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using RestSharp;
using RestSharp.Extensions;
namespace IO.Swagger.Client
{
public class ApiClient
{
private readonly Dictionary<String, String> _defaultHeaderMap = new Dictionary<String, String>();
public ApiClient(String basePath="/xyz/api")
{
BasePath = basePath;
RestClient = new RestClient(BasePath);
}
public string BasePath { get; set; }
public RestClient RestClient { get; set; }
public Dictionary<String, String> DefaultHeader
{
get { return _defaultHeaderMap; }
}
public Object CallApi(String path, RestSharp.Method method, Dictionary<String, String> queryParams, String postBody,
Dictionary<String, String> headerParams, Dictionary<String, String> formParams,
Dictionary<String, FileParameter> fileParams, String[] authSettings)
{
var request = new RestRequest(path, method);
UpdateParamsForAuth(queryParams, headerParams, authSettings);
// add default header, if any
foreach(var defaultHeader in _defaultHeaderMap)
request.AddHeader(defaultHeader.Key, defaultHeader.Value);
// add header parameter, if any
foreach(var param in headerParams)
request.AddHeader(param.Key, param.Value);
// add query parameter, if any
foreach(var param in queryParams)
request.AddParameter(param.Key, param.Value, ParameterType.GetOrPost);
// add form parameter, if any
foreach(var param in formParams)
request.AddParameter(param.Key, param.Value, ParameterType.GetOrPost);
// add file parameter, if any
foreach(var param in fileParams)
request.AddFile(param.Value.Name, param.Value.Writer, param.Value.FileName, param.Value.ContentType);
if (postBody != null) // http body (model) parameter
request.AddParameter("application/json", postBody, ParameterType.RequestBody);
return (Object)RestClient.Execute(request);
}
public void AddDefaultHeader(string key, string value)
{
_defaultHeaderMap.Add(key, value);
}
public string EscapeString(string str)
{
return RestSharp.Contrib.HttpUtility.UrlEncode(str);
}
public FileParameter ParameterToFile(string name, Stream stream)
{
if (stream is FileStream)
return FileParameter.Create(name, stream.ReadAsBytes(), Path.GetFileName(((FileStream)stream).Name));
else
return FileParameter.Create(name, stream.ReadAsBytes(), "no_file_name_provided");
}
public string ParameterToString(object obj)
{
if (obj is DateTime)
// Return a formatted date string - Can be customized with Configuration.DateTimeFormat
// Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
// For example: 2009-06-15T13:45:30.0000000
return ((DateTime)obj).ToString (Configuration.DateTimeFormat);
else if (obj is List<string>)
return String.Join(",", (obj as List<string>).ToArray());
else
return Convert.ToString (obj);
}
public object Deserialize(string content, Type type, IList<Parameter> headers=null)
{
if (type == typeof(Object)) // return an object
{
return content;
}
if (type == typeof(Stream))
{
var filePath = String.IsNullOrEmpty(Configuration.TempFolderPath)
? Path.GetTempPath()
: Configuration.TempFolderPath;
var fileName = filePath + Guid.NewGuid();
if (headers != null)
{
var regex = new Regex(#"Content-Disposition:.*filename=['""]?([^'""\s]+)['""]?$");
var match = regex.Match(headers.ToString());
if (match.Success)
fileName = filePath + match.Value.Replace("\"", "").Replace("'", "");
}
File.WriteAllText(fileName, content);
return new FileStream(fileName, FileMode.Open);
}
if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
{
return DateTime.Parse(content, null, System.Globalization.DateTimeStyles.RoundtripKind);
}
if (type == typeof(String) || type.Name.StartsWith("System.Nullable")) // return primitive type
{
return ConvertType(content, type);
}
// at this point, it must be a model (json)
try
{
return JsonConvert.DeserializeObject(content, type);
}
catch (IOException e)
{
throw new ApiException(500, e.Message);
}
}
public string Serialize(object obj)
{
try
{
return obj != null ? JsonConvert.SerializeObject(obj) : null;
}
catch (Exception e)
{
throw new ApiException(500, e.Message);
}
}
public string GetApiKeyWithPrefix (string apiKeyIdentifier)
{
var apiKeyValue = "";
Configuration.ApiKey.TryGetValue (apiKeyIdentifier, out apiKeyValue);
var apiKeyPrefix = "";
if (Configuration.ApiKeyPrefix.TryGetValue (apiKeyIdentifier, out apiKeyPrefix))
return apiKeyPrefix + " " + apiKeyValue;
else
return apiKeyValue;
}
public void UpdateParamsForAuth(Dictionary<String, String> queryParams, Dictionary<String, String> headerParams, string[] authSettings)
{
if (authSettings == null || authSettings.Length == 0)
return;
foreach (string auth in authSettings)
{
// determine which one to use
switch(auth)
{
case "BasicAuth":
headerParams["Authorization"] = "Basic " + Base64Encode(Configuration.Username + ":" + Configuration.Password);
break;
case "BearerAuth":
break;
default:
//TODO show warning about security definition not found
break;
}
}
}
public static string Base64Encode(string text)
{
var textByte = System.Text.Encoding.UTF8.GetBytes(text);
return System.Convert.ToBase64String(textByte);
}
public static Object ConvertType(Object fromObject, Type toObject) {
return Convert.ChangeType(fromObject, toObject);
}
}
}
AuthApi
using System;
using System.Collections.Generic;
using RestSharp;
using IO.Swagger.Client;
using IO.Swagger.Model;
namespace IO.Swagger.Api
{
public interface IAuthApi
{
InlineResponse200 OauthTokenPost (string grantType);
}
public class AuthApi : IAuthApi
{
public AuthApi(ApiClient apiClient = null)
{
if (apiClient == null) // use the default one in Configuration
this.ApiClient = Configuration.DefaultApiClient;
else
this.ApiClient = apiClient;
}
public AuthApi(String basePath)
{
this.ApiClient = new ApiClient(basePath);
}
public void SetBasePath(String basePath)
{
this.ApiClient.BasePath = basePath;
}
public String GetBasePath(String basePath)
{
return this.ApiClient.BasePath;
}
public ApiClient ApiClient {get; set;}
public InlineResponse200 OauthTokenPost (string grantType)
{
var path = "/oauth/token";
path = path.Replace("{format}", "json");
var queryParams = new Dictionary<String, String>();
var headerParams = new Dictionary<String, String>();
var formParams = new Dictionary<String, String>();
var fileParams = new Dictionary<String, FileParameter>();
String postBody = null;
if (grantType != null) formParams.Add("grant_type", ApiClient.ParameterToString(grantType)); // form parameter
// authentication setting, if any
String[] authSettings = new String[] { "BasicAuth" };
// make the HTTP request
IRestResponse response = (IRestResponse) ApiClient.CallApi(path, Method.POST, queryParams, postBody, headerParams, formParams, fileParams, authSettings);
if (((int)response.StatusCode) >= 400)
throw new ApiException ((int)response.StatusCode, "Error calling OauthTokenPost: " + response.Content, response.Content);
else if (((int)response.StatusCode) == 0)
throw new ApiException ((int)response.StatusCode, "Error calling OauthTokenPost: " + response.ErrorMessage, response.ErrorMessage);
return (InlineResponse200) ApiClient.Deserialize(response.Content, typeof(InlineResponse200), response.Headers);
}
}
}
I am under impression that your main problem is Uri formating. I would suggest finding the exact problem by doing
string URL = #"http://192.168.1.10/xyz/api";
Uri baseAddress = new Uri(URL);
string clientId = "dev";
string clientSecret = #"pass";
IO.Swagger.Client.ApiClient client = new IO.Swagger.Client.ApiClient(baseAddress.ToString());
IO.Swagger.Client.Configuration.DefaultApiClient = client; //<<this throws error
IO.Swagger.Client.Configuration.Username = clientId;
IO.Swagger.Client.Configuration.Password = clientSecret;
client.AddDefaultHeader("Authorization", "bearer TOKEN");
IO.Swagger.Api.AuthApi authApi = new IO.Swagger.Api.AuthApi(client);

Dynamically assign type to List<T> and populate it with deserialized json

I have many classes that I use to keep objects of specific types together and do some things on them, eg. "CarKeeper" or "EmployeeKeeper":
public class CarKeeper
{
List<Car> Items;
}
public class EmployeeKeeper
{
List<Employee> Items;
}
I then populate Items property with data from web api service:
public async Task Refresh()
{
if (Items.Any())
{
Items.Clear();
}
using (var client = new HttpClient())
{
string url = Secrets.ApiAddress + "GetEmployees?token=" + Secrets.TenantToken + "&page=1";
using (var response = await client.GetAsync(new Uri(url)))
{
if (response.IsSuccessStatusCode)
{
var userJsonString = await response.Content.ReadAsStringAsync();
Items = JsonConvert.DeserializeObject<Employee[]>(userJsonString).ToList();
}
}
}
}
Data gets properly deserialized and all works good. As creating another class like that (e.g. "DocumentKeeper") means copying a lot of code from any other "..Keeper" classes (they share 90% of code), I want to build single "Keeper" class to handle all objects.
The problem is that I need to somehow tell "Keeper" class what type it keeps, because it sometimes has to keep List<Employee> and sometimes list of other types. I thought about passing the type in "Keeper"'s contstructor and.. and I'm lost what to do with it later :) I also tried to make all lists List and it kinda works, but it's far from perfect.
How can I get my code working?
public class Keeper
{
List<T> Items;
public Keeper(Type T)
{
//make Items a list of int if int is passed as T
}
public async Task Refresh()
{
if (Items.Any())
{
Items.Clear();
}
using (var client = new HttpClient())
{
string url = Secrets.ApiAddress + "GetItems?token=" + Secrets.TenantToken + "&page=1";
using (var response = await client.GetAsync(new Uri(url)))
{
if (response.IsSuccessStatusCode)
{
var userJsonString = await response.Content.ReadAsStringAsync();
//how to put the passed type to DeserializeObject<>?
Items = JsonConvert.DeserializeObject<T[]>(userJsonString).ToList();
}
}
}
}
}
You want to pass the type as a generic type, not as a Type object:
public abstract class KeeperBase<T>
{
List<T> Items;
public Keeper() { }
protected abstract string GetRefreshApi { get; }
public async Task Refresh()
{
if (Items.Any())
{
Items.Clear();
}
using (var client = new HttpClient())
{
using (var response = await client.GetAsync(new Uri(GetRefreshApi)))
{
if (response.IsSuccessStatusCode)
{
var userJsonString = await response.Content.ReadAsStringAsync();
//how to put the passed type to DeserializeObject<>?
Items = JsonConvert.DeserializeObject<T[]>(userJsonString).ToList();
}
}
}
}
}
public class CarKeeper : KeeperBase<Car>
{
protected override string GetRefreshApi => Secrets.ApiAddress + "GetCars?token=" + Secrets.TenantToken + "&page=1";
}
public class EmployeeKeeper : KeeperBase<Employee>
{
protected override string GetRefreshApi => Secrets.ApiAddress + "GetEmployees?token=" + Secrets.TenantToken + "&page=1";
}
And, you can use them like this:
var carKeeper = new CarKeeper();
await carKeeper.Refresh();
foreach(var car in carKeeper.Items)
{
// ...
}

Accessing a .ToList() from a c# Class Library using Async Task Main

I have had a static version of this type of code working in a static version. However the API calls were just incredibly slow. I am trying to move to asynchronous now that C# 7 supports console async tasks (where I add code to connect to my DB and store data. I want to see this code output on the console to ensure it's working so I can assign variables for loading. I can't seem to figure out how to access the list from main. Here is the code I have so far:
Wrapper (or C# library):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AlphaVantageApiWrapper
{
public static class AlphaVantageApiWrapper
{
public static async Task<AlphaVantageRootObject> GetTechnical(List<ApiParam> parameters, string apiKey)
{
var stringRequest = parameters.Aggregate(#"https://www.alphavantage.co/query?", (current, param) => current + param.ToApiString());
stringRequest += "&apikey=" + apiKey;
var apiData = await CallAlphaVantageApi(stringRequest);
var technicalsObject = new AlphaVantageRootObject
{
MetaData = new MetaData
{
Function = parameters.FirstOrDefault(x => x.ParamName.Equals("function"))?.ParamValue ?? "NA?",
Interval = parameters.FirstOrDefault(x => x.ParamName.Equals("interval"))?.ParamValue ?? "NA?",
SeriesType = parameters.FirstOrDefault(x => x.ParamName.Equals("series_type"))?.ParamValue ?? "NA?",
Symbol = parameters.FirstOrDefault(x => x.ParamName.Equals("symbol"))?.ParamValue ?? "NA?"
},
TechnicalsByDate = apiData.Last.Values().OfType<JProperty>().Select(x => new TechnicalDataDate
{
Date = Convert.ToDateTime(x.Name),
Data = x.Value.OfType<JProperty>().Select(r => new TechnicalDataObject
{
TechnicalKey = r.Name,
TechnicalValue = Convert.ToDouble(r.Value.ToString())
}).ToList()
})
.ToList()
};
return technicalsObject;
}
public class ApiParam
{
public string ParamName;
public string ParamValue;
public ApiParam(string paramNameIn, string paramValueIn)
{
ParamName = paramNameIn;
ParamValue = paramValueIn;
}
public string ToApiString()
{
return $"&{ParamName}={ParamValue}";
}
}
public static string ToDescription(this Enum enumeration)
{
var type = enumeration.GetType();
var memInfo = type.GetMember(enumeration.ToString());
if (memInfo.Length <= 0) return enumeration.ToString();
var attrs = memInfo[0].GetCustomAttributes(typeof(EnumDescription), false);
return attrs.Length > 0 ? ((EnumDescription)attrs[0]).Text : enumeration.ToString();
}
public static async Task<JObject> CallAlphaVantageApi(string stringRequest)
{
try
{
using (var client = new HttpClient())
{
var res = await client.GetStringAsync(stringRequest);
return JsonConvert.DeserializeObject<JObject>(res);
}
}
catch (Exception e)
{
//fatal error
return null;
}
}
public class AlphaVantageRootObject
{
public MetaData MetaData;
public List<TechnicalDataDate> TechnicalsByDate;
}
public class MetaData
{
public string Function;
public string Interval;
public string SeriesType;
public string Symbol;
}
public class TechnicalDataDate
{
public DateTime Date;
public List<TechnicalDataObject> Data;
}
public class TechnicalDataObject
{
public string TechnicalKey { get; set; }
public double TechnicalValue { get; set; }
}
public class EnumDescription : Attribute
{
public string Text { get; }
public EnumDescription(string text)
{
Text = text;
}
}
public enum AvFuncationEnum
{
[EnumDescription("SMA")] Sma,
[EnumDescription("EMA")] Ema,
[EnumDescription("MACD")] Macd,
[EnumDescription("STOCH")] Stoch,
[EnumDescription("RSI")] Rsi,
}
public enum AvIntervalEnum
{
[EnumDescription("1min")] OneMinute,
[EnumDescription("5min")] FiveMinutes,
[EnumDescription("15min")] FifteenMinutes,
[EnumDescription("30min")] ThirtyMinutes,
[EnumDescription("60min")] SixtyMinutes,
[EnumDescription("daily")] Daily,
[EnumDescription("weekly")] Weekly,
[EnumDescription("monthly")] Monthly
}
public enum AvSeriesType
{
[EnumDescription("close")] Close,
[EnumDescription("open")] Open,
[EnumDescription("high")] High,
[EnumDescription("low")] Low,
}
}
}
`
The c# async main task (which obviously isn't working)...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AlphaVantageApiWrapper.Test
{
public static class AlphaVantageApiDbLoader
{
public static async Task Main(string[] args)
{
var API_KEY = "EnterAPIHERE";
var StockTickers = new List<string> { "AAPL" }; //eventualy becomes a list pulled in from the DB for processing
foreach (var ticker in StockTickers)
{
var parameters = new List<AlphaVantageApiWrapper.ApiParam>
{
new AlphaVantageApiWrapper.ApiParam("function", AlphaVantageApiWrapper.AvFuncationEnum.Sma.ToDescription()),
new AlphaVantageApiWrapper.ApiParam("symbol", ticker),
new AlphaVantageApiWrapper.ApiParam("interval", AlphaVantageApiWrapper.AvIntervalEnum.Daily.ToDescription()),
new AlphaVantageApiWrapper.ApiParam("time_period", "5"),
new AlphaVantageApiWrapper.ApiParam("series_type", AlphaVantageApiWrapper.AvSeriesType.Open.ToDescription()),
};
//Start Collecting SMA values
var SMA_5 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
///var SMA_5Result = AlphaVantageApiWrapper.TechnicalDataObject() // can't all method error just want values fron list
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "20";
var SMA_20 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "50";
var SMA_50 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "200";
var SMA_200 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
//Change function to EMA
//Change function to RSI
//Change function to MACD
}
}
}
}
Any help would be greatly appreciated! I know the code runs in the background, I just can't seem to get it to a point to view it on the console screen. Eventually I would assign the symbol, date, value returned variable and read these to a DB. I'm used to using DataTables, but the async and .ToList is new to me. Thanks!!

Categories