اشتراکها
نظرات مطالب
تبدیل html به pdf با کیفیت بالا
شاید این مثال هم کمک کند
نظرات مطالب
مقابله با XSS ؛ یکبار برای همیشه!
در فایل مثالی که قرار دادید از کتابخانه AntiXSS استفاده نشده!
ابتدا در پروژهی mvc خود یک پوشه با نامی دلخواه (مثلا MyHelpers) بسازید و سپس کلاسی با محتویات زیر را به آن اضافه کنید(نام کلاس به دلخواه Helpers گذاشته شده است) :
public static class Helpers { //در اینجا متدها ی کمکی قرار میگیرند }
1- تبدیل تاریخ میلادی به شمسی با استفاده از کتابخانه ی Persia :
public static MvcHtmlString FarsiDate(this HtmlHelper html, DateTime dateTime) { var tag = new TagBuilder("span"); tag.MergeAttribute("dir", "ltr"); tag.AddCssClass("farsi-date"); tag.SetInnerText(Calendar.ConvertToPersian(dateTime).ToString("W")); return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal)); }
مثال استفاده :
@Html.FarsiDate(news.DateTimeCreated)
2- زمان فارسی :
public static MvcHtmlString FarsiTime(this HtmlHelper html, DateTime dateTime) { var tag = new TagBuilder("span"); tag.MergeAttribute("dir", "ltr"); tag.AddCssClass("farsi-time"); tag.SetInnerText(Calendar.ConvertToPersian(dateTime).ToString("R")); return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal)); }
مثال استفاده :
@Html.FarsiTime(news.DateTimeCreated)
3- تاریخ و زمان فارسی :
public static MvcHtmlString FarsiDateAndTime(this HtmlHelper html, DateTime dateTime) { return MvcHtmlString.Create(FarsiTime(html, dateTime).ToHtmlString() + " , " + FarsiDate(html, dateTime).ToHtmlString()); }
مثال استفاده :
@Html.FarsiDateAndTime(news.DateTimeCreated)
4- زمان گذشته :
public static MvcHtmlString FarsiRemaining(this HtmlHelper html, DateTime dateTime) { var tag = new TagBuilder("span"); tag.MergeAttribute("dir", "rtl"); tag.AddCssClass("farsi-remaining"); tag.SetInnerText(Calendar.ConvertToPersian(dateTime).ToRelativeDateString("TY")); return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal)); }
مثال استفاده :
@Html.FarsiRemaining(news.DateTimeCreated)
5- خلاصهی مطلب با استفاده از کتابخانه ی Html Agility Pack (تعداد کلمات از کلمهی اول یک متن به اندازهی max )
public static string GetSummary(this HtmlHelper html, string text, int max) { string summaryHtml = string.Empty; // load our html document var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(text); int wordCount = 0; foreach (var element in htmlDoc.DocumentNode.ChildNodes) { // inner text will strip out all html, and give us plain text string elementText = element.InnerText; // we split by space to get all the words in this element string[] elementWords = elementText.Split(new char[] { ' ' }); // and if we haven't used too many words ... if (wordCount <= max) { // add the *outer* HTML (which will have proper // html formatting for this fragment) to the summary summaryHtml += element.OuterHtml; wordCount += elementWords.Count() + 1; } else { break; } } return summaryHtml; }
مثال استفاده :
@Html.Raw(Html.GetSummary(news.Content, 60))
6- گرفتن لیست Validation Errorها در ModelState :
public static List<string> GetListOfErrors(this ModelStateDictionary modelState) { var list = modelState.ToList(); var listErrors = new List<string>(); foreach (var keyValuePair in list) { listErrors.AddRange(keyValuePair.Value.Errors.Select(error => error.ErrorMessage)); } return listErrors; }
مثال استفاده (در کنترلر):
var listErrors = ModelState.GetListOfErrors();
از دوستان عزیز خواهشمندم متدهای کمکی مورد استفاده در پروژههای خود را در قسمت نظرات قرار دهند.
نظرات مطالب
استفاده از افزونهی jsTree در ASP.NET MVC
سلام من این تایپیکها رو بررسی کردم ولی نتونستم این فرایندها رو ادغام کنم ببینید من داخل همین پروژه شما دو تا مدل با نامهای Category.cs وDataBaseContext.cs تعریف کردم و کد هاشم بصورت زیر است حالا چه جوری با تابع بازگشتی داخل HomeController بجای populatetree اظلاعات رو دریافت کنم در ضمن فیلدها هم طبق گفته خودتون باید مطابق jstree باشه که در ملها تعریف کردم؟
کد مربوط به DataBaseContext
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Text; namespace MvcJSTree.Models.Entities { public class Category { public Category() { } public Category(string id,string parentid,string orginialid,string text,string position,string href) { this.Id = id; this.ParentId = parentid; this.OriginalId = orginialid; this.Text = text; this.Position = position; this.Href = href; } public string Id { set; get; } public string ParentId { set; get; } public string OriginalId { set; get; } public string Text { set; get; } public string Position { set; get; } public string Href { set; get; } public override string ToString() { return string.Format("{0},{1},{2},{3},{4},{5}",this.Id,this.ParentId,this.OriginalId,this.Text,this.Position,this.Href); } } }
کد مربوط به DataBaseContext
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Text; using System.Data.Entity; namespace MvcJSTree.Models { public class DataBaseContext:System.Data.Entity.DbContext { public DataBaseContext() { } static DataBaseContext () { System.Data.Entity.Database.SetInitializer( new System.Data.Entity.DropCreateDatabaseIfModelChanges<DataBaseContext>()); } public System.Data.Entity.DbSet<DataBaseContext> Category { get; set; } } }
با استفاده از IL Code Weaving علاوه بر مدیریت اعمال تکراری پراکنده در سراسر برنامه مانند ثبت وقایع، مدیریت استثناءها، کش کردن دادهها و غیره، میتوان قابلیتی را به کدهای موجود نیز افزود. برای مثال یک برنامه معمول WCF را درنظر بگیرید.
نیاز است کلاسها و خواص آن توسط ویژگیهای DataContract و DataMember مزین شوند. در این بین نیز اگر یکی فراموش گردد، کار دیباگ برنامه مشکل خواهد شد و در کل حجم بالایی از کدهای تکراری در اینجا باید در مورد تمام کلاسهای مورد نیاز انجام شود. در ادامه قصد داریم تولید این ویژگیها را توسط PostSharp انجام دهیم. به عبارتی یک پوشه خاص به نام DataContracts را ایجاد کرده و کلاسهای خود را به نحوی متداول و بدون اعمال ویژگی خاصی تعریف کنیم. در ادامه پس از کامپایل آن، به صورت خودکار با ویرایش کدهای IL توسط PostSharp، ویژگیهای لازم را به اسمبلی نهایی اضافه نمائیم.
تهیه DataContractAspect جهت اعمال خودکار ویژگیهای DataContract و DataMember
توضیحات مرتبط با قسمتهای مختلف این Aspect سفارشی، به صورت کامنت در کدهای فوق ارائه شدهاند.
برای اعمال آن به سراسر برنامه تنها کافی است به فایل AssemblyInfo.cs پروژه مراجعه و سپس سطر زیر را به آن اضافه کنیم:
به این ترتیب در زمان کامپایل پروژه، Aspect تعریف شده به تمام کلاسهای موجود در فضای نام AOP03.DataContracts اعمال خواهند شد.
در این حالت اگر کلیه ویژگیهای کلاس User فوق را حذف و برنامه را کامپایل کنیم، با مراجعه به برنامه ILSpy میتوان صحت اعمال ویژگیها را به کمک PostSharp بررسی کرد:
using System.Runtime.Serialization; namespace AOP03.DataContracts { [DataContract] public class User { [DataMember] public int Id { set; get; } [DataMember] public string Name { set; get; } } }
تهیه DataContractAspect جهت اعمال خودکار ویژگیهای DataContract و DataMember
using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using PostSharp.Aspects; using PostSharp.Extensibility; using PostSharp.Reflection; namespace AOP03 { [Serializable] //این ویژگی تنها نیاز است به کلاسها اعمال شود [MulticastAttributeUsage(MulticastTargets.Class)] public class DataContractAspect : TypeLevelAspect, IAspectProvider { public IEnumerable<AspectInstance> ProvideAspects(object targetElement) { var targetType = (Type)targetElement; //همان نوعی است که ویژگی جاری به آن اعمال خواهد شد //این سطر معادل است با درخواست تولید ویژگی دیتاکانترکت var introduceDataContractAspect = new CustomAttributeIntroductionAspect( new ObjectConstruction(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes))); //این سطر معادل است با درخواست تولید ویژگی دیتاممبر var introduceDataMemberAspect = new CustomAttributeIntroductionAspect( new ObjectConstruction(typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes))); //در اینجا کار اعمال ویژگی دیتاکانترکت به کلاسی که به عنوان پارامتر متد جاری //دریافت شده انجام خواهد شد yield return new AspectInstance(targetType, introduceDataContractAspect); //مرحله بعد کار اعمال ویژگی دیتاممبر به خواص کلاس است foreach (var property in targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance)) { if (property.CanWrite) yield return new AspectInstance(property, introduceDataMemberAspect); } } } }
برای اعمال آن به سراسر برنامه تنها کافی است به فایل AssemblyInfo.cs پروژه مراجعه و سپس سطر زیر را به آن اضافه کنیم:
[assembly: DataContractAspect(AttributeTargetTypes = "AOP03.DataContracts.*")]
در این حالت اگر کلیه ویژگیهای کلاس User فوق را حذف و برنامه را کامپایل کنیم، با مراجعه به برنامه ILSpy میتوان صحت اعمال ویژگیها را به کمک PostSharp بررسی کرد:
همانطور که در مطلب "NHibernate 3.0 و عدم وابستگی مستقیم به Log4Net" عنوان شد، از اینترفیس جدید IInternalLogger آن میتوان جهت ثبت وقایع داخلی NHibernate استفاده کرد. اگر در این بین صرفا بخواهیم SQL های تولیدی را لاگ کنیم، خلاصهی آن به صورت زیر خواهد بود:
public class LoggerFactory : ILoggerFactory
{
public IInternalLogger LoggerFor(System.Type type)
{
if (type == typeof(NHibernate.Tool.hbm2ddl.SchemaExport))
//log it
}
public IInternalLogger LoggerFor(string keyName)
{
if (keyName == "NHibernate.SQL")
//log it
}
}
سورس کامل این کتابخانهی کوچک را از اینجا میتوانید دریافت کنید. جهت استفاده از آن تنها کافی است چند سطر زیر به فایل app.config یا web.config برنامهی شما اضافه شوند:
<appSettings>
<add key="nhibernate-logger" value="NH3SQLLogger.LoggerFactory, NH3SQLLogger" />
</appSettings>
کلید nhibernate-logger ، به صورت مستقیم توسط NHibernate بررسی میشود و صرف نظر از اینکه از کدامیک از مشتقات NHibernate استفاده میکنید، با تمام آنها کار خواهد کرد.
لازم به ذکر است که اگر برنامهی شما از نوع ASP.NET است، این کتابخانه اطلاعات را در پوشهی استاندارد App_Data ثبت خواهد کرد؛ در غیراینصورت فایلها در کنار فایل اجرایی برنامه تشکیل خواهند شد.
EF اطلاعات تمام migrations اجرا شدهی بر روی بانک اطلاعاتی را در جدولی به نام MigrationHistory__ ذخیره میکند:
اگر به تصویر دقت کنید، در ستون Model آن، اطلاعات باینری ذخیره شدهاند. شاید در وهلهی اول اینطور به نظر برسد که این ستون حاوی هش نقل و انتقالات صورت گرفتهاست؛ اما ... خیر. اطلاعات این ستون، GZip شدهی یک رشتهی XML ایی یا همان EDMX معادل مدلها و نگاشتهای برنامه است.
در کدهای ذیل، نمونه مثالی را از نحوهی خواندن این اطلاعات، مشاهده میکنید:
در اینجا، اولین مدل ثبت شدهی در جدول migrations واکشی شدهاست. سپس به متد decompressMigrationModel برای رمزگشایی نهایی ارسال گردیدهاست.
بر اساس این اطلاعات، EF کاری به ساختار فعلی بانک اطلاعاتی شما ندارد. زمانیکه Add-Migration را اجرا میکنید، به جدول migrations مراجعه کرده، آخرین رکورد آنرا یافته و سپس اطلاعات آنرا از حالت فشرده خارج و XML نهایی آنرا استخراج میکند. در ادامه اطلاعات این فایل XML را با معادل مدلهای فعلی برنامه مقایسه میکند. اگر این دو یکی نبودند، اسکریپت اعمال تغییرات را تولید خواهد کرد.
مورد دیگری که در این جدول حائز اهمیت است، ستون ContextKey آن است: «رفع مشکل Migration با تغییر NameSpace در EF»
اگر به تصویر دقت کنید، در ستون Model آن، اطلاعات باینری ذخیره شدهاند. شاید در وهلهی اول اینطور به نظر برسد که این ستون حاوی هش نقل و انتقالات صورت گرفتهاست؛ اما ... خیر. اطلاعات این ستون، GZip شدهی یک رشتهی XML ایی یا همان EDMX معادل مدلها و نگاشتهای برنامه است.
در کدهای ذیل، نمونه مثالی را از نحوهی خواندن این اطلاعات، مشاهده میکنید:
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.IO; using System.IO.Compression; using System.Xml.Linq; namespace EF_General { public static class InsideMigrations { public static void PrintFirstMigrationModel() { const string connectionString = "Data Source=(local);Initial Catalog=TestDbIdentity;Integrated Security = true"; const string sqlToExecute = "select top 1 model from __MigrationHistory"; using (var connection = new SqlConnection(connectionString)) { connection.Open(); using (var command = new SqlCommand(sqlToExecute, connection)) { using (var reader = command.ExecuteReader()) { if (!reader.HasRows) { throw new KeyNotFoundException("Nothing to display."); } while (reader.Read()) { var model = (byte[]) reader["model"]; var decompressed = decompressMigrationModel(model); Console.WriteLine(decompressed); } } } } } private static XDocument decompressMigrationModel(byte[] bytes) { using (var memoryStream = new MemoryStream(bytes)) { using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { return XDocument.Load(gzipStream); } } } } }
بر اساس این اطلاعات، EF کاری به ساختار فعلی بانک اطلاعاتی شما ندارد. زمانیکه Add-Migration را اجرا میکنید، به جدول migrations مراجعه کرده، آخرین رکورد آنرا یافته و سپس اطلاعات آنرا از حالت فشرده خارج و XML نهایی آنرا استخراج میکند. در ادامه اطلاعات این فایل XML را با معادل مدلهای فعلی برنامه مقایسه میکند. اگر این دو یکی نبودند، اسکریپت اعمال تغییرات را تولید خواهد کرد.
مورد دیگری که در این جدول حائز اهمیت است، ستون ContextKey آن است: «رفع مشکل Migration با تغییر NameSpace در EF»
نظرات مطالب
ایجاد یک Repository در پروژه برای دستورات EF
با سلام من یک معماری طراحی کردم به شکل زیر
نظرتون چیه آقای نصیری
ابتدا یک اینترفیس به شکل زیر دارم
using System; using System.Collections; using System.Linq; namespace Framework.Model { public interface IContext { T Get<T>(Func<T, bool> prediction) where T : class; IEnumerable List<T>(Func<T, bool> prediction) where T : class; void Insert<T>(T entity) where T : class; int Save(); } }
بعد یک کلاس ازش مشتق شده
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Text; namespace Framework.Model { public class Context : IContext { private readonly DbContext _dbContext; public Context(DbContext context) { _dbContext = context; } public T Get<T>(Func<T,bool> prediction) where T : class { var dbSet = _dbContext.Set<T>(); if (dbSet!= null) return dbSet.Single(prediction); throw new Exception(); } public void Insert<T>(T entity) where T : class { var dbSet = _dbContext.Set<T>(); if (dbSet != null) { _dbContext.Entry(entity).State = EntityState.Added; } } public int Save() { return _dbContext.SaveChanges(); } IEnumerable IContext.List<T>(Func<T, bool> prediction) { var dbSet = _dbContext.Set<T>(); if (dbSet != null) return dbSet.Where(prediction).ToList(); throw new Exception(); } } }
سپس یک کلاش context دارم که مستقیما از dbcontext مشتق شده
using System.Data.Entity; using DataModel; namespace Model { public class EFContext : DbContext { public EFContext(string db): base(db) { } public DbSet<Product> Products { get; set; } } }
و سپس کلاس دارم که اومده پیاده سازی کرده context که خودم ساختمو
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; namespace Model { public class Context : Framework.Model.Context { public Context(string db): base(new EFContext(db)) { } } }
در پروژه دیگری اومدم یک کلاس context جدید ساختم
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Biz { public class Context : Model.Context { public Context(string db) : base(db) { } } }
و در کنترلر هم به این شکل ازش استفاده کردم
using System.Web.Mvc; using Framework.Model; namespace ProductionRepository.Controllers { public class BaseController : Controller { public IContext DataContext { get; set; } public BaseController() { DataContext = new Biz.Context(System.Configuration.ConfigurationManager.ConnectionStrings["Database"].ConnectionString); } } }
using System.Web.Mvc; using DataModel; using System.Collections.Generic; namespace ProductionRepository.Controllers { public class ProductController : BaseController { public ActionResult Index() { var x = DataContext.List<Product>(s => s.Name != null); return View(x); } } }
و این هم تست
using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Mvc; namespace TestUnit { [TestFixture] public class Test { [Test] public void IndexShouldListProduct() { var repo = new Moq.Mock<Framework.Model.IContext>(); var products = new List<DataModel.Product>(); products.Add(new DataModel.Product { Id = 1, Name = "asdasdasd" }); products.Add(new DataModel.Product { Id = 2, Name = "adaqwe" }); products.Add(new DataModel.Product { Id = 4, Name = "qewqw" }); products.Add(new DataModel.Product { Id = 5, Name = "qwe" }); repo.Setup(x => x.List<DataModel.Product>(p => p.Name != null)).Returns(products.AsEnumerable()); var controller = new ProductionRepository.Controllers.ProductController(); controller.DataContext = repo.Object; var result = controller.Index() as ViewResult; var model = result.Model as List<DataModel.Product>; Assert.AreEqual(4, model.Count); Assert.AreEqual("", result.ViewName); } } }
مقدمه
با اجرای این مثال نتیجه زیر حاصل میشود:
همانطور که میبنید عملیات replace برای سایر توکنها انجام نمیشود.
در این روش عملیات replace تا زمانیکه تغییری در رشته جاری ایجاد نشود ادامه مییابد. با استفاده از این متد، خروجی مثال قبل درست و به صورت زیر خواهد بود:
استفاده از این متد هم نتیجه درستی برای مثال آخر ارائه میدهد.
این متد نیز نتیجه مشابهی ارائه میکند. حال به مثال زیر توجه کنید:
خروجی صحیح مثال فوق باید به صورت زیر باشد:
درصورتیکه رشتهای که دو متد از سه متد آخر (3 و 4) به عنوان خروجی ارائه میدهند بهصورت زیر است:
برای آخرین متد که ازحلقه while (درواقع با اندیس معکوس) استفاده میکند (5) مثالی که خطای مورد بحث را نشان میدهد به صورت زیر است:
که خروجی اشتباه زیر را برمیگرداند:
درصورتیکه باید مقدار زیر را برگشت دهد:
دلیل رخدادن این خطا اجرای عملیات replace به صورت جداگانه و کامل برای هر توکن، از اول تا آخر برای رشتههای replace شده جاری است که کار را خراب میکند.
خروجی این مثال بهصورت زیر است:
پیادهسازی زیر برای حل این مشکل استفاده میشود.
با استفاده از این متد جدید خروجی مثالهای قبل درست خواهد بود.
که خروجی زیر را ارائه میدهد:
.
در اینجا با استفاده از یک عبارت RegExp پیچیدهتر و کنترل تکرار کاراکترهای { و } در متد سفارشی جایگزینی در دستور replace، پیادهسازی اولیه این ویژگی ارائه شده است..
.
برای حالت prototype نیز داریم:
دقت کنید قسمت ابتدایی این متد که برای بررسی اعتبار آرگومانهای ورودی است، برای سادگی عملیات کامنت شده است. همانطور که میبینید این متد پیادهسازی نسبتا مفصلی دارد و امکانات بیشتری نیز در اختیار برنامه نویسان قرار میدهد. البته سایر متدهای مربوطه بدلیل طولانی بودن در اینجا آورده نشده است. برای مثال امکانات پیشرفتهتری مثل زیر با استفاده از این کتابخانه در دسترس هستند:
همانطور که میبینید این کتابخانه امکانات کاملتری نیز دارد. مثالهای مربوط به این کتابخانه به صورت زیر هستند که تواناییهای نسبتا کامل آنرا نشان میدهد:.
با اینکه زبان برنامه نویسی جاوا اسکریپت زبانی بسیار قدرتمند و با امکانات زیاد است، اما فقدان برخی متدهای کمکی پرمصرف در آن در برخی موارد باعث دردسرهایی میشود. امکانی برای فرمتبندی رشتهها یکی از این نیازهای نسبتا پرکاربرد است.
متدی که در این مطلب قصد توضیح پیادهسازی آنرا داریم، String.format نام دارد که فرایندی مشابه متد متناظر در دات نت را انجام میدهد. همچنین سعی شده است تا نحوه پیادهسازی این متد کمکی از ابتداییترین نمونهها تا نسخههای پیشرفتهتر برای درک بهتر مطلب نشان داده شود.
.
پیادهسازی متد String.format
1. در این پیادهسازی از اولین فرایندی که ممکن است به ذهن یک برنامهنویس خطور کند استفاده شده است. این پیادهسازی بسیار ساده به صورت زیر است:
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
.
2. پیادهسازی مشابهی هم با استفاده از نوع دیگری از حلقه for که تقریبا! مشابه با حلقه foreach در #C است به صورت زیر میتوان درنظر گرفت:
String.format = function () { var s = arguments[0]; for (var arg in arguments) { var i = parseInt(arg); s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
در این متدها ابتدا فرمت واردشده توسط کاربر از لیست آرگومانهای متد خوانده شده و در متغیر s ذخیره میشود. سپس درون یک حلقه به ازای هر توکن موجود در رشته فرمت، یک عملیات replace با مقدار متناظر در لیست آرگومانهای متد انجام میشود. نحوه استفاده از این متد نیز به صورت زیر است:
console.log(String.format("{0} is nice!", "donettips.info"));
هر دو متد خروجی یکسانی دارند، به صورت زیر:
donettips.info is nice!
تا اینجا به نظر میرسد که عملیات بهدرستی پیش میرود. اما اولین و بزرگترین مشکل در این دو متد نحوه کارکردن متد replace در جاوا اسکریپت است. این متد با این نحوه فراخوانی تنها اولین توکن موجود را یافته و عملیات جایگزینی را برای آن انجام میدهد. برای روشنتر شدن موضوع به مثال زیر توجه کنید:
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
donettips.info is very nice! {0} is {1} nice!
.
3. برای حل مشکل فوق میتوان از روش ساده زیر استفاده کرد:
String.format = function () { var original = arguments[0], replaced; for (var i = 0; i < arguments.length - 1; i++) { replaced = ''; while (replaced != original) { original = replaced || original; replaced = original.replace("{" + i + "}", arguments[i + 1]); } } return replaced; };
donettips.info is very nice! donettips.info is very nice!
.
4. راه حل دیگر استفاده از امکانات شی RegExp در دستور replace است. نکته مهم استفاده از modifier کلی یا global (که با حرف g مشخص میشود) در شی تولیدی از RegExp است (^ و ^ و ^، برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار میشود). برای استفاده از این شی متد ما به صورت زیر تغییر میکند:
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]); } return s; };
.
5. روش دیگری که کمی از دو متد قبلی سریعتر اجرا میشود (به دلیل استفاده از حلقه while) به صورت زیر است:
String.format = function () { var s = arguments[0], i = arguments.length - 1; while (i--) { s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]); } return s; };
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
zero:0 {2}:1 two:2
zero:0 two:1 two:2
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
zero:0 one:1 one:2
zero:0 one:1 {1}:2
.
6. برای حل مشکل بالا نیز میتوان از یکی دیگر از امکانات دستور replace استفاده کرد که به صورت زیر است:
String.format = function () { var args = arguments; return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; }); };
در اینجا از قابلیت سفارشیسازی عملیات جایگزینی در دستور replace استفاده شده است. با استفاده از این ویژگی عملیات replace برای هر توکن جداگانه انجام میشود و بنابراین تغییرات اعمالی در حین عملیات تاثیر مستقیمی برای ادامه روند نخواهد گذاشت.
دقت کنید که برای بکاربردن RegExp درون دستور replace به جای تولید یک نمونه از شی RegExp میتوان عبارت مربوطه را نیز مستقیما بکار برد. در اینجا از عبارتی کلی برای دریافت تمامی توکنهای با فرمتی به صورت {عدد} استفاده شده است.
متد سفارشی مربوطه نیز شماره ردیف توکن یافتهشده به همراه خود عبارت یافتهشده را به عنوان آرگومان ورودی دریافت کرده و مقدار متناظر را از لیست آرگومانهای متد اصلی پس از تبدیل شماره ردیف توکن به یک عدد، برگشت میدهد (در اینجا نیز برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار میشود).
برای جلوگیری از تداخل بین آرگومانهای متد اصلی و متد تهیهشده برای سفارشیسازی عملیات جایگزینی، در ایتدای متد اصلی، لیست آرگومانهای آن درون متغیر جداگانهای (args) ذخیره شده است.
با استفاده از این متد خروجی درست نشان داده میشود. حال مثال زیر را درنظر بگیرید:
console.log(String.format("{0} is {1} nice!", "donettips.info"));
donettips.info is undefined nice!
.
7. برای کنترل بیشتر و رفع خطاهای احتمالی در متد بالا، میتوان ابتدا از وجود آرگومان مربوطه در متغیر args اطمینان حاصل کرد تا از جایگزینی مقدار undefined در رشته نهایی جلوگیری کرد. مانند نمونه زیر:
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/{(\d+)}/g, function (match, number) { var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
در فرمت بندی رشتهها برای نمایش خود کاراکتر { یا } از تکرار آنها (یعنی {{ یا }}) استفاده میشود. اما متد ما تا این لحظه این امکان را ندارد. برای مثال:
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}", "zero", "{2}", "two"));
zero:0 {2}:1 two:2, {zero} {{{2}}} {{{two}}} two
8. برای پیادهسازی امکان اشارهشده در بالا میتوان از کد زیر استفاده کرد:
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
این متد خروجی صحیح زیر را برای مثال آخر ارائه میدهد:
zero:0 {2}:1 two:2, {0} {{2}} {{2}} two
پیادهسازی بهصورت یک خاصیت prototype
تمامی متدهای نشان دادهشده تا اینجا بهصورت مستقیم از طریق String.format در دسترس خواهند بود (تعریفی شبیه به متدهای استاتیک در دات نت). درصورتیکه بخواهیم از این متدها به صورت یک خاصیت prototype شی string استفاده کنیم (چیزی شبیه به متدهای instance در اشیای دات نت) میتوانیم از تعریف زیر استفاده کنیم:
String.prototype.format = function () { ... }
تنها فرق مهم این پیادهسازی این است که رشته مربوط به فرمت وارده در این متد از طریق شی this در دسترس است و بنابراین شماره اندیس آرگومانهای متد یکی کمتر از متدهای قبلی است که باید مدنظر قرار گیرد. مثلا برای متد آخر خواهیم داشت:
String.prototype.format = function () { var s = this.toString(), args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } return typeof args[number] != 'undefined' ? args[number] : match; }); };
نکته: در تمامی خواص prototype هر شی در جاوا اسکریپت، متغیر this از نوع object است. بنابراین برای جلوگیری از وقوع هر خطا بهتر است ابتدا آنرا به نوع مناسب تبدیل کرد. مثل استفاده از متد toString در متد فوق که موجب تبدیل آن به رشته میشود.
.
ازآنجاکه نیاز به تغییر اندیس در متد سفارشی عملیات replace وجود ندارد، بنابراین خط مربوط به تبدیل آرگومان number به یک مقدار عددی (با دستور parseInt) حذف شده است و از این متغیر به صورت مستقیم استفاده شده است. در این حالت عملیات تبدیل توسط خود جاوا اسکریپت مدیریت میشود که کار را راحتتر میسازد.
بنابراین متد ما به صورت زیر قابل استفاده است:console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}".format("zero", "{2}", "two"));
.
پیادهسازی با استفاده از توکنهای غیرعددی
برای استفاده از توکنهای غیرعددی میتوانیم به صورت زیر عمل کنیم:
String.format = function () { var s = arguments[0], args = arguments[1]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
String.prototype.format = function () { var s = this.toString(), args = arguments[0]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
با استفاده از این دو متد داریم:
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" })); console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
.
تا اینجا متدهایی نسبتا کامل برای نیازهای عادی برنامهنویسی تهیه شده است. البته کار توسعه این متد برای پشتیبانی از امکانات پیشرفتهتر فرمتبندی رشتهها میتواند ادامه پیدا کند.
.
کتابخانههای موجود
یکی از کاملترین کتابخانههای کار با رشتهها همان کتابخانه معروف Microsoft Ajax Client Libray است که بیشتر امکانات موجود کار با رشتهها در دات نت را در خود دارد. صرفا جهت آشنایی، پیادهسازی متد String.format در این کتابخانه در زیر آورده شده است:
String.format = function String$format(format, args) { /// <summary locid="M:J#String.format" /> /// <param name="format" type="String"></param> /// <param name="args" parameterArray="true" mayBeNull="true"></param> /// <returns type="String"></returns> // var e = Function._validateParams(arguments, [ // { name: "format", type: String }, // { name: "args", mayBeNull: true, parameterArray: true } // ]); // if (e) throw e; return String._toFormattedString(false, arguments); }; String._toFormattedString = function String$_toFormattedString(useLocale, args) { var result = ''; var format = args[0]; for (var i = 0; ; ) { var open = format.indexOf('{', i); var close = format.indexOf('}', i); if ((open < 0) && (close < 0)) { result += format.slice(i); break; } if ((close > 0) && ((close < open) || (open < 0))) { if (format.charAt(close + 1) !== '}') { throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); } result += format.slice(i, close + 1); i = close + 2; continue; } result += format.slice(i, open); i = open + 1; if (format.charAt(i) === '{') { result += '{'; i++; continue; } if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); var brace = format.substring(i, close); var colonIndex = brace.indexOf(':'); var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1; if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid); var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1); var arg = args[argNumber]; if (typeof (arg) === "undefined" || arg === null) { arg = ''; } if (arg.toFormattedString) { result += arg.toFormattedString(argFormat); } else if (useLocale && arg.localeFormat) { result += arg.localeFormat(argFormat); } else if (arg.format) { result += arg.format(argFormat); } else result += arg.toString(); i = close + 1; } return result; }
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001)); // result: 100.00, ¤100.00, 10,000.01 %, 100.0001 console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45))); // result: 02/01/2015, 10:45
آخرین نسخه این کتابخانه از اینجا قابل دریافت است (این متدها درون فایل MicrosoftAjax.debug.js قرار دارند). این کتابخانه دیگر به این صورت و با این نام توسعه داده نمیشود و چند سالی است که تصمیم به توسعه ویژگیهای جدید آن به صورت پلاگینهای jQuery گرفته شده است.
.
کتابخانه دیگری که میتوان برای عملیات فرمتبندی رشتهها در جاوا اسکریپت از آن استفاده کرد، کتابخانه معروف jQuery Validation است. این کتابخانه یک متد نسبتا خوب با نام format برای فرمت کردن رشتهها دارد. نحوه استفاده از این متد به صورت زیر است:
var template = jQuery.validator.format("{0} is not a valid value"); console.log(template("abc")); // result: 'abc is not a valid value'
.
کتابخانه نسبتا کامل دیگری که وجود دارد، با عنوان Stringformat از اینجا قابل دریافت است. برای استفاده از این کتابخانه باید به صورت زیر عمل کرد:
String.format([full format string], [arguments...]); // or: [date|number].format([partial format string]);
// Object path String.format("Welcome back, {username}!", { id: 3, username: "JohnDoe" }); // Result: "Welcome back, JohnDoe!" // Date/time formatting String.format("The time is now {0:t}.", new Date(2009, 5, 1, 13, 22)); // Result: "The time is now 01:22 PM." // Date/time formatting (without using a full format string) var d = new Date(); d.format("hh:mm:ss tt"); // Result: "02:28:06 PM" // Custom number format string String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111); // Result: "Please call me at +46 (0) 111-11 11." // Another custom number format string String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346); // Result: "The last year result was -$5,543.35." // Alignment String.format("|{0,10:PI=0.00}|", Math.PI); // Result: "| PI=3.14|" // Rounding String.format("1/3 ~ {0:0.00}", 1/3); // Result: "1/3 ~ 0.33" // Boolean values String.format("{0:true;;false}", 0); // Result: "false" // Explicitly specified localization // (note that you have to include the .js file for used cultures) msf.setCulture("en-US"); String.format("{0:#,0.0}", 3641.667); // Result: "3,641.7" msf.setCulture("sv-SE"); String.format("{0:#,0.0}", 3641.667); // Result: "3 641,7"
.
یک کتابخانه دیگر نیز از این آدرس قابل دریافت است. این کتابخانه با عنوان String.format نامگذاری شده است. نحوه استفاده از این کتابخانه نیز به صورت زیر است:
//inline arguments String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value'); //returns: 'some string with first value and second value injected argument {number}' //single array String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]); //returns: 'some string with first value and second value injected using array {number}' //single object String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'}); //returns: 'some string with first value and second value injected using {propertyName}'
کتابخانه نسبتا معروف و کامل sprintf نیز در اینجا وجود دارد. این کتابخانه امکانات بسیاری همچون متدهای متناظر در زبان C دارد.
.
منابع