/* * ╔══════════════════════════════════════════════════════════════════╗ * ║ VTPRestAPI — C# Client ║ * ║ Usage: drop this file into any .NET 4.5+ / .NET Core project. ║ * ║ NuGet required: Newtonsoft.Json ║ * ╚══════════════════════════════════════════════════════════════════╝ * * FLOW: * 1. (Optional) Load live URLs from server → await VTPApiClient.LoadConfigAsync(anyBaseUrl) * 2. Create client → new VTPApiClient(VTPApiClient.PROD) * 3. Login → await client.LoginAsync("username","password") * 4. Call any API → await client.GetAsync("vtp/valid-trading-date") * 5. Session expires → client auto-re-logins and retries (if creds stored) * * URL MANAGEMENT (single source of truth): * Server URLs are stored in VTPRestAPI Web.config. * Call await VTPApiClient.LoadConfigAsync(VTPApiClient.TEST) * once at app start — LOCAL / TEST / PROD are updated automatically. * No need to change this file when the server URL changes. */ using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace VTPRestAPI.Clients { public class VTPApiClient : IDisposable { // ── Server endpoints (defaults — updated by LoadConfigAsync) ────── public static string LOCAL = "http://localhost:51631/api/"; public static string TEST = "http://vivekamih.gotdns.com:8033/api/"; public static string PROD = "http://vtpapi.vivekam.com/api/"; // ── Load live URLs from server (call ONCE at app startup) ───────── /// /// Fetches /api/config from the server and updates LOCAL / TEST / PROD. /// This is the single source of truth — change Web.config on the server /// and all clients pick up the new URLs without any code changes. /// /// Any URL you already know (TEST or PROD). /// Used to bootstrap the config call. public static async Task LoadConfigAsync(string knownBaseUrl = null) { var url = (knownBaseUrl ?? TEST).TrimEnd('/'); // Strip /api suffix if present so we get the site root if (url.EndsWith("/api", StringComparison.OrdinalIgnoreCase)) url = url.Substring(0, url.Length - 4); try { using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }) { http.DefaultRequestHeaders.Accept .Add(new MediaTypeWithQualityHeaderValue("application/json")); var res = await http.GetAsync(url + "/api/config"); var json = await res.Content.ReadAsStringAsync(); var obj = JObject.Parse(json); var envs = obj["environments"] as JObject; if (envs != null) { if (envs["LOCAL"] != null) LOCAL = envs["LOCAL"].ToString(); if (envs["TEST"] != null) TEST = envs["TEST"].ToString(); if (envs["PROD"] != null) PROD = envs["PROD"].ToString(); Console.WriteLine($"[VTPApi] Config loaded. PROD={PROD}"); } } } catch (Exception ex) { Console.WriteLine($"[VTPApi] Could not load config, using defaults. ({ex.Message})"); } } private readonly HttpClient _http; private readonly string _baseUrl; // ── Session state ───────────────────────────────────────────────── private string _sessionToken; private DateTime _expiresAt; private string _savedUsername; private string _savedPassword; public string CurrentUser { get; private set; } public string CurrentRole { get; private set; } public string FullName { get; private set; } public string SessionToken => _sessionToken; public DateTime ExpiresAt => _expiresAt.ToLocalTime(); public bool IsLoggedIn => !string.IsNullOrEmpty(_sessionToken) && DateTime.UtcNow < _expiresAt; // ── Constructor ─────────────────────────────────────────────────── public VTPApiClient(string baseUrl = null) { _baseUrl = (baseUrl ?? TEST).TrimEnd('/') + "/"; _http = new HttpClient { BaseAddress = new Uri(_baseUrl) }; _http.DefaultRequestHeaders.Accept .Add(new MediaTypeWithQualityHeaderValue("application/json")); _http.Timeout = TimeSpan.FromSeconds(120); } // ══════════════════════════════════════════════════════════════════ // AUTH // ══════════════════════════════════════════════════════════════════ /// /// Login and get session API Key. /// Call this ONCE — the client stores the token automatically. /// public async Task LoginAsync(string username, string password) { var body = JsonConvert.SerializeObject( new { username, password }); var content = new StringContent(body, Encoding.UTF8, "application/json"); var res = await _http.PostAsync("auth/login", content); var json = await res.Content.ReadAsStringAsync(); var obj = JObject.Parse(json); if (!res.IsSuccessStatusCode || !(bool)obj["success"]) { Console.WriteLine($"[VTPApi] Login failed: {obj["message"]}"); return false; } _sessionToken = obj["sessionToken"]?.ToString(); CurrentUser = obj["username"]?.ToString(); FullName = obj["fullName"]?.ToString() ?? CurrentUser; CurrentRole = obj["role"]?.ToString(); _expiresAt = obj["expiresAt"] != null ? DateTime.Parse(obj["expiresAt"].ToString()).ToUniversalTime() : DateTime.UtcNow.AddHours(8); // Save credentials for auto-refresh _savedUsername = username; _savedPassword = password; // Attach token to all future requests _http.DefaultRequestHeaders.Remove("X-Session-Token"); _http.DefaultRequestHeaders.Add("X-Session-Token", _sessionToken); Console.WriteLine($"[VTPApi] Logged in as [{CurrentRole}] {CurrentUser}. " + $"Session valid until {_expiresAt.ToLocalTime():HH:mm:ss}"); return true; } /// Logout and clear session. public async Task LogoutAsync() { try { await _http.PostAsync("auth/logout", null); } catch { } _sessionToken = null; _savedUsername = null; _savedPassword = null; _http.DefaultRequestHeaders.Remove("X-Session-Token"); Console.WriteLine("[VTPApi] Logged out."); } // ══════════════════════════════════════════════════════════════════ // HTTP METHODS — auto-refresh session on 401 // ══════════════════════════════════════════════════════════════════ public async Task GetAsync(string endpoint) { await EnsureSessionAsync(); var res = await _http.GetAsync(endpoint); return await HandleAsync(res, () => GetAsync(endpoint)); } public async Task PostAsync(string endpoint, object body) { await EnsureSessionAsync(); var content = Serialize(body); var res = await _http.PostAsync(endpoint, content); return await HandleAsync(res, () => PostAsync(endpoint, body)); } public async Task PutAsync(string endpoint, object body) { await EnsureSessionAsync(); var content = Serialize(body); var res = await _http.PutAsync(endpoint, content); return await HandleAsync(res, () => PutAsync(endpoint, body)); } public async Task DeleteAsync(string endpoint) { await EnsureSessionAsync(); var res = await _http.DeleteAsync(endpoint); return await HandleAsync(res, () => DeleteAsync(endpoint)); } // ══════════════════════════════════════════════════════════════════ // INTERNALS // ══════════════════════════════════════════════════════════════════ private async Task EnsureSessionAsync() { if (IsLoggedIn) return; if (!string.IsNullOrEmpty(_savedUsername)) { Console.WriteLine("[VTPApi] Session expired. Auto re-login…"); await LoginAsync(_savedUsername, _savedPassword); } else { throw new InvalidOperationException( "Not logged in. Call LoginAsync(username, password) first."); } } private async Task HandleAsync(HttpResponseMessage res, Func> retry) { // Session expired → re-login and retry ONCE if (res.StatusCode == System.Net.HttpStatusCode.Unauthorized) { Console.WriteLine("[VTPApi] 401 received. Session expired. Re-logging in…"); if (!string.IsNullOrEmpty(_savedUsername)) { var ok = await LoginAsync(_savedUsername, _savedPassword); if (ok) return await retry(); } throw new UnauthorizedAccessException( "Session expired and re-login failed. Call LoginAsync() again."); } var json = await res.Content.ReadAsStringAsync(); if (!res.IsSuccessStatusCode) throw new HttpRequestException( $"[VTPApi] {(int)res.StatusCode} {res.ReasonPhrase}: {json}"); return JsonConvert.DeserializeObject(json); } private static StringContent Serialize(object obj) => new StringContent( JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); public void Dispose() => _http?.Dispose(); } } /* ═══════════════════════════════════════════════════════════════════════ QUICK-START — copy this into your own class / form load / button click DO NOT include the Demo class in this file — it will conflict with your own Program.cs / Main() method. ═══════════════════════════════════════════════════════════════════════ private static VTPApiClient _api; // Call once at app start (e.g. Form_Load or Program.Main) async Task InitApiAsync() { // Step 1 — load live URLs from server (single source of truth) await VTPApiClient.LoadConfigAsync(VTPApiClient.TEST); // Step 2 — create client for the target environment _api = new VTPApiClient(VTPApiClient.PROD); // Step 3 — login bool ok = await _api.LoginAsync("your_username", "Your@Password"); if (!ok) MessageBox.Show("Login failed."); } // Example GET call async Task GetTradingDateAsync() { var result = await _api.GetAsync("vtp/valid-trading-date"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(result)); } // Example GET with query params async Task GetMemberLedgerAsync(int memberId, int partnerId) { var result = await _api.GetAsync( $"vtp/member-ledger?memberId={memberId}&partnerId={partnerId}"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(result)); } // Example POST with body async Task GetBillSummaryAsync() { var result = await _api.PostAsync("vtp/bill-summary", new { MpcId = 1234, Date = DateTime.Today.ToString("yyyy-MM-dd"), BillType = "Q" }); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(result)); } // Call on app exit async Task LogoutAsync() { if (_api != null) await _api.LogoutAsync(); } ═══════════════════════════════════════════════════════════════════════ */