1/6 - MongoDb, Rest API, ASP .Net Core

Ebben a leckében arról lesz szó, hogyen kössük össze az 1/4-es leckében készített API-t egy állandó tárolóval. Valahogy így:



Korábban már volt erről szó, de akkor CosmosDb-vel. Ezt fogjuk most lecserélni MongoDb-re. Úgy találtam, hogy a CosmosDb-nek van néhány hátránya, amivel nem tudok együtt élni:

  • az ingyenes vonalon sok minden le van korlátozva, ezért aztán egy az egyben nem lehet újra használni a kódot, amikor a felhőbe szeretnénk majd költöztetni az alkalmazást. 
  • Az is problémát jelent, hogy (legalábbis számomra) maga az adatbázis használata sem elég intuitív.
  • Harmadik érvként pedig az elterjedtséget tudom felhozni. A MongoDb egy sokkal elterjedtebb megoldás. Ezért fogjuk most kipróbálni ezt, és összekötni az API-val.

Az alkalmazásunkat most is postmannel fogjuk tesztelni. Ha nincs fent a gépeden innen letöltheted
Postman | The Collaboration Platform for API Development 

1., Szerezzük be az eszközöket

A MongoDb-t egy community serverről fogjuk egyelőre futtatni, ami azt jelenti, hogy az adatbázis, és a hozzáférési réteg is a mi gépünkön lesz.

MongoDB Community Download | MongoDB


Windows install

  • Installáld fel az msi-t
  • A környezeti változók között add hozzá a mongo elérési útvonalá

  • A C meghajtón hozz létre egy data könyvtárat, ebben pedig egy db könyvtárat. A mongo ezt használja default-ként arra, hogy hol tárolja az adatokat
  • Indítsd el a szervert
    • Indíts el egy parancssort, és írd be, hogy 
mongod.exe --dbpath "C:\data"
Ez kiír egy csomó szöveget, amelynek az utolsó sora
"ctx":"listener","msg":"Waiting for connections","attr":{"port":27017,"ssl":"off"}}
    • Ezután indíts el mégegy parancssort
mongo.exe
      • Ekkor a következőket kell látnod (én ConEmu-t használok ezért ilyen szép)



Ubuntu install

A MongoDb Ubuntura is könnyen installálható. Lásd az alábbi linket.

Első lépések


Ez egy vezérlő interfész (shell) amivel különböző parancsokat adhatunk az adatbázis kezelő szervernek. Próbáljunk ki pár egyszerű parancsot. Mindegyik után üssünk entert.

show dbs - kiírja milyen adatbázisok elérhetőek jelenleg
use <name-of-db> - kiválasztja a használni kívánt adatbázist, ha nincs, akkor létrehozza
db.<collectionName>.insert(...) - beilleszt egy elemet az adott collection-be 


Pár dolgot máris mefigyelhetünk. Az adatbázis addig nem jön létre, ameddig nincs benne legalább 1 db adat. Egy collection-re pedig egyelőre tekinthetünk úgy mint egy lista extrákkal. A legfőbb különbség, hogy ebben a listában nem csak azonos típusú elemeket tudunk tárolni. Nem muszáj hogy minden tárolt elemen jelen legyen az összes property.

A collection-ökben JSON elemeket tudunk tárolni, mint ahogy azt a fenti példa is mutatja. Most pedig, hogy "lássunk" is valamit, töltsük le, és installáljuk a Robo 3T-t (korábban Robomongo-t), egy MongoDb-hez tervezett szoftvert, ami segít az adatbázi vizualizációban.


Ha ez sikderült, akkor az indulóképernyőn adjuk meg az adatbázis kapcsolat nevét (pl default), majd az utána lévő képernyőn kattintunk a "Connect"-re.


A Robo 3T a default beállítást használja, és a localhost:27017-en keresi a lokális adatbázis szervered. Ha ezt átállítottad akkor értelem szerűen azt állítsd be.

A bal oldali sávban látni fogod a frissen létrehozott localTestDb-t. Ebben pedig a testData collection-t. Nyissuk meg jobb gombbal, és bontsuk ki a fát a jobb oldalon.


Gyönyörű szépen látszik, hogy egyetlen tárolt objektumunk van. Van továbbá egy extra property is "_id", amelyet maga az adatbázis generált, hiszen külön nem adtunk meg id-t. A sötétszürke sávban pedig egy kereső kifejezést látunk, amelyik megadja, hogy az alatta lévő táblázat mi szerint legyen szűrve.

db.getCollection('testData').find({})
A find ()-jei között lévő kifejezés üres objektum {} tehát az összes elem meg fog jelenni.

2., Kössük be az API-t

Induló kódként a 4-es lecke végénél fogjuk kezdeni. Ezt le tudod tölteni innen.


Másold be a zip könyvtár tartalmát egy lokális mappába, csomagold ki, és indítsd el visual studio 2019 programmal.

A project alatt, a Controllers mappában találsz egy ValuesController.cs nevű fájlt melyben az API-t megvalósító osztály található. Ha F5-tel elindítod az alkalmazást megnyílik egy böngészőablak, amelyben "value1" "value2" fog megjelenni.

Hozzunk létre egy Services mappát a projekt alatt, és ebben hozzunk létre egy osztályt MongoDbService.cs


Ez a service arra fog szolgálni, hogy tárolja az adatbázis kapcsolat függőségeit, mint az elérési pont, és az adatbázis neve. Ezeket az appsettings.json fájlban az alábbi módon tudjuk megadni.




Ezután hivatkozzuk be ezeket az értékeket az alábbi módon a MongodDbService.cs-ben.

Az osztályunkban először adjunk meg 2 változozót, amelyek az előbb megadott appsettings értékeket fogják tárolni

private readonly IMongoClient _mClient;
private readonly IMongoDatabase _db;
Ezután hozzunk létre egy konstruktort. A konstruktor egy speciális metódus, amely akkor fut le, amikor egy osztályból létrehozunk egy példányt. Jelen esetben a keretrendszer fogja létrehozni az osztályunk példányát, és itt fogjuk betölteni az értékeket az appsettingsből.

Írjuk be, hogy "ctor" és nyomjuk meg kétszer a tab-ot. Ekkor létrejön a konstruktor. Ennek bemenő paraméterként adjunk meg egy IConfiguration típusú változót configuration Névvel. Az appsettings-ben lévő sorokra a GetSection metódussal hivatkozhatunk az alábbiak szerint.

public class MongoDbService
    {
        private readonly IMongoClient _mClient;
        private readonly IMongoDatabase _db;
 
        public MongoDbService(IConfiguration configuration)
        {
            var dbUri = configuration.GetSection("AppSettings")
                .GetSection("DbUri").Value;
            var databaseId = configuration.GetSection("AppSettings")
                .GetSection("DatabaseId").Value;
            //string dbUri = "mongodb://localhost";
            _mClient = new MongoClient(dbUri);
 
            var dbList = _mClient.ListDatabases().ToList();
            if (dbList.Select(d => d.ToString()).Contains(databaseId))
            {
                Console.WriteLine($"Error: databse {databaseId} was not found on Uri {dbUri}");
            }
 
            _db = _mClient.GetDatabase(databaseId);
        }
    }
Ha a fenti kódot bemásoljuk, még hibákat kapunk például az IConfiguration-re. Menjünk oda a kurzorral, és nyomjuk le a Ctrl+. -ot. Válasszuk a "using Microsoft.Extensions.Configuration" opciót. Ezzel hozzáadtunk egy névteret, ahol ez az interfész található. A kód többi része pedig létrehoz egy klienst az adatbázis szerver felé, valamint beállítja a _db változót a megfelelő adatbázisra.

Ezután menjünk kurzorral az IMongoClient hez, és szintén nyomjuk le a Ctrl+.-ot.


Válasszuk az Install package 'MongoDb.Driver' -> install latest version-t. Ez feltelepíti a projektünkbe azokat a szoftver könyvtárakat, amelyekkel kapcsolódni tudunk az adatbázishoz. 


Ezután már csak két metódus hiányzik, amelyekkel majd kívülről elérjük az inicializált változókat.

      public IMongoDatabase GetDb()
      {
          return _db;
      }
 
      public IMongoClient GetClient()
      {
          return _mClient;
      }

És hogy mi a trutymák azaz "I" a típusnevek előtt? Ez jelöli, hogy ez a típus valójában egy interface. Az interface (interfész) egy limitált hozzáférést biztosít egy osztály funkcionalitásához. Valami olyasmi, mint amikor az okosóra gyártó cég nem a nyomtatott áramkört adja oda a kezedbe, hanem egy becsomagolt terméket, amin van 2 gomb meg egy touchscreen. Nem látod azt, hogy hogyan alakítja a mozgást elektromos árammá a kütyü, amit aztán egy memóriában adatként eltárol, hogy később az antennát vezérelve a wifidre kapcsolódva feltöltse a felhős fiókodba. Ezt nem is kell látnod. Elég, ha megnyomod a megosztás gombot. Az okosóra interface-e neked csak a "edzés indítása" meg a "megosztás" gombot jelenti.

Hasonló módon ez a 2 metódus az adatbázishoz illetve a klienshez fog limitált hozzáférést biztosítani. A többi részletet mi is el szeretnénk rejteni, ezért a mi osztályunkat (MongoDbService) is egy interface-en keresztül fogjuk elérhetővé tenni más komponensek számára. Szerencsére ez visual studio-ban egyszerű.

Álljunk a kurzorral az osztály nevére, és gondolom sejted melyik billentyűket kell lenyomni. Ctrl + .  Ezután válaszd az extract interface opciót, majd a felugró modal-on ok, és csiribi-csiribá.



Nézzük meg ezután hogy érjünk el egy collection-t a MongoDb-ből. A ValuesController.cs-ben vegyük a sima HttpGet metódus-t és írjuk be a következő kódot. Először is hozzunk létre egy konstruktort a ValuesController osztálynak a ctor kulcszóval, mint az előbb a service-ben.

Ezután adjunk hozzá egy bemenő paramétert IMongoDbService (az előbb létrehozott interface-t), Ctrl+.-tal hivatkozzuk be a namespace-t (using RestApiTest.Services). Ezután pedig ismét menjünk rá a bemenő paraméterre, és Ctrl+.-tal hozzunk létre egy field-et az osztályon.


A Visual studio egyből felismeri, hogy ezt a változót inicializálni szeretnénk a bemenő paraméterre. Meg is teszi helyettünk.


Ezután már csak meg kell hívni a service megfelelő metódusát, és lekérni a collection-t.

        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            _mongoDbService.GetDb().GetCollection<>("newCollection");
        }

Mivel azonban a .Net típusos nyelv, ezért a GetCollection-nek egy típus paramétert is át kell adni, ha F12-t nyomunk a GetCollection kijelölése után, akkor a keretrendszer (VS) kiadja nekünk hogyan kéne meghívnunk ezt a metódust.


Hozzunk hát létre egy ún. model osztályt, amiben ezeket az adatokat fogjuk átadni.(Person.cs). A property-ket úgy adhatjuk meg legegyszerűbben, hogy beírjuk, hogy "prop" majd kétszer megnyomjuk a tab-ot, ezután beírjuk a property nevét. Egyelőre 3 property-t hozzunk létre Name, Age, Occupation.



Ezután a ValuesController.cs fájlban adjuk meg a Get kéréshez tartozó metódus kódját.

        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<Person>> Get()
        {
            var coll = _mongoDbService.GetDb().GetCollection<Person>("testData");
            var people = coll.Find(p => true).ToList();
            return people;
        }

A szükséges namespace-eket Ctrl+.-al tudjuk behivatkozni. Az első sor lekéri a megfelelő collection-t. A második so pedig lekéri a collection-ben lévő Person objektumokat. a "p => true" kifejezés egy szűrő, ami azt a feltételt adja meg, hogy melyik objektumokat adja vissza (jelenleg az összeset).

Ha most F5-öt nyomunk, akkor megnyílik a böngésző és hupsz ? Na mi ez már megint?


A keretrendszer nem tudta betölteni a values controller-t mert nem találja a service-t, amit létrehoztunk. Hát persze, mert hogy nem adtuk meg a keretrendszernek, hogy hol találja. Állítsuk le a programunkat, nyissuk meg a Startup.cs fájlt, és adjuk hozzáa a ConfigureServices metódushoz a következő kódot.

services.AddScoped<IMongoDbService, MongoDbService>();

 Ez a sor megadja, hogy ha a keretrendszer az IMongoDbService interface-t látja, akkor melyik objektumot hozza létre. Jelen esetben a MongoDbService osztály egy példányát.

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IMongoDbServiceMongoDbService>();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

Majdnem. Ha most F5-öt nyomunk, akkor ezt a hibaüzenetet kapjuk

Az a gond, hogy az adatbázis által létrehozott _id nincs rajta a modell objektumunkon. Módosítsuk a Person osztályt.

    public class Person
    {
        [BsonElement("_id")]
        public ObjectId Id { getset; }
 
        [BsonElement("name")]
        public string Name { getset; }
 
        [BsonElement("age")]
        public int Age { getset; }
 
        [BsonElement("occupation")]
        public string Occupation { getset; }
 
    }

A BsonElement-ekre azért van szükség, mert az adatbázisban kisbetűs, míg a modell osztályban nagybetűs property nevekkel tároljuk az adatokat (ugyanis .NET-ben az utóbbi a konvenció). Újabb F5 után.


Most már csak a többi CRUD (Create Read Update Delete) művelet kell megvalósítani. Minden beírt kód után újra kell fordítani, és indítani az alkalmazást F5-tel, ahhoz hogy a változások érvénybe lépjenek.

// GET api/values/5
[HttpGet("{id}")]
public ActionResult<Person> Get(string id)
{
    var coll = _mongoDbService.GetDb().GetCollection<Person>("testData");
    var person = coll.Find(p => p.Id == new MongoDB.Bson.ObjectId(id)).FirstOrDefault();
    return person;
}
Alig különbözik az előzőtől. A Find-nak most már egy valós feltételt adunk meg (ha az Id megegyezik, a paraméterben kapott id-val). A FirstOrDefault pedig visszaadja az első elemet, ha pedig nem talál ilyet, akkor null-t. Teszteljük le Postman-nel.


Most pedig a hozzáadás

        // POST api/values
        [HttpPost]
        public ActionResult Post([FromBodyPerson person)
        {
           var coll = _mongoDbService.GetDb().GetCollection<Person>("testData");
            try
           {
               coll.InsertOne(person);
               return Ok("success");
           }
            catch (Exception ex)
            {
               return new ObjectResult(500) { Value = ex.ToString(), StatusCode = 500 };
            }
        }
Az API-nak egy JSON objektumot adunk be person, amit aztán az InsertOne kulcsszóval adunk hozzá a colleciton-höz. Teszteljük Postman-ből

Ha ezután a böngésző ablakban F5-tel frissítjük a lekérdezést:


Az elem törléshez az alábbi kódot tudjuk használni.

        [HttpDelete("{id}")]
        public ActionResult Delete(string id)
        {
            var coll = _mongoDbService.GetDb().GetCollection<Person>("testData");
            try
            {
                coll.DeleteOne(p => p.Id == new MongoDB.Bson.ObjectId(id));
                return Ok("success");
            }
            catch (Exception ex)
            {
                return new ObjectResult(500) { Value = ex.ToString(), StatusCode = 500 };
            }
        }

Egy elem módosításához pedig ezt. Az elemet a POST request body részében adjuk át, míg az id-t az útvonalban. Azért, hogy a body-ban ne kelljen még egyszer megadni az id-t, a szerveren a person objet Id property-jét beállítjuk, hogy ne vesszel el amikor a mongo lecseréli az adott elemet.

        // PUT api/values/5
        [HttpPut("{id}")]
        public ActionResult Put(string id, [FromBodyPerson person)
        {
            var coll = _mongoDbService.GetDb().GetCollection<Person>("testData");
            try
            {
                person.Id = new MongoDB.Bson.ObjectId(id);
                coll.ReplaceOne(p => p.Id == new MongoDB.Bson.ObjectId(id), person);
                return Ok("success");
            }
            catch (Exception ex)
            {
                return new ObjectResult(500) { Value = ex.ToString(), StatusCode = 500 };
            }
        }



További infók

1., A végső állapotot itt találod:

2., Ha elakadtál, vagy kérdésed van tedd fel itt.
Hogy tetszik a blog? (google.com)

3., Ha szeretnél értesülni a frissítésekről iratkozz fel jobb felül.


Nincsenek megjegyzések:

Megjegyzés küldése