نظرات مطالب
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
با سلام
قسمت JWT Parser  رولهای کاربر را بصورت [Admin,User] استخراج میکنه و کاربر دارای دسترسی Admin , با بررسی متد user.IsInRole("Admin") مقدار false را بر میگرداند  و با اضافه کردن قسمت زیر مشکل برطرف میشود .
public static IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
        {
            var claims = new List<Claim>();
            var payload = jwt.Split('.')[1];

            var jsonBytes = ParseBase64WithoutPadding(payload);

            var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
           
            claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
            ExtractRolesFromJwt(claims, keyValuePairs);
            return claims;
        }

private static void ExtractRolesFromJwt(List<Claim> claims, Dictionary<string, object> keyValuePairs)
        {
            keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
            if (roles != null)
            {
                var parsedRoles = roles.ToString().Trim().TrimStart('[').TrimEnd(']').Split(',');
                if (parsedRoles.Length > 1)
                {
                    claims.AddRange(parsedRoles.Select(parsedRole => new Claim(ClaimTypes.Role, parsedRole.Trim('"'))));
                }
                else
                {
                    claims.Add(new Claim(ClaimTypes.Role, parsedRoles[0]));
                }
                keyValuePairs.Remove(ClaimTypes.Role);
            }
        }

بازخوردهای دوره
تزریق وابستگی‌ها در فیلترهای ASP.NET MVC
با تشکر از زحمات فراوان شما، راه حل دیگر :
public class StructureMapGlobalFilterProvider : IFilterProvider
    {
        public StructureMapGlobalFilterProvider(IContainer container, GlobalFilterRegistrationList filterList)
        {
            _container = container;
            _filterList = filterList;
        }

        private IContainer _container;
        private GlobalFilterRegistrationList _filterList;

        public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            var filters = new List<Filter>();
            if (_filterList == null || _filterList.Count == 0)
                return filters;
            foreach (GlobalFilterRegistration registration in _filterList)
            {
                var actionFilter = _container.GetInstance(registration.Type);
                var filter = new Filter(actionFilter, FilterScope.Global, registration.Order);
                filters.Add(filter);
            }
            return filters;
        }
    }

    public class GlobalFilterRegistration
    {
        public Type Type { get; set; }
        public int? Order { get; set; }
    }

    public class GlobalFilterRegistrationList : List<GlobalFilterRegistration>
    {
    }

و تنظیمات Global:
 var globalFilterRegistrationList = new GlobalFilterRegistrationList
                {
                    new GlobalFilterRegistration
                    {
                        Type = typeof (LogAttribute),
                        Order
                            = 1
                    }
                };
                container.Configure(x =>
                {
                    x.For<IFilterProvider>().Use<StructureMapGlobalFilterProvider>();
                    x.For<GlobalFilterRegistrationList>().Use(globalFilterRegistrationList);
                });
نظرات مطالب
معرفی کتابخانه PdfReport
بسیار بسیار ممنون و متشکر
جدا برای گزارشات خصوصا در وب مشکل داشتم
مطالب
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
در ادامه مطالب قبل
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1# 
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2#

قبل از شروع توضیحات متد‌های کلاس Shape در ادامه پست‌های قبل در ^ و ^ ابتدا به تشریح یک تصویر می‌پردازیم.

نحوه ترسیم شی

خوب همانگونه که در تصویر بالا مشاده می‌نمایید، برای رسم یک شی چهار حالت متفاوت ممکن است پیش بیاید. (دقت کنید که ربع اول محور مختصات روی بوم گرافیکی قرار گرفته است، در واقع گوشه بالا و سمت چپ بوم گرافیکی نقطه (0 و 0) محور مختصات است و عرض بوم گرافیکی محور X‌ها و ارتفاع بوم گرافیکی محور Y‌ها را نشان می‌دهد)
  1. در این حالت StartPoint.X < EndPoint.X و StartPoint.Y < EndPoint.Y خواهد بود. (StartPoint نقطه ای است که ابتدا ماوس شروع به ترسیم می‌کند، و EndPoint زمانی است که ماوس رها شده و پایان ترسیم را مشخص می‌کند.)
  2. در این حالت StartPoint.X > EndPoint.X و StartPoint.Y > EndPoint.Y خواهد بود.
  3. در این حالت StartPoint.X > EndPoint.X و StartPoint.Y > EndPoint.Y خواهد بود.
  4. در این حالت StartPoint.X < EndPoint.X و StartPoint.Y > EndPoint.Y خواهد بود.

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

using System;
using System.Drawing;

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Helpers
    /// </summary>
    public static class Helpers
    {
        /// <summary>
        /// Draws the preview.
        /// </summary>
        /// <param name="g">The g.</param>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        /// <param name="foreColor">Color of the fore.</param>
        /// <param name="thickness">The thickness.</param>
        /// <param name="isFill">if set to <c>true</c> [is fill].</param>
        /// <param name="backgroundBrush">The background brush.</param>
        /// <param name="shapeType">Type of the shape.</param>
        public static void DrawPreview(Graphics g, PointF startPoint, PointF endPoint, Color foreColor, byte thickness, bool isFill, Brush backgroundBrush, ShapeType shapeType)
        {
            float x = 0, y = 0;
            float width = Math.Abs(endPoint.X - startPoint.X);
            float height = Math.Abs(endPoint.Y - startPoint.Y);
            if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                x = startPoint.X;
                y = startPoint.Y;
            }
            else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                x = endPoint.X;
                y = endPoint.Y;
            }
            else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                x = endPoint.X;
                y = startPoint.Y;
            }
            else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                x = startPoint.X;
                y = endPoint.Y;
            }

            switch (shapeType)
            {
                case ShapeType.Ellipse:
                    if (isFill)
                        g.FillEllipse(backgroundBrush, x, y, width, height);
                    //else
                    g.DrawEllipse(new Pen(foreColor, thickness), x, y, width, height);
                    break;
                case ShapeType.Rectangle:
                    if (isFill)
                        g.FillRectangle(backgroundBrush, x, y, width, height);
                    //else
                    g.DrawRectangle(new Pen(foreColor, thickness), x, y, width, height);
                    break;
                case ShapeType.Circle:
                    float raduis = Math.Max(width, height);

                    if (isFill)
                        g.FillEllipse(backgroundBrush, x, y, raduis, raduis);
                    //else
                    g.DrawEllipse(new Pen(foreColor, thickness), x, y, raduis, raduis);
                    break;
                case ShapeType.Square:
                    float side = Math.Max(width, height);

                    if (isFill)
                        g.FillRectangle(backgroundBrush, x, y, side, side);
                    //else
                    g.DrawRectangle(new Pen(foreColor, thickness), x, y, side, side);
                    break;
                case ShapeType.Line:
                    g.DrawLine(new Pen(foreColor, thickness), startPoint, endPoint);
                    break;
                case ShapeType.Diamond:
                    var points = new PointF[4];
                    points[0] = new PointF(x + width / 2, y);
                    points[1] = new PointF(x + width, y + height / 2);
                    points[2] = new PointF(x + width / 2, y + height);
                    points[3] = new PointF(x, y + height / 2);
                    if (isFill)
                        g.FillPolygon(backgroundBrush, points);
                    //else
                    g.DrawPolygon(new Pen(foreColor, thickness), points);
                    break;
                case ShapeType.Triangle:
                    var tPoints = new PointF[3];
                    tPoints[0] = new PointF(x + width / 2, y);
                    tPoints[1] = new PointF(x + width, y + height);
                    tPoints[2] = new PointF(x, y + height);
                    if (isFill)
                        g.FillPolygon(backgroundBrush, tPoints);
                    //else
                    g.DrawPolygon(new Pen(foreColor, thickness), tPoints);
                    break;
            }
            if (shapeType != ShapeType.Line)
            {
                g.DrawString(String.Format("({0},{1})", x, y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), x - 20, y - 25);
                g.DrawString(String.Format("({0},{1})", x + width, y + height), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), x + width - 20, y + height + 5);
            }
            else
            {
                g.DrawString(String.Format("({0},{1})", startPoint.X, startPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), startPoint.X - 20, startPoint.Y - 25);
                g.DrawString(String.Format("({0},{1})", endPoint.X, endPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), endPoint.X - 20, endPoint.Y + 5);
            }

        }
    }
}
متد های این کلاس:
  • DrawPreview : این متد پیش نمایشی برای شی در زمان ترسیم، جابجایی و تغییر اندازه آماده می‌کند، پارامترهای آن عبارتند از : بوم گرافیکی، نقطه شروع، نقطه پایان و رنگ قلم ترسیم پیش نمایش شی، ضخامت خط، آیا شی توپر باشد؟، الگوی پر کردن پس زمینه شی ، و نوع شی ترسیمی می‌باشد.
در ادامه پست‌های قبل ادامه کد کلاس Shape را تشریح می‌کنیم.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Net;

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Shape (Base Class)
    /// </summary>
    public abstract partial class Shape
    {
#region Constructors (2) 

        /// <summary>
        /// Initializes a new instance of the <see cref="Shape" /> class.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        /// <param name="zIndex">Index of the z.</param>
        /// <param name="foreColor">Color of the fore.</param>
        /// <param name="thickness">The thickness.</param>
        /// <param name="isFill">if set to <c>true</c> [is fill].</param>
        /// <param name="backgroundColor">Color of the background.</param>
        protected Shape(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor)
        {
            CalulateLocationAndSize(startPoint, endPoint);
            Zindex = zIndex;
            ForeColor = foreColor;
            Thickness = thickness;
            IsFill = isFill;
            BackgroundColor = backgroundColor;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Shape" /> class.
        /// </summary>
        protected Shape() { }

#endregion Constructors 
        
#region Methods (10) 

// Public Methods (9) 

        /// <summary>
        /// Draws the specified g.
        /// </summary>
        /// <param name="g">The g.</param>
        public virtual void Draw(Graphics g)
        {
            if (!IsSelected) return;
            float diff = Thickness + 4;
            Color myColor = Color.DarkSeaGreen;
            g.DrawString(String.Format("({0},{1})", StartPoint.X, StartPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(myColor), StartPoint.X - 20, StartPoint.Y - 25);
            g.DrawString(String.Format("({0},{1})", EndPoint.X, EndPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(myColor), EndPoint.X - 20, EndPoint.Y + 5);
            if (ShapeType != ShapeType.Line)
            {
                g.DrawRectangle(new Pen(myColor), X, Y, Width, Height);

                //  1 2 3
                //  8   4 
                //  7 6 5   
                var point1 = new PointF(StartPoint.X - diff / 2, StartPoint.Y - diff / 2);
                var point2 = new PointF((StartPoint.X - diff / 2 + EndPoint.X) / 2, StartPoint.Y - diff / 2);
                var point3 = new PointF(EndPoint.X - diff / 2, StartPoint.Y - diff / 2);
                var point4 = new PointF(EndPoint.X - diff / 2, (EndPoint.Y + StartPoint.Y) / 2 - diff / 2);
                var point5 = new PointF(EndPoint.X - diff / 2, EndPoint.Y - diff / 2);
                var point6 = new PointF((StartPoint.X - diff / 2 + EndPoint.X) / 2, EndPoint.Y - diff / 2);
                var point7 = new PointF(StartPoint.X - diff / 2, EndPoint.Y - diff / 2);
                var point8 = new PointF(StartPoint.X - diff / 2, (EndPoint.Y + StartPoint.Y) / 2 - diff / 2);


                g.FillRectangle(new SolidBrush(myColor), point1.X, point1.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point2.X, point2.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point3.X, point3.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point4.X, point4.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point5.X, point5.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point6.X, point6.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point7.X, point7.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point8.X, point8.Y, diff, diff);
            }
            else
            {
                var point1 = new PointF(StartPoint.X - diff / 2, StartPoint.Y - diff / 2);
                var point2 = new PointF(EndPoint.X - diff / 2, EndPoint.Y - diff / 2);
                g.FillRectangle(new SolidBrush(myColor), point1.X, point1.Y, diff, diff);
                g.FillRectangle(new SolidBrush(myColor), point2.X, point2.Y, diff, diff);
            }
        }

        /// <summary>
        /// Points the in sahpe.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <param name="tolerance">The tolerance.</param>
        /// <returns>
        ///   <c>true</c> if [has point in sahpe] [the specified point]; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool HasPointInSahpe(PointF point, byte tolerance = 5)
        {
            return point.X > (StartPoint.X - tolerance) && point.X < (EndPoint.X + tolerance) && point.Y > (StartPoint.Y - tolerance) && point.Y < (EndPoint.Y + tolerance);
        }

        /// <summary>
        /// Moves the specified location.
        /// </summary>
        /// <param name="location">The location.</param>
        /// <returns></returns>
        public virtual PointF Move(Point location)
        {
            StartPoint = new PointF(location.X, location.Y);
            EndPoint = new PointF(location.X + Width, location.Y + Height);
            return StartPoint;
        }

        /// <summary>
        /// Moves the specified dx.
        /// </summary>
        /// <param name="dx">The dx.</param>
        /// <param name="dy">The dy.</param>
        /// <returns></returns>
        public virtual PointF Move(int dx, int dy)
        {
            StartPoint = new PointF(StartPoint.X + dx, StartPoint.Y + dy);
            EndPoint = new PointF(EndPoint.X + dx, EndPoint.Y + dy);
            return StartPoint;
        }

        /// <summary>
        /// Resizes the specified dx.
        /// </summary>
        /// <param name="dx">The dx.</param>
        /// <param name="dy">The dy.</param>
        /// <returns></returns>
        public virtual SizeF Resize(int dx, int dy)
        {
            EndPoint = new PointF(EndPoint.X + dx, EndPoint.Y + dy);
            return new SizeF(Width, Height);
        }

        /// <summary>
        /// Resizes the specified start point.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="currentPoint">The current point.</param>
        public virtual void Resize(PointF startPoint, PointF currentPoint)
        {
            var dx = (int)(currentPoint.X - startPoint.X);
            var dy = (int)(currentPoint.Y - startPoint.Y);
            if (startPoint.X >= X - 5 && startPoint.X <= X + 5)
            {
                StartPoint = new PointF(currentPoint.X, StartPoint.Y);
                if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
                {
                    Height = Width;
                }
            }
            else if (startPoint.X >= EndPoint.X - 5 && startPoint.X <= EndPoint.X + 5)
            {
                Width += dx;
                if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
                {
                    Height = Width;
                }
            }
            else if (startPoint.Y >= Y - 5 && startPoint.Y <= Y + 5)
            {
                Y = currentPoint.Y;
                if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
                {
                    Width = Height;
                }
            }
            else if (startPoint.Y >= EndPoint.Y - 5 && startPoint.Y <= EndPoint.Y + 5)
            {
                Height += dy;
                if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
                {
                    Width = Height;
                }
            }
        }

        /// <summary>
        /// Sets the background brush as hatch.
        /// </summary>
        /// <param name="hatchStyle">The hatch style.</param>
        public virtual void SetBackgroundBrushAsHatch(HatchStyle hatchStyle)
        {
            var brush = new HatchBrush(hatchStyle, BackgroundColor);
            BackgroundBrush = brush;
        }

        /// <summary>
        /// Sets the background brush as linear gradient.
        /// </summary>
        public virtual void SetBackgroundBrushAsLinearGradient()
        {
            var brush = new LinearGradientBrush(StartPoint, EndPoint, ForeColor, BackgroundColor);
            BackgroundBrush = brush;
        }

        /// <summary>
        /// Sets the background brush as solid.
        /// </summary>
        public virtual void SetBackgroundBrushAsSolid()
        {
            var brush = new SolidBrush(BackgroundColor);
            BackgroundBrush = brush;
        }
// Private Methods (1) 

        /// <summary>
        /// Calulates the size of the location and.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        private void CalulateLocationAndSize(PointF startPoint, PointF endPoint)
        {
            float x = 0, y = 0;
            float width = Math.Abs(endPoint.X - startPoint.X);
            float height = Math.Abs(endPoint.Y - startPoint.Y);
            if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                x = startPoint.X;
                y = startPoint.Y;
            }
            else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                x = endPoint.X;
                y = endPoint.Y;
            }
            else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                x = endPoint.X;
                y = startPoint.Y;
            }
            else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                x = startPoint.X;
                y = endPoint.Y;
            }
            StartPoint = new PointF(x, y);
            EndPoint = new PointF(X + width, Y + height);
        }

#endregion Methods 
    }
}

حال به تشریح سازنده کلاس می‌پردازیم:
  • Shape: پارامترهای این سازنده به ترتیب عبارتند از نقطه شروع، نقطه پایان، عمق شی، رنگ قلم، ضخامت خط، آیا شی توپر باشد؟، و رنگ پر کردن شی، در این سازنده ابتدا توسط متدی به نام CalulateLocationAndSize(startPoint, endPoint); b نقاط ابتدا و انتهای شی مورد نظر تنظیم می‌شود، در متد مذکور بررسی می‌شود در صورتی که نقاط شروع و پایان یکی از حالت‌های 1 ، 2، 3، 4 از تصویر ابتدا پست باشد همگی تبدیل به حالت 1 خواهد شد.

سپس به تشریح متدهای کلاس Shape می‌پردازیم:

  • Draw: این متد دارای یک پارامتر ورودی است که بوم گرافیکی مورد نظر می‌باشد، در واقع شی مورد نظر خود را بروی این بوم گرافیکی ترسیم می‌کند. در کلاس پایه کار این متد زیاد پیچیده نیست، در صورتی که شی در حالت انتخاب باشد (IsSelected = true) بروی شی مورد نظر 8 مربع کوچک ترسیم می‌شود و اگر شی مورد نظر خط باشد دو مربع کوچک در طرفین خط رسم می‌شود که نشان دهنده انتخاب شدن شی مورد نظر است. این متد به صورت virtual تعریف شده است یعنی کلاس هایی که از Shape ارث میبرند می‌توانند این متد را برای خود از نو بازنویسی کرده (override کنند) و تغییر رفتار دهند.
  • HasPointInSahpe : این متد نیز به صورت virtual تعریف شده است دارای خروجی بولین می‌باشد. پارامتر‌های این متد عبارتند از یک نقطه و یک عدد که نشان دهنده تلرانش نقطه بر حسب پیکسل می‌باشد. کار این متد این است که یک نقطه را گرفته و بررسی می‌کند که آیا نقطه مورد نظر با تلرانس وارد شده آیا در داخل شی واقع شده است یا خیر (مثلا وجود نقطه در مستطیل یا وجود نقطه در دایره فرمول‌های متفاوتی دارند که در اینجا پیش فرض برای تمامی اشیا حالت مستطیل در نظر گرفته شده که می‌توانید آنها را بازنویسی (override) کنید).
  • Move: این متد به عنوان پارامتر یک نقطه را گرفته و شی مورد نظر را به آن نقطه منتقل می‌کند در واقع نقطه شروع و پایان ترسیم شی را تغییر می‌دهد.
  • Move: این متد نیز برای جابجایی شی به کار می‌رود، این متد دارای پارامترهای جابجابی در راستای محور Xها , جابجایی در راستای محور Yها؛ و شی مورد نظر را به آن نقطه منتقل می‌کند در واقع نقطه شروع و پایان ترسیم شی را با توجه به پارامترهای ورودی تغییر می‌دهد. 
  • Resize: این متد نیز برای تغییر اندازه شی به کار می‌رود، این متد دارای پارامترهای تغییر اندازه در راستای محور Xها , تغییر اندازه در راستای محور Yها می‌باشد و نقطه پایان شی مورد نظر را تغییر می‌دهد اما نقطه شروع تغییری نمی‌کند.
  • Resize: این متد نیز برای تغییر اندازه شی به کار می‌رود، در زمان تغییر اندازه شی با ماوس ابتدا یک نقطه شروع وجود دارد که ماوس در آن نقطه کلیک شده و شروع به درگ کردن شی جهت تغییر اندازه می‌کند (پارامتر اول این متد نقطه شروع درگ کردن جهت تغییر اندازه را مشخص می‌کند startPoint)، سپس در یک نقطه ای درگ کردن تمام می‌شود در این نقطه باید شی تغییر اندازه پیدا کرده و ترسیم شود ( پارامتر دوم این متد نقطه مذکور می‌باشد currentLocation). سپس با توجه با این دو نقطه بررسی می‌شود که تغییر اندازه در کدام جهت صورت گرفته است و اعداد جهت تغییرات نقاط شروع و پایان شی مورد نظر محاسبه می‌شوند. (مثلا تغییر اندازه در مستطیل از ضلع بالا به طرفین، یا از ضلع سمت راست به طرفین و ....). البته برای مربع و دایره باید کاری کنیم که طول و عرض تغییر اندازه یکسان باشد.
  • CalulateLocationAndSize: این متد که در سازنده کلاس استفاده شده در واقع دو نقطه شروع و پایان را گرفته و با توجه به تصویر ابتدای پست حالت‌های 1 و 2 و3  و 4 را به حالت 1 تبدیل کرده و StartPoint و EndPoint را اصلاح می‌کند.
  • SetBackgroundBrushAsHatch: این متد یک الگوی Brush گرفته و با توجه به رنگ پس زمینه شی خصوصیت BackgroundBrush را مقداردهی می‌کند.
  • SetBackgroundBrushAsLinearGradient: این متد با توجه به خصوصیت ForeColor و BackgroundColor یک Gradiant Brush ساخته و آن را به خصوصیت
    BackgroundBrush نسبت می‌کند. 
  • SetBackgroundBrushAsSolid: یک الگوی پر کردن توپر برای شی مورد نظر با توجه به خصوصیت BackgroundColor شی ایجاد کرده و آن را به خصوصیت BackgroundBrush شی نسبت می‌دهد.

تذکر: متد‌های Move، Resize و HasPointInShape به صورت virtual تعریف شده تا کلاس‌های مشتق شده در صورت نیاز خود کد رفتار مورد نظر خود را override کرده یا از همین رفتار استفاده نمایند.

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

در پست‌های آینده به بررسی و پیاده سازی دیگر کلاس‌ها خواهیم پرداخت.

مطالب
پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش سوم (پایانی)
در بخش پیشین  به بررسی جزئی‌تر ایجاد پایگاه داده و همچنین توسعه Custom Filter Attribute پرداختیم که وظیفه تایید صلاحیت کاربر جاری و بررسی دسترسی وی به API Method مورد نظر را بررسی می‌کرد. در این مقاله به این بحث می‌پردازیم که در Filter Attribute توسعه داده شده، قصد داریم یک سرویس Access Control ایجاد نماییم.
این سرویس وظیفه تمامی اعمال مربوط به نقش‌ها و دسترسی‌های کاربر را بر عهده خواهد داشت. این سرویس به صورت زیر تعریف می‌گردد:
public class AccessControlService
{
        private DbContext db;

        public AccessControlService()
        {
            db = new DbContext();
        }

        public IEnumerable<Permission> GetUserPermissions(string userId)
        {
            var userRoles = this.GetUserRoles(userId);
            var userPermissions = new List<Permission>();
            foreach (var userRole in userRoles)
            {
                foreach (var permission in userRole.Permissions)
                {
                    // prevent duplicates
                    if (!userPermissions.Contains(permission))
                        userPermissions.Add(permission);
                }
            }
            return userPermissions;
        }
        public IEnumerable<Role> GetUserRoles(string userId)
        {
            return db.Users.FirstOrDefault(x => x.UserId == userId).Roles.ToList();
        }

        public bool HasPermission(string userId, string area, string control)
        {
            var found = false;
            var userPermissions = this.GetUserPermissions(userId);
            var permission = userPermissions.FirstOrDefault(x => x.Area == area && x.Control == control);
            if (permission != null)
                found = true;
            return found;
        }
{
همانطور که ملاحظه می‌کنید، ما سه متد GetUserPermissions، GetUserRoles و HasPermission را توسعه داده‌ایم. حال اینکه بر حسب نیاز، می‌توانید متدهای بیشتری را نیز به این سرویس اضافه نمایید. متد اول، وظیفه‌ی واکشی تمامی permissionهای کاربر را عهده دار می‌باشد. متد GetUserRoles نیز تمامی نقش‌های کاربر را در سیستم، بازمی‌گرداند و در نهایت متد سوم، همان متدی است که ما در Filterattribute از آن استفاده کرد‌ه‌ایم. این متد با دریافت پارامترها و بازگردانی یک مقدار درست یا نادرست، تعیین می‌کند که کاربر جاری به آن محدوده دسترسی دارد یا خیر.
تمامی حداقل‌هایی که برای نگارش سمت سرور نیاز بود، به پایان رسید. حال به بررسی این موضوع خواهیم پرداخت که چگونه این سطوح دسترسی را با سمت کاربر همگام نماییم. به طوری که به عنوان مثال اگر کاربری حق دسترسی به ویرایش مطالب یک سایت را ندارد، دکمه مورد نظر که او را به آن صفحه هدایت می‌کند نیز نباید به وی نشان داده شود. سناریویی که ما برای این کار در نظر گرفته‌ایم، به این گونه می‌باشد که در هنگام ورود کاربر، لیستی از تمامی دسترسی‌های او به صورت JSON به سمت کلاینت ارسال می‌گردد. حال وظیفه مدیریت نمایش یا عدم نمایش المان‌های صفحه، بر عهده زبان سمت کلاینت، یعنی AngularJs خواهد بود. بنابراین در ابتدایی‌ترین حالت، ما نیاز به یک کنترلر و متد Web API داریم تا لیست دسترسی‌های کاربر را بازگرداند. این کنترلر در حال حاضر شامل یک متد است. اما بر حسب نیاز، می‌توانید متدهای بیشتری را به کنترلر اضافه نمایید.
    [RoutePrefix("َAuth/permissions")]
    public class PermissionsController : ApiController
    {
        private AccessControlService _AccessControlService = null;
        public PermissionsController()
        {
            _AccessControlService = new AccessControlService();
        }

        [Route("GetUserPermissions")]
        public async Task<IHttpActionResult> GetUserPermissions()
        {
            if (!User.Identity.IsAuthenticated)
            {
                return Unauthorized();
            }
            return Ok(_AccessControlService.GetPermissions(User.Identity.GetCurrentUserId()));
        }        
    }
در متد فوق ما از متد سرویس Access Control که لیست تمامی permissionهای کاربر را باز می‌گرداند، کمک گرفتیم. متد GetUserPermissions پس از ورود کاربر توسط کلاینت فراخوانی می‌گردد و لیست تمامی دسترسی‌ها در سمت کلاینت، در rootScope انگیولار ذخیره سازی می‌گردد. حال نوبت به آن رسیده که به بررسی عملیات سمت کلاینت بپردازیم.

توسعه سرویس‌ها و فرآیندهای سمت وب کلاینت AngularJS

در ابتدا در سمت کلاینت نیاز به سرویسی داریم که دسترسی‌های سمت سرور را دریافت نماید. از این رو ما نام این سرویس را permissionService می‌نامیم.
'use strict';
angular.module('App').factory('permissionService', ['$http', '$q', function ($http, $q) {

    var _getUserPermissions = function () {
        return $http.get(serviceBaseUrl + '/api/permissions/GetUserPermissions/');
    }

    var _isAuthorize = function (area, control) {
        return _.some($scope.permissions, { 'area': area, 'control': control });
    }

    return {
        getUserPermissions: _getUserPermissions,
        isAuthorize: _isAuthorize
    };
}]);
اگر بخواهیم مختصری درباره‌ی این سرویس صحبت کنیم، متد اول که یک دستور GET ساده است و لیست دسترسی‌ها را از PermissionController دریافت می‌کند. متد بعدی که در آینده بیشتر با آن آشنا می‌شویم، عملیات تایید صلاحیت کاربر را به ناحیه مورد نظر، انجام می‌دهد (همان مثال دسترسی به دکمه ویرایش مطلب در یک صفحه). در این متد برای جستجوی لیست permissions از کتابخانه‌ای با نام Lodash کمک گرفته‌ایم. این کتابخانه کاری شبیه به دستورات Linq را در کالکشن‌ها و آرایه‌های جاوااسکریپتی، انجام می‌دهد. متد some یک مقدار درست یا نادرست را بازمی‌گرداند. بازگردانی مقدار درست، به این معنی است که کاربر به ناحیه‌ی مورد نظر اجازه‌ی دسترسی را خواهد داشت.
حال باید متد‌های این سرویس را در کنترلر لاگین فراخوانی نماییم. در این مرحله ما از rootScope dependency استفاده می‌کنیم. برای نحوه‌ی عملکرد rootScope میتوانید به مقاله‌ای در این زمینه در وب سایت toddomotto مراجعه کنید. در این مقاله ویژگی‌ها و اختلاف‌های scope و rootScope به تفصیل بیان شده است. مقاله‌ای دیگر در همین زمینه نوشته شده است که در انتهای مقاله به بررسی چند نکته در مورد کدهای مشترک پرداخته شده‌است. تکه کد زیر، متد login را نمایش می‌دهد که در loginController قرار گرفته است و ما در آن از نوشتن کل بلاک loginController چشم پوشی کرده‌ایم. متد savePermissions تنها یک کار را انجام می‌دهد و آن هم این است که در ابتدا، به سرویس permissionService متصل شده و تمامی دسترسی‌ها را واکشی می‌نماید و پس از آن، آنها را درون rootScope قرار می‌دهد تا در تمامی کنترلرها قابل دسترسی باشد.
    $scope.login = function () {
        authService.login($scope.loginData).then(function (response) {
            savePermissions();
            $location.path('/userPanel');
        },
         function (err) {
             $scope.message = err.error_description;
         });
    };

    var savePermissions = function () {
        permissionService.getUserPermissions().then(function (response) {
            $rootScope.permissions = response.data;
        },
        function (err) {
        });
    }
}

   حال تمامی اطلاعات دسترسی، در سمت کلاینت نیز قابل دسترسی می‌باشد. تنها کاری که نیاز است، broadCast کردن متد isAuthorize است که آن هم باید در rootScope قرار بگیرد. ما برای این انتساب یک راهکار را ارائه کرده‌ایم. معماری سیستم کلاینت به این صورت است که تمامی کنترلرها درون یک parentController قرار گرفته‌اند. از این رو می‌توان در parentController این انتساب (ایجاد دسترسی عمومی برایisAuthorize) صورت گیرد. برای این کار در parentController تغییرات زیر صورت می‌گیرد:
App.controller('parentController', ['$rootScope', '$scope', 'authService', 'permissionService', function ($rootScope, $scope, authService, permissionService) {
        $scope.authentication = authService.authentication;

        // isAuthorize Method
        $scope.isAuthorize = permissionService.isAuthorize();

        // rest of codes
}]);
در کد فوق ما isAuthorize را درون scope قرار داده‌ایم. دلیل آن هم این است که هر چه که در scope قرار بگیرد، تمامی کنترلر‌های child نیز به آن دسترسی خواهند داشت. البته ممکن است که این بهترین نوع پیاده سازی برای به اشتراک گذاری یک منبع نباشد.
 در گام بعدی کافیست المان‌های صفحه را بر اساس همین دسترسی‌ها فعال یا غیر فعال کنیم. برای این کار از دستور ng-if میتوان استفاده کرد. برای این کار به مثال زیر توجه کنید:
<div ng-controller="childController">
    <div ng-if="isAuthorize('articles', 'edit')" >
    <!-- the block that we want to not see unauthorize person -->
    </div>
</div>
همانطور که مشاهده می‌کنید، تمامی المان‌ها را می‌توان با دستور ساده ng-if، از دید کاربران بدون صلاحیت، پنهان نمود. البته توجه داشته باشید که شما نمی‌توانید تنها به پنهان کردن این اطلاعات اکتفا کنید. بلکه باید تمامی متدهای کنترلرهای سمت سرور را هم با همین روش (فیلتر کردن با Filter Attribute) بررسی نمایید. به ازای هر درخواست کاربر باید بررسی شود که او به منبع مورد نظر دسترسی دارد یا خیر.
تنها نکته‌ای که باقی می‌ماند این است که طول عمر scope و rootScope چقدر است؟! برای پاسخ به این سوال باید بگوییم هر بار که صفحه refresh می‌شود، تمامی مقادیر scope و rootScope خالی می‌شوند. برای این کار هم یک راهکار خیلی ساده (و شاید کمی ناشیانه) در نظر گرفته شده‌است. میتوان بلاک مربوط به پر کردن rootScope.permissions را که در loginController نوشته شده بود، به درون parentController انتقال داد و آن را با استفاده از emit اجرا کرد و در حالت عادی، در هنگام refresh شدن صفحه نیز چون parentController در اولین لحظه اجرا می‌شود، میتوان تمامی مقادیر rootScope.permissions را دوباره از سمت سرور دریافت کرد.
مطالب
فراخوانی GraphQL API در یک کلاینت ASP.NET Core
در قسمت قبل، ایجاد کردن Mutation‌ها را در GraphQL تمام کردیم. در این قسمت تصمیم بر این است که از GraphQL API در یک برنامه ASP.NET Core استفاده کنیم. برای فراخوانی GraphQL API در یک برنامه ASP.NET Core، از یک کتابخانه ثالث که به ما در این فرآیند کمک می‌کند استفاده خواهیم کرد.

Preparing the ASP.NET Core Client Project 

کار را با ایجاد کردن یک پروژه ASP.NET Core Web API شروع می‌کنیم :
dotnet new api -n DotNetGraphQLClient
 
به محض اینکه پروژه را ایجاد کردیم فایل launchsettings.json را باز می‌کنیم و مقدار خصوصیت launchBrowser را به false  و خصوصیت applicationUrl  را به
https://localhost:5003;http://localhost:5004
تغییر می‌دهیم. در ادامه فایل appsettings.json را مطابق زیر ویرایش می‌کنیم (اضافه کردن کلید GraphQLURI ):
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "GraphQLURI": "https://localhost:5001/graphql",
  "AllowedHosts": "*"
}

اکنون می‌توانیم کتابخانه مورد نیاز را نصب کنیم. در ترمینال مربوط به VS Code دستور زیر را وارد کنید:
dotnet add package GraphQL.Client
بعد از نصب، برای ثبت آن، کلاس Startup را مطابق زیر ویرایش کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(x => new GraphQL.Client.GraphQLClient(Configuration["GraphQLURI"]));
   ...
}

در سازنده کلاس GraphQLClient، آدرس endpoint را معرفی می‌کنیم (که در فایل appsettings.json می باشد) .
قدم بعد، ایجاد کردن یک کلاس به نام OwnerConsumer است که عملیات مربوط به فراخوانی API، در این کلاس می‌باشد. 
public class OwnerConsumer
{
    private readonly GraphQL.Client.GraphQLClient _client;
 
    public OwnerConsumer(GraphQL.Client.GraphQLClient client)
    {
        _client = client;
    }
}
سپس کلاس ایجاد شده را در متد ConfigureServices از کلاس Startup  ثبت می‌کنیم: 
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(x => new GraphQL.Client.GraphQLClient(Configuration["GraphQLURI"]));
    services.AddScoped<OwnerConsumer>();
    ...
}


Creating Model Classes 
 
در قسمت اول تعدادی کلاس را در پوشه Models  ایجاد کردیم. همه آن کلاس‌ها را برای این client application نیاز داریم؛ پس آن‌ها را ایجاد می‌کنیم: 
public enum TypeOfAccount
{
    Cash,
    Savings,
    Expense,
    Income
}
public class Account
{
    public Guid Id { get; set; }
    public TypeOfAccount Type { get; set; }
    public string Description { get; set; }
}
public class Owner
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
 
    public ICollection<Account> Accounts { get; set; }
}

در قسمت سوم ( GraphQL Mutation ) یک کلاس Input type را برای اکشن‌های Mutation ایجاد کردیم. این کلاس هم مورد نیاز می‌باشد. پس آن را ایجاد می‌کنیم:
public class OwnerInput
{
    public string Name { get; set; }
    public string Address { get; set; }
}
اکنون همه چیز آماده است تا Query و Mutation ‌ها را ایجاد کنیم. 

Creating Queries and Mutations to Consume GraphQL API 

کلاس OwnerConsumer را باز می‌کنیم و متد GetAllOwners  را به آن اضافه می‌کنیم:
public async Task<List<Owner>> GetAllOwners()
{
    var query = new GraphQLRequest
    {
        Query = @"
                query ownersQuery{
                  owners {
                    id
                    name
                    address
                    accounts {
                      id
                      type
                      description
                    }
                  }
                }"
    };
 
    var response = await _client.PostAsync(query);
    return response.GetDataFieldAs<List<Owner>>("owners");
}

در ابتدا یک شیء جدید از نوع GraphQLRequest را ایجاد می‌کنیم که شامل خصوصیت Query که برای ارسال queryی که می‌خواهیم به GraphQL API ارسال کنیم، می‌باشد. این query مثل همانی که در ابزار UI.Playground اجرا کردیم، می‌باشد. جهت اجرای این query، متد PostAsync را فراخوانی می‌کنیم که یک پاسخ را برگشت می‌دهد. در نهایت می‌توان نتیجه را تبدیل به نوع مورد نیاز، با استفاده از متد GetDataFieldsAs کرد. 
توجه کنید که آرگومان متد GetDataFieldAs باید با نام query مطابقت داشته باشد .


Implementing a Controller 
یک کنترلر جدید را به نام OwnerController ایجاد می‌کنیم و آن را مطابق زیر ویرایش می‌کنیم: 
[Route("api/owners")]
public class OwnerController: Controller
{
    private readonly OwnerConsumer _consumer;
 
    public OwnerController(OwnerConsumer consumer)
    {
        _consumer = consumer;
    }
 
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var owners = await _consumer.GetAllOwners();
        return Ok(owners);
    }
}

مثال ضمیمه شده در پایان قسمت قبل را اجرا کنید که بر روی پورت 5001 می‌باشد و سپس پروژه را اجرا کنید ( بر روی پورت 5003 است ).
اکنون می‌توانیم آن را در Postman تست کنیم.
جهت بازیابی تمامی owner ‌ها، در خواست زیر را در Postman صادر کنید:


جهت ایجاد یک owner جدید، کلاس OwnerConsumer را مطابق زیر ویرایش می‌کنیم ( اضافه کردن متد  CreateOwner ) :
 public async Task<Owner> CreateOwner(OwnerInput ownerToCreate)
 {
     var query = new GraphQLRequest
     {
         Query = @"
         mutation($owner: ownerInput!){
           createOwner(owner: $owner){
             id,
             name,
             address
           }
         }",
         Variables = new { owner = ownerToCreate }
     };

     var response = await _client.PostAsync(query);
     return response.GetDataFieldAs<Owner>("createOwner");
 }
و سپس ، کلاس OwnerController را باز می‌کنیم و متد PostItem را به آن اضافه می‌کنیم :
[HttpPost]
public async Task<IActionResult> PostItem([FromBody]OwnerInput model)
{
    var result = await _consumer.CreateOwner(model);
    return Json(result);
}

 اکنون، در خواست ایجاد یک owner جدید را در Postman به صورت زیر صادر می‌کنیم: 


همانطور که مشخص است، همه چیز به درستی کار می‌کند. شما می‌توانید مابقی Query و Mutation ‌ها را با استفاده از Postman تست کنید.

نتیجه گیری 
در این قسمت یادگرفتیم که چگونه :
  1. یک کلاینت ASP.NET Core را برای فراخوانی GraphQL API  آماده سازی کنیم. 
  2. چه کتابخانه‌هایی مورد نیاز است که می‌توانند به ما در انجام فرآیند فراخوانی GraphQL API کمک کنند.
  3. ایجاد کردن query‌ها و mutation‌ها در یک کلاینت ASP.NET Core. 

کد‌های کامل این قسمت را از اینجا دریافت کنید: DotNetGraphQLClient_4.zip 
کد‌های کامل قسمت قبل را می‌توانید از اینجا دریافت کنید:  ASPCoreGraphQL_3.rar

نظرات مطالب
ارسال عکس به stimulsoft و ایجاد گزارش
با سلام، من از این روش برای ارسال عکس به Bussiness Object استفاده کردم، که در واقع یک لیست رو که در برنامه C# ساخته شده و یک ستون عکس داره رو میخوام گزارش بگیرم، علاوه بر توضیحات شما از این مطلب هم استفاده کردم. اما ستون تصاویر نمایش داده نمیشه و یا اینکه به ازای type‌های مختلف عبارت هایی مثل عبارت زیر نمایش داده میشه:
System.Drawing.Bitmap
تصاویری که داشتم در دیتابس sql ذخیره شده  و من برای یک گزارش جدید دیگه نمیخوام توی sql إخیره کنم  و نیاز دارم که لیست رو مستقیما به گزارش ارسال کنم. ممنون میشم راهنمایی کنید.
private void ModifyItemList()
        {
            ItemList.Clear();
            var AllItems = from BetaData in BetaContext.BetaDatas
                           where BetaData.ProjectName == Globals.ProjectName &&
                           BetaData.ProjectCode == Globals.ProjectCode &&
                           (BetaData.Type == Globals.Types.Partition || BetaData.Type == Globals.Types.Door)
                           select BetaData;
                           
            foreach(BetaData data in AllItems)
            {
                byte[] imageByte = data.Image.ToArray();
                MemoryStream MS = new MemoryStream(imageByte);
                Image img = Image.FromStream(MS);
                StiImage stiImg = new StiImage();
                stiImg.Image = img;
                Items newItem = new Items
                {
                    Image = img,
                    Name = data.Name
                };
                ItemList.Add(newItem);
            }
                         
        }

        private void modiefieItemListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            
        }

        private void modifiedItemListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ModifyItemList();
            ModifiedItemListReport.RegBusinessObject("Items",ItemList);
            ModifiedItemListReport.Compile();
            ModifiedItemListReport.Render();
            ModifiedItemListReport.Show();
        }

        public class Items
        {
            public string Name { get; set; }
            public Image Image { get; set; }
        }
نظرات مطالب
کار با کلیدهای اصلی و خارجی در EF Code first
من 3تا جدول زیر رو در بانک ساختم :

و کلاسها به صورت زیر تعریف کردم:
 public class Tb1 
    {
        public Tb1()
        {
            ListTb2 = new List<Tb2>();
        }
        public int Id { get; set; }
        public string NameTb1 { get; set; }

        public virtual ICollection<Tb2> ListTb2 { get; set; }
    }
    public class Tb2 
    {
        public Tb2()
        {
            ListTb1 = new List<Tb1>();
        }
        public int Id { get; set; }
        public string NameTb2 { get; set; }

        public virtual ICollection<Tb1> ListTb1 { get; set; }
    }
و همینطور mapping :
    public class Tb1Map : EntityTypeConfiguration<Tb1>
    {
        public Tb1Map()
        {
            this.HasKey(x => x.Id);

            this.HasMany(x => x.ListTb2)
                .WithMany(xx => xx.ListTb1)
                .Map
                (
                    x =>
                        {
                            x.MapLeftKey("Tb1Id");
                            x.MapRightKey("Tb2Id");
                            x.ToTable("Tb1Tb2");
                        }
                );

        }
    }

    public class Tb2Map : EntityTypeConfiguration<Tb2>
    {
        public Tb2Map()
        {
            this.HasKey(x => x.Id);
        }
    }
موقعی که در برنامه به صورت زیر استفاده می‌کنم:
            var sv1 = new TableService<Tb1>(_uow);
            var sv2 = new TableService<Tb2>(_uow);

            var t1 = new Tb1 { NameTb1 = "T111" };
            sv1.Add(t1);
            //var res1= _uow.SaveChanges();
            
            var t2 = new Tb2 { NameTb2 = "T222" };
            sv2.Add(t2);
            //var res2 = _uow.SaveChanges();

            t1.ListTb2.Add(t2);
            var result = _uow.SaveChanges();
 هنگام SaveChanges این خطا رو می‌ده: 
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
همراه با innerException زیر:
{"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_Tb1Tb2_Tb2\". The conflict occurred in database \"dbTest\", table \"dbo.Tb2\", column 'Id'.\r\nThe statement has been terminated."}
در واقع همینطور که مشخصه من می‌خوام اون جدول رابطه رو در codeFirst حذف کنم یجورایی و رابطه رو بین 2 جدول اصلی بیارم. کجای کارم اشتباهه؟ و راهکارش چیه؟
من با پروفایلر هم نگاه کردم همه چی تا آخر داره پیش می‌ره!
(آیا ForeignKey رو باید طور دیگه ای تعریف کنم؟) 
با تشکر
مطالب
گروه بندی اطلاعات در jqGrid
فرض کنید لیستی از مطالب را به فرمت ذیل در اختیار داریم:
namespace jqGrid10.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string CategoryName { set; get; }
        public int NumberOfViews { set; get; }
    }
}
می‌خواهیم آن‌ها را با شرایط ذیل گروه بندی کنیم:
- گروه بندی بر روی ستون CategoryName انجام شود.
- ستونی که بر روی آن گروه بندی انجام می‌شود، نمایش داده نشود.
- در ابتدای نمایش گروه‌ها، تمام آن‌ها به صورت جمع شده و Collapsed نمایش داده شوند.
- پس از نمایش گروه‌ها، اولین گروه به صورت خودکار باز شود.
- تعداد ردیف هر گروه به عنوان گروه اضافه شود.
- جمع کل ستون تعداد بار مشاهدات هر گروه قابل محاسبه شود.
- جمع کل هر گروه در زمانیکه هر گروه نیز بسته‌است نمایش داده شود.
- رنگ ردیف جمع کل قابل تنظیم باشد.



فعال سازی گروه بندی در jqGrid

فعال سازی گروه بندی در jqGrid به سادگی افزودن تعاریف ذیل است:
            $('#list').jqGrid({
                caption: "آزمایش دهم",
                //...
                grouping: true,
                groupingView: {
                    groupField: ['CategoryName'],
                    groupOrder: ['asc'],
                    groupText : ['<b>{0} - {1} ردیف</b>'],
                    groupDataSorted: true,
                    groupColumnShow: false,
                    groupCollapse: true,
                    groupSummary: [true],
                    showSummaryOnHide: true
                }
            });
grouping: true سبب می‌شود تا گروه بندی فعال شود. سپس نیاز است ستون یا ستون‌هایی را که قرار است بر روی آن‌ها گروه بندی صورت گیرد، معرفی کنیم. اینکار توسط خواص شیء groupingView انجام می‌شوند.
groupField بیانگر آرایه‌ای از ستون‌هایی است که قرار است بر روی آن‌ها گروه بندی صورت گیرد.
groupOrder آرایه‌ای اختیاری از مقادیر asc یا desc است که متناظر هستند با نحوه‌ی مرتب سازی پیش فرض ستون‌های معرفی شده در آرایه groupField.
groupText آرایه‌ای اختیاری از عناوین گروه بندی‌های انجام شده‌است. اگر ذکر شود، {0} آن با نام گروه و {1} آن با تعداد عناصر گروه جایگزین می‌شود.
تنظیم groupDataSorted سبب خواهد شد تا نام ستونی که بر روی آن گروه بندی صورت می‌گیرد، به سرور ارسال شود (توسط پارامتر sidx). به این ترتیب در سمت سرور می‌توان اطلاعات را به صورت پویا مرتب سازی کرده و بازگشت داد.
با تنظیم groupColumnShow به false سبب خواهیم شد تا ستون‌های معرفی شده در قسمت groupField نمایش داده نشوند.
با تنظیم groupCollapse به true، در ابتدای نمایش گروه‌ها، ردیف‌های آن‌ها نمایش داده نخواهند شد و در حالت جمع شده قرار می‌گیرند.
groupSummary به معنای فعال سازی نمایش ردیف محاسبه‌ی summary مانند sum، min، max و امثال آن بر روی یک گروه است.
اگر مقدار showSummaryOnHide مساوی true باشد، ردیف محاسبه‌ی summary حتی در حالت groupCollapse: true نمایش داده خواهد شد.


فعال سازی محاسبه‌ی جمع ستون تعداد بار مشاهدات مطالب

برای فعال سازی نهایی محاسبه‌ی جمع ستون تعداد بار مشاهدات، علاوه بر تنظیم groupSummary به true، نیاز است در همان ستون مشخص کنیم که این محاسبات چگونه باید انجام شوند:
                colModel: [
// ........
                    {
                        name: '@(StronglyTyped.PropertyName<Post>(x => x.Title))',
                        index: '@(StronglyTyped.PropertyName<Post>(x => x.Title))',
                        align: 'right',
                        width: 150,
                        summaryTpl: '<div style="text-align: left;">خلاصه </div>',
                        summaryType: function (val, name, record) {
                            return "";
                        }
                    },
// ........
                    {
                        name: '@(StronglyTyped.PropertyName<Post>(x => x.NumberOfViews))',
                        index: '@(StronglyTyped.PropertyName<Post>(x => x.NumberOfViews))',
                        align: 'center',
                        width: 70,
                        summaryType: 'sum', summaryTpl: '<b>جمع مشاهدات: {0}</b>'
                    }
                ],
خاصیت summaryType نوع متد محاسبه‌ی summary مانند sum را مشخص می‌کند. خاصیت summaryTpl اختیاری است. اگر ذکر شود سبب فرمت مقدار محاسبه شده‌ی نهایی می‌گردد. در اینجا {0} به مقدار نهایی که قرار است در این سلول درج شود، اشاره می‌کند.
summaryType مانند ستون عنوان، سفارشی شده نیز می‌توان باشد. در ردیف summary و ستون عنوان تنها می‌خواهیم یک مقدار ثابت را نمایش دهیم، به همین جهت summaryType آن به یک مقدار خالی تنظیم شده‌است.


تغییر رنگ ردیف خلاصه عملیات هر گروه به همراه گشودن خودکار اولین گروه

گروه بندی به همراه یک سری متد توکار نیز هست. برای مثال اگر متد groupingToggle را بر روی Id هر گروه فراخوانی کنیم، می‌توان سبب باز یا بسته شده آن گروه شد. متدهای دیگری مانند groupingGroupBy برای گروه بندی پویا و groupingRemove برای حذف گروه بندی نیز وجود دارند:
            $('#list').jqGrid({
                caption: "آزمایش دهم",
                //.........
                loadComplete: function() {
                    //.........
                    $('#list').jqGrid('groupingToggle', 'list' + 'ghead_0_0');
                    $("tr.jqfoot td").css({
                        "background": "#2f4f4f",
                        "color": "#FFF"
                    });
                },
            });
بهترین محل اعمال رنگ به ردیف‌های summary، در روال رویدادگردان loadComplete گرید است.


مثال کامل این قسمت را از اینجا می‌توانید دریافت کنید:
jqGrid10.zip
نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
با فرض اینکه layout برنامه تعاریف لازم را دارد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/PersianDatePicker.css" rel="stylesheet" />
</head>
<body>
    @RenderBody()
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/PersianDatePicker.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
فایلی را به نام PersianDatePicker.cshtml در مسیر Views\Shared\EditorTemplates ایجاد کنید؛ با این محتوا:
@using System.Globalization
@model DateTime?
@{
    Func<DateTime, string> toPersianDate = date =>
    {
        var dateTime = new DateTime(date.Year, date.Month, date.Day, new GregorianCalendar());
        var persianCalendar = new PersianCalendar();
        return persianCalendar.GetYear(dateTime) + "/" + 
               persianCalendar.GetMonth(dateTime).ToString("00") + "/" + 
               persianCalendar.GetDayOfMonth(dateTime).ToString("00");
    };
    var today = toPersianDate(DateTime.Now);
    var name = this.ViewContext.ViewData.ModelMetadata.PropertyName;
    var value = Model.HasValue ? toPersianDate(Model.Value) : string.Empty;
}
<input type="text" dir="ltr" 
       name="@name" id="@name" 
       value="@value" 
       onclick="PersianDatePicker.Show(this,'@today');" />
بعد از این تنها کاری که باید انجام شود، استفاده از ویژگی UIHint است:
// Location: Views\Shared\EditorTemplates\PersianDatePicker.cshtml
[UIHint("PersianDatePicker")]
public DateTime AddDate { set; get; }
به صورت خودکار به متد EditorFor اعمال می‌شود:
<div>
    <label>تاریخ:</label>
    @Html.EditorFor(model => model.AddDate)
</div>

همچنین اگر نیاز است مقدار رشته تاریخ شمسی آن به یک DateTime میلادی انتساب داده شود (در حین ارسال اطلاعات به سرور)، باید از یک model binder سفارشی برای اینکار در ASP.NET MVC استفاده کنید. یک نمونه پیاده سازی شده آن‌را در بحث PersianDateModelBinder می‌توانید مشاهده و استفاده کنید.