Posted by
talgiladi on
6/28/2009 12:07 PM |
Comments (0)
המטרה: ליצור שפה אחידה שמתארת את ההנחות והציפיות שלי כשאני משתמש באובייקט של מישהו אחר, או כשמישהו אחר משתמש
באובייקט שאני כתבתי. נשמע לא מובן, אך כולנו בעצם עושים את זה יומיום, השינוי היחיד הוא שאנו רוצים לעשות את זה בצורה אחידה.
היום כשאני מקבל ערך לאיזה מאפיין במחלקה שלי, אני בודק האם הערך תקין, האם הוא לא ריק וכו’, ואם הערכים לא מתאימים לי, אני זורק
הודעת שגיאה בדרך כלל:
private int _age;
public int Age
{
get
{
return _age;
}
set
{
if (value < 0)
{
throw new ArgumentException("Age must be a positive value");
}
_age = value;
}
}
ואיך נכתוב את זה באמצעות חוזה?
private int _age;
public int Age
{
get
{
return _age;
}
set
{
Contract.Requires(value > 0);
_age = value;
}
}
זהו…
הקוד עדיין בתהליכי פיתוח במייקרוסופט, אך הוא מופץ כבר משולב בתוך דוט נט גירסה 4, וניתן להורדה גם עבור ויז’ואל סטודיו 2008.
אז מה יש לנו שם עוד?
יש כמה דברים מעניינים, לדוגמה:
- בדיקה של הערכים שפונקציה יכולה להחזיר, לפני שהיא מחזירה אותם. בדיקה
- סטטית(compile time) של קריאות עם ערכים לא תקינים לפונקציה כך שאנו מקבלים אזהרה עוד לפני זמן הריצה (רק שבינתיים הם הוסיפו את האופציה הזאת רק עבור גירסה team suite…)
- הגדרת דרישות עבור ממשק, כך שכל מחלקה שמיישמת אותו תצטרך למלא את הדרישות האלו.
אבל הדברים העיקריים הם אלו:
- תנאי מקדים- שאנו מבטאים באמצעות הקוד שעכשיו כתבנו : Contract.Requires, שבעצם בודק שהכל תקין לפני שאני מתחיל את המתודה שלי.
- תנאי מאוחר – שבודק את התקינות לאחר שהקוד שלי רץ והמתודה עומדת להסתיים.
- תנאים לא תלויי אובייקט- אלו תנאים שתמיד אמורים להיות נכונים לגבי האובייקט שלי, לא משנה מה מצבו- גיל לא יכול להיות שלילי, גם אם הלקוח לא מילא את פרטי גילו, ולכן בכל מצב זה אמור להיות נכון, ולא רק לאחר שהכנסתי ערך לפרטי הגיל.
על מנת שהחוזים שאנו כותבים יכנסו אכן לקוד, ולא יושמטו על ידי הקומפלייר, יש להגדיר סימבול.
אם אתם משתמשים בויז’ואל סטודיו 2010 לכו למאפייני הפרוייקט ( לחצן ימני על שם הפרוייקט-> מאפיינים)
תוסיפו שם את המילה CONTRACTS_FULL
בויז’ואל סטודיו 2008 נדרשת התקנה של הקוד, ולאחר מכן יש להוסיף הפניה ל microsoft.contracts, אך לאחר ההתקנה יהיה לכם בהגדרות פרוייקט טאב מיוחד להגדרות של החוזים.
מידע נוסף ניתן למצוא באתר של מייקרוסופט, יש שם וידאו ומסמך pdf עם הסברים רחבים ודוגמאות.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
ראיתי את הפרוייקט הזה ב code plex שמיקל על השימוש בService Locator בצורה יותר Loosley Coupled. לאחר מה שקרה לי
לפני כמה זמן, בדיוק בנושא הזה, החלטתי מעצמי לישם פיתרון כזה ואז ראיתי היום שיש כבר קוד מוכן… לא שזה כזה משנה, בסך הכל 3
מחלקות יצרתי שם עם כמה שורות קוד…
בכל מקרה, עדיין הפרוייקט שלהם טעון שיפור, שכן הוא לא מספק דרך לקנפג את השירות דרך הקוד. הם פשוט סומכים על כל מחלקה שמממשת את הממשק שתקנפג את עצמה, שזה די טוב לרוב האנשים שמשתמשים בקבצי XML חיצוניים לקינפוג, אבל אני טוען את המידע בעצמי ואז בזמן ריצה מקנפג את המיפוי, וזה אומר שאני אצטרך בכל מחלקה שמממשת את הממשק אני אצטרך להכניס מעל 100 שורות עם המיפויים, וכולנו יודעים מה זה אומר לשכפל קוד…
אז רק כדי להשלים את הפרוייקט שלהם נראה שצריך להוסיף 2 מתודות בממשק. הראשונה:
void RegisterType<TFrom, TTo>() where TTo : TFrom;
זאת שאמורה למפות ממשק למחלקה הדיפולטית שמיישמת אותו, והשניה:
void RegisterInstance<TType>(TType instance);
שתמפה אינסטס קיים של אובייקט שמממש את הממשק.
עכשיו אני יכול בנקודת הכניסה לאפליקציה (נניח ב global.asax) למפות את כל מה שאני רוצה בלי להיות תלוי במימוש מסוים.
Technorati Tags:
IOC,
ServiceLocator
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
הבעיה: אני מאפשר להוסיף תגובות בדף, אני רוצה לשלוח את הבקשה באג’קס לשרת, ואם היא מאושרת להוסיף את התגובה ישירות לדף, בלי לרפרש אותו. קוד ה HTML של תגובה נמצא ב user control . אני רוצה שהשרת ישלח לי בחזרה את קוד ה HTML של התגובה החדשה, לא סתם ישלח, אלא בפורמט JSON.
עם web forms לא הייתה לי בעיה, יש מתודה שנקראת RenderControl עבור כל פקד, והיא מדפיסה את קוד ה HTML שלו, ואחר כך אני עושה עם הטקסט הזה מה שאני רוצה, במקרה שלי, שולח לדפדפן בפורמט JSON.
ב MVC זה לא קיים. אני מתכוון שכל פעולה בנפרד קיימת, אבל הכל ביחד כמו שאני צריך, לא.
יש אפשרות לשלוח JSON על ידי שימוש במתודה Json שנמצאת בקונטרולר, או על ידי יצירת JsonResult.
יש אפשרות לרנדר קונטרול פשוט כמו שמרנדרים כל view אחר- פשוט לקרוא למתודה View() עם שם הקונטרול. הבעיה היא שהתוצאה מודפסת ישירות ל response בתור טקסט, ואני רוצה לשלוח אותה בתור json. אז ניסיתי לעבוד עליו, ליצור response מדומה ואז למשוך ממנו את הטקסט ולשלוח על ידי המתודה Json(), לא עבד. ניסיתי עוד כמה דברים שהיו בגוגל, לא עבד.
טוב, אז זה מה שיצא לי… לא שאני הכי שמח עם זה, אבל זה עובד..
- מחלקה שמרנדרת את הקונטרול לטקסט:
public class BlockRenderer
{
private readonly HttpContextBase _httpContext;
public BlockRenderer(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public partial class HttpResponse
{
public bool UsingHttpWriter { get { return true; } }
}
public string Capture(Action viewRenderer)
{
HttpResponseBase resp = _httpContext.Response;
Stream originalFilter = null;
CapturingResponseFilter innerFilter;
string capturedHtml = "";
if (viewRenderer != null)
{
try
{
resp.Flush();
originalFilter = resp.Filter;
innerFilter = new CapturingResponseFilter(resp.Filter);
resp.Filter = innerFilter;
viewRenderer();
resp.Flush();
capturedHtml = innerFilter.GetContents(resp.ContentEncoding);
}
finally
{
if (originalFilter != null)
{
resp.Filter = originalFilter;
}
}
}
return capturedHtml;
}
}
עכשיו כדי לקרוא לה, צריך מתוך הקונטרולר להוסיף את המתודה הבאה:
public string RenderPartialToString(string userControl, ViewDataDictionary viewData, ControllerContext controllerContext)
{
HtmlHelper h = new HtmlHelper(new ViewContext(controllerContext, new WebFormView("Index"), viewData, TempData), new ViewPage());
var blockRenderer = new BlockRenderer(controllerContext.HttpContext);
string s = blockRenderer.Capture(
() => RenderPartialExtensions.RenderPartial(h, userControl, viewData)
);
return s;
}
ופשוט לקרוא כל פעם שרוצים לרנדר משהו על ידי שליחת הפרמטרים המתאימים:
ViewDataDictionary viewData = new ViewDataDictionary(new List<Reply>() { reply });
string s = RenderPartialToString("~/Views/Shared/ReplyItem.ascx", viewData, this.ControllerContext);
טוב, אז עכשיו קיבלתי את הקונטרול בתור טקסט, ואז אתם חושבים, יפה פשוט נחזיר אותו על ידי קריאה למתודה JSON בקונטרולר, אז ניסיתי וקיבלתי שגיאה- מתברר שבמהלך הקריאה למחלקה שמרנדרת את הקונטרול, מישהו באמצע החליט להוסיף http headers, והקונטרולר לא מסכים לקריאה למתודה JSON כשיש לו headers כבר... טוב אז אחרי כמה משחקים עם הקוד הגעתי לפיתרון פשוט:
- לא לקרוא ל JSON, אלא להפוך את המידע לג'ייסון בעצמי, ולשלוח אותו בתור טקסט, אז לא איכפת לקונטרולר אם יש headers או לא. והקוד הפשוט הוא
JavaScriptSerializer js = new JavaScriptSerializer();
return new TextResult(js.Serialize(new { approved = reply.IsApproved.ToString(), replyHtml = html }));
כמו שאמרתי, אני לא כל כך מרוצה, אם יש למישהו פיתרון יותר אלגנטי, אני אשמח. ובכלל אני לא מבין למה זה כל כך מסובך, זה נשמע לי די תדיר שמישהו יפנה לשרת וירצה לרנדר קונטרול בתור סטרינג…
Technorati Tags:
mvc,
user control,
ajax
Currently rated 4.0 by 1 people
- Currently 4/5 Stars.
- 1
- 2
- 3
- 4
- 5
Technorati Tags:
asp.net,
mvc
כתבתי בעבר על איך ליצור captcha control פשוט, וגם כתבתי אחד קצת יותר מתקדם,
אבל עכשיו כשאני בתהליכי מעבר ל mvc הקונטרולים הקודמים לא כל כך מועילים, שהרי הם משתמשים ב webforms.
אז כמובן שאי אפשר פשוט לזרוק כאן קונטרול על הדף וזה יעבוד, זה חלק מהמחיר שאנו משלמים עם mvc,
אז נצטרך לכתוב כמה שורות קוד, לא יותר מדי…
-
אז קודם כל הורדתי קוד ליצירת התמונה, משהו נחמד עם צבעים וציורים :), זה בלוג של מישהו, אתם יכולים לקרוא מאמר עוד אם אתם רוצים. בקיצור נראה לי שהוא כתב קצת יותר מדי, אז שיפצרתי קצת את הקוד וזה מה שיצא.
קודם כל יש הקוד המקורי שיוצר את התמונה, זו מחלקה אחת לא גדולה בשם CaptchaImage
-
אחר כך יצרתי מחלקה סטטית בשם CaptchaManager שתעטוף את הפונקציונליות בארבע פונקציות:
א. GetCaptchaString – מייצר טקסט רנדומלי ושומר אותו בסשן להשוואה.
ב. ResetCaptcha – משמשת לייצור טקטס חדש- אם אתם רוצים לאפשר רינדור של תמונות על ידי אג'קס במקרה שהמשתמש לא רואה טוב את התמונה, אם לא אתם יכולים למחוק אותה.
ג. RenderImage מייצרת את התמונה עבור הקונטרולר שאמור לשלוח אותה לדפדפן.
ד. IsValid – ערך שמציין האם הפניה האחרונה שהגיע מהדפדפן מכילה ערך תקין בהתאם לתמונה שנשלחה. זוהי בעצם הבדיקה שלי.
public class CaptchaManager
{
public static string GetCaptchaString()
{
string result;
if (HttpContext.Current.Session["captchaText"] != null)
{
result = HttpContext.Current.Session["captchaText"] as string;
}
else
{
result = Guid.NewGuid().ToString("N").Substring(0, CaptchaImage.TextLength);
HttpContext.Current.Session["captchaText"] = result;
}
return result;
}
public static void ResetCaptcha()
{
HttpContext.Current.Session["captchaText"] = null;
}
public static Bitmap RenderImage()
{
CaptchaImage ci = new CaptchaImage()
{
Text = GetCaptchaString()
};
return ci.RenderImage();
}
public static bool IsValid
{
get
{
string request = HttpContext.Current.Request["captchaText"];
string user = HttpContext.Current.Session["captchaText"] as string;
return request == user;
}
}
}
-
יצרתי מחלקה שתשמש כ ActionFilter , למרות שאתם יכולים כמובן לדלג עליה ולדחוף את הקוד שבה ישירות לקונטרולר שבו אתם רוצים את הבדיקה.
הדבר היחיד שאנו עושים כאן זה לתפוס את הבקשה לפני שהיא מגיעה ל action, ואם ה captcha לא תקין, להחזיר הודעת שגיאה לדפדפן. במקרה שלי זה הודעה בפורמט JSON לפניה מאג'קס, אבל כמובן שהתוצאה יכולה להיות כל ActionResult אחר.
public class ValidateCaptchaAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!CaptchaManager.IsValid)
{
filterContext.Result = new JsonResult() { Data = new { error = "true" } };
return;
}
base.OnActionExecuting(filterContext);
}
}
-
מה נשאר לנו? נשאר לכתוב את קוד ה Html ולשלוח את התמונה פיזית לדף. לצורך יצירת התמונה ושליחתה לדף, יצרתי קונטרולר נוסף שזה כל הקוד שלו:
public class CaptchaController : Controller
{
public FileResult Index()
{
using (Bitmap b = CaptchaManager.RenderImage())
{
var ms = new MemoryStream();
b.Save(ms, ImageFormat.Gif);
return File(ms.ToArray(), "image/png");
}
}
public JsonResult Render()
{
CaptchaManager.ResetCaptcha();
return Json(new { success = "true" });
}
}
הוא עושה 2 פעולה – הפעולה הראשונה, והיא חובה, זה יצירת תמונה ושליחתה לדפדפן. השניה זו תוספת שאיננה חובה, אבל אני רוצה שתהיה אפשרות למשתמשים לרנדר תמונה חדשה אם הם לא מצליחים לקרוא מה כתוב בתמונה הנוכחית, אז הוספתי פעולה ל ajax שתוכל פשוט לרנדר טקסט חדש, ובדף עצמו אני מוסיף לינק ליד התמונה שעושה 2 דברים:
א. פונה לשרת כדי לאפס את הטקס (זו הפעולה שאנו רואים פה Render)
ב. טוען את המקור של התמונה שוב (פשוט לתת לתמונה src אחר), ואז כשהיא תגיע לשרת כדי לקבל את התמונה , ממילא תתקבל התמונה עם הטקסט החדש
-
בואו נראה את קוד ה HTML
<table>
<tr>
<td>
<img src="<%=Url.Action("Index", "Captcha") %>" alt="captcha" name="captchaImages" /><a href="javascript:void(0)" onclick="renderCaptcha();return false;">a</a>
</td>
<td>
<input type="text" id="txtReplyCaptchaText" name="captchaText"/>
<span>Enter the text in the image</span>
</td>
</tr>
</table>
6. ונשאר רק פונקציה ב javascript למי שרוצה אפשרות לרנדר מחדש את התמונה
<script type="text/javascript">
<!--
function renderCaptcha() {
$.ajax({
type: 'post',
url: '<%=Url.Action("Render", "Captcha") %>',
success: function(j) {
var src = '<%=Url.Action("Index", "Captcha") %>?r=' + Math.random();
document.getElementById('captchaImage').src=src;
}, error: function(err) {
alert('Error');
}
})
}
-->
</script>
אז כמו שאמרתי הפונקציה פונה לשרת כדי לאפס את הטקסט, ואז רק נשאר לתת לתמונה source חדש, שהוא בעצם אותו source כי הרי המיקום של הקונטרולר שלנו לא השתנה, אלא שאני מוסיף לו מספר רנדומלי כדי להכריח את הדפדפן לרפרש את התמונה
שימו לב ששם תיבת הטקסט הוא קבוע “captchaText” , כדי שהפילטר שלנו ידע מה לחפש
חיסרון אחד בשיטה שלי לעומת המאמר המקורי הוא שאצלנו שם התמונה הוא כתובת של קונטרולר, ואם תרצו לשים של תמונה עם סיומת תקינה (jpg, gif( אז תצטרכו לשחק בטבלת המיפויים של האפליקציה, לי זה לא מפריע שהשם הוא כזה ולכן לא נגעתי בזה.
להורדת פרוייקט דוגמה
Currently rated 4.0 by 1 people
- Currently 4/5 Stars.
- 1
- 2
- 3
- 4
- 5