פוסט זה נכתב בעברית מכיון שהוא המשך לפוסט שנכתב בעברית.
שלמה כתב על הנושא שמעצבן אותו לכתוב את כל קוד ההשמה של הערכים
מתוך ה Resources. והעלה פתרון של הגדרת ה Key של ה Resource בפורמט
“ControlID_ControlProperty" ומתודה שקוראת בזמן העליה ב Reflection
את ה Resources ומשימה כל ערך למקומו המתאים.
אמממה… שלמה בעצמו העלה את בעית הביצועים כאשר משתמשים
ב Reflection ב Run Time.
כאשר קראתי את הפוסט, הדבר הראשון שעלה בדעתי הוא “רגע למה שלא נעשה
את זה ב Compile Time ?” איך ? ע”י Code Generation (מחולל קוד בעברית).
חילול קוד ניתן לממש בכמה דרכים: ע”י CodeDom (הסבר יפה למתחילים
כאן) אבל זה נושא שלם לפוסט אחר,או ע”י כתיבת קובץ קוד ע”י StringWriter רגיל.
מכיון שהפתרון כאן הוא מאד פשוט בחרתי להשתמש בStringWriter עטוף
ב Helper Class שנקראת IndentWriter שמצאתי פה היודעת לשמור את עומק
ההזחה ע”מ שהקוד גם ייראה יפה.
זה הקוד של ה IndentWriter :
using System;
using System.Collections.Generic;
using System.Text;
namespace CodeGenerator
{
public class IndentingWriter
{
System.IO.StringWriter Writer = new System.IO.StringWriter();
int Depth;
public int IndentChars = 4;
public void OutDent()
{
Depth--;
if (Depth < 0) Depth = 0;
}
public void Indent()
{
Depth++;
}
public void Write(string value)
{
Writer.Write(value);
}
public void Write(string format, params object[] values)
{
Writer.Write(format, values);
}
public void WriteLine()
{
printIndenting();
Writer.WriteLine();
}
public void WriteLine(string format, params object[] values)
{
printIndenting();
Writer.WriteLine(format, values);
}
public void WriteLine(string value)
{
printIndenting();
Writer.WriteLine(value);
}
void printIndenting()
{
for (int x = 0; x < Depth; x++)
{
for (int y = 0; y < IndentChars; y++)
Writer.Write(' ');
}
}
public new string ToString()
{
return Writer.ToString();
}
}
}
הפתרון מורכב מהוספת פרויקט exe ל sln שיחזיק reference לפרויקט שלנו
וכאשר נריץ אותו הוא ידע לכתוב partial class המכיל את ההשמה של כל
הקונטרולים על הדף.
ראשית עלינו לדאוג שהפרויקט שלנו יתקמפל גם ללא מימוש המתודה של ההשמה
שאותה נממש אוטומטית בהמשך ולכן אם נקרא ב Page_Load למתודה שתעשה את
ההשמה מכיון שהיא עדיין לא מוגדרת הפרויקט לא יתקמפל.
לכן נשתמש ביכולת חדשה של .NET 3.5. הגדרת partial type כלומר כמו שניתן להגדיר
partial class בכמה מקומות והקומפיילר יודע למזג אותם לClass אחד כעת ניתן להגדיר
כל type כ partial.
ולכן נגדיר את הProtoType של המתודה ואז הפרויקט יתקמפל:
partial void AssignValuesFromResource();
protected void Page_Load(object sender, EventArgs e)
{
AssignValuesFromResource();
}
נוסיף קובץ ריק לפרויקט בשם Default.part.cs הקובץ כרגע נשאר ריק
(ניתן להוסיף גם אוטומטית ע”י ה Code Generator הדבר דורש פירסור של
קובץ הcsproj אבל גם זה כבר חומר ל
פוסט אחר…)
כעת נשלח ל Generator שלנו את הnamespace ושם הclass ולאיזה קובץ לשמור את התוצר
class Program ייראה כך :
namespace CodeGenerator
{
classProgram
{
static void Main(string[] args)
{
GenerateClass();
}
private static void GenerateClass()
{
ClassGenerator cg = new ClassGenerator(
"CodeGeneration", "_Default");
cg.Build();
cg.SaveCode(
@"..\..\..\CodeGeneration\Default.part.cs");
}
}
}
ועכשיו החלק הכי מעניין והוא ה Class Generator:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Web.UI;
using TestCodeGenerator;
namespace CodeGenerator
{
public class ClassGenerator
{
string ClassNamespace;
string ClassName;
public ClassGenerator( string NameSpace,string Name)
{
ClassNamespace = NameSpace;
ClassName=Name;
}
public string SourceCode = null;
public void Build()
{
IndentingWriter tw = new IndentingWriter();
tw.WriteLine("//-----------------------------------"+
"--------------------------//");
tw.WriteLine("// ....... This Class Was Written By "+
"Automated Tool .......... //");
tw.WriteLine("// ............. Class Code Generator "+
"Example ................ //");
tw.WriteLine("//------------------------------------"+
"-------------------------//");
tw.WriteLine();
tw.WriteLine();
tw.WriteLine("namespace {0}", ClassNamespace);
tw.WriteLine("{");
tw.Indent();
tw.WriteLine("public partial class {0}", ClassName);
tw.WriteLine("{");
tw.Indent();
tw.WriteLine("partial void AssignValuesFromResource()");
tw.WriteLine("{");
tw.Indent();
Typetype = typeof(TestCodeGenerator.Default);
PropertyInfo[] properties = type.GetProperties(
BindingFlags.Public | BindingFlags.Static);
var stringProperties = from property in properties
where property.PropertyType ==
typeof(string)
select new Pair(
property.Name,
property.GetValue(null, null));
foreach (var item instringProperties)
{
string[] arr = item.First.ToString().Split('_');
if(arr.Length == 2)
{
tw.WriteLine("{0}.{1}={2}.{3};",
arr[0],arr[1],"Default",item.First.ToString());
}
}
tw.OutDent();
tw.WriteLine("}");//method
tw.OutDent();
tw.WriteLine("}");//class
tw.OutDent();
tw.WriteLine("}"); // namespace
SourceCode = tw.ToString();
return;
}
public void SaveCode(stringPath)
{
if(string.IsNullOrEmpty(SourceCode)) this.Build();
System.IO.StreamWriter sw =
newSystem.IO.StreamWriter(Path);
sw.Write(SourceCode);
sw.Flush();
sw.Close();
}
}
}
מה שבעצם עושה הקוד הוא כותב את ה Partial Class ומשתמש במתודה ששלמה
הגדיר רק שה Iterator במקום להשים את הערכים כותב את קוד ההשמה …
(שיניתי את ה Accses Modifier של ה Resources ל Public אבל באותה מידה ניתן
וזה התוצר של המחולל :
namespace TestCodeGenerator
{
public partial class Default
{
partial voidAssignValuesFromResource()
{
lblBye.Text=Default.lblBye_Text;
lblHello.Text=Default.lblHello_Text;
rfvUserName.ErrorMessage=
Default.rfvUserName_ErrorMessage;
}
}
}
כעת נותר רק לקמפל שוב את הפרויקט ביחד עם הClass החדש.
דוגמא בסיסית ניתן להוריד
מכאן.
הוסיפו לדף ה asp קונטרולים דאגו להוסיף את ה Resource בפורמט המתאים
“ControlID_ControlProperty” קמפלו והריצו את ה ClassGenerator ולאחר מכן
קמפלו והריצו את CodeGeneration.