שימוש ב Unity באפליקציית ווב – איך לרשום רכיבים כסינגלטון בצורה נכונה פר בקשת HTTP

17 בדצמבר 2011

תגיות: , ,
2 תגובות

Unity הינו רכיב IOC Container מאוד שימושי, ואחד הדברים הראשונים שעושים באפליקציית asp.net mvc זה להשתמש ב-Unity על מנת לבצע רישום ושימוש ברכיבים שונים.

לדוגמא, אם רוצים לרשום DbContext של EntityFramework לשימוש עתידי, אפשר לעשות זאת בשתי דרכים קלאסיות.

רישום רגיל:

container.RegisterType<DbContext>();

ורישום כסינגלטון:

container.RegisterType<DbContext>(new ContainerControlledLifetimeManager());

כך ש-Unity מנהל בצורה שונה את ה”חיים” של הרכיב, וכשמבקשים את הרכיב הנ”ל ע”י:

container.Resolve<DbContext>();

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

מה שמעלה את השאלה איזו היא הדרך הנכונה.

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

מצד שני, האופציה השניה גם לא טובה, היות ולא נרצה ליצור את הרכיב הזה כל פעם שמבקשים אותו מ Unity, זה יכול להיות די בזבזני – לעיתים קרובות יותר מקונטרולר אחד משתתף בבקשת ה HTTP, וכן נרצה שהם יעבדו על אותו DbContext.

הדרך הנכונה לפתור את זה היא להגדיר LifetimeManager משלנו שיחזיר את אותו המופע במידה ואנחנו נמצאים באותה בקשת HTTP. לממש LifetimeManager ל-Unity זה די פשוט – כל מה שצריך לעשות זה לממש את המחלקה האבסטרקטית LifetimeManager שנראית כך:

// Summary:

//     Base class for Lifetime managers - classes that control how and when instances

//     are created by the Unity container.

public abstract class LifetimeManager : ILifetimePolicy, IBuilderPolicy

{

    protected LifetimeManager();

 

    // Summary:

    //     Retrieve a value from the backing store associated with this Lifetime policy.

    //

    // Returns:

    //     the object desired, or null if no such object is currently stored.

    public abstract object GetValue();

    //

    // Summary:

    //     Remove the given object from backing store.

    public abstract void RemoveValue();

    //

    // Summary:

    //     Stores the given value into backing store for retrieval later.

    //

    // Parameters:

    //   newValue:

    //     The object being stored.

    public abstract void SetValue(object newValue);

}

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

public class PerHttpRequestLifetime : LifetimeManager

{

    private readonly Guid _key = Guid.NewGuid();

 

    public override object GetValue()

    {

        return HttpContext.Current.Items[_key];

    }

 

    public override void SetValue(object newValue)

    {

        HttpContext.Current.Items[_key] = newValue;

    }

 

    public override void RemoveValue()

    {

        var obj = GetValue();

        HttpContext.Current.Items.Remove(obj);

    }

}

ואת הרישום ב-Unity לבצע ע”י המחלקה הזו:

container.RegisterType<DbContext>(new PerHttpRequestLifetime());

 

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

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

2 תגובות

  1. chen kinnrot27 בדצמבר 2011 ב 8:05

    קיים אחלה כלי ב sourceforge בשם unity wcf extensions

    הגב