Příručka ve formátu PDF

1. Základní informace

  • API je webová aplikace umístěná na webovém serveru, dostupná na konkrétní adrese/doméně pro všechna zařízení, která mají přístup k internetu.
  • API se používá v případech, kdy je potřeba vystavit endpoint, který
    • vrací data načtená z databáze,
    • přijímá data z klientských aplikací a ukládá je do databáze,
    • vrací obsah souborových příloh,
    • apod.

1.1. API s plným přístupem do databáze určené pro práci s knihovnou NETGeniumConnection.dll

  • Každé NET Genium má implementované API, které je možné volat z aplikací napsaných v jazyce C# ve spojení s knihovnou „NETGeniumConnection.dll”.
  • Toto API je určené pro klienty, kteří mají plný přístup do databáze. Tomu odpovídá i zdrojový kód, který je napsaný identicky, jako by šlo o aplikaci, která se do databáze připojuje napřímo (ne přes webové služby).
  • Typickým příkladem takové aplikace je konzolová aplikace, ale může to být i desktopová aplikace, webová aplikace apod.
  • Detailní popis psaní konzolových aplikací je uveden v samostatné příručce „Konzolové aplikace”.

1.2. API s omezeným přístupem do databáze

  • V každém NET Geniu je možné aktivovat API, které vrací data definovaná pomocí SQL dotazů. Tyto dotazy konfiguruje administrátor NET Genia v samostatné aplikaci s názvem „API”.
  • Každý SQL dotaz má svůj unikátní identifikátor „sqlid”, který klienti využívají k sestavení URL požadavku, například „http://localhost/netgenium/api/data/{sqlid}”.
  • URL požadavek může obsahovat více parametrů, obvykle sloužících k načtení konkrétního záznamu, například: „http://localhost/netgenium/api/data/{sqlid}/{id}”. Tyto parametry jsou pak přístupné pomocích následujících proměnných:
    • #path0#: data
    • #path1#: {sqlid}
    • #path2#: {id}
  • Samotný SQL dotaz může obsahovat volání serverových funkcí nebo používat proměnné, aby bylo možné omezit rozsah načtených dat, například:
    • SELECT * FROM ng_apidata WHERE ng_identifier = FORMATSTRINGSQL(#path1#)
    • SELECT * FROM ng_apidata WHERE id = FORMATINTSQL(#path2#)
    • SELECT * FROM ng_apidata WHERE ng_createdon < FORMATDATESQL(#now#)
  • API se vyvíjí prostřednictvím samostatného projektu v aplikaci „Visual Studio 2015” a vyšší, resp. programováním zdrojových kódů tohoto projektu, a následnou kompilací projektu do souboru formátu „dll”.

2. Import aplikace API do NET Genia

  • V prvním kroku je nutné importovat do NET Genia aplikaci „API.nga”, která je umístěná v adresáři „Install” každého NET Genia. Aplikace „API” slouží pro definici SQL dotazů, které bude výsledné API vracet klientským zařízením, a pro konfiguraci tokenů, kterými se musí klientská zařízení autorizovat.
  • Na nahlížecí stránce „Tokeny” je potřeba vytvořit alespoň jeden přístupový token. Tento token je nutné předat zabezpečenou cestou klientským zařízením, která budou data pomocí API načítat.
  • Na nahlížecí stránce „Endpointy” je potřeba definovat alespoň jeden SQL dotaz, například:
    • Identifikátor: susers
    • SQL: SELECT * FROM susers

3. Vytvoření projektu API ve Visual Studiu

  • Ve druhém kroku je nutné vytvořit nový projekt webové aplikace ve Visual Studiu pomocí následujících kroků:
    • Spustit Visual Studio
    • Z menu na hlavní liště zvolit „File / New / Project…” (Ctrl+Shift+N)
      • Project type: ASP.NET Web Application (.NET Framework)
      • Project name: api
      • Location: volitelné umístění projektu
      • Solution: Create new solution
      • Place solution and project in the same directory: Ano
      • Create directory for solution: Ne (Visual Studio 2015)
      • Framework: .NET Framework 4.5.2
      • Kliknout na tlačítko „Create”
      • Project Template: Empty
      • Configure for HTTPS: Ne
      • Kliknout na tlačítko „Create”
    • Kliknout pravým tlačítkem na „References”,
      • zvolit „Manage NuGet Packages…” ,
      • vybrat záložku „Installed”,
      • vybrat položku „Microsoft.CodeDom.Providers.DotNetCompilerPlatform”,
      • kliknout na tlačítko „Uninstall”.
    • Přes „Tento počítač” otevřít v průzkumníkovi adresář „bin”,
      • smazat soubor „Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll” ,
      • smazat soubor „Microsoft.CodeDom.Providers.DotNetCompilerPlatform.xml”.
    • Kliknout pravým tlačítkem na název projektu „api” ,
      • zvolit „Add” / „New Item…” (Ctrl+Shift+A) ,
      • vybrat složku „Installed / Visual C # / Web / General ”,
      • vybrat typ souboru "" Global Application Class "",
      • zkontrolovat obsah pole „Name”, aby měl hodnotu „Global.asax”, a potvrdit tlačítkem „Add”.
    • Zvolit režim kompilace „Debug”
      • Režim „Debug” ve výchozím nastavení generuje soubory formátu „dll” a „pdb”
      • Díky souboru formátu „pdb” se snadno odhalují chyby a přerušení v API, protože součástí „Stack Trace” každé chyby je i název souboru a číslo řádky, na které došlo k přerušení
      • Režim „Release” se doporučuje až pro finální verzi vyladěných zdrojových kódů v API. Ve výchozím nastavení režim „Release” generuje pouze soubor formátu „dll”.
    • Spustit projekt – z menu na hlavní liště zvolit „Debug” / „Start Debugging” (F5)

3.1. Přidání reference na knihovnu „NETGeniumConnection.dll”

  • Kliknout pravým tlačítkem na „References”, zvolit „Add Reference…”, a vybrat cestu k souboru „References\NETGeniumConnection.dll” na disku počítače

3.2. Úprava konfiguračního souboru „Web.config”

  • Obsah souboru „Web.config” vyměnit za následující kód:
<?xml version="1.0"?>
<configuration>

<appSettings>
<add key="ConnectionString" value="driver=firebird;datasource=localhost;user=SYSDBA;password=masterkey;database=C:\Firebird\netgenium.fdb;charset=WIN1250;collation=WIN_CZ" />
</appSettings>

<system.web>
<compilation debug="true" targetFramework="4.5.2" />
<sessionState cookieless="false" />
<httpRuntime targetFramework="4.5.2" />
</system.web>

</configuration>

3.3. Úprava souboru Global.asax.cs a metody Application_BeginRequest

  • Kliknout pravým tlačítkem na název souboru „Global.asax”, a zvolit „View Code” (F7)
  • Obsah souboru „Global.asax.cs” vyměnit za následující zdrojový kód:
using NETGenium;
using Newtonsoft.Json.Linq;
using System;

namespace api
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}

protected void Session_Start(object sender, EventArgs e)
{
}

protected void Application_BeginRequest(object sender, EventArgs e)
{
ApiResponse re = new ApiResponse();

if ((re.Request.Path[0] == "data" && (re.Request.Path.Length == 2 || re.Request.Path.Length == 3)) || re.Request.Path[0] == "import")
{
re.Request.LogUrl();
re.Request.LogBody();

using (DbConnection conn = DbConnection.UsingSettingsFromWebConfig(true))
using (DbCommand cmd = new DbCommand(conn))
try
{
string token = null, authorization = Request.Headers["Authorization"];
if (authorization != null)
{
token = authorization.StartsWith("Bearer ") ? authorization.Substring(7) : authorization;
}

if (token == null || !new DbRow("SELECT id FROM ng_apitoken WHERE ng_apitoken = " + conn.Format(token) + " AND ng_expiration > " + conn.Format(DateTime.Today), conn).Read())
{
throw new UnauthorizedAccessException();
}

if (re.Request.Path[0] == "data")
{
#region data

DbRow dr = new DbRow("SELECT ng_sql FROM ng_apidata WHERE ng_identifier = " + conn.Format(re.Request.Path[1]), conn);
if (!dr.Read())
{
throw new UnauthorizedAccessException();
}

string query = re.Request.ReplaceVariables(dr["ng_sql"].ToString(), conn);
if (!DbQuery.IsValidSelect(query))
{
throw new UnauthorizedAccessException(query);
}

re.LogVerbose(query);
re.DataTable = Data.Get(query, conn);

#endregion
}
else if (re.Request.Path[0] == "import")
{
#region import

var r = JObject.Parse(re.Request.Body);
if (r.ContainsKey("value"))
{
string guid = Guid.NewGuid().ToString();

DataSaver ds = new DataSaver("ng_data", 0, cmd);
ds.Add("ng_guid", guid);
ds.Add("ng_value", (string)r["value"]);
ds.Execute();

re.HTML = guid;
}
else
{
throw new Exception("Invalid request");
}

#endregion
}
}
catch (UnauthorizedAccessException ex)
{
re = new ApiResponse(HttpStatusCode.Unauthorized);
re.LogWarning("Application_BeginRequest", ex);
}
catch (ArgumentException ex)
{
re = new ApiResponse(HttpStatusCode.BadRequest);
re.LogWarning("Application_BeginRequest", ex);
}
catch (Exception ex)
{
re = new ApiResponse(HttpStatusCode.InternalServerError);
re.LogError("Application_BeginRequest", ex);
}

re.Flush();
}
}

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}

protected void Application_Error(object sender, EventArgs e)
{
}

protected void Session_End(object sender, EventArgs e)
{
}

protected void Application_End(object sender, EventArgs e)
{
}
}
}

3.4. Testování API pomocí konzolové aplikace

  • Vytvořit prázdnou konzolovou aplikaci s referencí na knihovnu „NETGeniumConnection.dll” viz příručka „Konzolové aplikace
  • Obsah souboru „Program.cs” vyměnit za následující zdrojový kód:
using NETGenium;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Text;

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
NETGeniumConsole console = new NETGeniumConsole();

using (DbConnection conn = new DbConnection())
{
conn.Open("http://localhost/netgenium", "Administrator", File.ReadAllText(Config.RootPath + "password.txt"));

string token = Data.ExecuteScalar("SELECT ng_apitoken FROM ng_apitoken WHERE ng_expiration > " + conn.Format(DateTime.Today), conn), url, errorresponse = null;

if (true)
{
#region data

url = "http://localhost/netgenium/api/data/susers";

try
{
Uploader.LogToConsole = true;
Uploader.Upload(url, null, token, null, Encoding.UTF8, out errorresponse);
}
catch (Exception ex)
{
L.W(url, ex);
if (errorresponse != null)
{
L.V("RESPONSE: " + errorresponse);
}
}

#endregion
}

if (false)
{
#region import

url = "http://localhost/netgenium/api/import";

StringBuilder sb;

using (JsonWriter writer = new JsonTextWriter(new StringWriter(sb = new StringBuilder())))
{
writer.WriteStartObject();

writer.WritePropertyName("value");
writer.WriteValue("test");

writer.WriteEndObject();
}

try
{
Uploader.LogToConsole = true;
Uploader.UploadJSon(url, null, sb.ToString(), token, null, Encoding.UTF8, out errorresponse);
}
catch (Exception ex)
{
L.W(url, ex);
if (errorresponse != null)
{
L.V("RESPONSE: " + errorresponse);
}
}

#endregion
}
}

console.Exit();
}
}
}

4. Serializace a deserializace requestu

4.1. Změna způsobu parsování dat

  • Serializace na straně klienta spočívá v převodu dat z konkrétní třídy (DTO objektu) do textového řetězce ve formátu JSON namísto použití obecného objektu "" JsonWriter „.
  • Deserializace na straně serveru spočívá v převodu textového řetězce ve formátu JSON do konkrétní třídy (DTO objektu) namísto použití obecného objektu pomocí ” JObject.Parse „.

4.2. Serializace v konzolové aplikaci

  • Obsah regionu ”import"" v souboru „Program.cs” vyměnit za následující zdrojový kód:
// Původní kód

StringBuilder sb;

using (JsonWriter writer = new JsonTextWriter(new StringWriter(sb = new StringBuilder())))
{
writer.WriteStartObject();

writer.WritePropertyName("value");
writer.WriteValue("test");

writer.WriteEndObject();
}

Uploader.UploadJSon(url, null, sb.ToString(), token, null, Encoding.UTF8, out errorresponse);

// Nový kód

namespace ConsoleApp1.DTO
{
public class Request
{
public string data;
}
}

var request = new DTO.Request();
request.data = "test";

Uploader.UploadJSon(url, null, JsonConvert.SerializeObject(request), token, null, Encoding.UTF8, out errorresponse);

4.3. Deserializace v API na straně serveru

  • Obsah regionu „import” v souboru "" Global.asax .cs"" vyměnit za následující zdrojový kód:
// Původní kód

var r = JObject.Parse(re.Request.Body);
if (r.ContainsKey("value"))
{
string guid = Guid.NewGuid().ToString();

DataSaver ds = new DataSaver("ng_data", 0, cmd);
ds.Add("ng_guid", guid);
ds.Add("ng_value", (string)r["value"]);
ds.Execute();

re.HTML = guid;
}
else
{
throw new Exception("Invalid request");
}

// Nový kód

var r = JsonConvert.DeserializeObject<DTO.Request>(re.Request.Body);
string guid = Guid.NewGuid().ToString();

DataSaver ds = new DataSaver("ng_data", 0, cmd);
ds.Add("ng_guid", guid);
ds.Add("ng_value", r.data);
ds.Execute();

re.HTML = guid;

5. Serializace a deserializace response

5.1. ApiResponse

  • Serializace na straně serveru je prováděna automaticky v metodě „Flush” objektu „ApiResponse”.
  • Deserializace na straně klienta spočívá v převodu response do konkrétní třídy (DTO objektu).

5.2. Serializace v API na straně serveru

  • K automatické serializaci objektu „ApiResponse” dochází v případě, že není nastavena ani jedna z následujících properties objektu „ApiResponse”:
    • Dictionary – datový typ Dictionary<string, string>
      • Automatická konverze do JSon
      • Obsah hlavičky „Content-Type” je nastaven na „application/json”
    • DataTable – datový typ DataTable
      • Automatická konverze do JSon
      • Obsah hlavičky „Content-Type” je nastaven na „application/json”
    • DataView – datový typ DataView
      • Automatická konverze do JSon
      • Obsah hlavičky „Content-Type” je nastaven na „application/json”
    • DbRow – datový typ DbRow
      • Automatická konverze do JSon
      • Obsah hlavičky „Content-Type” je nastaven na „application/json”
    • FilePath, případně FileName – datový typ string
      • Automatická konverze do streamu, takže výsledkem response je datový proud bajtů s obsahem stahovaného souboru
      • Obsah hlavičky „Content-Type” je nastaven v závislosti na typu stahovaného souboru – například „application/octet-stream”
      • Obsah hlavičky „Content-Disposition” je nastaven na „attachment ; filename= ” plus název souboru v proměnné „FileName”
      • Pokud je hodnota proměnné „FileName” null, je název souboru vyhodnocen automaticky podle názvu souboru v proměnné „FilePath”
    • HTML – datový typ string
      • Výsledkem response je právě tento string
      • Obsah hlavičky „Content-Type” je nastaven na „text/plain; charset=utf-8”
    • XML – datový typ string
      • Výsledkem response je právě tento string
      • Obsah hlavičky „Content-Type” je nastaven na „text/xml; charset=utf-8”
    • JSon – datový typ string
      • Výsledkem response je právě tento string
      • Obsah hlavičky „Content-Type” je nastaven na „application/json”
  • Pokud není nastavena hodnota ani jedné z výše uvedených properties, je výsledkem response automatická serializace objektu „ApiResponse” do formátu JSON
    • ApiResponse je
    • Obsah hlavičky „Content-Type” je nastaven na „application/json”
    • Typické použití Vlastní
// Původní kód

ApiResponse re = new ApiResponse();
// ...
re.Flush();

// Nový kód

public class MyResponse : ApiResponse
{
public string error;
}

// Nový kód – varianta 1

MyResponse re = new MyResponse();
// ...
re.Flush();

// Nový kód – varianta 2

ApiResponse re = new ApiResponse();
// ...

if (true)
{
re = new MyResponse();
}

re.Flush();

5.3. Deserializace v konzolové aplikaci

// Původní kód

Uploader.UploadJSon(url, null, JsonConvert.SerializeObject(request), token, null, Encoding.UTF8, out errorresponse);

// Nový kód

public class MyResponse : ApiResponse
{
public string error;
}

string response = Uploader.UploadJSon(url, null, JsonConvert.SerializeObject(request), token, null, Encoding.UTF8, out errorresponse);

var r = JsonConvert.DeserializeObject<MyResponse>(response);