ארכיון תגיות: dot net core

החזרת Http Response שונים באמצעות Asp.net core web api

בפוסט הזה אדגים כמה צורות להחזיר http response מקונטרולר של asp.net web api.  ועל הדרך נראה גם את הצורות הבסיסיות לכל הפעולות של CRUD.

הערה : לא תמיד אני מציג את הפונקציה במלואה, אז תתאימו את סוג המשתנה שמחזירה הפונקציה אל הדוגמא.

דוגמא 1 – להחזיר קוד Http

החזרת תשובה באמצעות אובייקט HttpResponseMessgae

return new HttpResponseMessage(HttpStatusCode.Created)

זה יחזיר תשובה 200 – כלומר "הכל תקין".

כדי להחזיר תשובה 400 באותו אובייקט , נקליד כך

return HttpResponseMessage(HttpResponseCode.BadRequest);

מתי משתמשים בזה ? 

ובכן, אני מראה כאן אפשרות לשימוש, אנחנו מנסים להכניס את התוכן לדאטאבייס, אם לא מצליח, מחזירים שגיאה, אם מצליח – מחזירים הצלחה.

הדוגמא הבאה משתמשת בפועל http post :

 [HttpPost("momo")]
 public HttpResponseMessage Moko([FromBody]Post post)
 {
 try
 {
 
 this._appContext.Posts.Add(post);
 if (this._appContext.SaveChanges() > 0 )
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.Created);
 } else
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
 }
 

 } catch(Exception e)
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
 }
 
 }

דוגמא 2 – להחזיר קוד Http עם פונקציות קיצור

בשתי המקרים הקודמים היינו צריכים להגדיר את הפונקציה שתחזיר HttpResponseMessage אך לאובייקטים האלו יש פונקציות קיצור.

בדוגמא הזו הפונקציה מחזירה "כרגיל" IActionResult .

פונקציית קיצור להחזרת תשובה תקינה – -200:

return Ok("everything fine");

פונקציית קיצור להחזרת תשובה לא תקינה 400 :

return NotFound("your requested item didnt found in db");

כמובן שאת המחרוזת בתשובה אפשר לרשום כפי שרוצים.

 

דוגמא 3  – להחזיר מידע בתוך התשובה, באמצעות הפועל Http Get :

בדוגמא הבאה אנחנו מחזירים תשובה מסוג מאוד ספציפי – רשימה של אובייקט שאנינו יצרנו.

כמובן שהפריימוורק Asp.net יצמיד לזה קוד 200 ויחזיר את הרשימה בתור מערך JSON.

[HttpGet]
public IEnumerable<MyObj> GetAll() {
return _context.MyObj.ToList();
}

יש אפשרות כמובן גם להחזיר אובייקט מידע בודד (ולא רשימה של אובייקטים רבים).

להלן דוגמא :

[HttpGet("{id}", Name = "GetById")]
public IActionResult GetById(long id)
{
 var item = _context.MyItems.FirstOrDefault(p => p.Id == id);
 if (item == null)
 {
 return NotFound();
 }
 return new ObjectResult(item);
}

במקרה זה החזרנו תשובה תקינה אם הכל נמצא תקין.

ותשובת שגיאה – אם לא נמצא.

דוגמא 4 – לקבל נתונים גם מה-URL וגם מה-BODY – פעולת Update באמצעות הפועל Http Put

בפונקציה הבאה, אנחנו מקבלים 2 נתונים, אחד פרימטיבי – דרך ה-url, והשני מגיע כאובייקט בתוך ה-body  של הבקשה.

התשובות שאנחנו מחזירים – הצלחה או כישלון, היא לפי מה שקורה בפועל.

שימו לב שזוהי קריאת PUT , כלומר במידה ומנסים אותה באמצעות PostMan למשל, אז צריך להגדיר את הקריאה כ-PUT.

[HttpPut("update/{id}")]
 public HttpResponseMessage Update([FromRoute] int id, [FromBody]Post post)
 {
 try
 {

 Post po = _appContext.Posts.FirstOrDefault(x => x.Id == id);
 if (po == null)
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.NotFound);
 }
 else
 {
 po.Title = post.Title;
 po.Content = post.Content;
 if(_appContext.SaveChanges() > 0 )
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.OK);
 } else
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);

 }

 }


 }
 catch (Exception e)
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
 }

 }

ישנה אפשרות נוספת לעשות פונקציה דומה, שמחזירה תשובה של IActionResult וגם משתמשת בפונקציות קיצור.

בפונקציה הבאה – אני עושה בדיוק את אותה פעולה – עדכון רשומה, רק שאני נצמד לשיטה שמוצגת באתר של מיקרוסופט :

 [HttpPut("update2/{id}")]
 public IActionResult Update2([FromRoute] int id, [FromBody] Post post)
 {
 if (post == null || post.Id != id)
 {
 return BadRequest();
 }
 Post po = _appContext.Posts.FirstOrDefault(x => x.Id == id);
 if (po == null)
 {
 return NotFound();
 }
 po.Title = post.Title;
 po.Content = post.Content;
 _appContext.Posts.Update(po);
 _appContext.SaveChanges();
 return new NoContentResult();
 }

בשלב זה, כבר אפשר לשאול – מה עדיף ? שימוש ב- HttpResponseMessage או החזרה של IActionResult.

אני באופן אישי מעדיף IActionResult, פשוט משום שהקוד נראה קצת יותר קצר, קריא ומסודר. מי שרוצה להרחיב יכול לעיין בקישור הזה, שמסביר את העיקרון – מדוע IActionResult עדיף מאשר HttpResponseMessage.

 

דוגמא 5 – פעולת מחיקה באמצעות הפועל Http Delete

כמו בדוגמא הקודמת, נדגים ראשית באמצעות החזרת HttpResponseMessage.

היות ואני לא מגדיר פה שם מפורש לניתוב ( route) אזי בעצם הניתוב של הקלאס עצמו משמש פה, בתוספת הפרמטר id  שהגדרתי.

[HttpDelete("{id}")]
 public HttpResponseMessage Delete([FromRoute] int id)
 {
 try
 {


 Post po = _appContext.Posts.FirstOrDefault(x => x.Id == id);

 if (po == null)
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.NotFound);
 }
 else
 {
 _appContext.Posts.Remove(po);

 if (_appContext.SaveChanges() > 0)
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.OK);
 }
 else
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
 }
 }
 } catch(Exception e)
 {
 return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
 }
 }

ודוגמא נוספת, לעשות את אותה פעולה, עם פונקציות קיצור ועם החזרת IActionResult .

[HttpDelete("delete2/{id}")]
 public IActionResult Delete2(int id)
 {
 Post po = _appContext.Posts.FirstOrDefault(x => x.Id == id);
 if (po == null)
 {
 return NotFound();
 }
 _appContext.Posts.Remove(po);
 _appContext.SaveChanges();
 return new NoContentResult();
 }

מקורות נוספים :

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api

 

 

קונטרולרים Asp.Net core Web api

תזכורת – מהם קונטרולרים ?

קונטרולרים הם הקלאסים שמבצעים את הפעולות בפועל.

בתבנית הבסיסית שנותן לנו visual studio מופיעים 2 מאפיינים מעל שם המחלקה

 [Produces("application/json")]
 [Route("api/Post")]
 public class PostController : Controller

המאפיין Produces מגדיר מה יוחזר מהקונטרולר.

המאפיין Route מגדיר את ניתוב ברירת המחדל לקונטרולר הזה, כאשר הפונקציות ( actions ) עצמן, שם הפוקנציה יהיה המשך ה-URL.

הזרקת התלות של החיבור לדאטאבייס

ב-asp.net core , החיבור לדאטאבייס מוגדר כ-service שצריך להזריק לכל מחלקה שזקוקה לו.

לצורך ההזרקה שלו, פשוט נוסיף אותו כפרמטר של הקונסטרקטור.

 private readonly MyDbContext _appContext;

 public PostController(MyDbContext db)
 {
 _appContext = db;
 }

יצירת הפונקציות = נקודות הגישה של ה-Api

כדי להמחיש את בניית הפונקציות פשוט אדגים פה כמה פונקציות, רובן ברורות מעצמן.

[HttpGet]
public IActionResult Get()
{
 return Ok(this.db.Post.ToList());
}

[HttpGet("{id}")]
public IActionResult Get(int id)
{
 return Ok(this.db.Post.FirstOrDefault(x=>x.Id == id));
}

יש לנו פה 2 פונקציות, שברור מהמאפיין HttpGet, שהם עובדות דרך GET.

הפונקציה הראשונה לא מצפה לקבל כלום, ולכן כאשר נגלוש אל localhost/api/post

נקבל את התוצאה של הפונקציה הראשונה כברירת מחדל – כלומר את כל הרשומות.

הערה : במקומות שבהם כתבתי localhost -זה יכול להשתנות כמובן לפי הפורט שקובע עבורכם הויזואל סטודיו.

הפונקציה השניה, היא OverLoad של הראשונה, עם חתימה מעט שונה – יש בה פרמטר של id שמאפשר לסנן את הרשומה המוחזרת.

כלומר אם נגלוש אל localhost/api/post?id=5 אז נקבל את רשומה 5.

כעת דוגמא לפונקציה שמקבלת POST   ( שימו לב שגם המודל של Entity אצלי בדוגמא נקרא Post, זה יכול קצת לבלבל, אבל כשמבינים את העיקרון – זה מובן).

[HttpPost]
public IActionResult Post([FromBody] Post post)
{
 this.db.Post.add(Post);
 this.db.SaveChanges();
 return Created("Get",post);
 
}

ברגע שנפנה בפעולת POST אל localhost/api/post אז נשלח בתוך ה-body של ה-Request אובייקט json שיכיל את המאפיינים של המודל post שלנו (לא להתבלבל עם הפועל POST של http).

ואנחנו נכניס אותו לדאטאבייס , ונחזיר תשובה שהוא נוצר בהצלחה.

מעט שליטה בשמות של נקודות הגישה (EndPoints ) של ה-Api – כלומר Routing

יש הרבה מאוד שליטה ב-Routing של Asp.net core web api , אני לא אעבור על הכל, רק אדגים אפשרות אחת לשלוט ב-url.

בדוגמאות הקודמות שלנו , כלל לא משנה איך קראנו לפונקציה, אלא כיוון שלא הוגדר שום route מיוחד, אז הפריימוורק web api הניח שיש פונקציה אחת שמטפלת בפעולת GET ואחת שמטפלת ב-POST, ולמעשה הסיבה היחידה שהפריימוורק לא החזיר שגיאה על הפונקציה השניה שטיפלה ב=GET, היא כיוון שהיא קיבלה פרמטר, אז הפריימוורק "הבין" מתי לפנות לכל אחת מהפונקציות.

אבל במקרה בו נרצה להחליט בעצמנו איך יקראו ה-EndPoints אז אחת השיטות היא לשים את השם הרצוי בתוך פועל ה-Http

למשל הדוגמא הבאה מהאתר של מיקרוסופט , ממחישה זאת :

[HttpGet("/products")]
public IActionResult ListProducts()
{
 // ...
}

[HttpPost("/products")]
public IActionResult CreateProduct(...)
{
 // ...
}

הערה חשובה :  בדוגמא שהבאתי כאן , הוספתי סימן "קו נטוי" / לפני הניתוב, מה שגורם לכתובת למעשה להיות ממש כמו שהיא כתובה בסוגרים , כלומר זה http://localhost/products , וזה לא  משתרשר לפי ה-attribute של הקלאס (אם היה כזה) .

אבל אם נוריד את סימן הקו נטוי בהתחלה, אז זה ישתרשר בהמשך לניתוב (route) שמוגדר לקונטרולר.

אז נניח והקונטרולר שלנו מתחיל כמו הדוגמא הראשונה בפוסט הזה,

אז התוצאה תהיה כך http://api/post/products .

שליפה של נתונים מתוך הכתובת \ body וכדומה.

כדי לשלוף נתונים מתוך הכתובת, כלומר מתוך ה- query string , אז ברוב המקרים מספיק פשוט להוסיף פרמטר של הפונקציה שמקביל בשמו לפרמטר שמגיע בכתובת.

יחד עם זאת, אם רוצים לקחת מתוך כתובת "יפה" ללא הסימן שאלה, וכדומה, אז אפשר להוסיף ברשימת הפרמטרים כל מיני מאפיינים שמאפשרים לשלוט במקור של הפרמטר, כלומר מאפשרים לומר מהיכן יגיע הפרמטר:

להלן 2 דוגמאות :

 public IActionResult Update2([FromRoute] int id, [FromBody] Post post)

ישנם עוד כמה אפשרויות, וכולן נסקרות להרחבה במקורות למטה.

בפוסט אחר, הבאתי דוגמאות רבות לפונקציות שמבצעות CRUD בקונטרולרים, וכיצד להחזיר תשובות Http שונות בכל מיני צורות.

יצירת Controllers באופן אוטומטי בעזרת Visual Studio

כשעובדים עם ויזואל סטודיו, שווה לציין, שקיימת אפשרות לייצר אוטומטית קונטרולרים לכל מודל שנרצה.

  • לוחצים מקש ימני על מקום מסוים בפרויקט
  • בוחרים ב- Add
  • Controller
  • ובמסך שיפתח נבחר בקונטרולר עבור ASP.NET Web Api  עם כל התכונות, ועם Entity וכו'
  • ואז בוחרים מודל רלוונטי, ו-Context, ובזה סיימנו.
  • כדאי כמובן לעבור על הקוד שנוצר לפני שמשתמשים בו.

מקורות להרחבה :

באופן כללי

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing

לגבי Attributes ששולטים במקור של הפרמטרים :

https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

Asp.Net Core Model Binding: Controlling The Binding Source

איך להפיץ פרויקט ASP.NET CORE 2 אל שרת לינוקס CentOs 7 – מדריך צעד אחר צעד

לאחרונה, עברתי לתכנת ב-C#. השפה מוכיחה את עצמה כבחירה מעולה, ואילולא הייתה האפשרות להריץ את התוכנות על לינוקס, לא הייתי מבצע את הבחירה הזו.

התיעוד של Microsoft די טוב, אבל עדין לוקה בחסר, כל מיני מדריכים מפוזרים בכל מיני מקומות. כל מדריך מניח ידע מוקדם שונה, ולא כל מדריך מסביר בצורה טובה מה משמעות כל שלב ושלב.

לכן, לפחות בשלב הקריטי של ההפצה, החלטתי לכתוב מדריך שמתעד מה עושים, צעד אחר צעד., בגדול נצמדתי למדריך של מיקרוסופט, ופשוט הוספתי הסברים נחוצים, וחלקים חסרים מעט בהתחלה.

linux photo

הנחות יסוד :

  • ידע בסיסי בלינוקס
  • ידע איך להתקין חבילות ( yum \ apt-get )
  • הרכיבים שלא קשורים ל dot net core – מותקנים כבר ( למשל רכיב ה Database, כמו MySql \ MariaDB ).
  • אני מניח ששרת apache מותקן כבר .
  • שרת ה-Web הוא Apache ( ולא ngnix ) .
  • יש לכם הרשאה מספיקה להריץ פקודות בתור מנהל – כלומר sudo
  • יש לכם אפליקציית dot net core כלשהיא להפצה (בדוגמא אני משתמש ב-MVC , אבל אפשר כל דבר…)
  • אינטלגנציה בסיסית

הסבר תיאורטי

מה שמסבירים במדריך הזה, הוא ששרת ה-Apache עובד בתוך Proxy Server אל שרת פנימי של ה- DotNetCore שנקרא kestrel.

כלומר, אנחנו נגדיר למעשה Virtual Host של Apache, שמנתב את כל קריאות ה-Http אל האפליקציה שהפעלנו ב-DotNetCore .

בנוסף – נגדיר שירות, ש"עוטף" את הפעלת ה-dot net core בתור damon – כלומר "שירות", ואחת מההגדרות, תהיה שבמקרה של תקלה, אז התפקיד של המעטפת, יהיה להפעיל מחדש את אפליקציית ה-dot net core שלנו.

הערה חשובה : קיימת אפשרות להפיץ אפליקציות dot net core יחד עם RunTime מובנה – המדריך הזה לא מדבר על האפשרות הזו.

צעדים מעשים – איך להתקין אפליקציית asp.net core על לינוקס cent os 7

שלב 1 – להתקין את ה-sdk ה-dot net core על השרת

  sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc

 sudo sh -c 'echo -e "[packages-microsoft-com-prod]\nname=packages-microsoft-com-prod \nbaseurl=https://packages.microsoft.com/yumrepos/microsoft-rhel7.3-prod\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/dotnetdev.repo'
  sudo yum update
 sudo yum install libunwind libicu
 sudo yum install dotnet-sdk-2.0.0

את ההתקנה צריך לבצע כמובן בשרת .

הפקודות הראשונה והשניה מוסיפות  את הכתובת של שרת החבילות ש מיקרוסופט, אל מאגר החבילות של yum

הפקודה השלישית מעדכנת את רשימת  החבילות של yum.

הפקודות הרביעית והחמישית מתקינות את סביבת dot net core.

שלב 2 – קימפול + אריזה של אפליקצית ה dot net core עם כל הספריות שלה

עכשיו, בסביבת הפיתוח (=המחשב שלכם ?) \ או בסביבת ה-build   נשתמש בפקודה דרך ה-Command Line כדי ליצור ספריה שמכילה את ה-dll של התוכנה שלנו, יחד עם כל קבצי ה-dll ועוד.

  • פתחו cmd
  • תגיעו לתיקיה של הפרויקט (לא ה- solution, אלא הפרוייקט ! )
  • תריצו את הפקודה הבאה  : dotnet publish -c Release
    המשמעות של publish היא ליצור "אריזה" מוכנה להפצה של התוכנה שלנו.
    המשמעות של Release היא לקמפל באופן מייטבי – כזה שהוא יותר יעיל מאשר קימפול שמיועד עבור מצב debug.
  • ההרצה יכולה לארוך בין שניות לדקות, ובסופה תקבלו
    • כל מיני הערות warning של הקומפיילר ( בצבע צהוב ) – אפשר להתעלם
    • הודעה על מיקום תיקית ה-publish
    • הודעה על מיקום נפרד של ה-dll שמכיל את התוכנה מקומפלת.
  • צריך לזכור שבפועל, גם בתוך תיקיית ה-publish יש העתק של ה-dll של התוכנה
    ובהמשך – אנחנו נשתמש דווקא בהעתק של ה-dll שנוצר בתוך תיקיית publish.

שלב 3 – להעביר את קבצי התוכנה לשרת

כעת, תעבירו את הקבצים לשרת (בכל צורה שנוחה לכם, ftp, git ….)

המקום הטבעי – הוא תיקיה בתוך ה-www  ( או html וכדומה).

 

שלב 4 – להגדיר את apache בתור reverse proxy

כעת, ניצור Virtual Host באפצ'ה

שמפנה את כל התעבורה – ישירות לאפליקציית ה-dot net

כלומר ה-אפצ'ה  יאזין לתקשורת HTTP על פורט 80 או 443, ויעביר את כל התעבורה לאפליקציית הדוט נט, שבדוגמא שלנו תשב על תאזין על פורט 5000.

לצורך כך, ניצור קובץ בתיקייה  הבאה ( עם nano \ vim \ מה שנוח לכם )  :

/etc/httpd/conf.d/

ובתוך הקובץ נרשום את ההגדרות של אפצה לוירטואל הוסט

<VirtualHost *:80>
 ProxyPreserveHost On
 ProxyPass / http://127.0.0.1:5000/
 ProxyPassReverse / http://127.0.0.1:5000/
 ErrorLog /var/log/httpd/hellomvc-error.log
 CustomLog /var/log/httpd/hellomvc-access.log common
 </VirtualHost>

שימו לב להגדרת ה-errorLog וגם ה- customLog

כי שתיהם דורשים שינוי הגדרה לעומת הקובץ שמוצג פה.

עכשיו – כדי לבדוק את ההגדרות, נקליד את הפקודה הבאה :

    sudo service httpd configtest

אם קיבלתם את ההודעה Syntax OK  – אז הוירטואל הוסט תקין, לפחות מבחינת צורת הכתיבה (ה- Syntax ).

כדי שההגדרות יכנסו לתוקף – נעשה ריסט לשירות האפצה.

 sudo systemctl restart httpd 
sudo systemctl enable httpd

שלב 5 – להגדיר את השרת של דוט נט בתור שירות (שרת kestrel )

סיכום ביניים :  כעת, האפצה משמש בתור reverse proxy ומפנה את התעבורה אל פורט 5000. עכשיו מה שנשאר הוא להפעיל את אפליקציית ה-dot net core כדי שתאזין לפורט 5000.

כאשר כתבתי לפני כן שאפליקציית הדוט נט מאזינה על פורט 5000 בדוגמא שלנו, אז היא עושה זאת באמצעות שרת שנקרא kestrel.

אנחנו "נעטוף" את הפעלת הדוט נט, באמצעות service של המערכת, כלומר ניצור שירות שאחראי על הפעלת הדוט נט, ואחראי גם "להרים" אותו מחדש, במקרה והוא קורס.

בדוגמא של מיקרוסופט, הם יוצרים שירות בשם kestrel-hellomvc.service , אני אצמד לדוגמא הזו – אבל בפרודקשיין, כמובן, תשנו את שם הקובץ=שירות לשם משמעותי, שרלוונטי למקרה שלכם.

  •  ראשית ניצור קובץ הגדרות לשירות :
sudo nano /etc/systemd/system/kestrel-hellomvc.service
  • שנית — בתוך הקובץ נכתוב את התוכן הבא :
[Unit]

Description=Example .NET Web API Application running on CentOS 7


[Service]

WorkingDirectory=/var/aspnetcore/hellomvc

ExecStart=/usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll

Restart=always

# Restart service after 10 seconds if dotnet service crashes

RestartSec=10

SyslogIdentifier=dotnet-example

User=apache

Environment=ASPNETCORE_ENVIRONMENT=Production 


[Install]

WantedBy=multi-user.target

שימו לב – הקובץ רגיש לרווחים בתחילת שורה, אז צריך להקפיד לא לשים רווחים בתחילת שורה, ולא תווים מיותרים.

כמובן  – תתאימו את התוכן לאפליקציה שלכם.

  • עכשיו – שמרו את הקובץ, וצאו מ- nano.
  • וסוף סוף נפעיל את השירות :
systemctl enable kestrel-hellomvc.service

systemctl start kestrel-hellomvc.service
  • וכדי לבדוק שהשירות עובד כמו שצריך – נקליד :
systemctl status kestrel-hellomvc.service
  • אם קיבלתם הודעה עם המילה Active (בצבע ירוק – תלוי במערכת)
    אז הכל תקין – ואפשר סוף סוף לגלוש מהדפדפן ולראות שזה עובד.

 

מה נותר ?

ובכן, במדריך של מיקרוסופט (קישור למטה) יש גם התייחסות ללוגים, ל SSL, לחלוקת עומסים ועוד ועוד. אבל שאר החלקים – יותר נהירים וברורים, אז אפשר להשתמש בהם בהחלט.

מקורות :

שלב 1 – מדריך להתקנת sdk של dot net core

שלב 2 ואילך – במדריך לאירוח של asp.net core עם apache

 

 

 

דיסיריליזציה של XML מקוננים באמצעות XmlReader + XmlSerialization ב- C#

האתגר  – דיסיריליזציה מאוד מהירה של XML בכל גודל שהוא – בלי צריכת משאבים גבוהה.

כאשר עובדים עם קבצי XML גדולים , נרצה להפוך אותם לאובייקטים שלנו, שאיתם נוכל לעשות פעולות רגילות.

יחד עם זאת, המחלקה הרגילה לצורך כך, יכולה להתמודד עם קבצי XML בגודל קטן, אך בקבצי XML גדולים – היא צורכת משאבים רציניים (זיכרון וכו' )

בנוסף, קריאת XML עם מחלקת XmlReader היא הרבה יותר מהירה מאשר מחלקת XmlDocument

המטרה של הפוסט הזה היא לא להחליף את התיעוד הרשמי , אלא לרכז יחד כמה נקודות מעניינות שיכולות לחסוך זמן למי שעושה זאת בהמשך.

הפתרון – שימוש ב – XmlReader + XmlSerialization

נחלק את ההסבר ל-2 חלקים, חלק 1 יעסוק ב-Reader , וחלק שני בדיסיריליזציה.

חלק 1 – Xml.Reader

החלק של ה-Reader הולך כך : ניצור אוביקט Reader בתחילת הפעולה, ונשחרר את המשאבים בסוף הפעולה.

 /** Init XML reader */
 XmlReader reader = XmlReader.Create(fileName);


... Your code here
...
...

/** Close **/ 
 reader.Dispose();
  • הערה לגבי .net core : עדיף להשתמש במתודה Dispose ולא במתודה Close
    כי רק Dispose קיימת ב- .net core.

 

  • אם יש לנו בלוקים גדולים, אפשר ומומלץ ליצור אוביקט Reader נפרד עבורם – שמכיל רק את השורות של הבלוק הגדול שלנו, ולא מכיל את שאר השורות בקובץ

הנה דוגמא מלאה :

 // Position the reader on the second book node
 reader.ReadToFollowing("MyBigElement");
 
 // Create another reader that contains just the second book node.
 XmlReader readerMyBigElement = reader.ReadSubtree();

 // init new xmlSerlizer - To new instance of class MyWantedClass
 // Be Aware - MyWantedClass is the class that will get into 
 // the values from MyBigElement in the XML.
 XmlSerializer xmlSerlizer = new XmlSerializer(typeof(MyWantedClass));
 
 MyWantedClass myWantedClass;
 // Deserialize
 myWantedClass = (MyWantedClass)xmlSerlizer.Deserialize(readerMyBigElement);

 readerKoteretKovetz.Dispose();

הפתרון הזה מצריך יצירה מראש מחלקות עבור הבלוקים השונים ב-XML.

חלק 2 – דיסירליזציה

כאשר צריך להשתמש ב-Attributes  ( תיעוד על שימוש ב-attributes הספציפיים  – בקישור הזה ).

אני מתמצת כאן נקודות חשובות :

  • אם שם האלמנט בתוך ה-XML הוא בדיוק כמו שם המשתנה – אין צורך להשתמש ב-Attribute
  • בכל מקרה אחר – נשתמש ב-Attribute
  • דוגמא פשוטה ל- String
[XmlElement(ElementName = "My-Xml-Element-Name", IsNullable = true)]
public string myVariable;
  • משתנים במחלקה שלא קשורים לקובץ ה-XML, נסמן אותם ב- Attribute של XmlIgnore

 

  • טיפול ב-Null   :   בכל אלמנט שלא ידוע לנו אם הוא יגיע בוודאות, או שיגיע ללא ערך , או שיגיע null \ nil
    אז נוסיף את ההגדרה

    IsNullable = true
  • טיפול ב-Null עבור ערכים מספריים :  – אם המשתנה שצריך לקבל ערך הוא מסוג int \ decimal , אז עבור המקרים שבהם הערך יהיה ריק , צריך להגדיר אותם עם סימן שאלה בסוף, כדי שיוכלו לקבל ערך null
    לדוגמא
public int myVar
  • טיפול ב-Null עבור אובייקטים – לייצר את האובייקט מראש בזמן ההגדרה :
    MyObject myObject = new() myObject();
  • טיפול ב- Null עבור ערכי תאריך  – במקרה של תאריך, נצטרך לשים getter\setter מתאימים.
    בפירוט : נניח שהתאריך מגיע כ ddmmyyyy
    אז ניצור

    • משתנה מסוג string שמקבל את הערך המקורי
    • משתנה מסוג date עם getter + setter
    • ה-setter – בודק אם הערך המקורי הוא null ורק אם לא, אז הוא עושה לו Parse
    • ה-getter – מחזיר את התאריך.
[XmlIgnore]
 public DateTime dtRealDate { get; set; }

 
 [XmlElement(ElementName = "MyDateInXml", IsNullable = true)]
 public string MyDateBlaBla
 {
 get
 {
 return this.dtRealDate.ToString();
 }
 set
 {
 if (value != null)
 {
 this.dtRealDate = DateTime.ParseExact(value, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture);
 }
 }
 }
  • קינון :
    • נניח שיש לנו כמה בלוקים של XML בתוך הקובץ, כל בלוק עוסק בישות אחרת.
    • אז כל בלוק – הוא מחלקה נפרדת.
    • זאת אומרת שבתוך המחלקה של הבלוק הראשי, יהיו משתנים מסוג אוביקט הבלוק הרלוונטי 
    • ואז – כדי להתמודד עם Null , כמו שכבר כתבתי למעלה , ניצור כבר new instance בזמן הגדרת המשתנה.
      כך שבכל מקרה שהבלוק הזה יגיע ריק או null, לא נקבל Exception
    • אפשר גם לקנן לתוך List !!! וזה עובד היטב.
 [XmlElement(ElementName = "ElementThatArriveMultipleTimes", IsNullable=true)]
 public List<Product>productsList = new List<Product>();

 

כלים נחמדים לטיפול ב-XML וליצירת המחלקות

 

  • מיקרוסופט מציעה למי שמוריד את Windows SDK , כלי קטן שנקרא xsd.exe
    ובמידה ויש לכם קובץ xsd שמתאר את ה-XML שלכם, אז הכלי הזה יכול לייצר אוטומטית את המחלקות.
    אני לא מצאתי שהוא שימושי, כי הוא מייצר הרבה קוד זבל, ואין לי שליטה על כל המאפיינים שלו, אבל אולי למישהו אחר זה יכול לעזור ( שוב…בהנחה שיש לכם קובץ XSD שמתאר את ה-XML).
  • כדי לקרוא XML גדולים מאוד בצורה נוחה
    לא צריך עורך טקסט או IDE \ דפדפן – שמן הסתם יקרסו באמצע.
    אפשר להשתמש בכלי XML Explorer  שעובד ממש יפה , וגם מאפשר שאילתות XPATH בקלות.
    זה כלי אחד לדוגמא, ובטוח יש עוד כאלו https://xmlexplorer.codeplex.com/ 

בהצלחה!