نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر
ایجاد نام جداول به صورت جمع (Pluralized) و داینامیک :
اگه از روش خودکار کردن تعاریف DbSet ها استفاده کرده باشیم چون دیگر در داخل کلاس Context برنامه، Dbset تعریف نمی‌شود معمولا برای نام گذاری جداول که به صورت جمع باشند از اتریبیوت Table استفاده می‌کنیم.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("Users")]
public class User : BaseEntity
{
    public long Id { get; set; }

    [Required]
    public string FullName { get; set; } = null!;
}
برای اینکه نام جداول به صورت خودکار به صورت جمع ایجاد شوند میتوان از این متد استفاده کرد:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;

namespace ProjectName.Common.EfHelpers;

public static class EfToolkit
{
    /// <summary>
    /// ایجاد نام موجودیت‌ها
    /// نام موجودیت اگر اتریبیوت تیبل داشته باشد
    /// از همان نام استفاده میشود و اگر نداشته باشد
    /// نامش جمع بسته میشود
    /// </summary>
    /// <param name="builder"></param>
    public static void MakeTableNamesPluralized(this ModelBuilder builder)
    {
        var entityTypes = builder.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            // Get the CLR type of the entity
            var entityClrType = entityType.ClrType;
            var hasTableAttribute = entityClrType.GetCustomAttribute<TableAttribute>();

            // Apply the pluralized table name for the entity
            if (hasTableAttribute is null)
            {
                // Get the pluralized table name
                var pluralizedTableName = GetPluralizedTableName(entityClrType);
                builder.Entity(entityClrType).ToTable(pluralizedTableName);
            }
        }
    }

    /// <summary>
    /// گرفتن نام تایپ و عوض کردن نام آن از مفرد به جمع
    /// Singular to plural
    /// Category => Categories
    /// Box => Boxes
    /// Bus => Buses
    /// Computer => Computers
    /// </summary>
    /// <param name="entityClrType"></param>
    /// <returns></returns>
    private static string GetPluralizedTableName(Type entityClrType)
    {
        // Example implementation (Note: This is a simple pluralization logic and might not cover all cases)
        var typeName = entityClrType.Name;
        if (typeName.EndsWith("y"))
        {
            // Substring(0, typeName.Length - 1)
            // Range indexer
            var typeNameWithoutY = typeName[..^1];
            return typeNameWithoutY + "ies";
        }

        if (typeName.EndsWith("s") || typeName.EndsWith("x"))
        {
            return typeName + "es";
        }
        return typeName + "s";
    }
}
نحوه فراخوانی در متد OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
    // it should be placed here, otherwise it will rewrite the following settings!
    base.OnModelCreating(builder);
    builder.RegisterAllEntities(typeof(BaseEntity));
    builder.MakeTableNamesPluralized();
    builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
هر موجودیتی که اتریبیوت Table داشته باشد از همان نام برای جدول استفاده می‌شود در غیر اینصورت نام انتیتی به صورت جمع ایجاد می‌گردد.
قبل از اینکه نام جداول نیز جمع بسته شود تمامی موجودیت‌ها به صورت خودکار اضافه می‌شوند.
public static class EfToolkit
{
    /// <summary>
    /// ثبت تمامی انتیتی‌ها
    /// <param name="builder"></param>
    /// <param name="type"></param>
    /// </summary>
    public static void RegisterAllEntities(this ModelBuilder builder, Type type)
    {
        var entities = type.Assembly.GetTypes()
            .Where(x => x.BaseType == type);
        foreach (var entity in entities)
            builder.Entity(entity);
    }
}  
هر کلاسی در لایه موجودیت‌ها از BaseEntity ارث بری کرده باشد به عنوان یک Entity شناخته می‌شود.
نظرات مطالب
طریقه بررسی صحت کدملی به کمک متدهای الحاقی
با تشکر از شما متد فوق بصورت زیر اصلاح شد و کلاس مربوطه بروزرسانی شده است:
        /// <summary>
        /// Validate IR National Code
        /// </summary>
        /// <param name="nationalcode">National Code</param>
        /// <param name="lastNumber">Last Number Of National Code</param>
        /// <returns></returns>
        public static bool IsValidNationalCode(this string nationalcode, out int lastNumber)
        {
            lastNumber = -1;
            if (!nationalcode.IsItNumber()) return false;
            var invalid = new[]
                                    {
                                        "0000000000", "1111111111", "2222222222", "3333333333", "4444444444", "5555555555",
                                        "6666666666", "7777777777", "8888888888", "9999999999"
                                    };
            if (invalid.Contains(nationalcode)) return false;
            var array = nationalcode.ToCharArray();
            if (array.Length != 10) return false;
            var j = 10;
            var sum = 0;
            for (var i = 0; i < array.Length - 1; i++)
            {
                sum += Int32.Parse(array[i].ToString(CultureInfo.InvariantCulture)) * j;
                j--;
            }

            var diff = sum % 11;

            if (diff < 2)
            {
                lastNumber = diff;
                return diff == Int32.Parse(array[9].ToString(CultureInfo.InvariantCulture));
            }
            var temp = Math.Abs(diff - 11);
            lastNumber = temp;
            return temp == Int32.Parse(array[9].ToString(CultureInfo.InvariantCulture));
        }
البته من فرض کردم که کد ملی 0010350829 معتبر و کد 0010350822 نامعتبر است. آیا شما مطمئن هستید که کد 0010350829 معتبر است؟
نظرات مطالب
SQL Antipattern #2
نیازی به استفاده از Id نیست. مسیر زیر را در نظر بگیرید:
/// Example: "00001.00042.00005".
مسیر بالا متناظر با نودی در درخت می‌باشد که در عمق 2 بوده و فرزند 5 ام مربوط به نود 00001.00042 می‌باشد. اگر نیاز باشد فرزند جدیدی به نود 00001.00042 اضافه شود، باید ابتدا مسیر آخرین فرزند آن یعنی الگوی بالایی واکشی شده و سپس مسیر جدیدی برای نود جدید به شکل زیر تشکیل شود:
/// Example: "00001.00042.00006".
دقیقا مشابه به کاری می‌باشد که نوع داده hierarchyid موجود در Sql Server انجام می‌دهد. با این روش دقیقا مشخص می‌باشد که نود x در چه مکانی قرار داد.

مدیریت واحدهای سازمانی
یکسری متد کمکی هم برای مدیریت فیلد Path در نظر گرفته شده است.
    public class OrganizationalUnit : TrackableEntity<User>, IHasRowVersion, IPassivable
    {
        #region Constants

        /// <summary>
        /// Maximum depth of an UO hierarchy.
        /// </summary>
        public const int MaxDepth = 16;

        /// <summary>
        /// Length of a code unit between dots.
        /// </summary>
        public const int PathUnitLength = 5;

        /// <summary>
        /// Maximum length of the <see cref="Path"/> property.
        /// </summary>
        public const int MaxPathLength = MaxDepth * (PathUnitLength + 1) - 1;

        public const char HierarchicalDisplayNameSeperator = '»';

        #endregion

        #region Properties

        public string Name { get; set; }
        public string NormalizedName { get; set; }
        public string HierarchicalDisplayName { get; set; }
        /// <summary>
        /// Hierarchical Path of this organization unit.
        /// Example: "00001.00042.00005".
        /// It's changeable if OU hierarch is changed.
        /// </summary>
        public string Path { get; set; }
        public bool IsActive { get; set; } = true;
        public byte[] RowVersion { get; set; }

        #endregion

        #region Navigation Properties

        public OrganizationalUnit Parent { get; set; }
        public long? ParentId { get; set; }
        public ICollection<OrganizationalUnit> Children { get; set; } = new HashSet<OrganizationalUnit>();
        public ICollection<UserOrganizationalUnit> UserOrganizationalUnits { get; set; } =
            new HashSet<UserOrganizationalUnit>();

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates path for given numbers.
        /// Example: if numbers are 4,2 then returns "00004.00002";
        /// </summary>
        /// <param name="numbers">Numbers</param>
        public static string CreatePath(params int[] numbers)
        {
            if (numbers.IsNullOrEmpty())
            {
                return null;
            }

            return numbers.Select(number => number.ToString(new string('0', PathUnitLength))).JoinAsString(".");
        }

        /// <summary>
        /// Appends a child path to a parent path. 
        /// Example: if parentPath = "00001", childPath = "00042" then returns "00001.00042".
        /// </summary>
        /// <param name="parentPath">Parent path. Can be null or empty if parent is a root.</param>
        /// <param name="childPath">Child path.</param>
        public static string AppendPath(string parentPath, string childPath)
        {
            if (childPath.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(childPath), "childPath can not be null or empty.");
            }

            if (parentPath.IsNullOrEmpty())
            {
                return childPath;
            }

            return parentPath + "." + childPath;
        }

        /// <summary>
        /// Gets relative path to the parent.
        /// Example: if path = "00019.00055.00001" and parentPath = "00019" then returns "00055.00001".
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="parentPath">The parent path.</param>
        public static string GetRelativePath(string path, string parentPath)
        {
            if (path.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(path), "Path can not be null or empty.");
            }

            if (parentPath.IsNullOrEmpty())
            {
                return path;
            }

            if (path.Length == parentPath.Length)
            {
                return null;
            }

            return path.Substring(parentPath.Length + 1);
        }

        /// <summary>
        /// Calculates next path for given path.
        /// Example: if code = "00019.00055.00001" returns "00019.00055.00002".
        /// </summary>
        /// <param name="path">The path.</param>
        public static string CalculateNextPath(string path)
        {
            if (path.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(path), "Path can not be null or empty.");
            }

            var parentPath = GetParentPath(path);
            var lastUnitPath = GetLastUnitPath(path);

            return AppendPath(parentPath, CreatePath(Convert.ToInt32(lastUnitPath) + 1));
        }

        /// <summary>
        /// Gets the last unit path.
        /// Example: if path = "00019.00055.00001" returns "00001".
        /// </summary>
        /// <param name="path">The path.</param>
        public static string GetLastUnitPath(string path)
        {
            if (path.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(path), "Path can not be null or empty.");
            }

            var splittedPath = path.Split('.');
            return splittedPath[splittedPath.Length - 1];
        }

        /// <summary>
        /// Gets parent path.
        /// Example: if path = "00019.00055.00001" returns "00019.00055".
        /// </summary>
        /// <param name="path">The path.</param>
        public static string GetParentPath(string path)
        {
            if (path.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(path), "Path can not be null or empty.");
            }

            var splittedPath = path.Split('.');
            if (splittedPath.Length == 1)
            {
                return null;
            }

            return splittedPath.Take(splittedPath.Length - 1).JoinAsString(".");
        }

        #endregion
    }

البته یک ویو نمایشی برای حالت درختی هم بهتر است داشته باشید.


یکسری متد DomainService

       public virtual async Task<string> GetNextChildPathAsync(long? parentId)
        {
            var lastChild = await GetLastChildOrNullAsync(parentId).ConfigureAwait(false);
            if (lastChild == null)
            {
                var parentPath = parentId != null ? await GetPathAsync(parentId.Value).ConfigureAwait(false) : null;
                return OrganizationalUnit.AppendPath(parentPath, OrganizationalUnit.CreatePath(1));
            }

            return OrganizationalUnit.CalculateNextPath(lastChild.Path);
        }

        public async Task<string> GetNextChildHierarchicalDisplayNameAsync(string name, long? parentId)
        {
            var parent = parentId != null
                ? await _organizationalUnits.SingleOrDefaultAsync(a => a.Id == parentId.Value).ConfigureAwait(false)
                : null;

            return parent == null
                ? name
                : $"{parent.HierarchicalDisplayName} {OrganizationalUnit.HierarchicalDisplayNameSeperator} {name}";
        }

        public virtual async Task<OrganizationalUnit> GetLastChildOrNullAsync(long? parentId)
        {
            return await _organizationalUnits.OrderByDescending(c => c.Path)
                .FirstOrDefaultAsync(ou => ou.ParentId == parentId).ConfigureAwait(false);
        }

        public virtual async Task<string> GetPathAsync(long id)
        {
            Guard.ArgumentNotZero(id, nameof(id));
            var organizationalUnit = await _organizationalUnits.SingleOrDefaultAsync(ou => ou.Id == id).ConfigureAwait(false);
            if (organizationalUnit == null)
            {
                throw new KeyNotFoundException();
            }
            return organizationalUnit.Path;
        }

        public async Task<List<OrganizationalUnit>> FindChildrenAsync(long? parentId, bool recursive = false)
        {
            if (!recursive)
            {
                return await _organizationalUnits.Where(ou => ou.ParentId == parentId).ToListAsync().ConfigureAwait(false);
            }

            if (!parentId.HasValue)
            {
                return await _organizationalUnits.ToListAsync().ConfigureAwait(false);
            }

            var path = await GetPathAsync(parentId.Value).ConfigureAwait(false);

            return await _organizationalUnits.Where(
                ou => ou.Path.StartsWith(path) && ou.Id != parentId.Value).ToListAsync().ConfigureAwait(false);
        }

        public virtual async Task MoveAsync(long id, long? parentId)
        {
            Guard.ArgumentNotZero(id, nameof(id));
            var organizationalUnit = await _organizationalUnits.SingleOrDefaultAsync(ou => ou.Id == id).ConfigureAwait(false);
            if (organizationalUnit == null || organizationalUnit.ParentId == parentId)
            {
                return;
            }

            //Should find children before Path change
            var children = await FindChildrenAsync(id, true).ConfigureAwait(false);

            //Store old Path of OU
            var oldPath = organizationalUnit.Path;

            //Move OU
            organizationalUnit.Path = await GetNextChildPathAsync(parentId).ConfigureAwait(false);
            organizationalUnit.ParentId = parentId;

            //Update Children Paths
            foreach (var child in children)
            {
                child.Path = OrganizationalUnit.AppendPath(organizationalUnit.Path, OrganizationalUnit.GetRelativePath(child.Path, oldPath));
            }
        }



نظرات مطالب
تولید فایل‌های اکسل حرفه‌ای بدون نیاز به نصب مجموعه‌ی آفیس
پروژه PdfReport، برای تهیه خروجی اکسل، از همین کتابخانه استفاده می‌کند. متدی که در آن تصویر را به یک سلول اضافه می‌کند، به شرح زیر است (پارامتر data آن محتوای تصویر است؛ مثلا File.ReadAllBytes):
        void addImageFromStream(byte[] data)
        {
            if (data == null) return;
            using (var ms = new MemoryStream(data))
            {
                var image = Image.FromStream(ms);
                _worksheet.Row(_row).Height = (image.Height + 1).Pixel2RowHeight();
                _worksheet.Column(_col).Width = _worksheet.Pixel2ColumnWidth(image.Width + 1);
                var picture = _worksheet.Drawings.AddPicture("pic" + _row + _col, image);
                picture.From.Column = _col - 1;
                picture.From.Row = _row - 1;
                picture.From.ColumnOff = 2.Pixel2Mtu();
                picture.From.RowOff = 2.Pixel2Mtu();
                picture.SetSize(image.Width, image.Height);
            }
        }
بازخوردهای پروژه‌ها
مشکل با نوشتن تابع تجمعی سفارشی(از طریق پیاده سازی IAggregateFunction)
با سلام؛ ضمن تشکر از اینکه تجربیاتتون رو رایگان در اختیار بقیه قرار می‌دید، به شخصه خیلی استفاده کردم.
سوالی داشتم در رابطه با پیاده سازی اینترفیس IAggregateFunction  
من میخوام یه گزارش بنویسم که تو اون ستون آخرش میخواد مانده تجمعی را حساب کنه.
بنابراین میخواستم با پیاده سازی این اینترفیس و همچنین بازنویسی متد ProcessingBoundary آخرین مقدار رو به عنوان خروجی تابع تجمعی ارسال کنم.
public object ProcessingBoundary(IList<SummaryCellData> columnCellsSummaryData)
        {
            if (columnCellsSummaryData == null || !columnCellsSummaryData.Any()) return 0;

            var list = columnCellsSummaryData;
            var lastItem = list.Last();

            return lastItem.CellData.PropertyValue;

        }
در پروژه‌ی دیگه ای این اینترفیس رو پیاده سازی کردم و مشکلی نبود ولی در پروژه جاری
که پروژه ایست با مشخصات:
نوع پروژه : WPF with MVVM
از Prism و Unity هم برای ماژولار شدن استفاده کردم.
خطای زیر رو میده : 
Method 'set_DisplayFormatFormula' in type 'Hezareh.Modules.Accounting.Reporting.ViewModels.MySampleAggregateFunction' from assembly 'Hezareh.Modules.Accounting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
در صورتی که اینترفیس IAggregateFunction به صورت کامل توسط کلاس  MySampleAggregateFunction پیاده سازی شده است و این هم کد کامل کلاس که همون کد مثال Sum خودتونه، که فقط تابع  ProcessingBoundary رو تغییر دادم. این هم کد کاملش :
 public class MySampleAggregateFunction : IAggregateFunction
    {
        public MySampleAggregateFunction()
        {

        }

        /// <summary>
        /// Fires before rendering of this cell.
        /// Now you have time to manipulate the received object and apply your custom formatting function.
        /// It can be null.
        /// </summary>
        public Func<object, string> DisplayFormatFormula { set; get; }

        #region Fields (6)

        double _groupAvg;
        long _groupRowNumber;
        double _groupSum;
        double _overallAvg;
        long _overallRowNumber;
        double _overallSum;

        #endregion Fields

        #region Properties (2)

        /// <summary>
        /// Returns current groups' aggregate value.
        /// </summary>
        public object GroupValue
        {
            get { return _groupAvg; }
        }

        /// <summary>
        /// Returns current row's aggregate value without considering the presence of the groups.
        /// </summary>
        public object OverallValue
        {
            get { return _overallAvg; }
        }

        #endregion Properties

        #region Methods (4)

        // Public Methods (1) 

        /// <summary>
        /// Fires after adding a cell to the main table.
        /// </summary>
        /// <param name="cellDataValue">Current cell's data</param>
        /// <param name="isNewGroupStarted">Indicated starting a new group</param>
        public void CellAdded(object cellDataValue, bool isNewGroupStarted)
        {
            checkNewGroupStarted(isNewGroupStarted);

            _overallRowNumber++;
            _groupRowNumber++;

            double cellValue;
            if (double.TryParse(cellDataValue.ToSafeString(), NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out cellValue))
            {
                groupAvg(cellValue);
                overallAvg(cellValue);
            }
        }
        // Private Methods (3) 

        private void checkNewGroupStarted(bool newGroupStarted)
        {
            if (newGroupStarted)
            {
                _groupRowNumber = 0;
                _groupAvg = 0;
                _groupSum = 0;
            }
        }

        private void groupAvg(double cellValue)
        {
            _groupSum += cellValue;
            _groupAvg = _groupSum / _groupRowNumber;
        }

        private void overallAvg(double cellValue)
        {
            _overallSum += cellValue;
            _overallAvg = _overallSum / _overallRowNumber;
        }

        /// <summary>
        /// A general method which takes a list of data and calculates its corresponding aggregate value.
        /// It will be used to calculate the aggregate value of each pages individually, with considering the previous pages data.
        /// </summary>
        /// <param name="columnCellsSummaryData">List of data</param>
        /// <returns>Aggregate value</returns>
        public object ProcessingBoundary(IList<SummaryCellData> columnCellsSummaryData)
        {
            if (columnCellsSummaryData == null || !columnCellsSummaryData.Any()) return 0;

            var list = columnCellsSummaryData;
            var lastItem = list.Last();

            return lastItem.CellData.PropertyValue;

        }
        #endregion Methods

    }
و همچنین این هم تنظیمات ستونی که از این تابع تجمعی میخوام استفاده کنم.
columns.AddColumn(column =>
                {
                    column.PropertyName<VoucherRowPrintViewModel>(x => x.CaclulatedRemains);
                    column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Right);
                    column.IsVisible(true);
                    column.Order(5);
                    column.Width(1.5f);
                    column.ColumnItemsTemplate(template =>
                    {
                        template.TextBlock();
                        template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                    });
                    column.AggregateFunction(aggregateFunction =>
                    {
                        aggregateFunction.CustomAggregateFunction(new MySampleAggregateFunction());
                        aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                    });
                    column.HeaderCell("مانده");
                });
ممنون میشم در صورت امکان کمکم کنید.
مطالب
تهیه قالب برای ایمیل‌های ارسالی یک برنامه ASP.Net

فرض کنید ایمیل اطلاع رسانی برنامه ASP.Net شما قرار است ایمیل زیر را پس از تکمیل یک فرم ارسال کند.


برای ارسال این قالب که مطابق تصویر هر بار باید سه برچسب آن تغییر کند چه راهی را پیشنهاد می‌دهید؟

راه اول: (راه متداول)
این فرم را در یک html editor درست کرده و جای سه برچسب را خالی می‌گذاریم. سپس html مورد نظر را در تابع ارسال ایمیل خود به صورت یک رشته تعریف نموده و جاهای خالی را پر خواهیم کرد. مثلا:
            string Name = "علی";
string Desc = "منابع مورد نیاز";
int Number = 10;
string content =
"<div dir=\"rtl\" style=\"text-align: right; font-family:Tahoma; font-size:9pt\">" +
"با سلام<br />" +
"احتراما آقای/خانم" +
Name +
"&nbsp;درخواست چاپ" +
Desc +
"&nbsp;دارای" +
Number +
"&nbsp;صفحه را داده‌اند. لطفا جهت تائید درخواست ایشان به برنامه مراجعه بفرمائید.<br />" +
"<br />" +
"با تشکر</div>";

ایرادات:
  • الف) امکان مشاهده شکل نهایی تا زمانیکه ایمیل مورد نظر را دریافت نکرده باشیم، وجود ندارد.
  • ب) اعمال تغییرات جدید به این فرمت رشته‌ای مشکل است. همیشه استفاده از ابزارهای بصری برای بهبود کار کمک بزرگی هستند که در این حالت از آن‌ها محروم خواهیم شد.
  • ج)اگر تغییر رسیده جدید، درخواست اضافه کردن لیست پرینت‌های قبلی این شخص بود چه باید کرد؟ آیا جدول مورد نظر را باید به صورت دستی ایجاد و باز هم به صورت یک رشته به این مجموعه اضافه کرد؟ در این حالت از کنترل‌های استانداردی مانند GridView و امثال آن محروم خواهیم شد.
  • د) هر بار تغییر، نیاز به recompile برنامه دارد.
بنابراین همانطور که مشاهده می‌کنید، نگهداری این روش مشکل است.

راه دوم: استفاده از قالب‌ها

خوشبختانه در ASP.Net امکان رندر کردن کنترل‌ها به صورت یک string‌ نیز موجود است. در مثال ما نیاز است تا چندین کنترل در کنار هم قرار گیرند تا شکل نهایی را ایجاد کنند. بنابراین می‌توان تمام آنها را در یک یوزر کنترل قرار داد. سپس باید کل یوزر کنترل را به صورت یک رشته، رندر کرد که در ادامه به آن خواهیم پرداخت.

اگر قالب فوق را بخواهیم در یک یوزر کنترل طراحی کنیم، سورس صفحه html یوزر کنترل به صورت زیر خواهد بود (فایل WebUserControl1.ascx) :
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs"
Inherits="testWebForms87.WebUserControl1" %>
<div dir="rtl" style="text-align: right; font-family:Tahoma; font-size:9pt">
با سلام<br />
احتراما آقای/خانم
<asp:Label ID="lblName" runat="server"></asp:Label>
&nbsp;درخواست چاپ
<asp:Label ID="lblDesc" runat="server"></asp:Label>
&nbsp;دارای
<asp:Label ID="lblNumber" runat="server"></asp:Label>
&nbsp;صفحه را داده‌اند. لطفا جهت تائید درخواست ایشان به برنامه مراجعه بفرمائید.<br />
<br />
با تشکر</div>

همچنین می‌توان مقادیر برچسب‌ها را از طریق خواصی که برای یوزر کنترل تعریف خواهیم کرد، در تابع ارسال ایمیل خود مقدار دهی نمائیم. در این حالت در سورس صفحه یوزر کنترل داریم (فایل WebUserControl1.ascx.cs) :
        public string Name { get; set; }
public int Number { get; set; }
public string Desc { get; set; }

protected void Page_Load(object sender, EventArgs e)
{
lblNumber.Text = Number.ToString();
lblName.Text = Name;
lblDesc.Text = Desc;
}

تا اینجا طراحی اولیه محتوای ایمیل به پایان می‌رسد. دیگر از آن رشته کذایی خبری نیست و همچنین می‌توان از designer ویژوال استودیو برای طراحی بصری قالب مورد نظر استفاده کرد که این مزیت بزرگی است و در آینده اگر نیاز به تغییر متن ایمیل ارسالی وجود داشت، تنها کافی است فایل ascx ما ویرایش شود (بدون نیاز به کامپایل مجدد پروژه). یا در اینجا به سادگی برای مثال می‌توان یک GridView را تعریف، طراحی و bind کرد.

مرحله بعد، رندر کردن خودکار این یوزر کنترل و سپس تبدیل محتوای حاصل به یک رشته است. برای این منظور از تابع زیر می‌توان کمک گرفت (برای مثال تعریف شده در کلاس دلخواه CLoadUC) :
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Web;
using System.Web.UI;

/// <summary>
/// تبدیل یک یوزر کنترل به معادل اچ تی ام ال آن
/// </summary>
/// <param name="path">مسیر یوزر کنترل</param>
/// <param name="properties">لیست خواص به همراه مقادیر مورد نظر</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
public static string RenderUserControl(string path,
List<KeyValuePair<string,object>> properties)
{
Page pageHolder = new Page();

UserControl viewControl =
(UserControl)pageHolder.LoadControl(path);

Type viewControlType = viewControl.GetType();

foreach (var pair in properties)
{
PropertyInfo property =
viewControlType.GetProperty(pair.Key);

if (property != null)
{
property.SetValue(viewControl, pair.Value, null);
}
else
{
throw new NotImplementedException(string.Format(
"UserControl: {0} does not have a public {1} property.",
path, pair.Key));
}
}

pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}

ماخذ اصلی تابع فوق این آدرس است.
که البته تابع نهایی آنرا کمی اصلاح کردم تا بتوان لیستی از خواص پابلیک یک یوزر کنترل را به آن پاس کرد و محدود به یک خاصیت نبود.
اکنون استفاده از یوزر کنترلی که تاکنون طراحی کرده‌ایم به سادگی زیر است:
            List<KeyValuePair<string, object>> lst =
new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("Name", "علی"),
new KeyValuePair<string, object>("Number", 10),
new KeyValuePair<string, object>("Desc", "منابع مورد نیاز")
};

string content = CLoadUC.RenderUserControl("WebUserControl1.ascx", lst);

ابتدا لیستی از خواص پابلیک یوزر کنترل تهیه شده و مقدار دهی می‌شوند . سپس مسیر فایل یوزر کنترل به همراه این لیست به تابع رندر کردن نهایی پاس خواهند شد. حاصل، رشته‌ html محتوای ایمیل ما را تشکیل خواهد داد.

مطالب
استفاده از گرافیک برداری در iTextSharp


در مورد «ترسیم اشکال گرافیکی با iTextSharp» مطلب مفصلی را در اینجا می‌توانید مطالعه کنید؛ که قصد تکرار مجدد آن‌را ندارم. فقط این روش‌ها یک مشکل مهم دارند : «کار من ترسیم این نوع اشکال گرافیکی نیست!». مثلا من الان نیاز دارم در گزارشی، بجای ستون Boolean آن در مواردی که مقدار ردیف true هست، مثلا یک «چک مارک» را بجای true/false یا بله/خیر نمایش دهم. می‌شود اینکار را با یک تصویر معمولی هم انجام داد. فقط حجم فایل حاصل، بیش از اندازه بالا می‌رود و همچنین نتیجه استفاده از یک bitmap، به زیبایی بکارگیری گرافیک برداری با قابلیت تغییر ابعاد بدون نگرانی در مورد از دست دادن کیفیت آن، نیست.

خوشبختانه هستند سایت‌هایی که این نوع تصاویر برداری را به رایگان ارائه دهند؛ برای مثال: سایت Openclipart، تعداد قابل توجهی فایل با فرمت SVG دارد. فایل‌های SVG را مستقیما نمی‌توان توسط iTextSharp استفاده کرد؛ اما یک سری برنامه‌ی کمکی برای تبدیل فرمت SVG به مثلا XAML (قابل توجه برنامه نویس‌های WPF و Silverlight) یا WMF و غیره وجود دارد. برای نمونه iTextSharp امکان خواندن فایل‌های WMF را داشته (توسط همان متد معروف Image.GetInstance آن) و اینبار این Image حاصل، یک تصویر برداری است و نه یک Bitmap.
در بین این برنامه‌های تبدیل کننده‌ فرمت‌های برداری، برنامه‌ی معروف و سورس باز Inkscape، در صدر محبوبیت قرار دارد. تنها کافی است فایل SVG خود را در آن گشوده و سپس به انواع و اقسام فرمت‌های دیگر تبدیل (Save As) کنید:



یکی از فرمت‌های جالب خروجی آن، Tex است (مربوط به یک برنامه ادیتور، به نام LaTeX است). فرض کنید یکی از این «چک مارک»های سایت Openclipart را در برنامه Inkscape باز کرده‌ و سپس با فرمت Tex ذخیره کرده‌ایم. خروجی فایل متنی آن مثلا به شکل زیر خواهد بود:

%LaTeX with PSTricks extensions
%%Creator: 0.48.0
%%Please note this file requires PSTricks extensions
\psset{xunit=.5pt,yunit=.5pt,runit=.5pt}
\begin{pspicture}(190,190)
{
\newrgbcolor{curcolor}{0 0 0}
\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor]
{
\newpath
\moveto(52.73079005,101.89500456)
\curveto(31.29686559,101.89500456)(13.84575258,84.04652127)(13.8457479,62.12456369)
\curveto(13.8457479,40.20259605)(31.29686559,22.35412714)(52.73079005,22.35412235)
\curveto(74.16470983,22.35412235)(91.6158322,40.20259605)(91.61582751,62.12456369)
\curveto(91.61582751,71.60188248)(88.48023622,80.07729424)(83.15553076,87.02034164)
\lineto(79.49425309,82.58209245)
\curveto(84.13622847,76.73639073)(85.95313131,70.24630402)(85.95313131,62.12456369)
\curveto(85.95313131,43.33817595)(71.09893654,28.1547277)(52.73079005,28.1547277)
\curveto(34.36263419,28.15473249)(19.50844879,43.33817595)(19.50844879,62.12456369)
\curveto(19.50844879,80.91094185)(34.36264355,96.10336589)(52.73079005,96.10336589)
\curveto(58.55122776,96.10336589)(62.90459266,95.2476225)(67.65721002,92.5630926)
\lineto(71.13570481,97.23509821)
\curveto(65.57113223,100.3782653)(59.52269945,101.89500456)(52.73079005,101.89500456)
\closepath
}
}
{
\newrgbcolor{curcolor}{0 0 0}
\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor]
{
\newpath
\moveto(38.33889376,67.35513328)
\curveto(39.90689547,67.35509017)(41.09296342,66.03921993)(41.89711165,63.40748424)
\curveto(43.50531445,58.47289182)(44.65118131,56.00562195)(45.33470755,56.0056459)
\curveto(45.85735449,56.00562195)(46.40013944,56.41682961)(46.96305772,57.23928802)
\curveto(58.2608517,75.74384316)(68.7143666,90.71198997)(78.32362116,102.14379168)
\curveto(80.81631349,105.10443984)(84.77658911,106.58480942)(90.20445269,106.58489085)
\curveto(91.49097185,106.58480942)(92.35539361,106.46145048)(92.79773204,106.21480444)
\curveto(93.23991593,105.96799555)(93.4610547,105.65958382)(93.46113432,105.28956447)
\curveto(93.4610547,104.71379041)(92.7976618,103.58294901)(91.47094155,101.89705463)
\curveto(75.95141033,82.81670149)(61.55772504,62.66726353)(48.28984822,41.44869669)
\curveto(47.36506862,39.96831273)(45.47540199,39.22812555)(42.62081088,39.22813992)
\curveto(39.72597184,39.22812555)(38.0172148,39.35149407)(37.49457722,39.5982407)
\curveto(36.12755286,40.2150402)(34.51931728,43.36081778)(32.66987047,49.03557823)
\curveto(30.57914689,55.32711903)(29.53378743,59.27475848)(29.53381085,60.87852533)
\curveto(29.53378743,62.60558406)(30.94099884,64.27099685)(33.75542165,65.87476369)
\curveto(35.48425582,66.86164481)(37.01207517,67.35509017)(38.33889376,67.35513328)
}
}

\end{pspicture}


استفاده از این خروجی در iTextSharp بسیار ساده است. برای مثال:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace HtmlToPdf
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

var cb = pdfWriter.DirectContent;

cb.MoveTo(52.73079005f, 101.89500456f);
cb.CurveTo(31.29686559f, 101.89500456f, 13.84575258f, 84.04652127f, 13.8457479f, 62.12456369f);
cb.CurveTo(13.8457479f, 40.20259605f, 31.29686559f, 22.35412714f, 52.73079005f, 22.35412235f);
cb.CurveTo(74.16470983f, 22.35412235f, 91.6158322f, 40.20259605f, 91.61582751f, 62.12456369f);
cb.CurveTo(91.61582751f, 71.60188248f, 88.48023622f, 80.07729424f, 83.15553076f, 87.02034164f);
cb.LineTo(79.49425309f, 82.58209245f);
cb.CurveTo(84.13622847f, 76.73639073f, 85.95313131f, 70.24630402f, 85.95313131f, 62.12456369f);
cb.CurveTo(85.95313131f, 43.33817595f, 71.09893654f, 28.1547277f, 52.73079005f, 28.1547277f);
cb.CurveTo(34.36263419f, 28.15473249f, 19.50844879f, 43.33817595f, 19.50844879f, 62.12456369f);
cb.CurveTo(19.50844879f, 80.91094185f, 34.36264355f, 96.10336589f, 52.73079005f, 96.10336589f);
cb.CurveTo(58.55122776f, 96.10336589f, 62.90459266f, 95.2476225f, 67.65721002f, 92.5630926f);
cb.LineTo(71.13570481f, 97.23509821f);
cb.CurveTo(65.57113223f, 100.3782653f, 59.52269945f, 101.89500456f, 52.73079005f, 101.89500456f);

cb.MoveTo(38.33889376f, 67.35513328f);
cb.CurveTo(39.90689547f, 67.35509017f, 41.09296342f, 66.03921993f, 41.89711165f, 63.40748424f);
cb.CurveTo(43.50531445f, 58.47289182f, 44.65118131f, 56.00562195f, 45.33470755f, 56.0056459f);
cb.CurveTo(45.85735449f, 56.00562195f, 46.40013944f, 56.41682961f, 46.96305772f, 57.23928802f);
cb.CurveTo(58.2608517f, 75.74384316f, 68.7143666f, 90.71198997f, 78.32362116f, 102.14379168f);
cb.CurveTo(80.81631349f, 105.10443984f, 84.77658911f, 106.58480942f, 90.20445269f, 106.58489085f);
cb.CurveTo(91.49097185f, 106.58480942f, 92.35539361f, 106.46145048f, 92.79773204f, 106.21480444f);
cb.CurveTo(93.23991593f, 105.96799555f, 93.4610547f, 105.65958382f, 93.46113432f, 105.28956447f);
cb.CurveTo(93.4610547f, 104.71379041f, 92.7976618f, 103.58294901f, 91.47094155f, 101.89705463f);
cb.CurveTo(75.95141033f, 82.81670149f, 61.55772504f, 62.66726353f, 48.28984822f, 41.44869669f);
cb.CurveTo(47.36506862f, 39.96831273f, 45.47540199f, 39.22812555f, 42.62081088f, 39.22813992f);
cb.CurveTo(39.72597184f, 39.22812555f, 38.0172148f, 39.35149407f, 37.49457722f, 39.5982407f);
cb.CurveTo(36.12755286f, 40.2150402f, 34.51931728f, 43.36081778f, 32.66987047f, 49.03557823f);
cb.CurveTo(30.57914689f, 55.32711903f, 29.53378743f, 59.27475848f, 29.53381085f, 60.87852533f);
cb.CurveTo(29.53378743f, 62.60558406f, 30.94099884f, 64.27099685f, 33.75542165f, 65.87476369f);
cb.CurveTo(35.48425582f, 66.86164481f, 37.01207517f, 67.35509017f, 38.33889376f, 67.35513328f);

cb.SetRGBColorFill(0, 0, 0);
cb.Fill();
}

Process.Start("Test.pdf");
}
}
}

در اینجا، pdfWriter.DirectContent یک Canvas را جهت ترسیمات گرافیکی در اختیار ما قرار می‌دهد. سپس مابقی هم آن مشخص است و یک تناظر یک به یک را می‌شود بین خروجی Tex و متدهای فراخوانی شده، مشاهده کرد. PDF خروجی هم به شکل زیر است:



تا اینجا یک مرحله پیشرفت است. مشکل از اینجا شروع می‌شود که خوب! من که یک «چک مارک» این اندازه‌ای لازم ندارم! آن هم قرار گرفته در پایین صفحه. یک راه حل این مشکل استفاده از متد Transform شیء cb فوق است. این متد یک System.Drawing.Drawing2D.Matrix را دریافت می‌کند و سپس می‌شود توسط آن، اعمال تغییر اندازه (Scale)، تغییر مکان (Translate) و غیره را اعمال کرد. راه دیگر تعریف یک Template از دستورات فوق است. سپس متد Image.GetInstance کتابخانه iTextSharp ورودی از نوع Template را هم قبول می‌کند. خروجی حاصل یک تصویر برداری خواهد بود که اکنون با اکثر اشیاء iTextSharp سازگار است. برای مثال متد سازنده PdfPCell، آرگومان از نوع Image را هم قبول می‌کند. به علاوه شیء Image در اینجا متدهای تغییر اندازه و امثال آن‌را نیز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace HtmlToPdf
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

var cb = pdfWriter.DirectContent;
var template = createCheckMark(cb);

var image = Image.GetInstance(template);
image.ScaleAbsolute(40, 40);

var table = new PdfPTable(3);
var cell = new PdfPCell(image)
{
HorizontalAlignment = Element.ALIGN_CENTER
};

for (int i = 0; i < 9; i++)
table.AddCell(cell);

pdfDoc.Add(table);
}

Process.Start("Test.pdf");
}

private static PdfTemplate createCheckMark(PdfContentByte cb)
{
var template = cb.CreateTemplate(140, 140);

template.MoveTo(52.73079005f, 101.89500456f);
template.CurveTo(31.29686559f, 101.89500456f, 13.84575258f, 84.04652127f, 13.8457479f, 62.12456369f);
template.CurveTo(13.8457479f, 40.20259605f, 31.29686559f, 22.35412714f, 52.73079005f, 22.35412235f);
template.CurveTo(74.16470983f, 22.35412235f, 91.6158322f, 40.20259605f, 91.61582751f, 62.12456369f);
template.CurveTo(91.61582751f, 71.60188248f, 88.48023622f, 80.07729424f, 83.15553076f, 87.02034164f);
template.LineTo(79.49425309f, 82.58209245f);
template.CurveTo(84.13622847f, 76.73639073f, 85.95313131f, 70.24630402f, 85.95313131f, 62.12456369f);
template.CurveTo(85.95313131f, 43.33817595f, 71.09893654f, 28.1547277f, 52.73079005f, 28.1547277f);
template.CurveTo(34.36263419f, 28.15473249f, 19.50844879f, 43.33817595f, 19.50844879f, 62.12456369f);
template.CurveTo(19.50844879f, 80.91094185f, 34.36264355f, 96.10336589f, 52.73079005f, 96.10336589f);
template.CurveTo(58.55122776f, 96.10336589f, 62.90459266f, 95.2476225f, 67.65721002f, 92.5630926f);
template.LineTo(71.13570481f, 97.23509821f);
template.CurveTo(65.57113223f, 100.3782653f, 59.52269945f, 101.89500456f, 52.73079005f, 101.89500456f);

template.MoveTo(38.33889376f, 67.35513328f);
template.CurveTo(39.90689547f, 67.35509017f, 41.09296342f, 66.03921993f, 41.89711165f, 63.40748424f);
template.CurveTo(43.50531445f, 58.47289182f, 44.65118131f, 56.00562195f, 45.33470755f, 56.0056459f);
template.CurveTo(45.85735449f, 56.00562195f, 46.40013944f, 56.41682961f, 46.96305772f, 57.23928802f);
template.CurveTo(58.2608517f, 75.74384316f, 68.7143666f, 90.71198997f, 78.32362116f, 102.14379168f);
template.CurveTo(80.81631349f, 105.10443984f, 84.77658911f, 106.58480942f, 90.20445269f, 106.58489085f);
template.CurveTo(91.49097185f, 106.58480942f, 92.35539361f, 106.46145048f, 92.79773204f, 106.21480444f);
template.CurveTo(93.23991593f, 105.96799555f, 93.4610547f, 105.65958382f, 93.46113432f, 105.28956447f);
template.CurveTo(93.4610547f, 104.71379041f, 92.7976618f, 103.58294901f, 91.47094155f, 101.89705463f);
template.CurveTo(75.95141033f, 82.81670149f, 61.55772504f, 62.66726353f, 48.28984822f, 41.44869669f);
template.CurveTo(47.36506862f, 39.96831273f, 45.47540199f, 39.22812555f, 42.62081088f, 39.22813992f);
template.CurveTo(39.72597184f, 39.22812555f, 38.0172148f, 39.35149407f, 37.49457722f, 39.5982407f);
template.CurveTo(36.12755286f, 40.2150402f, 34.51931728f, 43.36081778f, 32.66987047f, 49.03557823f);
template.CurveTo(30.57914689f, 55.32711903f, 29.53378743f, 59.27475848f, 29.53381085f, 60.87852533f);
template.CurveTo(29.53378743f, 62.60558406f, 30.94099884f, 64.27099685f, 33.75542165f, 65.87476369f);
template.CurveTo(35.48425582f, 66.86164481f, 37.01207517f, 67.35509017f, 38.33889376f, 67.35513328f);

template.SetRGBColorFill(0, 0, 0);
template.Fill();

return template;
}
}
}

در این مثال، با کمک متد CreateTemplate مرتبط با Canvas دریافتی، یک قالب جدید ایجاد و سپس روی آن نقاشی خواهیم کرد. اکنون می‌توان از این قالب تهیه شده، یک Image دریافت کرده و سپس مثلا در سلول‌های یک جدول نمایش داد. اینبار خروجی نهایی ما به شکل زیر خواهد بود:



نظرات مطالب
تغییر اندازه تصاویر #2
سلام
دوست گلم توی پستی که شما ارجاع دادین گفته شده که برای تغییر اندازه تصاویر دو راه میشه در نظر گرفت:
 1- در زمان ثبت، تصویر تغییر اندازه داده شود.
 2- در زمان نیاز تغییر اندازه داده شود.

که در مورد هر کدوم توضیحاتی داده شده است، توی این پست با استفاده از هندلر در زمان اجرا (و استفاده از تابع تغییر اندازه تصویر) تصویر مورد نظر تغییر اندازه داده شده است.
در مورد هندلر هم لینک داده بودم توی پست.
موفق و موید باشید.
مطالب
نحوه اضافه کردن Auto-Complete به جستجوی لوسین در ASP.NET MVC و Web forms
پیشنیازها:
چگونه با استفاده از لوسین مطالب را ایندکس کنیم؟
چگونه از افزونه jQuery Auto-Complete استفاده کنیم؟
نحوه استفاده صحیح از لوسین در ASP.NET


اگر به جستجوی سایت دقت کرده باشید، قابلیت ارائه پیشنهاداتی به کاربر توسط یک Auto-Complete به آن اضافه شده‌است. در مطلب جاری به بررسی این مورد به همراه دو مثال Web forms و MVC پرداخته خواهد شد.


قسمت عمده مطلب جاری با پیشنیازهای یاد شده فوق یکی است. در اینجا فقط به ذکر تفاوت‌ها بسنده خواهد شد.

الف) دریافت لوسین
از طریق NuGet آخرین نگارش را دریافت و به پروژه خود اضافه کنید. همچنین Lucene.NET Contrib را نیز به همین نحو دریافت نمائید.

ب) ایجاد ایندکس
کدهای این قسمت با مطلب برجسته سازی قسمت‌های جستجو شده، یکی است:
using System.Collections.Generic;
using System.IO;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using LuceneSearch.Core.Model;
using LuceneSearch.Core.Utils;

namespace LuceneSearch.Core
{
    public static class CreateIndex
    {
        static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_30;

        public static Document MapPostToDocument(Post post)
        {
            var postDocument = new Document();
            postDocument.Add(new Field("Id", post.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            var titleField = new Field("Title", post.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS);
            titleField.Boost = 3;
            postDocument.Add(titleField);
            postDocument.Add(new Field("Body", post.Body.RemoveHtmlTags(), Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
            return postDocument;
        }

        public static void CreateFullTextIndex(IEnumerable<Post> dataList, string path)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(path));
            var analyzer = new StandardAnalyzer(_version);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                foreach (var post in dataList)
                {
                    writer.AddDocument(MapPostToDocument(post));
                }

                writer.Optimize();
                writer.Commit();
                writer.Close();
                directory.Close();
            }
        }
    }
}
تنها تفاوت آن اضافه شدن titleField.Boost = 3 می‌باشد. توسط Boost به لوسین خواهیم گفت که اهمیت عبارات ذکر شده در عناوین مطالب، بیشتر است از اهمیت متون آن‌ها.


ج) تهیه قسمت منبع داده Auto-Complete

namespace LuceneSearch.Core.Model
{
    public class SearchResult
    {
        public int Id { set; get; }
        public string Title { set; get; }
    }
}

using System.Collections.Generic;
using System.IO;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using LuceneSearch.Core.Model;
using LuceneSearch.Core.Utils;

namespace LuceneSearch.Core
{
    public static class AutoComplete
    {
        private static IndexSearcher _searcher;

        /// <summary>
        /// Get terms starting with the given prefix
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public static IList<SearchResult> GetTermsScored(string indexPath, string prefix, int maxItems = 10)
        {
            if (_searcher == null)
                _searcher = new IndexSearcher(FSDirectory.Open(new DirectoryInfo(indexPath)), true);

            var resultsList = new List<SearchResult>();
            if (string.IsNullOrWhiteSpace(prefix))
                return resultsList;

            prefix = prefix.ApplyCorrectYeKe();

            var results = _searcher.Search(new PrefixQuery(new Term("Title", prefix)), null, maxItems);
            if (results.TotalHits == 0)
            {
                results = _searcher.Search(new PrefixQuery(new Term("Body", prefix)), null, maxItems);
            }

            foreach (var doc in results.ScoreDocs)
            {
                resultsList.Add(new SearchResult
                {
                    Title = _searcher.Doc(doc.Doc).Get("Title"),
                    Id = int.Parse(_searcher.Doc(doc.Doc).Get("Id"))
                });
            }

            return resultsList;
        }
    }
}
توضیحات:
برای نمایش Auto-Complete نیاز به منبع داده داریم که نحوه ایجاد آن‌را در کدهای فوق ملاحظه می‌کنید. در اینجا توسط جستجوی سریع لوسین و امکانات PrefixQuery آن، به تعدادی مشخص (maxItems)، رکوردهای یافت شده را بازگشت خواهیم داد. خروجی حاصل لیستی است از SearchResultها شامل عنوان مطلب و Id آن. عنوان را به کاربر نمایش خواهیم داد؛ از Id برای هدایت او به مطلبی مشخص استفاده خواهیم کرد.


د) نمایش Auto-Complete در ASP.NET MVC

using System.Text;
using System.Web.Mvc;
using LuceneSearch.Core;
using System.Web;

namespace LuceneSearch.Controllers
{
    public class HomeController : Controller
    {
        static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\idx";

        public ActionResult Index(int? id)
        {
            if (id.HasValue)
            {
                //todo: do something
            }
            return View(); //Show the page
        }

        public virtual ActionResult ScoredTerms(string q)
        {
            if (string.IsNullOrWhiteSpace(q))
                return Content(string.Empty);

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = this.Url.Action(actionName: "Index", controllerName: "Home", routeValues: new { id = item.Id }, protocol: "http");
                result.AppendLine(item.Title + "|" + postUrl);
            }

            return Content(result.ToString());
        }
    }
}

@{
    ViewBag.Title = "جستجو";
    var scoredTermsUrl = Url.Action(actionName: "ScoredTerms", controllerName: "Home");
    var bulletImage = Url.Content("~/Content/Images/bullet_shape.png");
}
<h2>
    جستجو</h2>

<div align="center">
    @Html.TextBox("term", "", htmlAttributes: new { dir = "ltr" })
    <br />
    جهت آزمایش lu را وارد نمائید
</div>

@section scripts
{
    <script type="text/javascript">
        EnableSearchAutocomplete('@scoredTermsUrl', '@bulletImage');
    </script>
}

function EnableSearchAutocomplete(url, img) {
    var formatItem = function (row) {
        if (!row) return "";
        return "<img src='" + img + "' /> " + row[0];
    }

    $(document).ready(function () {
        $("#term").autocomplete(url, {
            dir: 'rtl', minChars: 2, delay: 5,
            mustMatch: false, max: 20, autoFill: false,
            matchContains: false, scroll: false, width: 300,
            formatItem: formatItem
        }).result(function (evt, row, formatted) {
            if (!row) return;
            window.location = row[1];
        });
    });
}
توضیحات:
- ابتدا ارجاعاتی را به jQuery، افزونه Auto-Complete و اسکریپت سفارشی تهیه شده، در فایل layout پروژه تعریف خواهیم کرد.
در اینجا سه قسمت را مشاهده می‌کنید: کدهای کنترلر، View متناظر و اسکریپتی که Auto-Complete را فعال خواهد ساخت.
- قسمت مهم کدهای کنترلر، دو سطر زیر هستند:
result.AppendLine(item.Title + "|" + postUrl);
return Content(result.ToString());
مطابق نیاز افزونه انتخاب شده در مثال جاری، فرمت خروجی مدنظر باید شامل سطرهایی حاوی متن قابل نمایش به همراه یک Id (یا در اینجا یک آدرس مشخص) باشد. البته ذکر این Id اختیاری بوده و در اینجا جهت تکمیل بحث ارائه شده است.
return Content هم سبب بازگشت این اطلاعات به افزونه خواهد شد.
- کدهای View متناظر بسیار ساده هستند. تنها نام TextBox تعریف شده مهم می‌باشد که در متد جاوا اسکریپتی EnableSearchAutocomplete استفاده شده است. به علاوه، نحوه مقدار دهی آدرس دسترسی به اکشن متد ScoredTerms نیز مهم می‌باشد.
- در متد EnableSearchAutocomplete نحوه فراخوانی افزونه autocomplete را ملاحظه می‌کنید.
جهت آن، به راست به چپ تنظیم شده است. با 2 کاراکتر ورودی فعال خواهد شد با وقفه‌ای کوتاه. نیازی نیست تا انتخاب کاربر از لیست ظاهر شده حتما با عبارت جستجو شده صد در صد یکی باشد. حداکثر 20 آیتم در لیست ظاهر خواهند شد. اسکرول بار لیست را حذف کرده‌ایم. عرض آن به 300 تنظیم شده است و نحوه فرمت دهی نمایشی آن‌را نیز ملاحظه می‌کنید. برای این منظور از متد formatItem استفاده شده است. آرایه row در اینجا در برگیرنده اعضای Title و Id ارسالی به افزونه است. اندیس صفر آن به عنوان دریافتی اشاره می‌کند.
همچنین نحوه نشان دادن عکس العمل به عنصر انتخابی را هم ملاحظه می‌کنید (در متد result مقدار دهی شده).  window.location را به عنصر دوم آرایه row هدایت خواهیم کرد. این عنصر دوم مطابق کدهای اکشن متد تهیه شده، به آدرس یک صفحه اشاره می‌کند.


ه) نمایش Auto-Complete در ASP.NET WebForms

قسمت عمده مطالب فوق با وب فرم‌ها نیز یکی است. خصوصا توضیحات مرتبط با متد EnableSearchAutocomplete ذکر شده.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LuceneSearch.WebForms.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>جستجو</title>
    <link href="Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.autocomplete.js" type="text/javascript"></script>
    <script src="Scripts/custom.js" type="text/javascript"></script>
</head>
<body dir="rtl">
    <h2>
        جستجو</h2>
    <form id="form1" runat="server">
    <div align="center">
        <asp:TextBox runat="server" dir="ltr" ID="term"></asp:TextBox>
        <br />
        جهت آزمایش lu را وارد نمائید
    </div>
    </form>
    <script type="text/javascript">
        EnableSearchAutocomplete('Search.ashx', 'Content/Images/bullet_shape.png');
    </script>
</body>
</html>

using System.Text;
using System.Web;
using LuceneSearch.Core;

namespace LuceneSearch.WebForms
{
    public class Search : IHttpHandler
    {
        static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\idx";

        public void ProcessRequest(HttpContext context)
        {
            string q = context.Request.QueryString["q"];
            if (string.IsNullOrWhiteSpace(q))
            {
                context.Response.Write(string.Empty);
                context.Response.End();
            }

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = "Default.aspx?id=" + item.Id;
                result.AppendLine(item.Title + "|" + postUrl);
            }

            context.Response.ContentType = "text/plain";
            context.Response.Write(result.ToString());
            context.Response.End();
        }

        public bool IsReusable
        { get { return false; } }
    }
}

در اینجا بجای Controller از یک Generic handler استفاده شده است (Search.ashx).
result.AppendLine(item.Title + "|" + postUrl);
context.Response.Write(result.ToString());
در آن، عنوان مطالب یافت شده به همراه یک آدرس مشخص، تهیه و در Response نوشته خواهند شد.


کدهای کامل مثال فوق را از اینجا می‌توانید دریافت کنید:
همچنین باید دقت داشت که پروژه MVC آن از نوع MVC4 است (VS2010) و فرض براین می‌باشد که IIS Express 7.5 را نیز پیشتر نصب کرده‌اید.
کلمه عبور فایل: dotnettips91
 
مطالب
استفاده از لوسین برای انجام محاسبات آماری بر روی متون
احتمالا یک سری از کارهای اینفوگرافیک مانند tags cloud و words cloud را دیده‌اید. برای مثال در یک سخنرانی خاص، سخنران بیشتر از چه واژه‌هایی استفاده کرده است و سپس ترسیم درشت‌تر واژه‌هایی با تکرار بیشتر در یک تصویر نهایی. محاسبات آماری این نوع بررسی‌ها را توسط لوسین نیز می‌توان انجام داد که در ادامه به نحوه انجام آن خواهیم پرداخت.

بررسی آماری واژه‌های بکار رفته در شاهنامه

مرحله اول: ایجاد ایندکس

using System;
using System.Collections.Generic;
using System.IO;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;

namespace ShaahnamehAnalysis
{
    public static class CreateIndex
    {
        static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_CURRENT;

        static HashSet<string> getStopWords()
        {
            var result = new HashSet<string>();
            var stopWords = new[]
            {
                "به",
                "با",
                "از",
                "تا",
                "و",
                "است",
                "هست",
                "هستم",
                "هستیم",
                "هستید",
                "هستند",
                "نیست",
                "نیستم",
                "نیستیم",
                "نیستند",
                "اما",
                "یا",
                "این",
                "آن",
                "اینجا",
                "آنجا",
                "بود",
                "باد",
                "برای",
                "که",
                "دارم",
                "داری",
                "دارد",
                "داریم",
                "دارید",
                "دارند",
                "چند",
                "را",
                "ها",
                "های",
                "می",
                "هم",
                "در",
                "باشم",
                "باشی",
                "باشد",
                "باشیم",
                "باشید",
                "باشند",
                "اگر",
                "مگر",
                "بجز",
                "جز",
                "الا",
                "اینکه",
                "چرا",
                "کی",
                "چه",
                "چطور",
                "چی",
                "چیست",
                "آیا",
                "چنین",
                "اینچنین",
                "نخست",
                "اول",
                "آخر",
                "انتها",
                "صد",
                "هزار",
                "میلیون",
                "ملیون",
                "میلیارد",
                "ملیارد",
                "یکهزار",
                "تریلیون",
                "تریلیارد",
                "میان",
                "بین",
                "زیر",
                "بیش",
                "روی",
                "ضمن",
                "همانا",
                "ای",
                "بعد",
                "پس",
                "قبل",
                "پیش",
                "هیچ",
                "همه",
                "واما",
                "شد",
                "شده",
                "شدم",
                "شدی",
                "شدیم",
                "شدند",
                "یک",
                "یکی",
                "نبود",
                "میکند",
                "میکنم",                
                "میکنیم",
                "میکنید",
                "میکنند",
                "میکنی",
                "طور",
                "اینطور",
                "آنطور",
                "هر",
                "حال",
                "مثل",
                "خواهم",
                "خواهی",
                "خواهد",
                "خواهیم",
                "خواهید",
                "خواهند",
                "داشته",
                "داشت",
                "داشتی",
                "داشتم",
                "داشتیم",
                "داشتید",
                "داشتند",
                "آنکه",
                "مورد",
                "کنید",
                "کنم",
                "کنی",
                "کنند",
                "کنیم",
                "نکنم",
                "نکنی",
                "نکند",
                "نکنیم",
                "نکنید",
                "نکنند",
                "نکن",
                "بگو",
                "نگو",
                "مگو",
                "بنابراین",
                "بدین",
                "من",
                "تو",
                "او",
                "ما",
                "شما",
                "ایشان",
                "ی",
                "ـ",
                "هایی",
                "خیلی",
                "بسیار",
                "1",
                "بر",
                "l",
                "شود",
                "کرد",
                "کرده",
                "نیز",
                "خود",
                "شوند",
                "اند",
                "داد",
                "دهد",
                "گشت",
                "ز",
                "گفت",
                "آمد",
                "اندر",
                "چون",
                "بد",
                "چو",
                "همی",
                "پر",
                "سوی",
                "دو",
                "گر",
                "بی",
                "گرد",
                "زین",
                "کس",
                "زان",
                "جای",
                "آید"
            };

            foreach (var item in stopWords)
                result.Add(item);

            return result;
        }

        public static void CreateShaahnamehIndex(string file = "shaahnameh.txt")
        {
            var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex"));
            var analyzer = new StandardAnalyzer(_version, getStopWords());
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                var section = string.Empty;
                foreach (var line in File.ReadAllLines(file))
                {
                    int result;
                    if (int.TryParse(line, out result))
                    {
                        var postDocument = new Document();
                        postDocument.Add(new Field("Id", result.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
                        postDocument.Add(new Field("Body", section, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
                        writer.AddDocument(postDocument);
                        section = string.Empty;
                    }
                    else
                        section += line;
                }

                writer.Optimize();
                writer.Commit();
                writer.Close();
                directory.Close();
            }
        }
    }
}

با ایجاد ایندکس‌های لوسین پیشتر در این سایت آشنا شده‌اید . روش کار نیز همانند سابق است. اطلاعات خود را، به هر فرمتی که تهیه شده باید تبدیل به اشیاء Document لوسین کرد. برای مثال در اینجا فقط یک فایل txt داریم که تشکیل شده است از تمام صفحات. به ازای هر صفحه، یک شیء Document تهیه و نوشته خواهد شد. همچنین در تهیه ایندکس از یک سری از واژه‌‌های بسیار متداول مانند «از»، «به»، «اندر»، (stopWords) صرفنظر شده است.


مرحله دوم: ایجاد ابر واژه‌ها

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Lucene.Net.Index;
using Lucene.Net.Store;

namespace ShaahnamehAnalysis
{
    [DebuggerDisplay("{Frequency}, {Text}")]
    public class Tag
    {
        public string Text { set; get; }

        /// <summary>
        /// The frequency of a term is defined as the number of 
        /// documents in which a specific term appears.
        /// </summary>
        public int Frequency { set; get; }
    }

    public static class WordsCloud
    {
        /// <summary>
        /// Create Words Cloud
        /// </summary>
        /// <param name="threshold">every term that appears in more than x Body</param>
        public static IList<Tag> Create(int threshold = 200)
        {
            var path = Environment.CurrentDirectory + "\\LuceneIndex";

            var results = new List<Tag>();
            var field = "Body";

            IndexReader indexReader = IndexReader.Open(FSDirectory.Open(path ), true);

            var termFrequency = indexReader.Terms();
            while (termFrequency.Next())
            {
                if (termFrequency.DocFreq() >= threshold && termFrequency.Term.Field == field)
                {
                    results.Add(new Tag { Text = termFrequency.Term.Text, Frequency = termFrequency.DocFreq() });
                }
            }
            return results.OrderByDescending(x => x.Frequency).ToList();
        }
    }
}

پس از اینکه ایندکس لوسین تهیه شد، می‌توان به مداخل موجود در آن توسط متد indexReader.Terms دسترسی یافت.
نکته جالب آن فراهم بودن DocFreq هر واژه ایندکس شده است (فرکانس تکرار واژه؛ تعداد اشیاء Document ایی که واژه مورد نظر در آن‌ها تکرار شده است). برای مثال در اینجا اگر واژه‌ای 200 بار یا بیشتر در صفحات مختلف شاهنامه تکرار شده باشد، به عنوان یک واژه پر اهمیت انتخاب شده و به ابر واژه‌های نهایی اضافه می‌گردد.


مرحله سوم: استفاده از نتایج

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace ShaahnamehAnalysis
{
    class Program
    {
        static void Main(string[] args)
        {
            CreateIndex.CreateShaahnamehIndex();
            var wordsCloudList = WordsCloud.Create();

            var data = wordsCloudList.Select(x => x.Text + ", " + x.Frequency)
                                     .Aggregate((s1, s2) => s1 + Environment.NewLine + s2);
            var output = "ShaahnamehAnalysis.txt";
            File.WriteAllText(output, data);
            Process.Start(output);
        }
    }
}

که نتیجه 15 مورد اول آن به صورت زیر است:
واژه |  فرکانس
شاه, 1191
دل, 1088
سر, 1070
کار, 840
لشکر, 801
تخت, 755
روز, 745
ایران, 740
جهان, 724
مرد, 660
دست, 630
تاج, 623
نزدیک, 623
گیتی, 585
راه, 584


فایل‌های کامل این مثال را از اینجا می‌توانید دریافت کنید:
ShaahnamehAnalysis.zip