DCSIMG
December 2009 - Posts - שלמה גולדברג (הרב דוטנט) - net.rabbi@gmail.com

שלמה גולדברג (הרב דוטנט) - net.rabbi@gmail.com

מרצה בסלע ויועץ בעולם ה - net.

December 2009 - Posts

Redirect to login page in Session_End event

 

טוב, אי אפשר באמת לעשות את מה שכתבתי בכותרת, מכיוון שהשרת מפעיל את האירוע של Session_End והחוק הידוע של http, שחייב להיות Request כדי שיחזור Response.
 
אבל אם אתם בכל זאת רוצים שכשנגמר ה - Seesion במידה והדפדפן עדיין פתוח שזה יעבור בצורה אוטומטית לדף הלוגין
תוכלו לכתוב את הקוד הזה: (במתודת Page_Load - וממולץ שיהיה לכם דף PageBase שכל הדפים יורשים ממנו ושם תכתבו את הקוד הזה)
 

Response.AddHeader("Refresh", Convert.ToString((Session.Timeout * 60) + 5));

 

if (Session.IsNewSession)

{

    Response.Redirect("Login.aspx");

}

 
 
השורה הראשונה תגרום לריענון של הדף חמש שניות אחרי שה - Seesion_End קרה.
 
במידה ומדובר בגישה של ראשונה של ה - Session הזה (מה שיהיה נכון) נעבור לדף הלוגין.
 
 
הרעיון והמימוש הגיע מכאן.

SDP - החדש והעתיד

 

אני מניח ששמעתם או קראתם על הכנס שסלע מארגנת - Sela Developer Practice - זה הולך להיות משהו ממש מרתק (לדעתי) אני לא זוכר מתי התכנסו עשרים וחמשה מבכירי המרצים של סלע למקום אחד כדי לדבר על הטכנולוגיות.
 
מי שמעוניין לשמוע על מה חדש בשפות (C# 4.0, C++0X, Visual Studio 2010)
 
על תמיכה של שפות מודרניות לכתיבת אפלקיציות מקביליות.
 
ומה יהיה בעתיד (C# 5.0 ?)
 
 
מוזמן לבוא להרצאה שאלון פליס ואנוכי נעביר בסוף היום השלישי (29.12)
 
לפרטים נוספים על ההרצאה.
 
להרשמה
Posted: Dec 23 2009, 10:48 AM by Shlomo | with 1 comment(s)
תגים:, ,

הפעלת כמה מתודות ב - onload של java script

 

נתקלתי בבעייה הבאה
 
ב - Master Page כתבתי את הקוד הבא:
 

<body onload="a()">

 

function a() {

    alert("a");

}

 
 
באחד מהדפים שמשתמש ב - Mater Page כתבתי את הדבר הבא:
 

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">

 

    <script>

        onload = function b() {

            alert("b");

        }

    </script>

 

</asp:Content>

 
 
הבעייה היא שהמתודה b לעולם לא תופעל מכיון שההגדרה של ה - body onload דורסת את ההגדרה הקודמת.
 
שאלתי את פיני והוא הפנה אותי לכאן. מה שהם מציעים זה את הדבר הבא:
 
נכתוב את המתודה הזאת:

function addLoadEvent(func) {

    var oldonload = window.onload;

    if (typeof window.onload != 'function') {

        window.onload = func;

    }

    else {

        window.onload = function() {

            if (oldonload) {

                oldonload();

            }

            func();

        }

    }

}

 
כעת במקום להרשם ל - onload תמיד נקרא למתודה הזאת
 
ב - Master נכתוב
 

function a() {

    alert("a");

}

 

addLoadEvent(a);

 
ובדף נכתוב
 

function b() {

    alert("b");

}

addLoadEvent(b);

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

ICallbackEventHandler Example

 

בתפוז דברו על ICallbackEventHandler, חשבתי לכתוב דוגמא קטנה.
 
בעזרת ה - interface הזה אנחנו יכולים להפעיל מתודות בצד השרת ללא PostBack, לדוגמא UpdatePanel מממש את ה - Interface.
 
 
נניח שיש לנו UserControl שיש בו לחצן html שבלחיצה אנחנו רוצים להפעיל מתודה בצד השרת ללא PostBack
 
 

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs"

    Inherits="WebApplication48.WebUserControl1" %>

 

<input type="button" id='btn' runat="server" value='Click' />

<input type="text" id='txt' />

 

<script type="text/javascript">

 

    function MyFunction(response, context) {

        alert(response);

    }

 

</script>

 
 
בצד השרת (ב - User Control) נעשה override ל - PreRender ונכתוב:
 

protected override void OnPreRender(EventArgs e)

{

    string callBackStr = Page.ClientScript.

                                GetCallbackEventReference(

                                            this,

                                            "document.getElementById('txt').value",

                                            "MyFunction",

                                            null);

 

    btn.Attributes["onclick"] = callBackStr;

 

    base.OnPreRender(e);

}

 
הפרמטר הראשון הוא this מכיון שה - User Control מממש את ICallbackEventHandler
הפרמטר השני הוא עבור ה - eventArgs - ונשלח את מה שיש בתיבת הטקסט.
הפרמטר השלישי הוא מתודת ה - JS שנפעיל בסיום הריצה לשרת
 
למעשה נקבל מחרוזת כזאת:
 

WebForm_DoCallback('WebUserControl11',

                   document.getElementById('txt').value,

                   MyFunction,

                   null,

                   null,

                   false)

 
 
את כל המחרוזת אנחנו שמים על onclick של הלחצן.
 
מה שנשאר זה לראות את הקוד של המתודות שאנחנו מממשים בגלל ה - ICallbackEventHandler
 

#region ICallbackEventHandler Members

 

public string GetCallbackResult()

{

    return string.Format("return {0}", Session["eventArgument"]);

}

 

public void RaiseCallbackEvent(string eventArgument)

{

    Session["eventArgument"] = eventArgument;

}

 

#endregion

 
 
המתודה RaiseCallbackEvent מופעלת ראשונה כשב - eventArgument יש את הערך שיש בתיבת הטקסט
בדוגמא אני שומר את הערך ב - Session.
והמתודה GetCallbackResult אמורה להחזיר מחרוזת כלשהי, שנכנסת לפרמטר response בפונקצייה myFunction
 
 
וכשנלחץ על הלחצן - נקבל alert עם המחרוזת return + הטקסט בתיבת הטקסט 

הוספת קובץ CSS ל - User Control ול - Master Page

 

כשעובדים עם Master Page או עם User Control - אחד מהבעיות הנפוצות הוא כיצד לקשר אליו קובץ CSS, הרי בכל פעם הנתיב משתנה מכיון שה - User Control משתלב בתוך הדף, והדף יכול להיות כל פעם מקונן בתוך תיקיות אחרות.
 
אפשר לעשות את זה מקוד, אבל אני לא אוהב את זה (אלא אם כן אתם נוהגים לעשות את זה תמיד ובמקום קבוע, כך שלא צריך לחפש בכל פעם מי הוא קובץ ה - CSS.
 
ב - User Controls אפשר לכתוב משהו כזה
 

<link rel="stylesheet" type="text/css" href='<%=ResolveClientUrl("~/CSS") %>/Header.css' />

 
ב - Master Page לעומת זאת חייבים לכתוב את זה בתוך ה - body ולא ב - head.
Posted: Dec 21 2009, 10:16 PM by Shlomo | with 1 comment(s)
תגים:, , ,

Session Manager

 

אחד מהדברים שאני לא אוהב בעבודה עם Session זה העובדה שאחנו עובדים איתו עם מחרוזות.
בפרוייקטים בינוניים פתאום מתחילים לשכוח עם איזה שם השתמשנו והאם השתמשנו בשם מסויים, והעניינים מתחילים להסתבך.
 
ידיד יקר שי קרן כתב class בשם SessionManager שפותר את הבעיות האלו, מכיוון שהשימוש ב - Session הוא בעזרת enum.
 
אפשר להוריד את הקוד מכאן (אני משתמש בו כבר שנתיים בכל הפרוייקטים שלי)
 
 
זה עובד בצורה הבאה. (אני מציג כאן רק חלק מהקוד - הקוד המלא ניתן להורדה מהלינק למעלה)
 
יש enum עם כל השמות שנרצה להשתמש ב - Session (בכל פעם שנרצה להוסיך משהו חדש - נוסיף תחילה ל - enum)
 

public enum SessionParam

{

    User,

    Entities,

    LastException,

    Info

}

 
המחלקה SessionManager ממומשת עם Singelton
 

public sealed class SessionManager

{

    public static SessionManager Session

    {

        get

        {

            if (m_Instance == null)

            {

                lock (m_ObjLocker)

                {

                    if (m_Instance == null)

                    {

                        m_Instance = new SessionManager();

                    }

                }

            }

            return m_Instance;

        }

    }

 
 
בנוסף יש לו שני indexers
 

public object this[SessionParam sessionParam]

public object this[string name]

 
יש גם עם string עבור מצבים שאין ברירה אלא לעבוד עם מחרוזת ואי אפשר להגדיר מראש.
 
 
כעת העבודה מול ה - Session תהיה כך
 
 

SessionManager.Session[SessionParam.LoginUser] = new User();

 
 
מה שיגרום לכך - שנוכל לעשות Find All Reference על מופע של enum ולמצוא איפה משתמשים בזה מבלי לעשות חיפושים על מחרוזות.
 
תהנו

ה - debugger מתעלם מ - F10 וממשיך כאילו F5 נלחץ - למה ?

 

יצא לי לעבוד בפרוייקט מסויים שכל הזמן ה - debugger היה מתנהג כאילו לחצו על F5, וזה היה בלתי אפשרי לדבג.
 
לא ממש הבנתי מה הבעייה, אבל ההתקנה הזאת פתרה את הנושא.
Posted: Dec 20 2009, 02:00 PM by Shlomo | with 1 comment(s)
תגים:,

גישה ל - Session מתוך handler

 

אם אתם יוצרים handler כלשהו (ashx) ואתם רוצים להגיע ל - session בעזרת contex.Session, תווכחו לדעת שזה null, כדי שתוכלו לגשת אתם צריכים אחד מהשניים,
 
אם אתם צריכים רק לקרוא תוסיפו את ה - interface הבא IReadOnlySessionState
 
ואם אתם רוצים גם לכתוב תוסיפו את ה - interface הבר IRequiresSessionState
 
 
שני ה - interface הם marked interface כלומר ללא שום מימוש (יותר מתאים שהם היו attributes)
 
 
הקרדיט מגיע ל - 15Secounds.com בפוסט הבא HTTP Handlers and HTTP Modules in ASP.NET (מומלץ לקריאה)

The Timeouts Bible

 

יש לי חבר יקר אלכס אבוגוב אחד מהאנשים שאני מאוד מעריך מקצועית ואישית. כתב את ה - Timeouts Bible - מסמך המתאר הגדרות Timeout להרבה דברים.
 
אני מעתיק לכאן את תוכן העניינים, תוכלו להוריד את הקובץ כדי לצפות במידע.
 
  • Database Timeouts
    • Transaction
      • Transaction Maximum Timeout
      • Transaction Timeout
      • Changing Transaction Timeout and Transaction Maximum Timeout
      • Distributed Transaction Coordinator
    • Connection
      • SQL Server Connection Timeout
      • Oracle Connection Timeout and Connection Lifetime (ODP.NET)
    • Command
      • SQL Server Command Timeout
      • Oracle Command Timeout (ODP.NET)
      • SQL CE Command Timeout
      • Changing Command Timeout
  • WCF Timeouts
    • Binding
      • Send Timeout
      • Receive Timeout
      • Open Timeout
      • Reliable Session Inactivity Timeout
    • Channel
      • Operation Timeout
      • Close Timeout
      • Check Connection Timeout
Posted: Dec 19 2009, 10:20 PM by Shlomo | with 3 comment(s)
תגים:,

שיעור בסיסי על ADO

 

 ניתן להוריד את דוגמת הקוד מכאן.
 
בפוסט הזה אני רוצה לדבר על ADO, אנחנו נבין את המושגים הבאים:
 
  • SqlConnection
  • SqlCommand
  • SqlParameter
  • SqlDataReader
  • ExecuteNonQuery
  • ExecuteScalar
  • SqlTransaction
  • TransactionScope
 
 
המטרה של הפוסט היא - שמי שחדש בתחום ידע להתחיל לעבוד מול בסיסי נתונים בצורה הבסיסית ביותר - אני יוצר מתוך הנחה שהקורא מכיר SQL.
 
נתחיל.
 
בבסיס הנתונים הכי ידוע בעולם Northwind יש כמה טבלאות - בדוגמא נעבוד עם טבלת Categroies ו - Products.
 
יש כמה שיטות לעבוד עם נתונים, אבל כאמור אנחנו מדברים כרגע על ADO הפשוט, למעשה גם בו יש שני שיטות אחת זה עבודה עם DataSets והשנייה שאותה נדגים זה עבודה עם אובייקטים שנייצר אותם בעצמנו בעזרת ADO.
 
אז נניח שיש לנו את האובייקטים הבאים
 

public enum State

{

    Database,

    Update,

    Delete,

    Insert

}

 

public class ItemBase

{

    public int Id { get; set; }

    public State CurrentState { get; set; }

}

 
 
האובייקט ItemBase הינו האבא של כל האובייקטים שלנו - הוא מכיל Id (מכיוון שלכל טבלה מן הסתם יש Id) ומכיל State, בעזרת ה - State נוכל יותר מאוחר להחליט איזה פעולות לעשות על האובייקט (וזה אחד מהחסרונות הכי גדולים של עבודה בשיטה הזאת - שצריך לנהל לבד את המצב של האובייקטים השונים כדי לדעת איזה שאילתות להריץ מול בסיס הנתונים)
 
בנוסף יש לנו את האובייקטים:
 

public class Product : ItemBase

{

    public string ProductName { get; set; }

}

 

public class Categrory : ItemBase

{

    public string CategoryName { get; set; }

    public string Description { get; set; }

    public List<Product> Products { get; set; }

 

    public Categrory()

    {

        Products = new List<Product>();

    }

}

 
 
הנה ה - GUI שלנו.
 
ADO
 
נתחיל עם הלחיצה על Bind Data - אננו רוצים ללכת לבסיס הנתונים להביא את כל האובייקטים ולהציג אותם בגריד.
 
כעת אנחנו צריכים להכיר כמה מושגים.
 
הראשון הוא - SqlConnection. נניח שאנחנו יודעים מה אנחנו רוצים לשאול את בסיס הנתונים ואיזה מידע בדיוק אנחנו רוצים לשלוף - אנחנו צריכים להגדיר איפה יושב אותו בסיס נתונים, בלי המידע הזה לא נוכל לשלוח את השאילתא לבסיס הנתונים - זה בדיוק כמו לשלוח פיצה בלי לספק לשליח את הכתובת היכן הפיצה צריכה להגיע, בעזרת SqlConnection נוכל להגדיר היכן השאילתות שלנו נשלחות.
 
נכתוב את הקוד הבא:
 
 

private SqlConnection _connection;

private SqlConnection GetConnection()

{

    if (_connection == null)

    {

        _connection = new SqlConnection();

        _connection.ConnectionString = "server=.;initial catalog=northwind;integrated security=true";

    }

 

    return _connection;

}

 
הבעייה היחידה במה שכתבנו - מה יקרה כשניתן את האפליקציה ללקוח - והכתובת או השם של בסיס הנתונים תשתנה, ולכן אנחנו צריכים להוציא את ההגדרה של הכתובת עצמה לקובץ קונפיגורצייה,
 
נוסיף לפרוייקט קובץ מסוג Application Configuration File שזה (app.config) ונוסיף שם את הקוד הבא:
 

<connectionStrings>

  <add name="northwindDB" connectionString="server=.;initial catalog=northwind;integrated security=true"/>

</connectionStrings>

 
כעת נוסיף לפרוייקט שלנו reference בשם system.configuration ונשנה בקוד לשורה הבאה:
 

_connection.ConnectionString = ConfigurationManager.ConnectionStrings["northwindDB"].ConnectionString;

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

private SqlCommand GetCategoryCommand()

{

    SqlCommand categoryCommand = new SqlCommand();

    categoryCommand.Connection = GetConnection();

    categoryCommand.CommandText = "SELECT CategoryID, CategoryName, Description FROM Categories";

 

    return categoryCommand;

}

 
אנחנו יוצרים אובייקט מסוג SqlCommand ומגדירים לו מי ה - Connection שלו ומה השאילתא.
 
בנוסף אנחנו יודעים שבתהליך בנייה של Category נצטרך לייבא גם את כל ה - Products שלו ולכן נכתוב את הפונקצייה הבאה
 

private SqlCommand GetProductCommand()

{

    SqlCommand productCommand = new SqlCommand();

    productCommand.Connection = GetConnection();

    productCommand.CommandText = "SELECT ProductID, ProductName FROM Products

                                  WHERE CategoryID = @CategoryID";

 

    productCommand.Parameters.Add("CategoryID", SqlDbType.Int);

 

    return productCommand;

}

 
אנחנו נפעיל את השאילתא בכל פעם עבור Category אחר, ולכן נצטרך לספק אילו רשומות לאחזר - נשתמש במשהו שנקרא Parameters, אנחנו מגדירים בשאילתא פרמטרים בעזרת הסימון @ ולאחר מכן אנחנו מוסיפים לאובייקט ה - Command את הפרמטרים בעזרת השורה הבאה

productCommand.Parameters.Add("CategoryID", SqlDbType.Int);

 
 
כעת נראה את הקוד בלחיצה על הלחצן BindData
 

private List<Categrory> _categories;

 

private void btnBind_Click(object sender, EventArgs e)

{

    _categories = new List<Categrory>();

    SqlCommand categoryCommand = GetCategoryCommand();

 

    try

    {

        categoryCommand.Connection.Open();

 

        SqlDataReader reader = categoryCommand.ExecuteReader();

 

        while (reader.Read())

        {

            Categrory category = GetCategoryFromReader(reader);

            AddProductsToCategory(category);

            _categories.Add(category);

        }

 

        dgvCategories.DataSource = _categories;

    }

    finally

    {

        categoryCommand.Connection.Close();

    }

}

 
אנחנו מייצרים את ה - list של ה - categories.
 
מקבלים את ה - Command עבור ה - Category
 
פותחים את ה - Connection כדי שנוכל להתחיל לשלוח לבסיס הנתונים שאילתות ולקבל תשובות, כשהקוד עטוף בבלוק של try finally כשב - finally אנחנו סוגרים את ה - Connection בכל מקרה גם אם קרה שגיאה בזמן קריאת הנתונים, אנחנו עדיין נסגור את ה - Connection.
 
מקבלים אובייקט מסוג SqlDataReader שהוא אובייקט שבעזרתו נוכל לקרוא את הנתונים שחוזרים מהשאילתא, ואנחנו מקבלים את זה בעזרת קריאה לפונקצייה ExecuteReader של ה - Command.
אנחנו תמיד נשתמש ב - ExeuteReader כשאנחנו מפעילים שאילתת Select ואנחנו יודעים שחוזר יותר מערך בודד (ערך לא בודד הכוונה לכמה עמודות או לכמה שורות).
 
נקרא לפונקצייה Read של ה - raeder בלולאה, הפונקצייה עושה שני דברים.
1. בודקת האם יש שורה לקרוא.
2. במידה וכן היא מחזירה true
 
כל עוד שאנחנו מקבלים true אנחנו מבצעים את הקוד הבא.
יוצרים אובייקט מסוג Category בעזרת ה - reader.
ממלאים את ה - Products שלו.
ומוסיפים את ה - Category ל - ל - categoris_.
 
בסוף הלולאה אנחנו מקשרים את הגריד ל - categoris_
 
 
נראה את שני הפונקציות שקראנו מתוך הלולאה. הראשונה GetCategoryFromReader
 

private static Categrory GetCategoryFromReader(SqlDataReader reader)

{

    Categrory category = new Categrory()

    {

        CategoryName = reader["CategoryName"].ToString(),

        CurrentState = State.Database,

        Description = reader["Description"].ToString(),

        Id = (int)reader["CategoryID"],

        Products = new List<Product>()

    };

    return category;

}

 
 
והשנייה AddProductsToCategory
 

private void AddProductsToCategory(Categrory category)

{

    SqlCommand productCommand = GetProductCommand();

    productCommand.Parameters["CategoryID"].Value = category.Id;

    SqlDataReader reader = productCommand.ExecuteReader();

 

    while (reader.Read())

    {

        Product product = GetProductFromReader(reader);

        category.Products.Add(product);

    }

}

 
מאוד דומה לבניית Category.
מקבלים את ה - Command המתאים.
נותנים ערך לפרמטר CategoryID.
ובעזרת ה - SqlDataReader אנחנו מייצרים אובייקט Product.
 
המתודה GetProductFromReader
 

private static Product GetProductFromReader(SqlDataReader reader)

{

    Product product = new Product()

    {

        CurrentState = State.Database,

        Id = (int)reader["ProductID"],

        ProductName = reader["ProductName"].ToString()

    };

    return product;

}

 
כשנריץ את הקוד כמו שהוא, נקבל את השגיאה הבאה:
 
There is already an open DataReader associated with this Command which must be closed first.
 
דברתי על זה כאן, ומה שזה אומר - שאי אפשר להפעיל reader כשאנחנו נמצאים בתוך reader אחר, כלומר שעבור כל reader נצטרך לפתוח connection לבד (אנחנו מנסים לפתוח reader עבור ה - prodcts כשאנחנו באמצע בניית ה - categroy).
במידה ואתם עובדים עם NET 2.0 ומעלה ובסיס הנתונים הוא 2005, אפשר להוסיף ל - ConnectionString (שנמצא בקובץ הקונפיג) את ההוראה הבאה:

MultipleActiveResultSets=true

 
מה שאומר - שמותר לפתוח reader בתוך reader.
 
אחרי הלחיצה על הלחצן ה - GUI שלנו יראה כך:
Ado
 
אנחנו רוצים להציג תמיד את ה - Products לפי ה - Categry שנבחר בגדריד העליון.
 
נגדיר לגריד העליון את שני המאפיינים הבאים:
MultiSelect=False
SelectionMode=FullRowSelect
 
בנוסף נרשם לארוע SelectionChanged של הגריד - ונרשום את הקוד הבא.
 

private void dgvCategories_SelectionChanged(object sender, EventArgs e)

{

    if (dgvCategories.SelectedRows.Count == 1)

    {

        Categrory selectedCategory =

                  (Categrory)dgvCategories.SelectedRows[0].DataBoundItem;

 

        dgvProducts.DataSource = selectedCategory.Products;

    }

}

 
אנחנו מוציאים מתוך השורה שנבחרה את ה - DataBoundItem שאנחנו יודעים שזה אובייקט מסוג Category מכיוון שכל הגריד הוא bound לאוסף של Categories.
 
 
 
עד עכשיו עברנו על ארבעת הנושאים הראשונים, דברנו על Connection, Command, Parameter, DataReader.
 
כעת אנחנו רוצים לעבור על ExecuteNonQuery ו - ExecuteScalar.
 
נתחיל במימוש עבור הוספת Category, נניח שהמשתמש כתב שם ותיאור עבור קטגורייה חדשה ולחץ על Insert.
נראה את הקוד
 

private void btnInsert_Click(object sender, EventArgs e)

{

    Categrory category = new Categrory()

    {

        CategoryName = txtName.Text,

        Description = txtDescription.Text,

        CurrentState = State.Insert

    };

 

    _categories.Add(category);

 

    dgvCategories.DataSource = null;

    dgvCategories.DataSource = _categories;

}

 
מייצרים אובייקט מסוג Category כשאנחנו מגדירים את ה - State שלו כ - Insert (כדי שנדע איזה פעולות לעשות עליו).
מוספים את החדש ל - categories_
ומקשרים את הגריד מחדש.
 
 
כעת נראה את הקוד עבור DeleteSelected.
 

private void btnDelete_Click(object sender, EventArgs e)

{

    if (dgvCategories.SelectedRows.Count == 1)

    {

        Categrory category = (Categrory)dgvCategories.SelectedRows[0].DataBoundItem;

        if (category.CurrentState == State.Insert)

        {

            _categories.Remove(category);

        }

        else

        {

            category.CurrentState = State.Delete;

        }

    }

 

    dgvCategories.DataSource = null;

    dgvCategories.DataSource = _categories;

}

 
מוציאים את ה - Category שאנחנו עובדים עליו.
במידה ומדובר ב - Category חדש, אנחנו יכולים פשוט להסיר אותו מהרשימה,
במידה ומדובר ב - Category שהגיע מבסיס הנתונים, אנחנו מסמנים אותו כ - Delete, כדי שבלחיצה SaveToDB, נפעיל עליו שאילתת Delete.
 
 
נשאר לנו רק Update.
נרשם לאירוע CellValueChanged של הגריד, ונכתוב את הקוד הבא
 

private void dgvCategories_CellValueChanged(object sender, DataGridViewCellEventArgs e)

{

    Categrory category = (Categrory)dgvCategories.Rows[e.RowIndex].DataBoundItem;

    if (category.CurrentState == State.Database)

    {

        category.CurrentState = State.Update;

    }

}

 
במידה ומדובר בשורה שהגיע מבסיס הנתונים ולא סומנה למחיקה, נסמן אותה כ - .Update
 
כעת נראה את הקוד ששומר לבסיס הנתונים (הלחצן Save To DB),
הרעיון שעומד מאחורי הדוגמא - היא שאנחנו לא מעוניים כל פעולה לרוץ לבסיס הנתונים, אלא לעבוד על האובייקטים, לסמן אותם איזה שינויים עשינו, ובסוף לעדכן את בסיס הנתונים בכל השינויים.
 
הנה הקוד:
 

private void btnSave_Click(object sender, EventArgs e)

{

    try

    {

        _connection.Open();

 

        foreach (Categrory item in _categories)

        {

            if (item.CurrentState == State.Delete)

            {

                DeleteItem(item);

            }

            else if (item.CurrentState == State.Update)

            {

                UpdateItem(item);

            }

            else if (item.CurrentState == State.Insert)

            {

                InsertItem(item);

            }

 

            item.CurrentState = State.Database;

        }

    }

    finally

    {

        _connection.Close();

    }

 

    dgvCategories.DataSource = null;

    dgvCategories.DataSource = _categories;

}

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

private void DeleteItem(Categrory item)

{

    SqlCommand deleteCommand = new SqlCommand();

    deleteCommand.Connection = GetConnection();

    deleteCommand.CommandText = "DELETE Categories WHERE CategoryID = @CategoryID";

    deleteCommand.Parameters.Add("CategoryID", SqlDbType.Int);

 

    deleteCommand.Parameters["CategoryID"].Value = item.Id;

 

    deleteCommand.ExecuteNonQuery();

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

private void UpdateItem(Categrory item)

{

    SqlCommand updateCommand = new SqlCommand();

    updateCommand.Connection = GetConnection();

    updateCommand.CommandText = @"UPDATE Categories SET CategoryName = @CategoryName,

                                                        Description = @Description

                                    WHERE CategoryID = @CategoryID";

 

    updateCommand.Parameters.Add("CategoryName", SqlDbType.VarChar);

    updateCommand.Parameters.Add("Description", SqlDbType.VarChar);

    updateCommand.Parameters.Add("CategoryID", SqlDbType.Int);

 

 

    updateCommand.Parameters["CategoryName"].Value = item.CategoryName;

    updateCommand.Parameters["Description"].Value = item.Description;

    updateCommand.Parameters["CategoryID"].Value = item.Id;

 

 

    updateCommand.ExecuteNonQuery();

}

 
ומתודת Insert
 

private void InsertItem(Categrory item)

{

    SqlCommand insertCommand = new SqlCommand();

    insertCommand.Connection = GetConnection();

    insertCommand.CommandText = @"INSERT INTO Categories (CategoryName, Description)

                                               VALUES (@CategoryName, @Description);

                                  SELECT CategoryID FROM Categories WHERE CategoryID = SCOPE_IDENTITY()";

 

    insertCommand.Parameters.Add("CategoryName", SqlDbType.VarChar);

    insertCommand.Parameters.Add("Description", SqlDbType.VarChar);

 

 

    insertCommand.Parameters["CategoryName"].Value = item.CategoryName;

    insertCommand.Parameters["Description"].Value = item.Description;

 

    item.Id = (int)insertCommand.ExecuteScalar();

}

 
שימו לב - שבהגדרה של ה - CommandText בסוף ההגדרה של שאילתת ה - INSERT, אנחנו כותבים ; ולאחריו עוד שאילתא,
הסיבה היא, שאנחנו רוצים אחרי הכנסת השורה החדשה לבסיס הנתונים, לקבל את ה - Id החדש שנוצר בצורה אוטומטית, (אנחנו מקבלים בעזרת קריאה לפונקציית SCOPE_IDENTITY)
 
היות שאנחנו יודעים שהשאילתא תחזיר ערך בודד (את ה - id החדש) אנחנו מפעילים את השאילתא בעזרת ExecuteScalar, שמחזיר object ואנחנו ממירים ל - int.
 
 
כעת אנחנו כבר יודעים לעבוד עם ExeuteScalar ועם NonQuery. 
נשאר לנו רק להבין מה זה טרנזקצייה.
 
הרבה פעמים אנחנו רוצים לעשות כמה פעולות מול בסיס הנתונים ולוודא שאו שהכל מצליח או שכלום לא מצליח, לדוגמא.
נחשוב על פעולה פשוטה בבנק, העברת כסף מחשבון לחשבון, מבחינת ADO מדובר בשני פעולות מול בסיס הנתונים, Update על השורה של מי שרוצה להוציא כסף מחשבונו, ו - Update לחשבון שאליו רוצים להעביר את הכסף.
נניח שאחרי שעדכנו את החשבון של מי שרוצה להעביר כסף יש הפסקת חשמל, עם הפעולות לא יוגדרו בתוך טרנזקצייה, החשבון של מי שרצה להעביר כסף יעודכן מבלי שהכסף באמת הגיע לחשבון השני.
 
ולכן אנחנו רוצים את היכולת להגדיר כמה פעולות מול בסיס הנתונים בטרנזקצייה.
יש שני סוגים, הראשון (והישן) כשמדובר ב - Connection אחד מול בסיס הנתונים, בדוגמא שלנו, אנחנו רוצים שכל הפעולות הלחיצה על Save יצליחו, או ששום דבר לא יצליח (עם נלחץ על Delete עבור אחד מהשורות שהיו במקור בבסיס הנתונים - נקבל שגיאה בזמן Save מכיון שמקושר Products אל אותו Category)
 
נשנה את הקוד של Save לזה.
 

private void btnSave_Click(object sender, EventArgs e)

{

    SaveWithSqlTransaction();

 

    dgvCategories.DataSource = null;

    dgvCategories.DataSource = _categories;

}

 
הפונקצייה SaveWithSqlTransaction
 

private SqlTransaction _transaction;

 

private void SaveWithSqlTransaction()

{

    _connection.Open();

    _transaction = _connection.BeginTransaction();

 

    try

    {

        foreach (Categrory item in _categories)

        {

            if (item.CurrentState == State.Delete)

            {

                DeleteItem(item);

            }

            else if (item.CurrentState == State.Update)

            {

                UpdateItem(item);

            }

            else if (item.CurrentState == State.Insert)

            {

                InsertItem(item);

            }

            item.CurrentState = State.Database;

        }

 

        _transaction.Commit();

    }

    catch

    {

        _transaction.Rollback();

    }

    finally

    {

        _connection.Close();

    }

}

 
 
אחרי פתיחת ה - Connection אנחנו פותחים טרנזקצייה
במידה וקורה Exception כלשהו אנחנו קוראים ל - Rollback
במידה והכל בסדר אנחנו קוראים ל - Commit.
 
לפעמים אנחנו צריכים להפעיל טרנזקצייה כשמדובר ביותר מ - Connection אחד (לדוגמא - עבודה מול שני בסיס נתונים במקביל) במקרה הזה נעבוד עם TransactionScope,
 
נוסיף reference ל - System.Transaction, ונכתוב את הקוד הבא
 

private void SaveWithTransactionScope()

{

    using (TransactionScope transaction = new TransactionScope())

    {

        _connection.Open();

 

        try

        {

            foreach (Categrory item in _categories)

            {

                if (item.CurrentState == State.Delete)

                {

                    DeleteItem(item);

                }

                else if (item.CurrentState == State.Update)

                {

                    UpdateItem(item);

                }

                else if (item.CurrentState == State.Insert)

                {

                    InsertItem(item);

                }

                item.CurrentState = State.Database;

            }

 

            transaction.Complete();

        }

        catch

        {

        }

        finally

        {

            _connection.Close();

        }

    }

}

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

חיבור של שני int כשאחד מהם מוגדר כ - nullable

 

כתבתי על זה בעבר אבל שוב נתקלתי בזה,
 
שימו לב לקוד הבא
 

class MyClass

{

    public int id;

    public int? data;

 

    public override int GetHashCode()

    {

        return id.GetHashCode() +

            data == null ? 0 : data.GetHashCode();

    }

}

 
 
מה יוחזר כש - data יהיה == null ?
 
מסתבר (כמו שהסברתי בפוסט המובא לעיל) שהמתודה תמיד תחזיר 0 ולא משנה מה הערך של id אם הערך של data הוא null
Posted: Dec 16 2009, 10:52 AM by Shlomo | with 1 comment(s)
תגים:, ,

מפגש חברי פורום תפוז - הקלטות

 

בלינק הבא אפשר לצפות בהרצאות שאני ואלון העברנו לחברי פורום תפוז NET.
 
על C# 4.0 ועל Architecture & Design Best Practices

עבודה עם מתודות של אובייקטים שמגיעים מ - WEB Service

 

נניח שיש לכם WebService שנראה ככה:

public class Service1 : WebService

{

    [WebMethod]

    public MyClass HelloWorld()

    {

        return new MyClass();

    }

}

 

public class MyClass

{

    public int MyProperty { get; set; }

    public string MyProperty1 { get; set; }

 

    public void Func()

    {

    }

}

 
כשתוסיפו WebRederence בפרויקט שלכם ל - WebService - תגלו שה - MyClass שמגיע מהמתודה HelloWoeld חסר מתודות, כלומר יש לו רק את המאפיינים
הסיבה היא די פשוטה - אין דרך להעביר מתודות - מה שיכול לעבור ברשת זה רק מידע, ולמעשה כשמוסיפים WebService בעצם נוצר Proxy שמדמה את ה - class בצד השני, הוא נראה כך:
 

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.4927")]

[System.SerializableAttribute()]

[System.Diagnostics.DebuggerStepThroughAttribute()]

[System.ComponentModel.DesignerCategoryAttribute("code")]

[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]

public partial class MyClass {

 

    private int myPropertyField;

 

    private string myProperty1Field;

 

    /// <remarks/>

    public int MyProperty {

        get {

            return this.myPropertyField;

        }

        set {

            this.myPropertyField = value;

        }

    }

 

    /// <remarks/>

    public string MyProperty1 {

        get {

            return this.myProperty1Field;

        }

        set {

            this.myProperty1Field = value;

        }

    }

}

 
 
אפשר לראות אותו - על ידי לחיצה על showAllFiles ב - Soluation Explorer (הקובץ נקרא Reference.cs - תחת ה - WebReference שלכם)
 
כדי שיהיה לו בכל זאת מתודות - אפשר באחת משני הדרכים
 
1. להוסיף בעזרת ה - partial
2. לייצר מחלקה נוספת עם המרה אוטמטית (מה שלפעמים נותן יכולות נוספות)
 
בעזרת partial:
תוסיפו בפרוייקט שלכם מחלקה חדשה עם אותו שם של המחלקה של ה - proxy (גם באותו namespace), לדוגמא:
 

namespace ClassLibrary1.localhost

{

    public partial class MyClass

    {

        public void Func()

        {

        }

    }

}

 
 
מחלקה חדשה עם המרה אוטומטית:
 
 

public class MyClass

{

    public int MyProperty { get; set; }

    public string MyProperty1 { get; set; }

 

    public void Func()

    {

    }

 

 

    public static implicit operator localhost.MyClass(MyClass mc)

    {

        return new ClassLibrary1.localhost.MyClass()

        {

            MyProperty = mc.MyProperty,

            MyProperty1 = mc.MyProperty1

        };

    }

 

    public static implicit operator MyClass(localhost.MyClass mc)

    {

        return new MyClass()

        {

            MyProperty = mc.MyProperty,

            MyProperty1 = mc.MyProperty1

        };

    }

}

 
ואז תוכלו לכתוב
 

MyClass mc = new Service1().HelloWorld();

 
ולמעשה המתודה החזירה מופע של MyClass מה - Proxy אבל זה עבר המרה אוטומטית ל - MyClass שאתם הגדרתם עם המתודות.
 
בדרך כלל השיטה של Partial עדיפה אבל לפעמים (למשל אם אתם עובדים עם יותר ממופע אחד של אותו WebService וכל מופע יושב במקום אחר - נוצרים למעשה כמה Proxies) במקרים האלו עדיך לעבוד השיטה השנייה ולא לתחזק הרבה Partial - אלא פשוט להוסיף מתודות להמרה אוטומטית.

מפגש חברי פורום תפוז - רשמים

 

מצטער שלא כתבתי על זה כאן לפני המפגש - (בפעם הבאה)
 
הערב התקיים בבית סלע מפגש לחברי פורום תפוז ב - .NET (פורום מעולה עם הרבה משתתפים)
 
העברתי תקציר על החידושים ב - C# 4.0 ואלון פליס העביר הרצאה על ארכיטקטורה
 
למעשה תוכנן הרצאה של משה על ביצועים ב - WEB - אבל עקב אילוצי זמן זה נדחה למפגש הבא.
 
 
ואם כבר מדברים על מפגשים - בסוף החודש סלע מקיימת את ה - SDP - כנס מעולה ששוה להירשם וללמוד על כל הטכנולוגיות החדשות של מייקרוסופט
 
 

שיעור על Attribute ושימוש ב - Reflection

 

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

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

}

 
הגדרנו מחלקה עם שני מאפיינים, מספר ושם, המשמעות של אותם מאפיינים שכשיהיה לנו מופעים של Person, לכל מופע יש את השם שלו והמזהה שלו.
 
לפעמים אנחנו רוצים להגדיר התנהגות לאובייקטים שלנו - כלומר אנחנו רוצים להגדיר איך האובייקטים יתנהגו במצבים מסויימים, והמידע הזה לא מידע של המחלקה אלא מידע על המחלקה, וזה מילת המפתח בהבדל בין Properties ל - Attribute, מאפיינים מגדירים מידע של המחלקה לעומת Attribute מגדירים מידע והתנהגות על המחלקה.
 
דוגמא טובה לכך מהעולם האמיתי, חולצה, אם נסתכל על חולצה נראה שכל חולצה היא מופע של אובייקט חולצה שיש לו מאפיינים כמו צבע, גובה, רוחב, ועוד, בנוסף לזאת לכל חולצה יש פתק לבן בצד שמגדיר בכמה מעלות לכבס ואיך לגהץ, המידע הזה אינו מאפיינים של החולצה אלא הגדרת התנהגות איך להשתמש בחולצה.
 
דוגמא מעולם התכנות - הגדרה איך ה - debugger יראה לנו את האובייקטים, כברירת מחדל כשאנחנו מסתכלים ב - watch על אובייקט, הוא מראה לנו את ה - ToString שלו, במידה ואנחנו רוצים שהוא יראה משהו אחר, אנחנו צריכים להגדיר לו איך אנחנו רוצים שהאובייקט יתנהג בזמן debug (פוסט מפורט בנושא - כאן) ואנחנו נגדיר אותו בצורה הזאת
 

[DebuggerDisplay("Name = {Name}")]

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

}

 
 
 
 
למעשה אפשר לחלק את העבודה עם Attribute לשלושה.
1. שימוש בהם.
2. הגדרה שלהם.
3. חקירה.
 
ואני אסביר
95 אחוזים מהעבודה שלנו עם Attribute זה השימוש בהם - כלומר - אנחנו משתמשים ב - Attribute שונים ומשונים שמישהו כתב עבורנו כדי לקבל פונקציונליות מסויימת, (DebuggerDisplay לדוגמא)
 
אבל לפעמים אנחנו רוצים להגדיר Attribute משלנו (עוד מעט נראה דוגמא). ואז אנחנו בעצם עושים את שלב 2, כלומר מגדירים Attribute משלנו ואחרים יעשו את שלב 1 (כלומר ישתמשו ב - Attribute שלנו).
אבל ללא שלב 3 (חקירה) אין לזה משמעות , במילה חקירה אני מתכוון שמישהו צריך לבדוק מי משתמש ב - Attribute שהגדרנו ולעשות עם זה משהו, נקח לדוגמא את DebuggerDisplay.
 
ישב מישהו במייקרוסופט והגדיר Attribute שנקרא DebuggerDisplay (שלב 2)
 
אנחנו משתמשים ב - Attribute הזה ושמים אותו על אובייקטים שלנו (שלב 1)
 
במידה שאף אחד לא היה מממש את החקירה (שלב 3) היינו יכולים להשתמש ב - Attribute הזה מהיום עד מחר, וה - Debugger לא היה עושה עם זה כלום, היות שכתבו מנגנון שחוקר את האובייקטים שלנו האם השתמשנו ב - DebuggerDisplay יש משמעות לשימוש ב - Attribute הזה.
 
 
דוגמא ל - Attribute ללא שלב 3. הוא Optional ו - DefaultParameterValue, הידעתם שאפשר לכתוב (כבר מ - 2.0) קוד כזה:
 

public void Func(int id,

                [Optional, DefaultParameterValue("shlomo")]

                string name)

{

}

 
מה שאומר שכשמישהו יקרא ל - Func הוא יהיה חייב לשלוח ערך עבור id ויוכל לשלוח ערך עבור name אבל אם הוא לא ישלח, name יקבל ערך ברירת מחדל (shlomo).
 
נסתכל על ה - Attribute הזה.
שלב 2 (הגדרה) קיים, ישב מישהו במייקרוסופט והגדיר את ה - Attribute
 
שלב 1 (שימוש) קיים, אנחנו משתמשים בזה.
 
שלב 3 (חקירה) לא קיים לצערנו - אף אחד לא בודק האם אנחנו משתמשים ב - Attribute הזה, ולכן אנחנו יכולים להשתמש ב - Attribute האלו כאוות נפשנו, אבל בפועל כשנפעיל את Func נצטרך לשלוח ערך גם ל - id וגם ל - name, בגלל שהם שכחו לממש את שלב 3.
 
(הערה: המתכנתים של VB.NET כן מימשו את שלב 3 - ולכן כשנפעיל את הפונקצייה מ - VB.NET לא נהיה חייבים לשלוח ערך ל - name, הערה שנייה: ב - C# 4.0 תקנו את העוול ומימשו את שלב 3 עבור ה - Attribute האלו, לקריאה נוספת, כאן)
 
 
 
כעת נראה איך אנחנו ממשים את שלב 2 (הגדרה) ושלב 3. (חקירה)
 
נניח שאנחנו רוצים לממש מנגנון שידע להגיד לנו איזה מתכנת כתב כל מחלקה ופונקצייה ואיזה מתכנת עדכן.
 
שלב ראשון נממש את שלב 2, ונגדיר שני Attribute.
נייצר פרויקט בשם AttributeDefinition ובו שני מחלקות
 

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

public class ItemCreatedAttribute : Attribute

{

    public string Name { get; private set; }

    public string Date { get; private set; }

 

    public ItemCreatedAttribute(string name, string date)

    {

        Name = name;

        Date = date;

    }

}

 

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]

public class ItemUpdatedAttribute : Attribute

{

    public string Name { get; private set; }

    public string Date { get; private set; }

 

    public ItemUpdatedAttribute(string name, string date)

    {

        Name = name;

        Date = date;

    }

}

 
כל Attribute הוא מחלקה שהתנאי היחיד שלה שהוא יורש מ - Attribute
נהוג שכל Attribute שם המחלקה מסתיים עם המילה Attribute.
 
יש לנו שני מאפיינים (שם ותאריך) שניהם מוגדרות כמחרוזת (אני אסביר יותר מאוחר למה אי אפשר DateTime) ובנאי.
בנוסף יש לנו Attribute על המחלקות שלנו שמגדיר איפה ואיך אפשר להשתמש ב - Attribute שלנו.
 
ה - Attribute מגדיר על מה מותר לשים את ה - Attribute שלנו (הגדרנו שרק על מחלקות ופונקציות) בנוסף על ItemUpdated הגדרנו AllowMultiple=true כדי שיוכלו לשים יותר ממופע אחד שלו על אותו פונקצייה (מכיון שאפשר לעדכן פונקצייה יותר מפעם אחת).
 
 
כדעת נראה את שלב 1 (שימוש) נקמפל את הפרויקט וניתן את ה - dll שלנו לכל מי שרוצה לעקוב מי יוצר ומעדכן את המחלקות והפונקציות - שימוש לדוגמא:
יש לי פרויקט שנקרא SomeProject ובו שני מחלקות:
 

[ItemCreated("Shlomo", "10/10/2007")]

public class Class1

{

    [ItemUpdated("Noam", "07/12/2007")]

    [ItemCreated("Shlomo", "10/11/2007")]

    public void Func1()

    {

    }

 

    [ItemUpdated("Dudu", "10/10/2008")]

    [ItemUpdated("David", "10/11/2007")]

    [ItemCreated("Ifat", "10/10/2007")]

    public void Func2()

    {

    }

}

 

[ItemUpdated("Simon", "07/09/2005")]

[ItemCreated("Noam", "01/01/2000")]

class Manager

{

    [ItemUpdated("Dudu", "02/12/2003")]

    [ItemCreated("Shlomo", "10/01/2000")]

    public void Work()

    {

    }

 

    [ItemUpdated("David", "12/07/2004")]

    [ItemCreated("Ifat", "14/04/20001")]

    public void WorkAgain()

    {

    }

}

 
כמו שאפשר לראות מתכנתים כתבו ועדכנו את המחלקות והפונקציות בזמנים שונים.
 
כעת אנחנו צריכים לממש את שלב 3 (חקירה) במידה ולא נעשה את זה אין טעם להשתמש ב - Attribute שלנו.
 
כתבתי WinForm שאמור לקבל dll לחקירה ולהציג תוצאות ממנו, הוא נראה כך:
 
Attribute
 
 
כשנבחר dll מסויים נקבל את כל המחלקות ברשימה, כשנבחר מחלקה במידה ויש עליו את ה - Attribute שלנו נוכל לראות תוצאות - לדוגמא אם נבחר את SomeProject נקבל:
 
 
att2
 
 
איך זה קורה, למעשה כל החקירה של Attribute היא בעזרת Reflection - ונסתכל על הקוד.
 
בזמן לחיצה ה - LinkLabel (שלושת הנקודות הכחולות ליד תיבת הטקסט) נבצע את הקוד הבא
 

if (openFileDialog1.ShowDialog() == DialogResult.OK)

{

    txtAssemblyName.Text = openFileDialog1.FileName;

    LoadClasses();

}

 
בעזרת OpenFileDialog נבחר קובץ כלשהו, ונפעיל פונקצייה בשם LoadClasses

private Assembly _assembly;

 

private void LoadClasses()

{

    try

    {

        _assembly = Assembly.LoadFile(txtAssemblyName.Text);

        Type[] allClasses = _assembly.GetTypes().Where(item => item.IsClass).ToArray();

 

        cmbClass.Items.Clear();

        cmbClass.Items.AddRange(allClasses);

 

        lvClass.Items.Clear();

        lvMethod.Items.Clear();

    }

    catch (BadImageFormatException)

    {

        MessageBox.Show("The file is not a .NET file");

    }

}

 
יש לנו מופע של ,Assembly המחלקה הזאת מייצגת dll דוטנטי כלשהו,
אנחנו מנסים לטעון את ה - dll, ומבקשים את כל ה - Type שהוא מסוג מחלקה, (אנחנו קוראים ל - GetTypes שיחזיר את כל ה - Types שמוגדרים  ב - dll (כולל enum, Interface ועוד))
 
אנחנו מוסיפים את כל מה שקבלנו ל - Combo. (כלומר את כל השמות של המחלקות שיש לנו במערך)
 
 
כעת נרשמנו לאירוע Combo_SelectedChange
 

private void cmbClass_SelectedIndexChanged(object sender, EventArgs e)

{

    Type currentType = _assembly.GetType(cmbClass.Text);

 

    lvClass.Items.Clear();

    lvMethod.Items.Clear();

 

    ItemCreatedClass(currentType);

    ItemUpdatedClass(currentType);

    FillMethodCombo(currentType);

}

 
נקבל את הטיפוס מתוך ה - Assembly לפי השם שלו (מה שבחרנו ב - Combo)
 
כעת אנחנו הולכים לחקור את ה - Type האם יש בו שימוש ב - ItemCreated או ב - itemUpdated
 

private void ItemCreatedClass(Type currentType)

{

    Type type = typeof(ItemCreatedAttribute);

    object[] itemCreatedAtt = currentType.GetCustomAttributes(type, false);

    if (itemCreatedAtt.Length == 1)

    {

        ItemCreatedAttribute att = (ItemCreatedAttribute)itemCreatedAtt[0];

        lvClass.Items.Add(new ListViewItem(new string[] { att.Name, att.Date },

                                            lvClass.Groups[0]));

    }

}

 
 
נפעיל את מתודת GetCustomAttribute על ה - Type שקבלנו - כפרמטר הוא מקבל את סוג ה - Attribute שאנחנו רוצים לחפש ו - false כדי להגיד שאנחנו רוצים לקבל רק את ה - attribute הזה ולא את היורשים שלו.
אנחנו מקבלים מערך של אובייקטים, היות שאנחנו יודעים שלא יכול להיות יותר ממופע אחד של ItemCreated על מחלקה, אנחנו יכולים להגיד שבמידה וקבלנו מערך בגודל של אחד מן הסתם השתמשו ב - Attribute שלנו.
 
נוציא את המופע מתוך המערך ונמיר אותו מ - object ל - ItemCreated, ונוסיף אותו ל - ListView
 
באותה צורה נוציא את ה - ItemUpdate וכמו כן נבדוק על המתודות, אני לא אעתיק לכאן את כל הקוד אבל זה הרעיון המרכזי, ותוכלו להוריד את הקוד מכאן
 
 
הערה:
כפי שאולי שמתם לב שם ה - Attribute הוא ItemCreatedAttribute אבל אפשר להשתמש בו בלי המילה Attribute
[ItemUpdated("Simon", "07/09/2005")]
 
 
More Posts Next page »