نظرات مطالب
صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid
باید از Dictionary استفاده کنید برای تعریف خواص پویا:
public class Field
{
  [JsonExtensionData]
  public Dictionary<string, object> Property { get; set; }
}
 
public class FieldType
{
  public string Type { get; set; }
}
و بعد نحوه استفاده از آن به صورت زیر خواهد بود:
var data = new
{
  model = new
  {
   fields = new List<Field>
   {
    new Field
    {
      Property = new Dictionary<string, object>
      {
       {"Id", new FieldType { Type = "number" }},
       {"Name", new FieldType { Type = "string" }}
      }
    }
   }
  }
};
var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented);
با این خروجی:
{
  "model": {
    "fields": [
      {
        "Id": {
          "Type": "number"
        },
        "Name": {
          "Type": "string"
        }
      }
    ]
  }
}
- اگر از Web API استفاده می‌کنید، ذکر سطر JsonConvert.SerializeObject ضروری نیست و به صورت توکار از JSON.NET‌ استفاده می‌کند.
- اگر از ASP.NET MVC استفاده می‌کنید، نیاز است از آن کمک بگیرید. از این جهت که خاصیت JsonExtensionData سبب می‌شود تا نام ثابت خاصیت Property، از خروجی نهایی حذف شود و اعضای دیکشنری، جزئی از خاصیت‌های موجود شوند.
- نکته‌ی «گرفتن خروجی CamelCase از JSON.NET» را هم باید مد نظر داشته باشید.
پاسخ به بازخورد‌های پروژه‌ها
هدر دو تکه
- عرض سلول‌ها را باید مشخص کنید و گرنه هر کدام نصف عرض کل را بر می‌دارند.
- هر سطر یا هر سلول یک جدول می‌تواند حاوی یک جدول جدید باشد.
- از نحوه‌ی تشکیل topTable و infoTable مطلب «تهیه‌ی کارت با فرمت PDF با استفاده از کتابخانه iTextSharp» ایده بگیرید.
- روش‌های دیگری هم برای تشکیل هدر وجود دارند؛ برای مثال استفاده از html.
پاسخ به بازخورد‌های پروژه‌ها
تنظیم چندین OverallSummarySettings برای ستون های مختلف
ممنون، ولی متاسفانه در حالت Landscape هم جواب گوی کار ما نیست و تغییر سایز کاغذ نیز امکان پذیر نیست.
اگر راهنمایی کنید که در سورس کد چه تفاوتی بین سلول مقدار جمع و سایر سلول‌ها هست که 'n\' را پردازش نمی‌کند، تا خودمان با تغییر سورس این مشکلمان را حل کنیم ممنون میشم.
با سپاس
پاسخ به بازخورد‌های پروژه‌ها
قراردادن فیلد Hyperlink
بله. از قالب سلول مخصوص به آن استفاده کنید:
columns.AddColumn(column =>
                {
                    //...
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Hyperlink(foreColor: System.Drawing.Color.Blue, fontUnderline: true);
                    });
                });
حالت پیش فرض، استفاده از TextBlock است؛ اگر چیزی تنظیم نشود:
columns.AddColumn(column =>
                {
                    //...
                    column.ColumnItemsTemplate(template =>
                    {
                        template.TextBlock();
                    });
                });
مطالب
OpenCVSharp #13
تشخیص قسمت‌های مشابه تصاویر در OpenCV

در شکل زیر، دو تصویر سمت چپ و راست، اندکی با هم تفاوت دارند و در تصویر سوم، نقاط مشابه یافت شده‌ی توسط OpenCV ترسیم شده‌اند:


کدهای مثال فوق را در ذیل مشاهده می‌کنید:
var img1 = new Mat(@"..\..\Images\left.png", LoadMode.GrayScale);
Cv2.ImShow("Left", img1);
Cv2.WaitKey(1); // do events
 
var img2 = new Mat(@"..\..\Images\right.png", LoadMode.GrayScale);
Cv2.ImShow("Right", img2);
Cv2.WaitKey(1); // do events
 
 
// detecting keypoints
// FastFeatureDetector, StarDetector, SIFT, SURF, ORB, BRISK, MSER, GFTTDetector, DenseFeatureDetector, SimpleBlobDetector
// SURF = Speeded Up Robust Features
var detector = new SURF(hessianThreshold: 400); //SurfFeatureDetector
var keypoints1 = detector.Detect(img1);
var keypoints2 = detector.Detect(img2);
 
// computing descriptors, BRIEF, FREAK
// BRIEF = Binary Robust Independent Elementary Features
var extractor = new BriefDescriptorExtractor();
var descriptors1 = new Mat();
var descriptors2 = new Mat();
extractor.Compute(img1, ref keypoints1, descriptors1);
extractor.Compute(img2, ref keypoints2, descriptors2);
 
// matching descriptors
var matcher = new BFMatcher();
var matches = matcher.Match(descriptors1, descriptors2);
 
// drawing the results
var imgMatches = new Mat();
Cv2.DrawMatches(img1, keypoints1, img2, keypoints2, matches, imgMatches);
Cv2.ImShow("Matches", imgMatches);
Cv2.WaitKey(1); // do events
 
Cv2.WaitKey(0);
 
Cv2.DestroyAllWindows();
img1.Dispose();
img2.Dispose();
در ابتدا نیاز به یک تشخیص دهنده یا Detector داریم. در OpenCVSharp، الگوریتم‌ها و کلاس‌های FastFeatureDetector, StarDetector, SIFT, SURF, ORB, BRISK, MSER, GFTTDetector, DenseFeatureDetector, SimpleBlobDetector برای اینکار قابل استفاده هستند. برای مثال در اینجا از الگوریتم SURF آن استفاده شده‌است.
کار این تشخیص دهنده، تشخیص نقاط کلیدی تصاویر است. برای مثال تشخیص گوشه‌ها، لبه‌ها و غیره.
سپس اطلاعات نواحی اطراف هر نقطه‌ی کلیدی را تحت عنوان descriptors استخراج می‌کنیم.
بعد از محاسبه‌ی نقاط کلیدی هر تصویر، اینبار نیاز است این نقاط را بین دو تصویر با هم مقایسه کرد و مشابه‌ها را یافت. برای مثال الگوریتم BFMatcher یک Brute force matcher است که بر اساس اطلاعات نواحی اطراف هر نقطه‌ی کلیدی، سعی در یافتن نقاط مشابه می‌کند.
پس از یافتن نقاط مشابه، نیاز است بر اساس آن‌ها نگاشتی بین دو تصویر صورت گیرد و مشابه‌ها ترسیم شوند. متد DrawMatches این‌کار را انجام می‌شود.

اگر علاقمند هستید که با ریز جزئیات ریاضی الگوریتم‌های استفاده شده نیز آشنا شوید، سری مطالب descriptors را دنبال نمائید.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
نمایش اشیاء موجود در View بر اساس دسترسی‌ها در ASP.NET MVC
سیستم دسترسی در یک سیستم، همیشه برای من چالش برانگیز بوده است. با دیدن کدهای مختلف از افراد مختلف، شیوه‌های گوناگونی از کدنویسی را دیده‌ام؛ ولی یکی از نکاتی که در بین آن‌ها بررسی نشده بود و یا از آن غافل مانده بودند، بررسی بعضی از عناصر موجود در ویو بود که باید با توجه به نقش کاربر سیستم، وضعیت آن بررسی میشد. 
برای مثال تصور کنید که شما دو کاربر دارید که هر دو سطح دسترسی به پروفایل کاربران دیگر را دارند. ولی یکی از کاربرها این توانایی را دارد تا با کلیک بر روی دکمه‌ای، لاگین کاربر مورد نظر را مسدود نماید، ولی دیگری نمی‌تواند این دکمه را ببیند، یا برای او به صورت غیرفعال نمایش داده شود. در اکثر این مواقع کاری که برنامه نویسان انجام میدهند، نوشتن توابع به شیوه‌های گوناگون در لابلای انبوهی از کدهای View هست تا بتوانند این مسئله را پیاده سازی کنند. ولی با اضافه شدن هر چه بیشتر این عناصر به صفحه و با توجه به انبوهی از ویووها و ویووکامپوننت‌ها کار بسیار سخت‌تر میشود.
 به همین جهت من از فیلترها در MVC کمک گرفتم و این موضوع را با توجه به خروجی نهایی صفحه بررسی میکنم. به این معنا که وقتی صفحه بدون هیچ گونه اعتبارسنجی در سطح ویو آماده شد، با استفاده از فیلتر مورد نظر که به صورت سراسری اضافه شده است، بررسی میکنم که آیا این کاربر حق دارد بعضی از المان‌ها را ببیند یا خیر؟ در صورتیکه برای هر المان موجود در صفحه اعتباری نداشته باشد، آن المان از صفحه حذف و یا غیرفعال میشود.
ابتدا یک صفحه را با المان‌های زیر میسازیم. از آنجا که بیشتر تگ‌های عملیاتی از نوع لینک و دکمه هستند، از هر کدام، سه عنصر به صفحه اضافه میکنیم:
<button data-perm="true" data-controller="c" data-action="r" data-method="post" data-type="disable">Test 1</button>
<button data-perm="true" data-controller="c" data-action="t" data-method="post">Test 2</button>
<button data-perm="false" data-controller="c" data-action="r" data-method="post">Test 3</button>
<a data-perm="true" data-controller="c" data-action="m" data-method="post">Test 4</a>
<a data-perm="false" data-controller="c" data-action="m" data-method="post">Test 5</a>
<a data-perm="true" data-controller="c" data-action="t" data-method="post">Test 6</a>
در اینجا ما از همان ساختار آشنای *-data استفاده میکنیم و معنی هر کدام از ویژگی‌ها برابر زیر است:
ویژگی
توضیحات
 perm   آیا نیاز به اعتبارسنجی دارد یا خیر؟ در صورتی که المانی مقدار Perm آن با مقدار true پر گردد، اعتبارسنجی روی آن اعمال خواهد شد.   
 controller   نام کنترلری که به آن دسترسی دارد.  
 action  نام اکشنی که به آن در کنترلر ذکر شده دسترسی دارد. 
 method   در صورتیکه دسترسی get و post و ... هر یک متفاوت باشد.  
 type  نحوه برخورد با المان غیرمجاز. در صورتیکه با disable مقداردهی شود، المان غیرفعال و در غیر اینصورت، از روی صفحه حذف میشود.

سپس یک کلاس جدید ساخته و با ارث بری از ActionFilterAttribute، کار ساخت فیلتر را آغاز میکنیم:

    public class AuthorizePage: ActionFilterAttribute
    {
        private HtmlTextWriter _htmlTextWriter;
        private StringWriter _stringWriter;
        private StringBuilder _stringBuilder;
        private HttpWriter _output;
        IAuthorization _auth;
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {        
            _stringBuilder = new StringBuilder();
            _stringWriter = new StringWriter(_stringBuilder);
            _htmlTextWriter = new HtmlTextWriter(_stringWriter);
            _output = (HttpWriter) filterContext.RequestContext.HttpContext.Response.Output;
            filterContext.RequestContext.HttpContext.Response.Output = _htmlTextWriter;
            _auth = new Auth();
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            var response = _stringBuilder.ToString();

            response = AuthorizeTags(response);
            _output.Write(response);
        }

        public string AuthorizeTags(string response)
        {

            var doc = GetHtmlDocument(response);
            var nodes=doc.DocumentNode.SelectNodes("//*[@data-perm]");

            if (nodes == null)
                return response;

            foreach(var node in nodes)
            {
                var dataPermission = node.Attributes["data-perm"];
                if(!dataPermission.Value.TryBooleanParse())
                {
                    continue;
                }

                var controller = node.Attributes["data-controller"].Value;
                var action = node.Attributes["data-action"].Value;
                var method = node.Attributes["data-method"].Value;

                var access=_auth.Authorize(HttpContext.Current.User.Identity.Name , controller, action, method);

                if (access)
                    continue;

                var removeElm = true;
                var type = node.Attributes["data-type"]?.Value;
                if (type!=null && type.ToLower()== "disable")
                {
                    removeElm = false;
                }

                if(removeElm)
                {
                    node.Remove();
                    continue;
                }


                node.Attributes.Add("disabled", "true");
            }

            return doc.DocumentNode.OuterHtml;
        }

        private HtmlDocument GetHtmlDocument(string htmlContent)
        {
            var doc = new HtmlDocument
            {
                OptionOutputAsXml = true,
                OptionDefaultStreamEncoding = Encoding.UTF8
            };
            doc.LoadHtml(htmlContent);

            return doc;
        }

    }
در خطوط اولیه، از متد OnActionExecuting به عنوان یک سازنده برای پر کردن در لحظه هر درخواست استفاده میکنیم. htmlTextWriter که یک پیاده سازی از TextWriter هست، برای نوشتن بهینه کدهای HTML سودمند میباشد و از آن جهت که نیاز زیادی برای کار با رشته‌ها دارد، از StringBuilder در پشت صحنه استفاده می‌کند. جهت آشنایی بیشتر با این کلاس‌ها، مطلب StringBuilder و قسمت نظراتش را مطالعه بفرمایید. همچنین یک httpWriter هم برای ایجاد خروجی در اکشن مورد نظر نیازمندیم و مورد آخر یک اینترفیس میباشد که جهت اعتبارسنجی مورد استفاده قرار میگیرد و این اینترفیس میتواند از طریق تزریق وابستگی‌ها پر شود:
  public interface IAuthorization
    {
        bool Authorize(string userId, string controller, string action, string method);
    }

ادامه کد در متد OnResultExecuted قرار دارد و متد اصلی کار ما میباشد. این متد بعد از صدور خروجی از اکشن، صدا زده شده اجرا میشود و شامل خروجی اکشن میباشد. خروجی اکشن را به متدی به نام AuthorizeResponse داده و با استفاده از بسته htmlagilitypack که یک HTML Parser میباشد، کدهای HTML را تحلیل میکنیم. قاعده فیلترسازی المان‌ها در این کتابخانه بر اساس قواعد تعریف شده در XPath میباشد. بر اساس این قاعده ما گفتیم هر نوع تگی که دارای ویژگی data-perm میباشد، باید به عنوان گره‌های فیلتر شده برگشت داده شود. سپس مقادیر نام کنترلر و اکشن و ... از المان دریافت شده و با استفاده از اینترفیسی که ما اینجا تعریف کرده‌ایم، بررسی میکنیم که آیا این کاربر به این موارد دسترسی دارد یا خیر. در صورتیکه پاسخ برگشتی، از عدم اعتبار کاربر بگوید، گره مورد نظر حذف و یا در صورتیکه ویژگی data-type وجود داشته و مقدارش برابر disable باشد، آن المان غیرفعال خواهد شد. در نهایت کد تولیدی سند را به رشته تبدیل کرده و جایگزین خروجی فعلی میکنیم.
جهت تعریف سراسری آن در Global.asax داریم:
protected void Application_Start()
{
  GlobalFilters.Filters.Add(new AuthorizePage());
}
مطالب
آشنایی با WPF قسمت دوم: Layouts بخش اول
layout‌‌‌ها یکی از مهمترین قسمت‌های یک برنامه‌ی کاربردی هستند. چیدمان کنترل‌ها روی یک ناحیه با دادن مختصات پیکسلی ثابت، ممکن است در یک محیط محدود خود را خوب نشان بدهد ولی به زودی با تغییر محیط برنامه و یا تغییر وضوح تصویر صفحه نمایش، برنامه از کنترل خارج خواهد شد؛ در نتیجه استفاده از Layout‌ها یا پنل‌ها در WPF امری حیاتی و مهم هستند. layout‌‌ها که با نام container هم شناخته می‌شوند وظیفه دارند که بگویند چه کنترل‌هایی در کجا و چگونه باید در صفحه‌ی برنامه قرار بگیرند. پنل‌های توکار در WPF به دسته‌های زیر تقسیم می‌شوند:
  • Grid Panel
  • Stack Panel
  • Dock Panel
  • Wrap Panel
  • Canvas Panel

StackPanel

این پنل یکی از ساده‌ترین و سودمندترین پنل هاست که بر اساس جهت Orientation افقی یا عمودی که به آن تنظیم می‌شود، کنترل‌ها را کنار یکدیگر یا زیر یکدیگر قرار می‌دهد. این کنترل برای ایجاد و تهیه لیست‌های مختلف مناسب است. در ساختار داخلی کنترل‌های لیست و کامبو و منو‌ها که در WPF موجود هستند این پنل استفاده شده است. کد زیر یک نمونه کاربرد Stack Panel را نشان می‌دهد که به صورت عمودی چینش شده است.
<StackPanel>
  <TextBlock Margin="10" FontSize="20">How do you like your coffee?</TextBlock>
  <Button Margin="10">Black</Button>
  <Button Margin="10">With milk</Button>
  <Button Margin="10">Latte machiato</Button>
  <Button Margin="10">Chappuchino</Button>
</StackPanel>

نکته‌ی مهم اینکه میتوانید در اینجا از یک nested layout هم استفاده کنید بدین صورت که یک layout را داخل یک layout دیگر قرار دهید. کد زیر ترکیب دو stack panel را به صورت افقی و عمودی به ما نشان می‌دهد:

<StackPanel Orientation="Vertical"> <!-- Vertical is the default -->
  <Label Background="Red">Red 1</Label>
  <Label Background="LightGreen">Green 1</Label>
  <StackPanel Orientation="Horizontal">
    <Label Background="Red">Red 2</Label>
    <Label Background="LightGreen">Green 2</Label>
    <Label Background="LightBlue">Blue 2</Label>
    <Label Background="Yellow">Yellow 2</Label>
    <Label Background="Orange">Orange 2</Label>
  </StackPanel>
  <Label Background="LightBlue">Blue 1</Label>
  <Label Background="Yellow">Yellow 1</Label>
  <Label Background="Orange">Orange 1</Label>
</StackPanel>


Dock Panel

احتمالا به خاطر نامش، نحوه کارش را حدس زده اید. این پنل، اشیاء موجود را در 4 جهت و مرکز می‌چسباند. مشخص نمودن جهت چسبیده شدن هر کنترل توسط خاصیت DockPanel.Dock صورت می‌گیرد و مقدار Left، مقدار پیش فرض است. در صورتی که بخواهید المانی را در مرکز بچسبانید باید آن را به عنوان آخرین المان معرفی کرده و در Dock Panel مقدار خاصیت LastChildFill را با True برابر کنید.

<DockPanel LastChildFill="True">
    <Button Content="Dock=Top" DockPanel.Dock="Top"/>
    <Button Content="Dock=Bottom" DockPanel.Dock="Bottom"/>
    <Button Content="Dock=Left"/>
    <Button Content="Dock=Right" DockPanel.Dock="Right"/>
    <Button Content="LastChildFill=True"/>
</DockPanel>


به نحوه‌ی تعریف خاصیت DockPanel.Dock دقت کنید به این نوع خاصیت‌ها،  Attached Dependency Property (شاید در فارسی بتوانیم خاصیت‌های وابستگی متصل صدا بزنیم) می‌گویند. این خاصیت‌ها نوع خاصی از خاصیت‌های وابستگی هستند که به شما اجازه می‌دهند مقداری را به شیء‌ایی نسبت دهید که آن شیء چیزی در مورد آن نمی‌داند. بهترین مثال در مورد این ویژگی، پنل‌ها هستند که یکی از موارد استفاده‌ی از آن را در بالا می‌بینید. هر پنل می‌تواند تا بی نهایت المان فرزند داشته باشد که هر المان باید خواصش توسط پنل مشخص گردد. ولی اگر پنل ما تعداد زیادی فرزند داشته باشد، نوشتن خواص هر کدام از فرزندها داخل تگ پنل، کاری غیر ممکن است. اینجاست که این نوع خاصیت‌ها خودشان را نشان می‌دهند. پس به این نحو مقادیر، داخل کنترل هر تگ تعریف می‌شود ولی توسط پنل مورد استفاده قرار می‌گیرد. نحوه‌ی نوشتن این نوع خاصیت: ابتدا یک پیشوند از نوع تگ پنل را در ابتدا آورده و سپس بعد از .(نقطه) نام خاصیت را ذکر می‌کنیم.

نحوه‌ی تعریف این نوع خاصیت‌ها در یک کلاس به صورت زیر است که برای شیء یا پنل canvas می‌باشد:

public static readonly DependencyProperty TopProperty =
    DependencyProperty.RegisterAttached("Top", 
    typeof(double), typeof(Canvas),
    new FrameworkPropertyMetadata(0d,
        FrameworkPropertyMetadataOptions.Inherits));
 
public static void SetTop(UIElement element, double value)
{
    element.SetValue(TopProperty, value);
}
 
public static double GetTop(UIElement element)
{
    return (double)element.GetValue(TopProperty);
}
در مثال dockPanel بالا در هر طرف تنها یک المان قرار دادیم. برای قرار دادن المان‌های بیشتر در طرفین، تنها ذکر یک المان جدید با خاصیت Dockpanel.dock کافی است و الویت نمایش آن‌ها بر اساس ترتیب نوشتن تگها توسط شماست. مثال زیر این نکته را نشان می‌دهد:
    <Button Content="Dock=Top" DockPanel.Dock="Top"/>
        <Button Content="Dock=Bottom" DockPanel.Dock="Bottom"/>
        <Button Content="Dock=Left"/>
        <Button Content="Dock=Left2"/>
        <Button Content="Right2" DockPanel.Dock="Right"/>
        <Button Content="Dock=Right" DockPanel.Dock="Right"/>
        <Button Content="LastChildFill=True"/>



Wrap Panel
این پنل بسیار شبیه StackPanel هست ولی مثل آن اشیاء را در یک سطر یا ستون ادامه نمی‌دهد؛ بلکه با رسیدن به انتهای پنجره، سطر یا ستون جدیدی را آغاز می‌کند. در stack panel با پایان پنجره، ادامه اشیا آن قابل مشاهده نبود ولی در این شیء با اتمام و رسیدن به لبه‌ی پنجره، اشیاء در سر جدید (افقی) یا ستون جدید (عمودی) نمایش داده می‌شوند. این پنل‌ها می‌توانند در ساخت تب‌ها و نوار ابزار استفاده شوند.

Canvas Panel

پایه‌ای‌ترین layout موجود در WPF است. موقعیت قرارگیری المان‌های فرزندش بر اساس نقاط تعیین شده است.این پنل بیشتر برای رسم اشکال و گرافیک دو بعدی مناسب است و اصلا برای قرارگیری کنترل‌های WPF روی آن توصیه نمی‌شود و مشکل winform‌ها در آن رخ خواهد داد.

شروع ترسیم یک شکل دو بعدی روی آن بر اساس دوتا از چهار "خاصیت‌های وابستگی متصل" صورت می‌گیرد که به شرح زیر هستند:

  • Canvas.LEFT
  • Canvas.RIGHT
  • Canvas.TOP
  • Canvas.BOTTOM

نمونه از کد نوشته شده آن به صورت زیر است:

<Canvas>
    <Rectangle Canvas.Left="40" Canvas.Top="31" Width="63" Height="41" Fill="Blue"  />
    <Ellipse Canvas.Left="130" Canvas.Top="79" Width="58" Height="58" Fill="Blue"  />
    <Path Canvas.Left="61" Canvas.Top="28" Width="133" Height="98" Fill="Blue" 
          Stretch="Fill" Data="M61,125 L193,28"/>
</Canvas>

ترتیب قرارگیری اشکال روی هم در canvas به ترتیبی انجام می‌گیرد که در XAML نوشته اید ولی می‌توان با استفاده از خاصیت Canvas.ZIndex این ترتیب را تغییر داد.

<Canvas>
    <Ellipse Fill="Green" Width="60" Height="60" Canvas.Left="30" Canvas.Top="20"    
             Canvas.ZIndex="1"/>
    <Ellipse Fill="Blue"  Width="60" Height="60" Canvas.Left="60" Canvas.Top="40"/>
</Canvas>
شکل زیر در سمت راست، نتیجه‌ی کد بالاست ولی بدون ذکر ZIndex شکل سمت چپ نتیجه‌ی کار خواهد بود.

ViewBox
شاید عده‌ای به سختی آن را یک Layout بدانند و بیشتر آن را یک کنترل معمولی می‌شناسند ولی وظیفه‌ی آن بسیار شبیه Layout هاست. خصوصیتی که این شیء دارد این است که با تغییر اندازه محیط برنامه به هر نحوی، یک تغییر مقیاس روی اشیاء داخل آن رخ می‌دهد و کنترل‌ها به همراه متون و هر چیزی که در درخت منطقی و بصری است Scale آن تغییر می‌یابند.
نمونه‌ی کد زیر را تست کنید تا تفاوت بین دو Button را ببینید:
  <StackPanel Orientation="Vertical">
        <Button Content="Test" />
        <Viewbox Stretch="Uniform">
            <Button Content="Test" />
        </Viewbox>
    </StackPanel>
نتیجه‌ی کار:

در بخش دوم Layout‌ها مبحث گرید و ساخت Layout اختصاصی و تعدادی از خاصیت‌ها را بررسی خواهیم کرد.

نظرات مطالب
گروه بندی اطلاعات و گزارشات Master-Details در PdfReport

سلام. 

من یک View در SQL دارم که چند جدول مختلف را با هم Join میزند. 

رکوردها باید با فیلد ReceptionPatientId گروه بندی شوند.

کد برنامه:

 return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata
                {
                    Author = username,
                    Application = "",
                    Keywords = "Executive Unit Work List Report",
                    Subject = "Executive Unit Work List Report",
                    Title = "Executive Unit Work List Report"
                });
            })
           .DefaultFonts(fonts => fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                             Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"))
           .PagesFooter(footer => footer.DefaultFooter(DateTime.Now.ToLongPersianDate()))
           .PagesHeader(header =>
           {
               header.CustomHeader(new UnitWorkListHeader { PdfRptFont = header.PdfFont });
           })
           .MainTableTemplate(template => template.BasicTemplate(BasicTemplate.RainyDayTemplate))
           .MainTablePreferences(table =>
           {
               table.ColumnsWidthsType(TableColumnWidthType.Relative);
               table.GroupsPreferences(new GroupsPreferences
               {
                   GroupType = GroupType.HideGroupingColumns,
                   RepeatHeaderRowPerGroup = true,
                   ShowOneGroupPerPage = false,
                   SpacingBeforeAllGroupsSummary = 5f,
                   NewGroupAvailableSpacingThreshold = 170
               });
           })
           .MainTableDataSource(dataSource => 
               dataSource.DataTable(new ReceptionPatientBl(context).
               GetReceptionSummaryView(_rcpIds).
               ToDataSet(false).Tables[0]))
           .MainTableColumns(columns =>
           {
               columns.AddColumn(column =>
               {
                   column.PropertyName("rowNo");
                   column.IsRowNumber(true);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                   column.IsVisible(true);
                   column.Order(0);
                   column.Width(1);
                   column.HeaderCell("#");
               });
               columns.AddColumn(column =>
               {
                   column.PropertyName("ReceptionPatientId");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("ReceptionPatientId");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("ReceptionDate");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("ReceptionDate");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("IsDelete");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("IsDelete");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Fname");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Fname");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Lname");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Lname");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Sex");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Sex");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("BirthDate");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("BirthDate");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Mobile");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Mobile");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("LabId");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("LabId");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1.ToString() == val2.ToString();
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("TestName");
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(4);
                   column.Width(2);
                   column.HeaderCell("نام تست");
                   column.IsVisible(true);
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("TestShortName");
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(5);
                   column.Width(2);
                   column.HeaderCell("نام اختصاری تست");
                   column.IsVisible(true);
               });

           })
           .MainTableEvents(events => events.DataSourceIsEmpty(message: "There is no data available to display."))
           //.Export(export =>
           //{
           //    export.ToExcel();
           //})
           .Generate(data =>
           {
               var fileName = "ExecutiveWorkListReport.pdf";
               fileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
               data.FlushInBrowser(fileName);
           }); 
        }

اما خروجی به صورت زیر است:

همانطور که مشاهده می‌کنید بدون توجه به فیلد ID سه مرتبه این گروه تکرار شده است.

ممکن است راهنمایی فرمایید مشکل کار کجاست؟

نظرات مطالب
ارسال انواع بی نام (Anonymous) بازگشتی توسط Entity framework به توابع خارجی
خواهش میکنم قابل نداشت . دوست عزیز این مطلب واقعا پیچیده و تخصصی هستش من یک نمونه واستون نوشتم که کد رو براتون میزارم ولی برای فهم کاملش نیاز به اشنایی عمقی با ساختار دات نت و کار با رفلکشن داره که توضیحش در چند خط نمیگنجه بحثه یک کتاب کامله. این نمونه کد که نوشتم دقیقا همون چیزی هست که درخواست توضیحش رو دادید .
        public static List<T> CreateGenericListFromAnonymous<T>(object obj, T example)
        {
            var newquery = new List<T>();
            var constructor = typeof(T).GetConstructors(
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
            ).OrderBy(c => c.GetParameters().Length).First();
            foreach (var item in ((IEnumerable<object>)obj))
            {
                var mapobj = new object[example.GetType().GetProperties().Count()];
                int counter = 0;
                foreach (var itemmap in example.GetType().GetProperties())
                {

                    object value = item.GetType().GetProperty(itemmap.Name).GetValue(item, null);
                    Type t = itemmap.PropertyType;
                    mapobj[counter] = Convert.ChangeType(value, t);
                    counter++;
                }
                newquery.Add((T)constructor.Invoke(mapobj));
            }

            return newquery;
        }


var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new
                              { 
                                   id = pst.id, 
                                   Title = pst.Title,
                                    Likes = pst.Likes,
                                    Unlikes = pst.Unlikes
                             }).OrderByDescending(c => c.id);

object test_custom_casting = CreateGenericListFromAnonymous(query.ToList(), new { id = 0, Title = string.Empty });
 همینطور که میبینید نتیجه‌ی بازگشتی از یک query مرجع یک list شامل فقط و فقط فیلدهایی هست که به صورت inline توسط انواع بی نام مشخص شده ! امیدوارم مفید واقع شده باشه . یا حق
مطالب
آشنایی با Jaeger
 در سال‌های اخیر، معماری میکروسرویس، یکی از محبوب‌ترین روش‌ها برای طراحی نرم‌افزار بوده‌است. جهت بهبود کارآیی، رفع خطا، درک  عملکرد سیستم در محیط عملیاتی و  نمایش چگونگی فراخوانی سرویس‌ها توسط یکدیگر می‌توانیم از ابزار‌های distributed tracing استفاده کنیم. ابزارهای متنوعی برای این منظور وجود دارند، اما بطور کلی همه با روش مشابهی کار می‌کنند. اطلاعات مربوط به فعالیت‌هایی مثل فراخوانی سرویس و مراجعه به دیتابیس که درون میکروسرویس رخ می‌دهد، در یک span  ذخیره می‌شوند. Span‌های جداگانه توسط شناسه‌ای یکتا به هم مرتبط می‌شوند و به عنوان یک trace نمایش داده می‌شوند. با استفاده از این trace‌ها، مجموعه‌ای از اطلاعات مثل تاریخ شروع و پایان هر درخواست و هر فعالیت را در اختیار داریم. 


جهت گرفتن دیتای مربوط به هر span، درون هر میکروسرویس می‌توانیم از پروژه‌های متن باز OpenTracing  و یا  OpenTelemetry استفاده کنیم. کتابخانه OpenTracing.Contrib.NetCore پیاده سازی OpenTracing در دات نت می‌باشد و می‌تواند  فعالیت‌های مربوط به ASP.NET Core، Entity Framework Core System.Net.Http (HttpClient)، System.Data.SqlClient و Microsoft.Data.SqlClient را دریافت و به tracer  ارسال کند. 

برای پیاده سازی distributed tracing، می‌توانیم از ابزار متن باز و محبوب Jaeger (با تلفظ یِگِر)  که ابتدا توسط شرکت Uber منتشر شد، استفاده کنیم. نحوه کارکرد Jaeger بصورت زیر می‌باشد:




ساده‌ترین روش  برای راه‌اندازی Jager، استفاده از داکر ایمیج All in one که شامل ماژول های agent ، collector،  query  و ui  است. پورت 6831 مربوط به agent  و پورت 16686 مربوط به ui می‌باشد. برای جزئیات مربوط به ماژول‌های مختلف از این لینک استفاده کنید.

docker run -d -p 6831:6831/udp -p 6832:6832/udp -p 14268:14268 -p 14250:14250 -p 16686:16686 -p 5778:5778  
--name jaeger jaegertracing/all-in-one:latest

بعد از اجرای دستور بالا، اطلاعات مربوط به سرویس‌ها و trace ها  در ماژول Jager UI  با آدرس http://localhost:16686 قابل مشاهده است. 

جهت استفاده از Jaeger از پروژه تستی که شامل دو سرویس User و Gateway می‌باشد، استفاده می‌کنیم. در سرویس User، متد AddUser در صورت عدم وجود کاربر در دیتابیس، اطلاعات کاربر از گیت‌هاب را دریافت و در دیتابیس ذخیره می‌کند. سرویس Gateway از Ocelot برای مسیردهی درخواست‌ها استفاده می‌کند. برای آشنایی با ocelot‌ این پست را  مطالعه نمایید. 


    public async Task<ApiResult<Models.User>> AddUserAsync(string username)
        {

            var result = new ApiResult<Models.User>();
            
            var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Login == username);

            if (user is null)
            {
                try
                {
                    var url = string.Format(_appConfig.Github.ProfileUrl, username);
                    var apiResult = await _httpClient.GetStringAsync(url);
                    var userDto = JsonSerializer.Deserialize<UserDto>(apiResult);
                    user = _mapper.Map<Models.User>(userDto);
                    await _applicationDbContext.Users.AddAsync(user);
                    await _applicationDbContext.SaveChangesAsync();
                    result.Result = user;
                    result.Message = "User successfully Created";
                    return result;
                }
                catch (Exception e)
                {
                    result.Message = "User not found";
                    return result;
                }
            }

            result.Message = "User already exist";
            result.Result = user;

            return result;

        }


برای ثبت Trace مربوط به درخواست‌ها در Jaeger ، بعد از نصب  پکیج‌های Jaeger و OpenTracing.Contrib.NetCore در هر دو سرویس، در کانفیگ هریک از سرویس‌ها مورد زیر را اضافه می‌کنیم:

"JaegerConfig": {
    "Host": "localhost",
    "Port": 6831,
    "IsEnabled": true,
  "SamplingRate": 0.5
  }


و برای اضافه شدن tracer به برنامه از متد الحاقی زیر استفاده می‌کنیم:

 public static class Extensions
    {
        public static void AddJaeger(this IServiceCollection services, IConfiguration configuration)
        {
            var config = configuration.GetSection("JaegerConfig").Get<JaegerConfig>();
            
            if (!(config?.IsEnabled ?? false))
                return;

            if (string.IsNullOrEmpty(config?.Host))
                throw new Exception("invalid JaegerConfig");

            services.AddSingleton<ITracer>(serviceProvider =>
            {
                string serviceName = Assembly.GetEntryAssembly()?.GetName().Name;

                ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();

                var sampler = new ProbabilisticSampler(config.SamplingRate); 

                var reporter = new RemoteReporter.Builder()
                    .WithLoggerFactory(loggerFactory)
                    .WithSender(new UdpSender(config.Host, config.Port, 0))
                    .WithFlushInterval(TimeSpan.FromSeconds(15))
                    .WithMaxQueueSize(300)
                    .Build();
                
                ITracer tracer = new Tracer.Builder(serviceName)
                    .WithLoggerFactory(loggerFactory)
                    .WithSampler(sampler)
                    .WithReporter(reporter)
                    .Build();

                GlobalTracer.Register(tracer);

                return tracer;
            });

            services.AddOpenTracing();
        }
    }


برای ثبت trace‌ها استراتژی‌های متفاوتی وجود دارد. در اینجا از ProbabilisticSampler استفاده شده‌است که در سازنده‌ی آن می‌توان درصد ثبت Trace‌ها را مقدار دهی کرد. در نهایت این متد الحاقی را در Startup اضافه می‌کنیم:

builder.Services.AddJaeger(builder.Configuration);


بعد از اجرای پروژه و فراخوانی https://localhost:6000/gateway/Users/Add ، سرویس Gateway، درخواست را به سرویس User ارسال می‌کند و این سرویس‌ها در  Jaeger UI  قابل مشاهده هستند.




جهت مشاهده trace ‌ها ، سرویس مورد نظر را انتخاب و روی Find Traces کلیک کنید. با کلیک روی Trace مورد نظر، جزئیات فعالیت هایی مثل فراخوانی سرویس و مراجعه به دیتابیس قابل مشاهده است. 


برای اضافه کردن لاگ سفارشی به یک span، می‌توان از اینترفیس ITracer  استفاده کرد:

        private readonly IUserService _userService;
        private readonly ITracer _tracer;

        public UsersController(IUserService userService, ITracer tracer)
        {
            _userService = userService;
            _tracer = tracer;
        }
        [HttpPost]
        public async Task<ActionResult> AddUser(AddUserDto model)
        {
            var actionName = ControllerContext.ActionDescriptor.DisplayName;
            
            using var scope = _tracer.BuildSpan(actionName).StartActive(true);
            
            scope.Span.Log($"Add user log username: {model.Username}");
            
            return Ok(await _userService.AddUserAsync(model.Username));
        }  



کدهای مربوط به این مطلب در اینجا قابل دسترسی است.