November 2009 - Posts
במידה וירשתם מ - MembershipProvider ומ - Membership יש סיכוי טוב מאוד שכשתנסו להפעיל את מתודת Membership.UpdateUser תקבלו שגיאה
System.ArgumentNullException: Key cannot be null
זה קצת מוזר כי גם אם הגדרתם בקונפיג את ה - defaultProvider למימוש שלכם זה עדיין יקרה, מכיון של - Membership יש שני בנאים הראשון מוגדר כ - protected והוא לא מקבל שום פרמטרים, השני מוגדר כ - public ומקבל כל מיני פרמטרים שאחד מהם הוא ה - providerName.
ברגע שהפעלתם את הבנאי ללא שום פרמטר הוא לא יודע לאיזה provider ללכת ולכן הוא נופל בקטע קוד הזה
internal virtual void Update()
{
Membership.Providers[this.ProviderName].UpdateUser(this);
this.UpdateSelf();
}
כשהוא מנסה לגשת בעזרת ProviderName שמחזיר null.
הייתי מצפה ממי שכתב את הקוד הזה שיבדוק במידה ואין ערך ל - ProviderName לקחת את ה - Provider שמוגדר כברירת מחדל, או לכל הפחות לזרוק שגיאה נורמלית.
כדי לפתור את זה צריך במחלקה שיורשת מ - Membership לכתוב.
public override string ProviderName
{
get
{
return Membership.Provider.Name;
}
}
רציתי להראות שימוש נחמד בדבר הזה.
אפשר להוריד את הפרויקט -
מכאן.
סיפור לקוח:
הוא מוציא עבודה לגרפיקאים שמחזירים לו דפי html ו - css שהוא משלב באתר שלו - באותם טפסים יש הרבה פעמים שליחה של מיילים - הוא לא רוצה לקחת את מה שהוא מקבל מהגרפיקאים ולהמיר את זה ל - asp.net, הוא רוצה להשתמש בהם כמו שהוא קיבל - והכי הרבה הוא מוכן לכתוב פונקציית JS שתאסוף את הנתונים מהדף ותשלח אותם במייל, איך עושים את זה ?
שלב ראשון נגדיר פרויקט מסוג WebService נקרא לו MailWebServices, בתוכו נגדיר מחלקה בשם MailData (שהלקוח ישלח מופע שלה לשרת)
public class MailData
{
public string From { get; set; }
public string[] To { get; set; }
public string[] CC { get; set; }
public string[] BCC { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public bool IsBodyHtml { get; set; }
}
נגדיר WebService בשם MailService עם מתודה אחת שנקראת SendMail
לא לשכוח להוריד את ההערה על ScriptService (על המחלקה) - אחרת לא יוכלו להפעיל את המתודות מ - JS.
[WebMethod]
public string SendMail(MailData mailData)
{
try
{
MailMessage mail = GetMailMessage(mailData);
SmtpClient client = new SmtpClient();
client.Send(mail);
return true.ToString();
}
catch (Exception ex)
{
return ex.ToString();
}
}
אנחנו מקבלים מופע של MailData, ממירים את זה ל - MailMessage (מיד נראה את הפונקצייה) מייצרים אובייקט מסוג SmtpClient ושולחים את ההודעה.
בנקודה הזאת אנחנו צריכים להגדיר כמה הגדרות בקונפיג.
1. תחת system.web צריך להוסיף:
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
אחרת לא נוכל להפעיל את ה - webService רק ב - SOAP.
2. צריך להגדיר את ה - SmtpClient (תחת configuration)
<system.net>
<mailSettings>
<smtp>
<network host="127.0.0.1" port="25"/>
</smtp>
</mailSettings>
</system.net>
כמובן שה - IP צריך להשתנות לפי הדומיין שלכם (כרגע זה מוגדר localhost).
בצד השרת נשאר לנו רק להסתכל על המתודה GetMailMessage
private static MailMessage GetMailMessage(MailData mailData)
{
MailMessage mail = new MailMessage();
if (mailData.BCC != null)
{
foreach (var item in mailData.BCC)
{
mail.Bcc.Add(item);
}
}
mail.Body = GlobalObject.unescape(mail.Body);
if (mailData.CC != null)
{
foreach (var item in mailData.CC)
{
mail.CC.Add(item);
}
}
mail.From = new MailAddress(mailData.From);
mail.Subject = GlobalObject.unescape(mailData.Subject);
foreach (var item in mailData.To)
{
mail.To.Add(item);
}
mail.IsBodyHtml = mailData.IsBodyHtml;
return mail;
}
קוד די פשוט - יוצרים אובייקט מסוג MailMessage ומכניסים לתוכו את כל הערכים מתוך mailData.
הדבר היחיד המעניין הוא המתודה unescape של GlobalObject - זה מתודה שיודעת להמיר תווים ששנשלחו ב - ASCII לעברית וכו' - (צריך להוסיף reference ל - Microsoft.JScript).
עד כאן ראינו הגדרה של WebService שיודע לשלוח מיילים, כעת נראה איך אפשר לפנות אליו מצד הלקוח.
נגדיר שני קבצי JS - הראשון נקרא לו JSONResponse.js שבו יהיה לנו קוד פשוט ליצירת ה - XMLHttp ועבודה איתו (כמובן שאפשר לעבוד עם ספריות של ajax או jquery, אבל רציתי לשמור על פשטות וקוד קטן)
הנה הקוד שלו:
var isIE = navigator.appName.indexOf('icros') > -1;
function GetSynchronousJSONResponse(url, postData) {
if (isIE) {
return IEXmlHttp(url, postData);
}
else {
return FFXmlHttp(url, postData);
}
}
function IEXmlHttp(url, postData) {
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.open("POST", url, false); //false means synchronous
xmlhttp.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xmlhttp.send(postData);
var responseText = xmlhttp.responseText;
return responseText;
}
function FFXmlHttp(url, postData) {
var xmlHttpFF = new XMLHttpRequest();
xmlHttpFF.open("POST", url, false);
xmlHttpFF.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xmlHttpFF.send(postData);
var responseText = xmlHttpFF.responseText;
return responseText;
}
הפונקצייה החשובה כאן היא GetSynchronousJSONResponse שמקבלת את הכתובת של ה - service ומה לשלוח כפרמטר, בנוסף זה יודע לייצר את האובייקט המתאים לפי הדפדפן.
בנוסף יש לי קובץ js שנקרא PostData.js שבו יש הגדרה של המלחקה MailData ב - JS וכן הפונקציות להמרה של מופע מהמחלקה לפורמט JSON.
בהתחלה יש את ההגדרה הבאה:
var MailData = new Object();
MailData.From = '';
MailData.Subject = '';
MailData.Body = '';
MailData.IsBodyHtml = false;
MailData.To = new Array();
MailData.CC = new Array();
MailData.BCC = new Array();
למעשה הגדרנו אובייקט חדש ב - JS שיש לו את כל המאפיינים הכתובים (מוגדר אחד לאחד כמו האוביירט בצד השרת)
בנוסף יש לנו פונקצייה שנקראת SendMail שמקבלת מופע שלו
function SendMail(postData) {
var to = JsonStringForArray(postData.To);
var cc = JsonStringForArray(postData.CC);
var bcc = JsonStringForArray(postData.BCC);
var jsonPostData = '{mailData: { Url: "' + postData.Url +
'", From: "' + postData.From +
'", Subject: "' + postData.Subject +
'", Body: "' + postData.Body +
'", To: ' + to +
', CC: ' + cc +
', BCC: ' + bcc +
', IsBodyHtml: ' + postData.IsBodyHtml +
' }}';
return GetSynchronousJSONResponse("http://localhost:51202/MailService.asmx/SendMail", jsonPostData);
}
הפונקצייה מקבלת מופע של MailData ומייצרת מחרוזת בפורמט JSON -
בנוסף יש לי מערכים (To, CC, BCC) שצריך להמיר אותם ל - JSON, יש את הפונקציה
function JsonStringForArray(arr) {
var obj = '[';
for (var i = 0; i < arr.length; i++) {
obj += '"' + arr[i] + '",';
}
if (obj == '[') {
obj = null;
}
else {
obj = obj.substr(0, obj.length - 1);
obj += ']';
}
return obj;
}
שמקבלת מערך של מחרוזת - ומחיזרה אותו ב - JSON.
בסוף הפונקצייה אנחנו קוראים ל -
GetSynchronousJSONResponse והמייל אמור להשלח.
לדוגמא - הקוד ב - html
<input type="button" onclick="send()" value="send" />
<script src="JSONResponse.js" type="text/javascript"></script>
<script src="PostData.js" type="text/javascript"></script>
<script type="text/javascript">
function send() {
var postData = MailData;
postData.From = 'a@a.co.il';
postData.To[0] = 'xxx@gmail.co.il';
postData.To[1] = 'zzzz@walla.co.il';
postData.CC[0] = 'aaa@gmail.com';
postData.BCC[0] = 'mywork@gmail.com';
postData.Subject = 'this is a subject';
postData.Body = 'this is a body 123';
postData.IsBodyHtml = false;
var res = SendMail(postData);
alert(res);
}
</script>
כעת אותו לקוח מקבל דפי ה - html מהגרפיקאים.
נותן אותם למתכנת שלו ומה שהוא צריך לעשות זה:
להוסיף referenct לאותם שני קבצי JS ולכתוב פונקציית JS שתאסוף את הנתונים מהדף ולקרוא למתודה SendMail.
בהצלחה
בפוסט הנוכחי אני אדגים איך פונים ל - WebService שמצפה לקבל טיפוס שהוא לא מה - Primitive Types.
נניח שיש WebService שנראה כך: (לא לשכוח להוריד את ההערה מ - ScriptService - אחרת אי אפשר להפעיל מ - JS)
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class WebService1 : WebService
{
[WebMethod]
public void SetPerson(Person person)
{
// Do Logic.....
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string[] Childern { get; set; }
}
function GetSynchronousJSONResponse(url, postData) {
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.open("POST", url, false);
xmlhttp.setRequestHeader("Content-Type",
"application/json; charset=utf-8");
xmlhttp.send(postData);
var responseText = xmlhttp.responseText;
return responseText;
}
כעת מה שנשאר לנו לעשות זה את הדבר הבא:
למעשה ה - postData צריך להיראות כך:
var jsonPostData = '{person: {Id: 10, Name: "noam", Childern: ["kivi", "banana", "orange"]}}';
בבנייה דינמית נעשה את זה כך:
function SetPerson() {
var id = 10;
var name = 'noam';
var childern = new Array();
childern[0] = 'kivi';
childern[1] = 'banana';
childern[2] = 'orange';
var jsonPostData = '{person: ' +
'{Id: ' + id +
', Name: "' + name +
'", Childern : ["' + childern[0] +
'","' + childern[1] +
'","' + childern[2] +
'"]}}';
GetSynchronousJSONResponse("http://localhost:49395/WebService1.asmx/SetPerson",
jsonPostData);
}
וזהו, נוכל להגיע לשרת עם האובייקט שלנו שנשלח מ - JS
יחד עם ה - MVC הגיע dll מאוד נחמד ששמו System.Web.Routing, הוא משמש מנגנון מאוד מרכזי ב - MVC כדי להפנות לדף המתאים לפי הבקשה .
היות שהוא כל כך מוצלח ניתן להשתמש בו גם ב - asp.net לצרכים אחרים.
(חשוב לציין שיכול שמן הסתם רוב הלינקים בפוסט לא יעבדו - אלא הם הדגמה למה אפשר לעשות)
אז כמובן שמן הסתם תהיה מערכת ניהול ויש לנו בסיס נתונים או קובץ XML שבו נשמרים כל קיצורי הדרך ולהיכן זה צריך להגיע - אבל מה שחשוב זה מנגנון ה - routing ואיך זה עובד.
(בשבוע הקרוב אני אעלה בעז"ה ל - CODE PLEX פרויקט של ה - routing יחד עם מנגנון הניהול שלו - כשזה יעלה אני אפרסם את הלינק)
לבנתיים נראה איך ה - routing עובד
אז ככה - דבר ראשון יש להוסיף לפרויקט שלכם שני reference
1. System.Web.Abstractions
2. System.Web.Routing
לאחר מכן ב - Global.asax בארוע Application_Start צריך לרשום מי יהיה אחראי לטפל בבקשות שיגיעו
protected void Application_Start(object sender, EventArgs e)
{ List<string> titleData = Dal.GetAllTitles();
WebFormRouteHandler webFormRouteHandler = new WebFormRouteHandler();
RouteTable.Routes.Add("4D81C761-A6ED-450f-8618-618A17EFBA99", new Route("4D81C761-A6ED-450f-8618-618A17EFBA99", webFormRouteHandler));
for (int i = 0; i < titleData.Count; i++)
{ RouteTable.Routes.Add(titleData[i], new Route(titleData[i], webFormRouteHandler));
}
}
ואני אסביר -
בשלב ראשון אני מקבל מבסיס הנתונים את כל קיצורי הדרך - לדוגמא syllabus/c2.
אני יוצר מופע של WebFormRouteHandler (עוד מעט נראה אותו).
נתעלם כרגע מהשורה הבאה ונדלג לללואה. (נתייחס לזה בהמשך)
אנחנו עושים מעבר על כל קיצורי הדרך - ומוסיפים ל - RouteTable (שזה מגיע מ - System.Web.Routing) את קיצור הדרך ומי אמור לטפל בבקשה - שזה האובייקט שיצרנו.
בפועל מה שהקוד עושה זה, להגדיר כשיגיע request שמכיל ב - url את קיצור הדרך (בדוגמה שלנו - syllabus/c2) מי שיטפל בבקשה זה לא המנגנון הרגיל של asp.net אלא המחלקה שיצרנו - כלומר המופע של WebFormRouteHandler).
כעת נראה את הקוד של המחלקה WebFormRouteHandler
public class WebFormRouteHandler : IRouteHandler
{ #region IRouteHandler Members
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{ return new RoutingHandler();
}
#endregion
}
כמו שאנחנו רואים - אין בו הרבה קוד, בסך הכל מימוש של interface בשם IRouteHandler (שזה מה שה - RouteTable מקבל) ובמימוש שלו צריך להחזיר מישהו שמממש את IHttpHandler - כשלמעשה אנחנו מחזירים מופע של RoutingHandler)
הנה המחלקה
public class RoutingHandler : UrlRoutingHandler
{ protected override void VerifyAndProcessRequest(IHttpHandler httpHandler,
HttpContextBase httpContext)
{ string tempTitle = httpContext.Request.Url.AbsolutePath;
if (tempTitle.Contains("4D81C761-A6ED-450f-8618-618A17EFBA99")) { Global.Register();
return;
}
tempTitle = tempTitle.Remove(0, 1);
tempTitle = tempTitle.Substring(tempTitle.IndexOf('/') + 1);
string titleData = Dal.GetUrl(tempTitle);
httpContext.Response.Redirect(titleData);
}
}
המחלקה יורשת מ - UrlRoutingHandler שמממשת את IHttpHandler - אנחנו נעשה override ל - VerifyAndProcessRequest שהיא המתודה שצריכה להחליט לאיפה לנווט ומה להחזיר כתשובה לבקשה.
בהתחלה אני לוקח את ה - absolutePath.
נדלג כרגע על הבדיקה עם ה - GUID (נתייחס בהמשך)
המשתנה tempTitle יהיה שווה ל - url/syllabus/c2/ (זה ה - absolutePath)
אני מעיף את תו ה - / הראשון. ואז את כל השאר עד תו ה - / השני - כלומר אני אשאר עם syllabus/c2 (שזה כזכור קיצור הדרך)
אני מתשאל את בסיס הנתונים לאיפה צריך לנווט בהתבסס על קיצור הדרך - ואני מנווט לאותו מיקום.
כמעט סיימנו - נשארו דברים.
הראשון איך אומרים ל - IIS שאנחנו מטפלים בבקשות - כלומר, כשמגיעה בקשה לשרת שנראית:
http://www.sela.co.il/url/syllabus/c2 אזי IIS אמור לחפש תיקייה שנקראת c2 תחת תיקייה שנקראת syllabus בתוך ה - virtual directory שנקרא url - וכמובן הוא לא ימצא, ולכן אנחנו צריכים להגדיר ל - IIS שלא הוא מטפל בבקשות אלא אנחנו.
כדי לעשות זאת זה תלוי האם מדובר ב - IIS 7 או בגרסה קודמת, במידה שמדובר ב - IIS 7 זה הגדרה פשוטה בקובץ הקונפיג, בחלק התחתון של ה - web.config יש מקטע שנקרא system.webServer בתוכו יש מקטע שנקרא modules, צריך להוסיף זם את הקוד הבא:
<add name="UrlRoutingModule"
type="System.Web.Routing.UrlRoutingModule,
System.Web.Routing, Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35" />
בנוסף במקטע של handlers צריך להוסיף את הקוד הבא:
<add name="UrlRoutingHandler"
preCondition="integratedMode"
verb="*"
path="UrlRouting.axd"
type="Routing.RoutingHandler, Routing"/>
כשצריך לשים לב לחלק של ה - type שזה צריך להיות האובייקט שלנו והשם של ה - namespace שלנו.
וזה יעבוד.
במידה ומדובר על גרסאות קודמות של IIS (הגרסה הכי מוקדמת שזה יכול לעבוד זה על server 2003 - צריך לקנפג את זה גם בקונפיג וגם ב - IIS.
בקונפיג תחת המקטע של httpHandlers צריך להוסיף
<add verb="*" path="*" type="Routing.RoutingHandler" />
ב - IIS (אין לי כרגע windows server 2003 אז הייתי צריך לחפש את התמונות בגוגל - ולכן תתעלמו מהעיגולים האדומים או מההגדרות שיש בתמונות ותתיחסו רק למה שאני כותב).
שלב ראשון אחרי הגדרת ה - virtual directory נלחץ על configuration
(העיגול מסביב ל - Script and Execute הגיע מהתמונה המקורית - אין לזה קשר על מה שאנחנו מדברים כרגע)
נקבל את החלון הבא:
נלחץ על Insert כדי להגדיר ל - IIS מי מטפל בבקשות שנכנסות
יש להכניס ב - execute את הנתיב של asp.net כמדומני שזה C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll אבל אני לא בטוח - אפשר תמיד לבדוק מה הנתיב של הסיומת של ה - aspx בחלון ה - Configuration.
לא לשכוח לוודא ש - Verify taht file exists לא מסומן.
הדבר האחרון שאני צריך להסביר - מה העניין עם ה - GUID ב - Applicatuin_Start וב - VerifyAndProcessRequest.
העניין הוא כזה - היות שאנחנו רושמים את כל קיצורי הדרך בזמן Application_Start, בזמן שנוסיף קיצורי דרך בבסיס הנתונים מנגנון ה - Routing לא יודע מזה ולכן זה לא ירשם ב - RouteTable עד שהאפליקצייה לא תמות (או שנעשה iisreset) ולכן הפיתרון היה לרשום ב - RouteTable מספר מיוחד שבמידה ומגיעים איתו זה ירענן את ה - RouteTable,
למעשה הקוד
RouteTable.Routes.Add("4D81C761-A6ED-450f-8618-618A17EFBA99", new Route("4D81C761-A6ED-450f-8618-618A17EFBA99", webFormRouteHandler));
string tempTitle = httpContext.Request.Url.AbsolutePath;
if (tempTitle.Contains("4D81C761-A6ED-450f-8618-618A17EFBA99")){ Global.Register();
return;
}
הקוד יושב במתודת VerifyAndProcessRequest הוא יבדוק האם ההפנייה היא עם ה - GUID - במידה וכן נפעיל מתודה סטטית ב - Global.asax שמעדכנת את ה - routeTable מבסיס הנתונים.
בתפוז עלתה השאלה הבאה:
יש טופס אב ויוצרים בזמן ריצה מופעים של טפסי בן ומוספים אותם לאב - במקרה שהמאפיין FormBorderStyle מוגדר כ - none על טופס הבן, מה שיקרה בפועל שלשנייה אחת יראו את המסגרת ואז זה יעלם - השאלה כיצד מתפטרים מהמסגרת לתמיד.
נניח שהקוד נראה ככה:
private void button1_Click(object sender, EventArgs e)
{ Form2 f = new Form2();
f.MdiParent = this;
f.Show();
}
למעשה הבעייה נובעת מכך ש - windows מציירת כל הזמן את כל הפקדים שעל המסך - ולכן הדרך היחידה לא לראות את המסגרת היא להגיד למערכת ההפעלה להפסיק לצייר את הטופס עד שהכל יסתדר במקום.
כדי לעשות את זה נוכל להשתמש ב - Extension Methods שכתבתי
בפוסט השני שלי (כל ההסברים נמצאים שם ולכן כאן אני רק אדגים שימוש בזה)
הנה קוד ה - Extension
namespace System.Windows.Forms
{ public static class ControlExtensions
{ [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
public static void BeginUpdate(this Control control)
{ SendMessage(new HandleRef(control, control.Handle), 11, 0, 0);
}
public static void EndUpdate(this Control control)
{ SendMessage(new HandleRef(control, control.Handle), 11, -1, 0);
control.Invalidate(true);
}
}
}
והנה הקוד השימוש בו
private void button1_Click(object sender, EventArgs e)
{ this.BeginUpdate();
Form2 f = new Form2();
f.MdiParent = this;
f.Show();
this.EndUpdate();
}
ועכשיו לא יראו את המסגרת של טופס הבן אפילו לשבריר שנייה
אז
בפרק האחרון דברנו על מה ה - designer עושה מאחורי הקלעים עבור הסכמה שלנו, אפשר להאריך בנושא הסכמה עוד הרבה, אבל חשבתי שכדאי קצת לראות קוד ואיך לעבוד עם ה - EF ואחרי זה נסבך קצת את הסכמה.
היות שבסיס הנתונים שלנו כרגע ריק - אנחנו נכתוב קוד שיכניס בו נתונים ונדון בכמה נושאים.
כדי לעבוד עם EF אנחנו חייבים לייצר מופע של מחלקה שיורשת מ - ObjectContext במקרה שלנו EFLabEntities,
using (EFLabEntities context = new EFLabEntities())
{
}
במקרה שלנו יהיה חכם לעשות את זה עם using כי אנחנו רוצים לייצר את ה - context להוסיף לו אובייקטים לשמור לבסיס הנתונים, וזהו.
בחיים האמיתיים יתכן ונרצה לשמור את ה - context ב - session וכו'.
כדי להוסיף משתמשים חדשים למערכת - אנחנו צריכים לייצר מופע של User - יש לנו שני דרכים, הראשונה עם המתודה הסטטית CreateUser (מתודה שנוצרת בצורה אוטומטית עבור כל מי שיורש מ - EntityObject) והאופצייה השנייה היא לייצר מופע של User ולתת ערכים למאפיינים, בסופו של דבר אנחנו צריכים להוסיף את ה - User שנוצר ל - Users (ל - EntitySet).
החתימה של CreateXXX נוצרת עם כל המאפיינים שאינם nullable, ולכן נכתוב את הקוד הבא
using (EFLabEntities context = new EFLabEntities())
{ User user1 = EntityFrameworkLab.User.CreateUser(1, "shlomo132", "shlomo@gmail.com");
user1.PenName = "shlomi";
context.AddToUsers(user1);
User user2 = new EntityFrameworkLab.User()
{ Id = 2,
Name = "noam",
Gender = UserGender.Male,
Email = "noam@gmail.com",
PenName = "noami"
};
context.AddToUsers(user2);
context.SaveChanges();
}
אחרי שהקוד ירוץ יתווספו לבסיס הנתונים שני משתמשים חדשים.
כמה נקודות:
אחרי שיצרנו את המופע של User אנחנו צריכים להוסיך אותו ל - EntitySet (במקרה שלנו Users), בסוף התהליך צריך לזכור לעשות SaveChanges כדי לשמור את הנתונים לבסיס הנתוניםץ
אין משמעות למאפיין Id (עבור פעולות insert) אפשר לתת לו כל ערך (אפשר אפילו להתעלם ממנו - ולא לתת לו שום ערך) היות שזה מוגדר כמספר רץ הוא יקבל מיד אחרי ה - SaveChanges את המספר הנכון (וכמובן זה יעדכן את האובייקט עם המספר הנכון), אפשר אפילו להוסיף מתודה סטטית עבור CreateXXX ל - partial כדי לוותר על ה - Id
public partial class User
{
public static User CreateUser(string name, string email)
{ return CreateUser(0, name, email);
}
}
דרך אגב יש באג ב - EF, במקרה ששדה מוגדר בבסיס הנתונים כ - NULL אבל בסכמה זה הוגדר (ידנית כמובן) כ - NOT NULL - המערכת תאפשר לייצר אובייקטים ולשמור אותם בבסיס הנתונים מבלי להתריע אבל ברגע שהנתונים ישלפו מבסיס הנתונים האפליקצייה תעוף ויצטרכו לעדכן ידנית את הנתונים בבסיס הנתונים, ולכן מומלץ להשתמש מה שיותר במתודת CreateXXX שמכריחה להכניס לכל מה שהוגדר כ - NOT NULL ערכים.
אם נתקלתם בהודעה כזאת או בדומה לה ב - windows 7 - בזמן נסיון גישה לבסיס נתונים.
תצטרכו לעשות את התהליך הבא: (למעשה אחרי חיפוש בגוגל הגעתי
לכאן שהפנה אותי
לכאן - אבל היות שזה כתוב בשפה מוזרה שאני לא מצליח אפילו להבין מה זה לדוגמא "เป็นปัญหาที่ผมพบเจอใน Windows 7 เผื่อใครเจอจะได้ลองเอาไปใช้กัน" אם אתם מכירים את השפה - אשמח לשמוע מה זה)
בכל מקרה הנה התהליך.
פתחו את ה - IIS (אפשר לכתוב בשורת הפקודה inetmgr)
נווטו ל - Application Pools.
בחרו ב - DefaultAppPool (או באותו אחד שמריץ את האפליקצייה שלכם)
לחצו על Advanced Settings.
בחלון המאפיינים שנפתח בחרו במאפיין Identity (תחת Process Model)
בחלון שנפתח תשנו ל - LocalSystem (לא לבלבל עם LocalService)
בהצלחה
כתבתי אפליקציית web פשוטה וניסתי לייצר קובץ מתוך הקוד בשרת - הקובץ כבר היה קיים בתיקייה ורציתי לדרוס אותו - כתבתי קוד כזה (לא בדיוק כזה אבל אותו רעיון)
File.CreateText(Request.MapPath("a.txt"));
כשהרצתי את הקוד בשרת קבלתי שגיאת חוסר הרשאות - חיפוש מהיר בגוגל גיליתי שאני צריך לתת הרשאות של modify ל - user שמריץ את האפליקצייה (network services)
ניסיתי - לא עבד.
נתתי לו FullControl - לא עבד.
ניסיתי להוסיף את כל ההרשאות שבעולם לכל המשתמשים שקיימים על המחשב של השרת - ואני כל הזמן מקבל שגיאה של הרשאות.
בסוף גיליתי את הבעייה.
מסתבר שהדרך הפשוטה ביותר להעלות אפליקציית asp.net לשרת היא להעתיק את כל התיקייה לשרת ולייצר virtual directory אליה.
היות שאני עובד עם TeamSystem כל הקבצים מסומנים כ - ReadOnly - ולכן אי אפשר לדרוס מקוד את הקובץ - ותמיד נקבל שגיאה של חוסר הרשאות.
הפיתרון היה קל ופשוט - להוריד את ה - ReadOnly (וכמובן להעיף את כל ההרשאות המיותרות שנתתי לכל העולם)
היום ישבתי עם עדי (ראש צוות הכי טוב שהכרתי) והוא אומר לי תפתח command line, התכוונתי להפעיל את שורת הפקודה לכתוב cmd ולנווט לתיקייה המתאימה.
ואז עדי אומר לי "תלחץ על shift באמצע התיקייה ותן קליק ימין בעכבר" - עשיתי זאת, ואז אני רואה את הדבר הבא
אז נכון שזה קיים כבר מויסטה - אבל אני קפצתי ישר מ - XP.
חשבתי שאולי זה יכול להביא תועלת לאחרים.
בפרק הקודם ראינו את ה - xml שנוצר מאחורי הקלעים והבנו מה המשמעות של האלמנטים ואיך הוא מקשר בין המבנה של בסיס הנתונים לבין מבנה האובייקטים.
בפרק הנוכחי נראה את הקוד שמתחולל כתוצאה מהמודל.
תזכורת: יש לנו ישות אחת שנראת כך:
כשפתחנו את קובץ ה - edmx ב - xml editor ראינו שהוא למעשה קובץ xml.
לקובץ הזה יש קובץ נוסף שנקרא Model.Designer.cs - כשנפתח אותו נראה הרבה קוד - ונסביר אותו.
החלק הראשון הוא מחלקה שיורשת מ - ObjectContext
public partial class EFLabEntities : ObjectContext
{
public EFLabEntities() :
base("name=EFLabEntities", "EFLabEntities")
{
this.OnContextCreated();
}
partial void OnContextCreated();
public ObjectQuery<User> Users
{
get
{
if ((this._Users == null))
{
this._Users = base.CreateQuery<User>("[User]");
}
return this._Users;
}
}
private ObjectQuery<User> _Users;
public void AddToUsers(User user)
{
base.AddObject("Users", user);
}
}
אותו אובייקט שיורש מ - ObjectContext הוא מאוד חשוב - למעשה כל העבודה שלנו מול בסיס הנתונים היא דרכו - בפרקים הבאים כשנתחיל לכתוב קוד מול ה - EF, נראה שהוא שומר לנו על המצב של האובייקטים והוא יודע האם להפעיל Update/Delete/Insert בהתאם למצב של האובייקט.
בתוך המחלקה יש לנו כמה בנאים (השמטתי כמה מהם) שיודעים לייצר את ה - Context שלנו -
בזמן יצירת ה - Context יש קריאה למתודה OnContextCreated (מוגדרת מיד אחרי הבנאי) שכברירת מחדל לא עושה כלום, אבל נוכל להרחיב את המחלקה (היות שהיא מוגדרת כ - partial) ולדאוג שיהיה מימוש למתודה.
לדוגמא - תוכלו להוסיף קובץ חדש לפרויקט ובתוכו הקוד הבא:
public partial class EFLabEntities
{
partial void OnContextCreated()
{
if (HttpContext.Current.Application["CountOfContextCreated"] == null)
{
HttpContext.Current.Application["CountOfContextCreated"] = 0;
}
int count = (int)HttpContext.Current.Application["CountOfContextCreated"];
count++;
HttpContext.Current.Application["CountOfContextCreated"] = count;
}
}
ובכל פעם שמישהו ייצר מופע של ה - Context המתודה שלנו תופעל ונוכל לספור את כמות ה - Context שנוצרו.
(מי שלא מכיר את הנושא של partial יכול לקרוא עליו
כאן.)
נמשיך:
אחרי הבנאי והמתודה OnContextCreated יש לנו מאפיין בשם Users
אם אתם זוכרים בפרקים הקודמים כל הזמן דברנו על EntitySet למעשה המאפיין Users הוא ייצוג של ה - EntitySet (אוסף של User)
המאפיין הזה הוא בעצם מופע של אובייקט שנראה כך:
private ObjectQuery<User> _Users;
ObjectQuery הוא אובייקט שיודע לייצר שאילתות מתוך המודל - למעשה ה - EntitySet הוא אוסף של כל ה - Users כשהדרך להביא את הנתונים זה על ידי Query שיביא את כל הנתונים וזה נראה כמו ושראינו למעלה
this._Users = base.CreateQuery<User>("[Users]");
הפרמטר ששולחים ב - CreateQuery היא מחרוזת שמתארת את השאילתא - במקרה שלנו זה השם של ה - EntitySet שיביא לנו את כל הנתונים (יתורגם ל משהו בסגנון Select * From user)
בסוף יש לנו מתודה להוספת User ל - EntitySet (פעולת Insert) (קריאה למתודה לא מכניסה את האובייקט לבסיס הנתונים אלא מוסיפה אותו ל - EntitySet ומסמנת אותו כאובייקט שצריך להפעיל עליו insert
public void AddToUsers(User user)
{
base.AddObject("Users", user);
}
אחרי ההגדרה של ה - EFLabEntities יש לנו הגדרה של כל הישויות שלנו (כרגע רק User) הוא נראה כך (אני מציג רק חלק ממנו)
[EdmEntityType(NamespaceName="EFLabModel", Name="User")]
[DataContract(IsReference=true)]
[Serializable]
public partial class User : EntityObject
{
public static User CreateUser(long id, string name, string email)
{
User user = new User();
user.Id = id;
user.Name = name;
user.Email = email;
return user;
}
[EdmScalarProperty(EntityKeyProperty=true, IsNullable=false)]
[DataMember]
public long Id
{
get
{
return this._Id;
}
set
{
this.OnIdChanging(value);
this.ReportPropertyChanging("Id");
this._Id = StructuralObject.SetValidValue(value);
this.ReportPropertyChanged("Id");
this.OnIdChanged();
}
}
private long _Id;
partial void OnIdChanging(long value);
partial void OnIdChanged();
[EdmScalarProperty(IsNullable=false)]
[DataMember()]
public string Name
{
get
{
return this._Name;
}
set
{
this.OnNameChanging(value);
this.ReportPropertyChanging("Name");
this._Name = StructuralObject.SetValidValue(value, false);
this.ReportPropertyChanged("Name");
this.OnNameChanged();
}
}
private string _Name;
partial void OnNameChanging(string value);
partial void OnNameChanged();
}
יש את ההגדרה של class בשם User (למעשה הוא Entity) יש לו Attributes של EntityFramework שממפים אותו ל - EntityType המתאים.
כמו כן יש לו Attributes שקשורים ל - WCF.
לאחר מכן יש מתודה סטטית בשם CreateUser שמקבלת כפרמטרים את כל המאפיינים שהוגדרו כ - Nullable=false (כלומר כל המאפיינים שחייבים לתת להם ערך) הפונקצייה תייצר מופע של User ותחזיר אותו.
לאחר מכן יש הגדרה של כל המאפיינים - לכל מאפיין יש Attributes של EF שמגדירים אותו - למעשה ההגדרות שאנחנו מגדירים בחלון המאפיינים במודל מוגדרים ב - xml ובקוד המחולל ל - atributes.
[EdmScalarProperty(EntityKeyProperty=true, IsNullable=false)]
[DataMember]
public long Id
{
get
{
return this._Id;
}
set
{
this.OnIdChanging(value);
this.ReportPropertyChanging("Id");
this._Id = StructuralObject.SetValidValue(value);
this.ReportPropertyChanged("Id");
this.OnIdChanged();
}
}
המאפיין Id מקבל את ה - Attribute ScalaProperty עם הגדרות שזה ה - key שלנו, ועוד.
בחלק ה - set של המאפיין אנחנו מפעילים קריאה לכל מיני מתודות של הודעות שהמאפיין הולך להשתנות או אחרי שהוא השתנה.
את מתודות ה - OnIdChanging ו - OnIdChnaged הם מתודות partial שאנחנו יכולים להרחיב.
בפרק הבא אנחנו נתחיל לעבוד על המודל בקוד - נראה כיצד נשלוף ונעדכן את בסיס הנתונים - אחרי שנכיר קצת את צורת העבודה עם EF נוכל להמשיך לסבך את המודל שלנו.