نوشتن در فایل resx.
برای نوشتن در یک فایل resx. میتوان از کلاس ResXResourceWriter استفاده کرد. این کلاس نیز در کتابخانه System.Windows.Forms در فایل System.Windows.Forms.dll قرار دارد:
System.Resources.ResXResourceWriter
متاسفانه در این کلاس امکان افزودن یا ویرایش یک کلید به تنهایی وجود ندارد. بنابراین برای ویرایش یا اضافه کردن حتی یک کلید کل فایل باید دوباره تولید شود. برای استفاده از این کلاس نیز میتوان به شکل زیر عمل کرد:
private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath)
{
using (var writer = new ResXResourceWriter(filePath))
{
foreach (var resource in resources)
{
writer.AddResource(resource.Key, resource.Value);
}
}
}
در متد فوق از همان کلاس ResXResourceEntry که در قسمت قبل معرفی شد، استفاده شده است. از متد زیر نیز میتوان برای حالت کلی حذف یا ویرایش استفاده کرد:
private static void AddOrUpdate(ResXResourceEntry resource, string filePath)
{
var list = Read(filePath);
var entry = list.SingleOrDefault(l => l.Key == resource.Key);
if (entry == null)
{
list.Add(resource);
}
else
{
entry.Value = resource.Value;
}
Write(list, filePath);
}
در این متد از متدهای Read و Write که در بالا نشان داده شدهاند استفاده شده است.
حذف یک کلید در فایل resx.
برای اینکار میتوان از متد زیر استفاده کرد:
private static void Remove(string key, string filePath)
{
var list = Read(filePath);
list.RemoveAll(l => l.Key == key);
Write(list, filePath);
}
در این متد، از متد Write که در قسمت معرفی شد، استفاده شده است.
راه حل نهایی
قبل از بکارگیری روشهای معرفی شده در این مطلب بهتر است ابتدا یکسری قرارداد بصورت زیر تعریف شوند:
- طبق راهنماییهای موجود در قسمت
قبل یک پروژه جداگانه با عنوان Resources برای نگهداری فایلهای resx. ایجاد شود.
- همواره آخرین نسخه از محتویات موردنیاز از پروژه Resources باید درون فولدری با عنوان Resources در پوشه App_Data قرار داشته باشد.
- آخرین نسخه تولیدی از محتویات موردنیاز پروژه Resource در فولدری با عنوان Defaults در مسیر App_Data\Resources برای فراهم کردن امکان "بازگرداندن به تنظیمات اولیه" وجود داشته باشد.
برای فراهم کردن این موارد بهترین راه حل استفاده از تنظیمات Post-build event command line است. اطلاعات بیشتر درباره Build Eventها در
اینجا.
برای اینکار من از دستور xcopy استفاده کردم که نسخه توسعه یافته دستور copy است. دستورات استفاده شده در این قسمت عبارتند از:
xcopy $(ProjectDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources /e /y /i /exclude:$(ProjectDir)excludes.txt
xcopy $(ProjectDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources\Defaults /e /y /i /exclude:$(ProjectDir)excludes.txt
xcopy $(ProjectDir)$(OutDir)*.* $(SolutionDir)MvcApplication1\App_Data\Resources\Defaults\bin /e /y /i
در دستورات فوق آرگومان e/ برای کپی تمام فولدرها و زیرفولدرها، y/ برای تایید تمام کانفیرم ها، و i/ برای ایجاد خودکار فولدرهای موردنیاز استفاده میشود. آرگومان exclude/ نیز همانطور که از نامش پیداست برای خارج کردن فایلها و فولدرهای موردنظر از لیست کپی استفاده میشود. این آرگومان مسیر یک فایل متنی حاوی لیست این فایلها را دریافت میکند. در تصویر زیر یک نمونه از این فایل و مسیر و محتوای مناسب آن را مشاهده میکنید:
با استفاده از این فایل excludes.txt فولدرهای bin و obj و نیز فایلهای با پسوند user. و vspscc. (مربوط به TFS) و نیز خود فایل excludes.txt از لیست کپی دستور xcopy حذف میشوند و بنابراین کپی نمیشوند. درصورت نیاز میتوانید گزینههای دیگری نیز به این فایل اضافه کنید.
همانطور که در
اینجا اشاره شده است، در تنظیمات Post-build event command line یکسری متغیرهای ازپیش تعریف شده (Macro) وجود دارند که از برخی از آنها در دستوارت فوق استفاده شده است:
(ProjectDir)$ : مسیر کامل و مطلق پروژه جاری به همراه یک کاراکتر \ در انتها
(SolutionDir)$ : مسیر کامل و مطلق سولوشن به همراه یک کاراکتر \ در انتها
(OutDir)$ : مسیر نسبی فولدر Output پروژه جاری به همراه یک کاراکتر \ در انتها
نکته: این دستورات باید در Post-Build Event پروژه Resources افزوده شوند.
با استفاده از این تنظیمات مطمئن میشویم که پس از هر Build آخرین نسخه از فایلهای موردنیاز در مسیرهای تعیین شده کپی میشوند. درنهایت با استفاده از کلاس ResXResourceManager که در زیر آورده شده است، کل عملیات را ساماندهی میکنیم:
public class ResXResourceManager
{
private static readonly object Lock = new object();
public string ResourcesPath { get; private set; }
public ResXResourceManager(string resourcesPath)
{
ResourcesPath = resourcesPath;
}
public IEnumerable<ResXResourceEntry> GetAllResources(string resourceCategory)
{
var resourceFilePath = GetResourceFilePath(resourceCategory);
return Read(resourceFilePath);
}
public void AddOrUpdateResource(ResXResourceEntry resource, string resourceCategory)
{
var resourceFilePath = GetResourceFilePath(resourceCategory);
AddOrUpdate(resource, resourceFilePath);
}
public void DeleteResource(string key, string resourceCategory)
{
var resourceFilePath = GetResourceFilePath(resourceCategory);
Remove(key, resourceFilePath);
}
private string GetResourceFilePath(string resourceCategory)
{
var extension = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en" ? ".resx" : ".fa.resx";
var resourceFilePath = Path.Combine(ResourcesPath, resourceCategory.Replace(".", "\\") + extension);
return resourceFilePath;
}
private static void AddOrUpdate(ResXResourceEntry resource, string filePath)
{
var list = Read(filePath);
var entry = list.SingleOrDefault(l => l.Key == resource.Key);
if (entry == null)
{
list.Add(resource);
}
else
{
entry.Value = resource.Value;
}
Write(list, filePath);
}
private static void Remove(string key, string filePath)
{
var list = Read(filePath);
list.RemoveAll(l => l.Key == key);
Write(list, filePath);
}
private static List<ResXResourceEntry> Read(string filePath)
{
lock (Lock)
{
using (var reader = new ResXResourceReader(filePath))
{
var list = reader.Cast<object>().Cast<DictionaryEntry>().ToList();
return list.Select(l => new ResXResourceEntry(l)).ToList();
}
}
}
private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath)
{
lock (Lock)
{
using (var writer = new ResXResourceWriter(filePath))
{
foreach (var resource in resources)
{
writer.AddResource(resource.Key, resource.Value);
}
}
}
}
}
در این کلاس تغییراتی در متدهای معرفی شده در قسمتهای بالا برای مدیریت دسترسی همزمان با استفاده از بلاک lock ایجاد شده است.
با استفاده از کلاس BuildManager عملیات تولید کتابخانهها مدیریت میشود. (در مورد نحوه استفاده از MSBuild در
اینجا توضیحات کافی آورده شده است):
public class BuildManager
{
public string ProjectPath { get; private set; }
public BuildManager(string projectPath)
{
ProjectPath = projectPath;
}
public void Build()
{
var regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0");
if (regKey == null) return;
var msBuildExeFilePath = Path.Combine(regKey.GetValue("MSBuildToolsPath").ToString(), "MSBuild.exe");
var startInfo = new ProcessStartInfo
{
FileName = msBuildExeFilePath,
Arguments = ProjectPath,
WindowStyle = ProcessWindowStyle.Hidden
};
var process = Process.Start(startInfo);
process.WaitForExit();
}
}
درنهایت مثلا با استفاده از کلاس ResXResourceFileManager مدیریت فایلهای این کتابخانهها صورت میپذیرد:
public class ResXResourceFileManager
{
public static readonly string BinPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", ""));
public static readonly string ResourcesPath = Path.Combine(BinPath, @"..\App_Data\Resources");
public static readonly string ResourceProjectPath = Path.Combine(ResourcesPath, "Resources.csproj");
public static readonly string DefaultsPath = Path.Combine(ResourcesPath, "Defaults");
public static void CopyDlls()
{
File.Copy(Path.Combine(ResourcesPath, @"bin\debug\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true);
File.Copy(Path.Combine(ResourcesPath, @"bin\debug\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true);
Directory.Delete(Path.Combine(ResourcesPath, "bin"), true);
Directory.Delete(Path.Combine(ResourcesPath, "obj"), true);
}
public static void RestoreAll()
{
RestoreDlls();
RestoreResourceFiles();
}
public static void RestoreDlls()
{
File.Copy(Path.Combine(DefaultsPath, @"bin\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true);
File.Copy(Path.Combine(DefaultsPath, @"bin\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true);
}
public static void RestoreResourceFiles(string resourceCategory)
{
RestoreFile(resourceCategory.Replace(".", "\\"));
}
public static void RestoreResourceFiles()
{
RestoreFile(@"Global\Configs");
RestoreFile(@"Global\Exceptions");
RestoreFile(@"Global\Paths");
RestoreFile(@"Global\Texts");
RestoreFile(@"ViewModels\Employees");
RestoreFile(@"ViewModels\LogOn");
RestoreFile(@"ViewModels\Settings");
RestoreFile(@"Views\Employees");
RestoreFile(@"Views\LogOn");
RestoreFile(@"Views\Settings");
}
private static void RestoreFile(string subPath)
{
File.Copy(Path.Combine(DefaultsPath, subPath + ".resx"), Path.Combine(ResourcesPath, subPath + ".resx"), true);
File.Copy(Path.Combine(DefaultsPath, subPath + ".fa.resx"), Path.Combine(ResourcesPath, subPath + ".fa.resx"), true);
}
}
در این کلاس از مفهومی با عنوان resourceCategory برای استفاده راحتتر در ویوها استفاده شده است که بیانگر فضای نام نسبی فایلهای Resource و کلاسهای متناظر با آنهاست که براساس استانداردها باید برطبق مسیر فیزیکی آنها در پروژه باشد مثل Global.Texts یا Views.LogOn. همچنین در متد RestoreResourceFiles نمونه هایی از مسیرهای این فایلها آورده شده است.
پس از اجرای متد Build از کلاس BuildManager، یعنی پس از build پروژه Resource در زمان اجرا، باید ابتدا فایلهای تولیدی به مسیرهای مربوطه در فولدر bin برنامه کپی شده سپس فولدرهای تولیدشده توسط msbuild، حذف شوند. این کار در متد CopyDlls از کلاسResXResourceFileManager انجام میشود. هرچند در این قسمت فرض شده است که فایل csprj. موجود برای حالت debug تنظیم شده است.
نکته: دقت کنید که در این قسمت بلافاصله پس از کپی فایلها در مقصد با توجه به توضیحات ابتدای این مطلب سایت Restart خواهد شد که یکی از ضعفهای عمده این روش به شمار میرود.
سایر متدهای موجود نیز برای برگرداندن تنظیمات اولیه بکار میروند. در این متدها از محتویات فولدر Defaults استفاده میشود.
نکته: درصورت ساخت دوباره اسمبلی و یا بازگرداندن اسمبلیهای اولیه، از آنجاکه وبسایت Restart خواهد شد، بنابراین بهتر است تا صفحه جاری بلافاصله پس از اتمام عملیات،دوباره بارگذاری شود. مثلا اگر از ajax برای اعمال این دستورات استفاده شده باشد میتوان با استفاده از کدی مشابه زیر در پایان فرایند صفحه را دوباره بارگذاری کرد:
window.location.reload();
در قسمت بعدی راه حل بهتری با استفاده از فراهم کردن پرووایدر سفارشی برای مدیریت فایلهای Resource ارائه میشود.