April 2009 - Posts
כמו שאתם
זוכרים המחשב שלי הלך פייפן, והצלחתי להוציא את כל החומר, אז עד שיהיה לי מחשב חדש, קבלתי מחשב ישן והעברתי אליו את אחד הפרוייקטים שאני עובד עליו,
כשאני מנסה לטעון את הפרויקט, אני מקבל הודעה מוזרה, של system.interopservices.comexception
ניסיתי לדבג עם VS אחר כדי לבדוק מה שקורה, ולא קבלתי שום מידע,
חפישתי קצת בגוגל, עד שמצאתי שמישהו כתב שאם לא מותקן IIS והפרויקט מוגדר לרוץ על IIS זה השגיאה שנקבל,
בדרך כלל אני מעדיף להגדיר את הפרוייקטים שלי לרוץ על IIS ולא על הסביבה של VS, כדי לדמות את המצב האמיתי, כי לא תמיד
ההתנהגות זהה
הבעייה היתה שאיך אני אמור לשנות את ההגדרות של הפרויקט, אם אני לא מצליח לטעון אותו,
מה שעשיתי, היה לפתוח את קובץ ה csproj ב notepad, (בסך הכול קובץ xml) מצאתי קטע שנקרא UseIIS, שיניתי אותו ל false, ושלום על ישראל.
חבל רק שמייקרוסופט לא יודעים לתת שגיאה נורמלית, כי כשניסיתי לשנות את הפרוייקט ל IIS, אחרי שהוא נטען, קבלתי הודעת שגיאה נורמלית שאומרת לי שאין לי IIS,
אני חושב שהיה אפשר לממש, שבמידה ולא מותקן IIS, לשנות את ההגדרה, ולתת הודעה מתאימה,
אולי זה יהיה ב 2010.
למי עדיין לא קרה שהמחשב מחליט בצורה פתאומית ולא ברורה, שאין לך יותר מערכת הפעלה, וכמובן אין לך גיבוי לשום דבר.
לי אין גיבוי לכלום, כרגע איש ה system הנהדר מ
סלע מציל לי את החומר, (ואת שלוש השנים האחרונות) אין על אנשי system, הם תמיד במקום הנכון.
אחרי השיחזור מצפה לי יום נחמד של התקנות.
ואני מתחייב שדבר ראשון יהיה להשיג דיסק קשיח חיצוני לגיבוי החומר.
לפני תקופה הייתי צריך לפתוח את חלון בחירת connection מקוד,
חפרתי באינטרנט ובסוף מצאתי את האתר
הזה שמסביר איך לעשות את זה.
בגדול צריך להוסיף refernce ל:
Microsoft.Data.ConnectionUI.Dialog
מתוך C:\Program Files\Microsoft Visual Studio 8\Common7\IDE (לגרסת 2005)
או מתוך C:\Program Files\Microsoft Visual Studio 9\Common7\IDE (לגרסת 2008)
ואז לכתוב את הקוד הבא:
DataConnectionDialog objDataConnectionDialog = new DataConnectionDialog();
DataSource.AddStandardDataSources(objDataConnectionDialog);
DataConnectionDialog.Show(objDataConnectionDialog);
ה dll מכיל את כל מה שנדרש כדי לעבוד עם ה dialogs של vs שעובדים מול Data Source.
אני משתמש הרבה עם Microsoft Outlook Web Access, אחד הדברים שמעצבנים, שהוא לא יודע לשמור שם משתמש וסיסמא ב Cookie,
יש תוכנות שעושת את זה, אבל אין לי זמן לחפש.
העליתי את הסקריפט הבא:
<SCRIPT LANGUAGE = "JavaScript">
var oWindow = window.external.menuArguments;
if (oWindow.location.host == "doar.sela.co.il") {
var oDocument = oWindow.document;
var un = oDocument.getElementById('username');
var pas = oDocument.getElementById('password');
var btn = oDocument.getElementById('SubmitCreds');
if(un != null && pas != null && btn != null)
{
un.value = 'UserName';
pas.value = 'Password';
btn.click();
}
}
</SCRIPT>
ועכשיו כשאני נכנס לאתר, אני לוחץ קליק ימין, לוחץ על LoginSela, ונכנס בלי צורך להקליד.
רציתי לכתוב UnitTest ל Dal שלי,
אבל ה Dal היה מוגדר כ internal, מה שגרם לכך שאני לא יכול לכתוב עבורו UnitTest, כי הם מכירים רק את ה public.
הפתרון ממש נחמד, attribute שלא הכרתי בשם InternalsVisibleTo שנותן את האפשרות שגם ה internals יוכרו ב asembly אחר.
לדוגמא:
[assembly: InternalsVisibleTo("Test.TestDal")]
יגרום לכך, שה TestDal יכיר את ה internals,
בהמשך ל
פוסט הקודם שכתבתי על איך להביא נתונים מטבלה שממופה לאוביקט (אחד לאחד - עבור כל עמודה יש מאפיין עם אותו שם).
אז כמו שכתב
רותם, ברגע שזה לא בדיוק אחד לאחד, זה לא יעבוד.
אז הנה הגירסא המורחבת של הפיתרון, (אפשר להוריד אותו
מכאן.) (אולי כדאי להעלות אותו גם ל Code Plex, מה אתם אומרים ?)
וכמובן על ידי attribute,
ואם כבר הרחבתי את הפיתרון, אז שיניתי את המתודה ל Extension.
הנה הקוד של ה attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class MapAttribute : Attribute
{
public string DatabaseColumn { get; set; }
public bool UseMap { get; set; }
public MapAttribute()
{
UseMap = true;
}
}
יש לנו שני מאפיינים,
הראשון מחזיק את שם העמודה ב DB,
השני אומר האם להשתמש בעמודה הזאת (כי יתכן שיש לנו מאפיינים שהערך לא מגיע מה DB)
כך נראה האובייקט שלי:
public class Courses
{
public int Id { get; set; }
public string Name { get; set; }
[Map(DatabaseColumn = "You")]
public bool? IsActive { get; set; }
[Map(UseMap = false)]
public double Age { get; set; }
}
לא חייבים להשתמש ב attribute,
במידה ואנחנו לא רוצים שעמודה מסויימת תמופה, או שאנחנו רוצים למפות עם שם אחר
נוכל להשתמש עם ה attribute.
והנה הקוד המעודכן של המתודה: (שימו לב זה הפך ל Extension)
public static object Build(this DbDataReader reader, Type type)
{
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
if (ctor == null)
{
throw new Exception(@"To use reader helper,
your class must have a
parameterless constructor");
}
object newObject = ctor.Invoke(null);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
string readerPropName = prop.Name;
object[] maps = prop.GetCustomAttributes(typeof(MapAttribute), false);
if (maps.Length == 1)
{
MapAttribute map = (MapAttribute)maps[0];
if (!map.UseMap)
{
continue;
}
if (!string.IsNullOrEmpty(map.DatabaseColumn))
{
readerPropName = map.DatabaseColumn;
}
}
object value = reader[readerPropName];
if (value is DBNull)
{
prop.SetValue(newObject, null, null);
}
else
{
prop.SetValue(newObject, value, null);
}
}
return newObject;
}
וההסבר:
נקבל את ה ctro,
נבדוק שיש אחד כזה ללא פרמטרים.
נייצר מופע שלו.
ונרוץ בלולאה על כל המאפיינים, עד כאן כמו בגירסה הקודמת.
נבדוק האם יש attribute מסוג MapAttribute
במידה וכן, והמאפיין UseMap הוגדר כ false, נתעלם מהמאפיין הזה
במידה ויש ערך במאפיין DatabaseColumn, נקח את ערכו.
וכעת זה ממשיך כמו בגירסה הקודמת, לוקח את הערך ומשים אותו בתוך המאפיין.
וכך נראה הקוד שמשתמש בו:
while (reader.Read())
{
reqs.Add((Courses)reader.Build(typeof(Courses)));
}
הרבה פעמים יש לי טבלה ב DB ובמקביל יש לי אובייקט שממופה אחד לאחד לטבלה,
ואז כשאני רוצה להביא את הנתונים, אני כל פעם צריך לכתוב את הקוד של בניית האובייקט מתוך ה reader,
אז כדי להקל עלי את העבודה, כתבתי את המתודה הבאה:
public static object Build(DbDataReader reader, Type type)
{
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
if (ctor == null)
{
throw new Exception(@"To use reader helper,
your class must have
a less parameter constructor");
}
object newObject = ctor.Invoke(null);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
object value = reader[prop.Name];
if (value is DBNull)
{
prop.SetValue(newObject, null, null);
}
else
{
prop.SetValue(newObject, value, null);
}
}
return newObject;
}
ועכשיו הקוד שלי נראה כך:
while (reader.Read())
{
reqs.Add((Courses)Builder.Build(reader, typeof(Courses)));
}
הסבר הקוד הוא פשוט.
המתודה מקבל reader ו type של אובייקט, ומחזירה מופע של ה type.
התנאי היחיד הוא שיהיה ל type בנאי ללא פרמטרים.
אחרי שיש לי את הבנאי, אני מייצר אובייקט.
ורץ על כל המאפיינים, וממלא אותם מתוך ה reader,
פשוט נחמד וחוסך עבודה.
קבלתי את הדרישה הבאה:
יש לנו master page שיש בו לחצן בשם back,
ברוב הדפים הוא אמור לקחת דף אחד אחורה,
בחלק מהדפים, הלחיצה עליו צריכה להפנות לדף בשם main.aspx,
ובדף מסוים שנפתח כדיאלוג, זה צריך לסגור אותו.
וכמובן שאם נרצה בעתיד להוסיף לדפים חדשים פעולות אחרות עבור הלחצן, שזה יהיה קל.
אז
פיני, העלה רעיון מבריק. ומימש אותו בצורה הבאה:
שלב ראשון:
נגדיר enum עבור הפעולות האפשריות, מעל כל אפשרות, נוסיף attribute, שיכלול את הסקריפט,
הנה הקוד:
public class StringValueAttribute : Attribute
{
private string m_Value;
public StringValueAttribute(string value)
{
m_Value = value;
}
public string Value
{
get { return m_Value; }
}
}
public enum ScriptActions
{
[StringValue("window.close()")]
WindowClose = 1,
[StringValue("history.go(-1)")]
GoBack = 2,
[StringValue("window.location.assign('tasklist.aspx')")]
GoToTaskList = 3,
[StringValue("window.location.assign('Main.aspx')")]
GoToMainPage = 4
}
שלב שני:
נכתוב מתודה שיודעת לאחזר מתוך instacne של ה enum את הערך של ה attribute
public class ScriptActionsRetrevier
{
private ScriptActionsRetrevier()
{
}
public static string GetStringValue(ScriptActions value)
{
string sRetVal = string.Empty;
Type type = typeof(ScriptActions);
//Look for our 'StringValueAttribute' in the field's custom attributes
FieldInfo oFieldInfo = type.GetField(value.ToString());
StringValueAttribute[] oStringValueAttributeARR =
oFieldInfo.GetCustomAttributes(typeof(StringValueAttribute),
false) as StringValueAttribute[];
if (oStringValueAttributeARR.Length > 0)
{
sRetVal = oStringValueAttributeARR[0].Value;
}
return sRetVal;
}
}
שלב שלישי:
כל הדפים שלנו יורשים מ PageBase, נוסיף שם את הקוד הבא:
public class PageBase : Page
{
public virtual ScriptActions ScriptToRunOnBackButton
{
get
{
return ScriptActions.GoBack;
}
}
}
בכל דף שבו נרצה לשנות את אופציית ברירת המחדל (GoBack) נוכל לדרוס את המאפיין,
לדוגמא:
public partial class WebForm2 : PageBase
{
public override ScriptActions ScriptToRunOnBackButton
{
get
{
return ScriptActions.WindowClose;
}
}
}
כעת ב master page נכתוב את הקוד הבא:
protected void Page_Load(object sender, EventArgs e)
{
PageBase pageBase = this.Page as PageBase;
if (pageBase != null)
{
string script = ScriptActionsRetrevier.GetStringValue
(pageBase.ScriptToRunOnBackButton);
btnBack.Attributes.Add("onclick", script);
}
}
כעת, כל דף שיעלה, יקבל סקריפט עבור הלחצן Back, וכמובן הוא יכול לשנות את המימוש.
במידה ובעתיד נרצה להוסיף מימוש לדף מסוים, נוסיף ל enum ערך חדש, ובדף שנרצה, נדרוס את המאפיין.
תוכלו להוריד
מכאן פרויקט דוגמא.
כתבתי בעבר פעמיים תוספות ל Context Menu של Internet Explorer
בכל מקרה הבנתי שבכל פעם שאני רוצה להוסיף או לערוך ב Context Menu, זה סיפור ארוך.
ולכן כתבתי תוכנה קטנה, שתעזור לי לנהל את ה Context Menu,
ואם כבר כתבתי אז העליתי את זה ל
Code Plex, (פרויקט ראשון שלי) תוכלו להוריד את זה
מכאן.
הנה התמונה של המסך הראשי באפליקציה.