April 2010 - Posts
פתרון להודעת שגיאה מהסוג הזה:
Sys.InvalidOperationException: Type [Ajax Object] has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded. A possible cause is a change of settings during a partial update
תיאור הבעייה:
במקרים מסויימים (מייד אתאר את המקרה הספציפי שלי) כשמציבים אובייקט כלשהו של AJAX בתוך Update Panel נקבל הודעת שגיאה מהסוג הזה.
במקרה שלי השתמשתי עם
Rating של AjaxControlTollkit ועטפתי אותו עם RadAjaxPanel של
טלריק, ובכל פעם שניסיתי לעשות Post Back מתוך הפאנל קבלתי את הודעת השגיאה הזאת.
פיתרון הבעייה:
אחרי קצת שוטטות בגוגל הגעתי ל
כאן מתואר שם בעייה דומה עם Timer של AJAX.
ההצעה שם הייתה להגדיר את המאפיין - ScriptMode של ה - ScriptManager ל - Release.
מסתבר שזה אכן הפיתרון הנכון, לא חקרתי את הסיבות למה ב - Debug Mode הם מנסים לרשום כמה פעמים את אותו סקריפט
אבל כל עוד שזה עובד זה בסדר מבחינתי.
אם תנסו למחוק אובייקט ב - collection ב - entity framework בעזרת remove בצורה הבאה
Lecture lecture = entities.Lectures.Include("SelaRelatedCourses").First();
lecture.SelaRelatedCourses.Remove(lecture.SelaRelatedCourses.ElementAt(0));
אתם יכולים לקבל הודעת שגיאה בהסגנון הזה
A relationship is being added or deleted from an AssociationSet 'FK_Relatedcourses_Lecture'. With cardinality constraints, a corresponding 'SelaRelatedCourses' must also be added or deleted.
כדי למחוק בצורה נכונה עדיף לכתוב ככה
entities.DeleteObject(lecture.SelaRelatedCourses.ElementAt(0));
אני מניח שכבר נתקלתם בהודעה המעצבנת הזאת - בדרך כלל מקבלים את ההודעה כשיש לנו סקריפט שכזה
btnUpdateId = '<%= btnUpdateCreate.ClientID %>';
imgLectId = '<%= imgLect.ClientID %>';
cvEmailUniqeId = '<%= cvEmailUniqe.ClientID %>';
לא ברור לי מתי מקבלים את ההודעה הזאת - כלומר מה צריך לעשות כדי לדפוק את הקוד כדי שזה יקרה, אבל הפיתרון הוא די פשוט.
פשוט תעטפו את הקוד עם div שהוגדר כ - runat=server
<div runat="server">
<script type="text/javascript">
btnUpdateId = '<%= btnUpdateCreate.ClientID %>';
imgLectId = '<%= imgLect.ClientID %>';
cvEmailUniqeId = '<%= cvEmailUniqe.ClientID %>';
</script>
</div>
היה לי בבסיס הנתונים קישור פשוט של רבים לרבים
מבחינת האובייקטים ב - Entity Framework זה נוצר בצורה הזאת
הקשר בין הרצאה לתגים הוא רבים לרבים. כשניסיתי לכתוב את הקוד הזה
Lecture lecture = entities.Lectures.First();
Tag tag = new Tag() { Name = "acbd" };
lecture.Tags.Add(tag);
entities.SaveChanges();
קבלתי את ההודעה הבאה
Unable to update the EntitySet 'TagLecture' because it has a DefiningQuery and no <InsertFunction> element exists in the <ModificationFunctionMapping> element to support the current operation
התחלתי לחקור את הנושא
ופתחתי את קובץ ה - xml שה - EF מייצר.
וראיתי את ההודעה הבאה
<!--Errors Found During Generation:
warning 6002: The table/view 'SCO.dbo.TagLecture' does not have a primary key defined. The key has been inferred and the definition was created as a read-only table/view.
מסתבר שלא היה בטבלת הקשר TagLecture אף מפתח ראשי. הגדרתי את שני העמודות כמפתח ראשי רפרשתי את המודל והכול בא על מקומו בשלום
זה נכון שכדי לעבוד עם AJAX אנחנו עובדים עם ספריות כמו של ScriptManager או של jQuery, אבל בכל זאת חשבתי לכתוב דוגמא לשימוש ב - XMLHttpRequest לבד.
את הדוגמא אפשר להוריד
מכאן.
נניח שיש לנו מסך שנראה כך:
המשתמש יבחר שם -
במידה והוא לוחץ על הלחצן server אנחנו ניגש לשרת בצורה רגילה (כלומר PostBack) ונביא נתונים אודות המשתמש.
במידה והוא לוחץ על הלחצן xml אנחנו ניגש לשרת בעזרת XMLHttpRequest ונביא את הנתונים.
קוד ה - HTML
<div id='mydiv'></div>
<asp:ListBox ID="name" runat="server">
<asp:ListItem>shlomo</asp:ListItem>
<asp:ListItem>noam</asp:ListItem>
<asp:ListItem>yossi</asp:ListItem>
</asp:ListBox>
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Server" />
<input id="Button2" type="button" value="xml" onclick="GetHtml()" />
יש לנו handler שיודע להחזיר html לפי פרמטר בשם name - הנה הקוד שלו
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
if (context.Request["name"] == "shlomo")
{
context.Response.Write("name=shlomo<br/>last name=goldberg<br/>age=25");
}
else if (context.Request["name"] == "noam")
{
context.Response.Write("name=noam<br/>last name=king<br/>age=40");
}
else if (context.Request["name"] == "yossi")
{
context.Response.Write("name=yossi<br/>last name=goldberg<br/>age=23");
}
}
כמו שאתם שמים לב - לפי הפרמטר name ה - handler מייצר קוד html ומוסיף את זה ל - Response.
כעת נראה מה קורה כשהמשתמש ילחץ על הלחצן Server
protected void Button1_Click(object sender, EventArgs e)
{
var x = new Data();
x.ProcessRequest(HttpContext.Current);
}
אנחנו מייצרים מופע של ה - Handler (הוא נקרא Data) ומפעילים את מתודת ProcessRequest שכאמור תוסיף ל - Response את ה - html המתאים. (הפרמטר name נשלח ב - Request היות שה - Id של ה - ListBox הוא name)
כמובן שהלחיצה על הלחצן תעשה PostBack מלא - וכל הדף יתרפרש כדי לייבא את הנתונים.
כעת נראה מה יקרה אם נלחץ על ה - xml
function GetHtml() {
var xhr = new XMLHttpRequest();
var data = '?name=' + document.getElementById('name').value;
var url = 'http://localhost:3319/Data.ashx' + data;
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
document.getElementById('mydiv').innerHTML = xhr.responseText;
}
}
xhr.send(null);
}
בשורה הראשונה אנחנו מייצרים מופע של XMLHttpRequest
לאחריו אנחנו מייצרים queryString שנראה כך:
?name=noam
כמובן שהוא לוקח את הערך מתוך מה שהמשתמש בחר.
נגדיר את ה - url ל - handler ונוסיף את ה - query string
נפתח connection ומגדיר שאנחנו שולחים ב - GET ל - url שהגדרנו ואנחנו רוצים לעשות את זה בצורה אסינכרונית (כלומר לא לחכות לתשובה)
נרשם לאירוע שנקרא onreadystatechange ונגדיר שאם ה - state הוא 4 (כלומר קבלנו תשובה) נכתוב ל - div את ה - html שקבלנו.
ונפעיל את הבקשה בעזרת קריאה ל - send.
כמובן שבשיטה הזאת אין PostBack והכל הרבה יותר מהיר וחסכנו בתעבורה.
אחד מהיכלות החדשות שיש לנו ב - IE8 זה ההוספה של prototype ל - DOM.
ב - IE8 הוסיפו ל - DOM את היכולת לשנות ולהוסיף פונקציות בעזרת prototype.
לדוגמא. אני מניח שהרבה פעמים יצא לכל אחד מכם לכתוב פונקציה שנקראת כך:
function HideFunc(obj) {
obj.style.display = 'none';
}
ומן הסתם השתשמתם בפונקציה כדי להסתיר אלמנטים במסך.
שימו לב לקוד הבא:
function HideFunc() {
this.style.display = 'none';
}
function ShowFunc() {
this.style.display = 'block';
}
Element.prototype.Hide = HideFunc;
Element.prototype.Show = ShowFunc;
<input type="button" value="click to hide" onclick="this.Hide()" />
למעשה הוספנו את המתודות Hide ו - Show לכל האלמנטים של ה - DOM - (מאוד מזכיר extension Method)
לקרחאה מלאה אודות היכולות של prototype על ה - DOM תוכלו לקרוא
כאן
בפוסט הזה התרעמתי על הצורה שמיקרוסופט מימשו את ה - PageMethods - כתבתי שהם כתבו harscode שכל הקריאות יהיו async.
כתוצאה מזה שאני חוקר כרגע את
IE8 חקרתי קצת את הנושא של
prototype ב - javascript מצאתי דרך לשנות ולהגדיר קריאות ב - PageMethods שיעכבו את המשך הריצה עד לקבלת תשובה.
לפני שאני אכתוב את הפיתרון נסתכל איך מיקרוסופט מימשו את ה - PageMethods. אם נחפור קצת בקובץ ה - javascript שמגיע עם ה - script manager נראה את הקוד הבא.
function Sys$Net$XMLHttpExecutor$executeRequest() {
// some code...
var verb = this._webRequest.get_httpVerb();
this._xmlHttpRequest.open(verb, this._webRequest.getResolvedUrl(), true);
// some code..
}
בכל פעם שנפעיל מתודה בעזרת ה - Script Manager נגיע למתודה הזאת שבו הם מפעילים את מתודת open שולחים האם זה GET או POST שולחים את הכתובת של המתודה ו - true כדי להגדיר שזה אסינכרוני.
כדי שנוכל להחליט האם אנחנו רוצים את ההפעלה בצורה סינכרונית (למשל ב - CustomValidator) נצטרך לכתוב את הקוד הבא.
XMLHttpRequest.prototype.base_open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
async = false;
var args = Array.prototype.slice.call(arguments);
return this.base_open.apply(this, args);
};
בהתחלה נשמור reference למתודה open של XMLHttpRequest בעזרת prorotype.
לאחר מכן נגדיר מימוש חדש למתודה open (כלומר בכל פעם שיפעילו את המתודה open יגיעו למימוש שלנו)
נוכל להגדיר שהפרמטר async יהיה true או false.
ונפעיל את המימוש הקודם של open.
כעת נוכל לכתוב custom validator ולממש אותו בעזרת PageMethods (ולא נצטרך להגיע ל - jQuery - כמו שכתבתי
כאן)
<asp:Button ID="Button1" runat="server" Text="Button" />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:CustomValidator
ValidateEmptyText="true"
ID="CustomValidator1"
ControlToValidate="TextBox1"
ClientValidationFunction="customValid"
runat="server"
ErrorMessage="CustomValidator">
</asp:CustomValidator>
function customValid(sender, args) {
PageMethods.IsOk(args.Value, function(res) {
args.IsValid = res;
});
}
//Server Code
[WebMethod]
public static bool IsOk(string str)
{
return str == "ok";
}
אני מכין חומר בימים אלו על Internet Explorer 8 - למעשה כבר התחלתי לכתוב על זה
בעבר, וכעת אני מתכוון להמשיך.
בפוסט הנוכחי אני אדבר על אחד מהתכונות הטובות ביותר שיש ב - IE8.
Dom Storage
מפתחי WEB יודעים שיש כמה דרכים לשמור מידע, חלק מהדרכים הם בצד הלקוח וחלק בצד השרת, בדרך כלל גם כששומרים מידע בצד הלקוח השרת מכיר את המידע, הדרך הוותיקה והנפוצה לשמור בצד הלקוח כמובן
עוגיות, עוגיות הם הדרך הטובה ביותר לשמור מידע בצד הלקוח לאורך זמן.
כמובן שגם לעוגיות יש חסרונות, כשהחיסרון הגדול הוא שזה לא קל לגשת אל המידע שנשמר בעוגייה (צריך לשחק עם מחרוזות) וכמובן שכל העוגייות נשלחות לשרת בכל Request (בין אם צריך אותם ובין אם לא).
יש מקרים שבהם אתם יכולים לכתוב קוד לדפדפן מסויים - כמובן שאם אתם כותבים אתר לציבור הרחב (אתרי חדשות וכדו') אתם אמורים לתמוך בהרבה דפדפנים, אבל אם אתם כותבים אתר פנימי (אתר לשימוש עובדי משרד הפנים - לא האתר שמשמש את הציבור הרחב אלא האתר הפנימי של העובדים) אפשר בהחלט לדרוש שישתמשו בדפדפן מסויים. במקרה ואתם יודעים שישתמשו ב - IE8 מן הסתם לא תרצו יותר לשמור מידע בעוגייות (אלא במקרי קצה) ותעדיפו לעבוד אם ה - DOM Stroage - הוא הרבה יותר פשוט לעבודה, ניתן לשמור בו כמויות עצומות של מידע (ביחס לעוגיות) והוא לא נשלח לשרת בכל Request.
(דרך אגב למי שמכיר ה - DOM Storage הוא התחליף ל -
UserData בגרסאות קודמות של IE)
השוואה בין DOM Storage לבין עוגיות
| |
Cookie |
DOM Storage |
| גודל מקסימלי |
4KB (IE8 - 10KB) |
10MB |
| מוכר בשרת |
נשלחים עם כל בקשה |
לא מוכר |
| הרשאות |
לפי דומיין ותיקייה |
לפי דומיין בלבד |
| גישה |
חיפוש על מחרוזות |
בעזרת אובייקטים ומתודות |
| אירועים |
אין |
בזמן שינוי והוספה ובזמן סיום הכתיבה |
| מחיקה |
לפי זמן |
ידנית או בזמן סגירת הדפדפן |
| סוג מידע |
מחרוזות |
מחרוזות מספרים וערכים בוליאניים |
אז איך משתמשים בזה:
למעשה יש שני אובייקטים שונים - הראשון הוא sessionStorage והשני הוא localStorage. ההבדל היחיד ביניהם הוא שה - sessionStorage חי כל עוד שהטאב הנוכחי פתוח, לעומת ה - localStorage שחי כל עוד שלא מחקו אותו.
השימוש הוא ממש פשוט, לדוגמא:
<input type="checkbox" onchange="sessionStorage.insurance = this.checked">
בדוגמא הזאת יצרנו (אם לא היה קיים) משתנה בשם insurance ב - sessionStorage וכעת אפשר בכל מקום לשאול
if (sessionStorage.insurance)
דוגמא נוספת (להצגת מספר הפעמים שמשתמש ביקר בדף מסוים)
<p>
You have viewed this page <span id="count"> an untold number of</span>
time(s).
</p>
<script>
var storage = window.localStorage;
if (!storage.pageLoadCount) {
storage.pageLoadCount = 0;
}
storage.pageLoadCount = parseInt(storage.pageLoadCount, 10) + 1;
document.getElementById('count').innerHTML = storage.pageLoadCount;
</script>
בנוסף לזה יש ל - storage מספר מתודות שימושיות - תוכלו לקרוא עליהם
כאן
מתודה מעניינת היא remainingSpace שמחזירה את גודל המקום הפנוי (בבתים).
בנוסף היכולת לרוץ בקלות בלולאה על כל הערכים בעזרת המתודה key ו - getItem
for (var i = 0; i < window.localStorage.length; i++) {
var key = window.localStorage.key(i);
var data = window.localStorage.getItem(key);
}
יש גם שני אירועים מעניינים שאפשר להירשם אליהם - onstorage כשמשהו משתנה ו - onstoragecommit כשה - localStorage סיים לשמור את עצמו בדיסק (קבצי xml)
נניח שיש לכם User Control שמשתמש בפונקציות שנמצאות בקובץ javascript, צריך כמובן להוסיף הפנייה ב - html לקובץ ה - javascript.
השאלה - איפה לשים את ההפנייה, הכי קל כמובן זה לשים אותו בדף שמשתמש ב - User Control, הבעייה היא כמובן שנצטרך תמיד לזכור בכל דף שבו משתמשים עם ה - User Control להוסיף את ההפנייה לקובץ ה - javascript.
אופצייה שנייה היא להוסיף את ההפנייה בקובץ ה - ascx (כלומר ב - UserControl בעצמו) ואז בכל דף שיהיה בו את ה - User Control הוא יביא איתו את קובץ ה - javascript שזה הרבה יותר הגיוני.
אבל זה גורם לנו לבעייה, אם בדף יהיה יותר ממופע אחד של ה - User Control הלקוח ינסה להוריד פעמיים את קובץ ה - javascript (מכיוון שכל מופע מביא איתו ביחד את הפנייה לקובץ ה - javascript)
הפיתרון שמצאתי הוא - להוסיף את ההפנייה מקוד ולוודא שהוא לא קיים על הדף, נעשה את זה בעזרת הקוד הבא.
if (!Page.ClientScript.IsClientScriptIncludeRegistered("key"))
{
string url = ResolveClientUrl("~/Scripts/file.js");
Page.ClientScript.RegisterClientScriptInclude("key", url);
}
כמו
שהבטחתי הנה הנמשך הסרטון על ולידציות ב - asp.net
בסרטון הנוכחי אני עושה היכרות עם פקדי הולידציות המובנים שקיימים
אפשר להוריד את זה
מכאן או לצפות בבלוג
תהנו
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" ControlToValidate="TextBox1"
ClientValidationFunction="customValid"
runat="server" ErrorMessage="CustomValidator"></asp:CustomValidator>
המתודה להפעלה בזמן ולידצייה (הייתי שמח להשתמש עם
ScriptManager אבל לצערי הם כתבו את הקוד שלהם בצורה מטופשת (קצת) שעושה את הקריאות
תמיד בצורה אסינכרונית וחבל שהם לא נתנו את היכולת להחליט האם להריץ בצורה סינכרונית או אסינכרונית)
function customValid(sender, args) {
$.ajax({
type: 'POST',
url: 'WebService1.asmx/IsOk',
data: { str: args.Value },
async: false,
success: function(result) {
args.IsValid = eval(result.text);
}
});
}
אני מפעיל את המתודה IsOk ב - WebService בצורה סינכרונית (אחרת יהיה לי PostBack עוד לפני שאני אקבל תשובה)
הנה המתודה ב - WebService
[WebMethod]
public bool IsOk(string str)
{
return str == "ok";
}
דוגמת קוד שכתב אחי יוסי גולדברג להעלאת קבצים ל - ftp
string url = "ftpUrl/FileName";
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(url);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential("user name", "password");
request.UsePassive = true;
request.UseBinary = true;
request.KeepAlive = false;
byte[] buffer = File.ReadAllBytes("path");
using (Stream reqStream = request.GetRequestStream())
{
reqStream.Write(buffer, 0, buffer.Length);
}
אם לא רוצים לקרוא את כל הקובץ בבת אחת אפשר כמובן לכתוב ככה
using (Stream reqStream = request.GetRequestStream())
{
int count = 0;
byte[] buffer = new byte[100];
using (FileStream file = new FileStream("FileName", FileMode.Open))
{
while ((count = file.Read(buffer, 0, 100)) > 0)
{
reqStream.Write(buffer, 0, count);
}
}
}
יש כמה דרכים לפתוח חלונות ב - JavaScript ולכל דרך יש פיצ'רים משלה.
בפוסט הזה אני ארכז את הדרכים ואת התכונות לכל אחת מהם.
הנה הרשימה של כל הדרכים ולאחר מכן פירוט התחביר והתכונות של כל אחד מהם.
window.open()
window.navigate()
window.location.href
window.location.assign()
window.location.replace()
window.showModalDialog()
window.showModelessDialog()
var oNewWindow = window.open( [sURL] [, sName] [, sFeatures] [, bReplace]);
הפרמטר sUrl הינו כמובן את מי רוצים לפתוח - הוא אופציונלי ואם הוא לא יקבל ערך יפתח חלון שרואים בו "about:blank"
הפרמטר sName יכול לקבל את השם של החלון (מקביל ל - target של לינק) מה שחשוב זה blank_ שזה ברירת המחדל מה שאומר שזה יפתח בחלון חדש, self_ שאומר שיפתח בחלון הנוכחי, לשאר האופציות אפשר לעיין בלינק לעיל (על window.open).
הפרמטר sFeatures מקבל רשימה של ערכים שקובעים איך יראה החלון (גודל, מסך מלא, מיקום, ועוד) לרשימה המלאה ניתן לעיין בלינק.
הפרמטר bReplace מקבל ערך true אם נרצה שהחלון החדש יתווסף לרשימת ההיסטורייה בדפדפן (כמובן שרק עם החלון נפתח בדף הנוכחי יש משמעות להגדיר true).
מקבל את השם של הדף פותח אותו בעמוד הנוכחי ומוסיף אותו לרשימת ההיסטורייה.
window.location.href = 'sUrl';
אותו התנהגות של navigate
window.location.assign('http://www.google.com');
אותו התנהגות של navigate
window.location.replace('http://www.google.com');
מחליף בעמוד הנוכחי אבל לא מוסיף להיסטורייה.
window.showModalDialog פותח חלון חדש בדיאלוג כלומר עד שהחלון לא יסגר לא יוכלו לעשות כלום בחלון הפותח - מקבל בתחביר הבא
var vReturnValue = window.showModalDialog(sURL [, vArguments] [, sFeatures]);
הפרמטר sUrl חובה - איזה עמוד לפתוח.
הפרמטר vArguments אופציונלי - ארגומנטים לשליחה לעמוד שנפתח כדיאולג, ניתן לגשת לפרמטרים האלו בחלון הנפתח בעזרת window.dialogArguments
הפרמטר sFeatures אופציונלי - משפיע איך יראה החלון (גולד מיקום ועוד) לרשימה המלאה ניתן לעיין בלינק לעיל.
הפרמטר שחוזר מהפונקצייה הוא מה שיושם לתוך המאפיין returnValue בחלון שנפתח כדיאלוג
למשחק עם הפרמטרים השונים ליצירת חלון מודלי. אפשר ללחוץ
כאן
לפיתרון בעיית ה - Post Back בחלון מודלי. אפשר לקרוא
כאן
דומה ל - showModalDialog אבל נותן את האפשרות לחזור לפוקוס לחלונות אחרים כשהוא נשאר מעל כל החלונות.
כדי להציג הודעות למשתמש ב - Javascripr בדרך כלל משתמשים ב - alert ה - syntax הוא
נקבל כתוצאה
אופצייה נוספת היא להשתמש ב - confirm ה - syntax הוא
var res = confirm('meesage ?');
if (res == true) {
}
else {
}
נקבל כתוצאה
במידה שהמשתמש ילחץ על ok המשתנה res יקבל את הערך true.
אופצייה שלישית ומעניינת היא להשתמש ב - promt. ה - syntax הוא
var res = prompt('meesage', 'default result');
נקבל כתוצאה
במידה והמשתמש ילחץ על ok ההשתנה res יקבל את מה שכתוב בתיבת הטקסט במידה והמשתמש ילחץ על cancel המשתנה res יהיה null
ב - Visual Studio 2010 סוף סוף הוסיפו מיקרוסופט את האפשרות של Close page after project load על ה - Start Page.
כדי שזה יקרה גם ב - Visual Studio 2008 תוכלו להוסיף את המאקרו הבא.
תחת Tools -> Macros -> Macros IDE או All + F11
נלחץ פעמיים על MyMacros (זה TreeView - רק שלא רואים את סימן הפלוס)
נכנס ל - EnvironmentEvents
ונוסיף את קטע הקוד הבא
Private Sub SolutionEvents_Opened() Handles SolutionEvents.Opened
If DTE.ActiveWindow.Caption = "Start Page" Then
DTE.ActiveWindow.Close()
End If
End Sub
אחרי שתשמרו ותצאו בכל פעם שתפתחו פרוייקט במידה וה - Start Page פתוח הוא יסגר.
More Posts
Next page »