ארכיון תגיות: C#

החזרת 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

יצירת Migartions ב-ASP.NET

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

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

איך עושים Migations ב-ASP.NET ?

הנחת יסוד :

 

  • מוגדר Connection תקין
  • קיים ב-Startup.cs הגדרה של הזרקת תלויות DI לקונקשיין.
  • יש תיקית Models , עם קלאסים מוכנים שמייצגים טבלאות.

דרך א לייצר Migrations

  • דרך Tools, או דרך מקש ימני על הפרויקט, נפתח את Nuget Manager Console
  • נקליד
Add-Migration YOUR_MIGRATION_NAME

לאחר כמה שניות של הרצה, נוצרת תיקיה בשם Migrations, ובתוכה נוצר קלאס בשם שביקשנו.

הערה :  אם הקלאס שיצרתם ל-DbContext הוא בשם שיכול להיות דו-משמעותי, למשל AppContext, אז תיכנסו למיגרשיון שנוצר, ותתנו הפניה מדוייקת שכוללת את שם ה Namespace, אחרת כמובן זה לא יעבוד.

 

דרך ב ליצירת Migrations

דרך נוספת היא ליצור את ה Migrations דרך שורת הפקודה הרגילה.

לצורך כך צריך לוודא שמותקנת חבילת    nuget מסויימת.

איך להריץ את ה-MIgrations כך שיעדכנו את המבנה ב-Database

הגיע רגע האמת.

הרצה מתוך Visual Studion

  • בתוך Package Manager Console נקליד
Update-Database

איך זה עובד

  • נוצרת בדאטאבייס טבלת ניהול בשם migrations, שמתעדת איזה מהעדכונים כבר הורצו ואיזה עדין לא.
  • בצורה הזו, הקוד "יודע" איך לעדכן את ה-DB שלנו.

EntityFrameworkCore – ירושה ממחלקת בסיס

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

להלן דוגמא למימוש :

 public class EntityBase
 {
 public int Id { get; set; }
 public DateTime CreatedAt { get;set }

 }

 public class Category : EntityBase
 {
 public string CategoryName { get; set; }

 public virtual ICollection<Post> Posts { get; set; }
 }

 public class Post:EntityBase
 {

 public string Title { get; set; }

 public string Content { get; set; }

 public virtual Category Category { get; set; }
 }

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

למשל

 public class EntityBase
 {
 public EntityBase()
 {
 CreatedAt = DateTime.Now;

 }

 public int Id { get; set; }
 public DateTime CreatedAt { get;set }

 }

 

EntityFrameworkCore – ליצור תלויות בין ישויות

דרך אחת ליצור תלויות בין ישויות ב- Entity Framework Core היא להכריז על מאפיינים וירטואלים.

למשל – נניח שיש לנו יחס כזה – אחד לרבים קלאסי :

  • פוסט אחד קשור לקטגוריה
  • קטגוריה מכילה פוסטים רבים.

אז נוסיף לקלאס של הפוסט – קישור ל "אבא" הקטגוריה :

 public class Post
 {
 public int Id { get; set; }

 public string Title { get; set; }

 public string Content { get; set; }

 public virtual Category Category { get; set; }
 }

ונוסיף לקלאס של הקטגוריה – קישור ל "בנים" הפוסטים :

public class Category
 {
 public int Id { get; set; }
 public string Category { get; set; }

 public virtual ICollection<Post> Posts { get; set; }
 }

מה הערך של משתנים וירטואלים ? האם אפשר להפוך אותם ללא וירטואלים.

  • כן, אפשר. היתרון הקטן שזה נותן כאשר זה נשאר וירטואלי הוא ב -lazy loading . כלומר טעינת התלויות היא "עצלנית" = במובן שהיא מתרחשת מאליה, ברגע ששלפנו את האבא, ישלפו אוטומטית גם הבנים התלויים בו.. השיטה הזו מסייעת ליעילות
    מכך אפשר להבין שכאשר זה לא וירטואלי – אזי זה לא כך.

 

הסבר נחמד אפשר למצוא כאן.

 

תיעוד רשמי : https://docs.microsoft.com/en-us/ef/core/modeling/relationships 

 

Entity Framework Core – יצירת מודלים וחיבור ל Database

רקע – Entity Framework

Entity Framwork היא ORM של מיקרוסופט, כלומר, כלי (=ספריה) שמשמש בתיכנות לגישה קלה לנתונים, לעבודה קלה יותר עם דאטאבייסים (אך לא רק).

הרעיון מבוסס על כך שיש לנו "מודל" לכל ישות, וכל מודל מקושר לטבלה מסויים ב-DB (רלציוני, ול-collection ב-DB לא רלציוני).

יצירת מודלים ב Entity Framework Core

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

במודל – כל שדה, מקביל לשדה בטבלה ( אפשר לבטל את הקישור הזה, אבל לצורך הלימוד, נתמקד באפשרות הרגילה הזו).

שם הטבלה – יכול להיות מקביל *בדיוק* לשם הקלאס, או שאפשר לקבוע אותו באמצעות מאפיין ( Attribute )
בצורה הבאה

[Table("Posts")]
 public class Post
 {

 

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

יש 3 דרכים (לפחות) להתקין חבילות של nuget :

  1.  מקש ימני על הפרוייקט – לבחור באפשרות Manage nuget packages ולחפש ולהתקין את הספריה.
  2. דרך תפריט Tools (או מקש ימני על הפרויקט ) לפתוח את nuget package manager -> Package manager console , ואז להקליד
    Install-package YourPackageExactName
    במקרה כזה – כדי להעתיק את הפקודה המדוייקת מהאתר של nuget.
  3. מקש ימני על הפרוייקט – Edit YOUR_PROJECT_NAME.csproj
    ואז להוסיף ענף בקובץ ה-XML שמכיל את הספריה הרלוונטית .
    ברגע ששומרים את הקובץ, ה-Visual studio מזהה את השינוי, ומתקין את הספריה.

יצירת Context

המונח Context הוא במילים אחרות "חיבור" , כלומר אנחנו הולכים ליצור חיבור לדאטאבייס. בשפות תיכנות אחרות זה נקרא Connection וכדומה. בסופו של דבר מדובר בסה"כ במחלקה עם הגדרות, אותה מחלקה תכיל רשימת מחלקות אחרות – שהם הישויות הרלוונטיות ( ה- Entities ).
ובסופו של דבר, המחלקה הזאת תוזרק (DI ) אל כל מחלקה שצריכה חיבור לדאטאבייס.
בדוגמא הנוכחית אנחנו נגדיר DbContext (=חיבור) אל דאטאבייס בזיכרון.

ניצור מחלקה שנקרא לה ApiContext, ונגדיר שהיא יורשת מ- DbContext .
במחלקה הזאת נגדיר פונקצית קונסטרטור, בצורה המוצגת למטה, ובהמשך, נרשום את שמות הטבלאות, בתור מאפיינים מסוג dbSet של המחלקה.

public class ApiContext : DbContext
 {
 public ApiContext(DbContextOptions<ApiContext> options) : base(options)
 {

}

public DbSet<Post> Posts { get; set; }


 }

עכשיו, נגדיר בקובץ Startap.cs את ההגדרות הנחוצות, כדי שנוכל להזריק את ה- DbContext שיצרנו אל כל מחלקה שתרצה להשתמש בו.
כך זה נראה

 // This method gets called by the runtime. Use this method to add services to the container.
 public void ConfigureServices(IServiceCollection services)
 {
 services.AddDbContext<ApiContext>(options => options.UseInMemoryDatabase("Hi"));
 services.AddMvc();
 }

 

חיבור לדאטאבייס אמיתי

כדי להתחבר לדאטאבייס אמיתי, צריך לעשות כמה דברים :

  • להתקין חבילת nuget, שתהווה את הדרייבר אל הדאטאבייס הספציפי.
  • להגדיר Connection String בקובץ הגדרות, ולדאוג לשלוף את ההגדרה במקום הנכון.

דוגמא 1 : שימוש ב-SqlServer

  • בתור דרייבר — נתקין את חבילת ה nuget שנקראת Microsoft.EntityFrameworkCore.SqlServer
  • לצורך ההגדרה – נפתח את קובץ appsetting.json, ונגדיר בו קטע של ConnectionStrings

השם שהענקתי לאלמנט ה-JSON, כלומר "MyConnectionString", הוא השם שישמש אותי אחר כך ליצור service שמוזרק בעת הצורך.

כדי להקל על כתיבת המחרוזת, השתמשתי ב-Server Explorer שמובנה בתוך Visual Studio.

 "ConnectionStrings": {
 "MyConnectionString": "Data Source=LAPTOP-JD3DQ8JM\\SQLEXPRESS;Initial Catalog=EZCourse;Integrated Security=True"
 }
  • לצורך יצירת ה-service , הוספתי בפונקציה configureServices שנמצאת בקובץ startap.cs את הקטע הבא :
public void ConfigureServices(IServiceCollection services)
 {
 var connectionString = Configuration.GetConnectionString("MyConnectionString");
 services.AddDbContext<Models.AppContext>(options => options.UseSqlServer(connectionString));
 services.AddMvc();
 }

 

 

 

דיסיריליזציה של 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/ 

בהצלחה!

 

מעבר מ-PHP ל – C# , ומ-Laravel אל ASP.NET MVC – רשמים ראשונים

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

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

ועכשיו לתכלס – ההבדלים העיקרים בין PHP ל- C# .

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

ה-Framework של C# מבית מיקרוסופט, נקרא ASP.NET MVC, וקיימת גם גירסה שונה שלו שמיועדת עבור API ונקראת WebAPI ( ניתנים לשילוב, אך בעיקרון – 2 מוצרים שונים).

והמקבילה בעולם ה-PHP שאיתה עבדתי בעיקר, הייתה ה-Framework המוביל כיום ב-PHP, שנקרא Laravel , גם אצלו קיימת גירסה ל-API שנקראת Lumen.

הבדלים ברמת מחיר :

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

הבדלים ברמת השפה – בין PHP ל- C# .

אז ההבדל העיקרי ביותר – הוא ש-C# מחייבת להצהיר על סוגי המשתנים, בניגוד ל-PHP.

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

  • בהכרזה של namespace, ב-C# ,  צריך להכניס  את כל ה- קלאס תחתיו גם כן בתוך סוגרים מסולסלות { } .
  • המילה use מתחלפת במילה using.
  • אין triats ב-C#
  • יש  ב-C# משתנה מסוג List שעובד די יעיל ולא קיים ב-PHP ( יותר נכון, קיים, אך רק דרך תוספות)
  • יש מתודות מובנות ב- C# למחרוזות, מערכים , ועוד ( בסגנון של JS – שכל משתנה הוא אוביקט … רק הרבה יותר מתודות מובנות ) .
  • מימוש של Interface נעשה ב-C# עם נקודותיים, לעומת implements ב-php
  • במקום final עבור מתודות \ קלאסים שלא יורשים מהם, משתמשים ב-seald ב-c# .
  • קיים מבנה של Enum ב-C# שלא קיים ב-PHP ( למרות שקל מאוד לממש – מפתחים רבים פותחים מחלקה שמכילה Consts בלבד , והיא משמשת בתור enum )
  • שתי השפות יכולות לרוץ על כל מערכת הפעלה
    כן כן, גם למיקרוסופט יש גירסה ל-dot net שנקרא dot net core ורצה היטב על Linux .
  • ב-c# לפני הרצה צריך לבצע תהליך Build (קומפלייר)
    מה שנחסך מאיתנו כשעובדים ב-PHP
    זה יכול לקחת זמן לפעמים….וזה קטע מעצבן.

הבדלים מבחינת Enviroment  בין PHP ל- C# :

  • מנהל החבילות המוכר ב-PHP נקרא composer , בסביבת .Net קיים מנהל חבילות שנקרא NuGet
  • "על הנייר" נראה שיש פחות חבילות ל-C# , אבל זו השוואה ממש לא רלוונטית, כיוון שהרבה מאוד ספריות, מובנות כבר בתור השפה, ואין שום צורך בחבילה חיצונית.
    • דוגמא פשוטה לכך : ב-PHP נפוץ מאוד השימוש בספריה Carbon לצורך מניפולציות על תאריכים, לעומת זאת ב-C# , הספריה של DateTime כבר מובנה בשפה, ואין צורך לייבא אותה.
      נכון אומנם שגם ב-PHP יש ספרית DateTime, אך מהניסיון שלי, היא פחות נוחה לעבודה, וכיוון שכך – משתמשים ב-Carbon.
  • בסביבת מיקרוסופט, קיים IDE חזק, ללא תשלום, שנקרא Visual Studio , בא נגיד שאם מקבילים את זה לסביבת ה-PHP, אז זה כמו לקבל בחינם IDE ברמה של PHP Storm ( ואולי יותר מזה )
    • חיסרון מעצבן שלו  בכל זאת – זה שאין לו באופן מובנה ctrl+d לשכפול שורה, אך למרבה המזל, בגיגול פשוט, מוצאים תוספים שעושים זאת.
  • שרת Web – ב-PHP נהוג לעבוד עם Apache או Ngnix , לעומת סביבת dot net , שבה עובדים עם IIS.
  • אפליקציות Mobile – כאן יש יתרון עצום לסביבת מיקרוסופט, עם כלי שנקרא xamarin, ומאפשר לפתח אפליקציות שמתקפלות ל-Native , באותה שפה ( C# ) שבה אתה מפתח כרגיל.
    זה חוסך שימוש בפתרונות כמו PhoneGap \ Cordova וכדומה.
    והביצועים – בשמים.
  • כלי Debug :  ב-PHP נפוץ מאוד השימוש ב-xdebug , שההתקנה שלו עשויה להיות לפעמים סיזיפית ( חפשו ב-Youtube ותראו את כמות המדריכים בנושא ) .
    ה-Xdebug הוא תוספת ל-PHP שמאיטה מאוד את ההרצה.
    ב-C#, כלי ה-Debug מובנה בתוך Visual Studio, גם מאט מעט את ההרצה, אך פחות לעומת Xdbeug בתחושה האישית שלי.
  • הבדלים בתיעוד : 
    • מיקרוסופט מנצחת בגדול, הכל מתועד עד לפרטי פרטים,
      יחד עם זאת, קיים חיסרון של "יותר מדי מידע" ולפעמים קשה להבין איפה נמצא כל דבר בתור הררי התיעוד של מיקרוסופט.
    • לנקודה הזו יש יתרון מסויים נוסף מה קורה כאשר יש משהו לא ברור בתיעוד הרשמי ? – התיעוד של Laravel הוא מעולה בעיני, אך יש קטעים מסויימים בהם הוא כתוב גרו ( למשל ההסברים על IOC וקונספטים אחרים ) ובמקרה כזה – אין הרבה אפשרויות חוץ מלהתחיל לחפש בפורומים – שזה חיסרון .
      במיקרוסופט – בגלל עודף המקורות הרשמיים, אין הרבה צורך בפורומים. כי כמעט כל מה שתחפש כתוב בתיעוד הרשמי.

הבדלים ברמת ה- Framework  בין Laravel אל- ASP.NET MVC:

  • במבט ראשון, יש המון דברים מקבילים בין ASP.NET MVC ל- C#
  • זה מעלה מחשבות קלות של "מי העתיק ממי" אם כי, אני נוטה להניח שמיקרוסופט היו ראשונים, ו-Taylor היוצר של Laravel פשוט מימש את הדברים ב-Laravel כמו שהוא הכיר אותם לפני כן בסביבת מיקרוסופט.
  • לשתיהם יש ORM חזק, המקבילה של Eloquent ב-Laravel נקראת Entity ב-.Net
  • למרות שקיבלתי רושם שמפתחים רבים מסתייגים משימוש ב-Entity, לאחר מחקר קטן, זה כנראה חסר בסיס ( אולי בעבר היא הייתה נחותה מבחינת ביצועים, אך כיום ממש לא כך). מבחינת ביצועים – היא בסדר גמור לכל השאילתות הרגילות והפשוטות, כך שבשיקול עלות-תועלת של מהירות פיתוח מול ביצועים – זה בהחלט אופציה טובה.
  • גודל ההשקעה בפרוייקט – Laravel היא בסופו של דבר יציר כפיו של אדם אחד, עם מעט שותפים. קיימת כמובן קהילת Open Source גדולה וכו', אבל הוא ה-PO שלה חד משמעית (טיילור).
    ב-ASP.NET MVC זה מוצר של חברת ענק, ולפחות מבחינתי האישית זה היווה שיקול משמעותי בהחלטה לעבור, היות ואני לא נסמך על אדם אחד, עם קהילת קוד פתוח ( גדולה, ופעילה ככל שתהיה, כל עוד אין אינטרס כספי, לדעתי האישית, זה סיכון מסוים שצריך להילקח בחשבון).

הבדלים ברמת ביצועים בין C# ל- PHP :

  • אין כלל ספק ש-C# עולה בכמה רמות על PHP מבחינת ביצועים.
  • זה מורגש מעט בקטעי קוד שמבצעים עיבודים רציניים.
  • שפת C# היא שפה חצי-מתקמפלת , זה משליך על הביצועים משמעותית לטובה לעומת PHP.
  • עוד דברים שמשפיעים על ביצועים :
    • ב–C# התוכנה "נשארת" במצב ריצה כל הזמן, לעומת PHP שבה כל Request הוא מופע עצמאי שמתחיל מחדש.
    • החיבור ל-DB הוא קבוע ב-C#, לעומת PHP שבה החיבור נוצר בכל Request מחדש (לפי הצורך כמובן ) .
  • בקטעי קוד קטנים, אין הבדל מורגש בכלל בין השפות.
  • אומנם ב-PHP יש פתרונות לעבודה במקביל, אבל הם לא פשוטים ליישום כמו ב-C#  ( מספר תהליכים יחד).

 

סיכום – PHP לעומת C#

עניינים של בחירת שפה הפכו להיות נושאים "דתיים" לפעמים, אבל תכלס, כשבאים לבחון איזה כלי יותר טוב, בלי לערב שיקולים "כאילו-אידיאולוגים" של קוד פתוח וכו' – אז נראה שמיקרוסופט נותנת יותר ב-C# +ASP.NET MVC  לעומת PHP עם Laravel

יחד עם זאת, חייבים להודות שאם מישהו מחפש ללמוד במהירות שפת Web – אז PHP עדיפה משמעותית ..פשוט פותחים עמוד html, כותבים תגית php וזה רץ….

לדעתי, עקומת הלמידה לא כל כך משמעותית כדי להצדיק להתחיל עם PHP, בוא נגיד שאילו הייתי היום חוזר לאחור, אז הייתי מתחיל מראש עם c# , אבל כדי ללמד את הילדים בבית תיכנות הייתי בוחר ב-PHP .

המנצחת בעיני היא C# .

בהצלחה.