مطالب
بررسی علت CPU Usage بالای برنامه در حال اجرا

فرض کنید به یک سرور مراجعه کرده‌اید و شکایت از CPU Usage مربوط به پروسه w3wp.exe یا همان IIS Worker Process است که بالای 90 درصد می‌باشد. بر روی این سرور هم هیچ چیز دیگری نصب نیست و مطابق مقررات موجود، قرار هم نیست که برنامه‌ای نصب شود. اکنون سؤال این است که چطور تشخیص می‌دهید، کدام قسمت یکی از برنامه‌ها‌ی دات نتی در حال اجرا (در اینجا یکی از برنامه‌های ASP.NET هاست شده)، سبب بروز این مشکل شده است؟ کدام ترد بیشترین زمان CPU را به خود اختصاص داده است؟ چطور باید خطایابی کرد؟
اولین کاری که در این موارد توصیه می‌شود مراجعه به برنامه‌ی معروف process explorer و بررسی برگه‌ی threads آن است. در اینجا حتی می‌توان call stacks مرتبط با یک ترد را هم مشاهده کرد. اما ... این برگه در مورد پروسه‌ها و تردهای دات نتی، اطلاعات چندانی را در اختیار ما قرار نمی‌دهد.
خوشبختانه امکان دیباگ پروسه‌های دات نتی در حال اجرا توسط کتابخانه‌ی MdbgCore.dll پیش بینی شده است. این فایل را در یکی از مسیر‌های ذیل می‌توانید پیدا کنید:
C:\Program Files\Microsoft SDKs\Windows\vXYZ\bin\MdbgCore.dll
C:\Program Files\Microsoft SDKs\Windows\vXYZ\bin\NETFX 4.0 Tools\MdbgCore.dll

در ادامه می‌خواهیم توسط امکانات این کتابخانه، به stack trace تردهای در حال اجرای یک برنامه دات نتی دسترسی پیدا کرده و سپس نام متدهای مرتبط را نمایش دهیم:
using System;
using System.Collections;
using System.Diagnostics;
using Microsoft.Samples.Debugging.MdbgEngine;

namespace CpuAnalyzer
{
class Program
{
static void Main(string[] args)
{
var engine = new MDbgEngine();

var processesByName = Process.GetProcessesByName("MyApp");
if (processesByName.Length == 0)
throw new InvalidOperationException("specified process not found.");
var processId = processesByName[0].Id;

var process = engine.Attach(processId);
process.Go().WaitOne();

foreach (MDbgThread thread in (IEnumerable)process.Threads)
{
foreach (MDbgFrame frame in thread.Frames)
{
if (frame == null || frame.Function == null) continue;
Console.WriteLine(frame.Function.FullName);
}
}

process.Detach().WaitOne();
}
}
}
در اینجا در ابتدا نیاز است تا pid یا process-id مرتبط با برنامه در حال اجرا یافت شود. سپس از این pid جهت اتصال (engine.Attach) به پروسه مورد نظر استفاده خواهیم کرد. در ادامه کلیه تردهای این پروسه در حال دیباگ لیست شده و سپس MDbgFrameهای این ترد بررسی می‌شوند و نام متدهای مرتبط در کنسول نمایش داده خواهند شد.
خوب در مرحله بعد شاید بد نباشد که این متدها را بر اساس CPU usage آن‌ها مرتب کنیم. به این ترتیب بهتر می‌توان تشخیص داد که کدام متد مشکل ساز بوده است. برای این منظور باید به API ویندوز و تابع GetThreadTimes مراجعه کرد و اولین پارامتر ورودی آن، همان thread.CorThread.Handle اولین حلقه مثال فوق می‌باشد. هر کدام که جمع KernelTime + UserTime بیشتری داشت، همان است که مشکل درست کرده است.
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetThreadTimes(IntPtr handle, out long creation, out long exit, out long kernel, out long user);
این مورد را به عنوان تمرین بررسی کرده و ادامه دهید! همچنین بهتر است جهت دستیابی به اطلاعاتی معتبر، اولین حلقه برنامه فوق، حداقل 10 بار اجرا شود تا اطلاعات آماری بهتری را بتوان ارائه داد. البته در این حالت نکته‌ی زیر باید رعایت شود:
for (int i = 0; i < 10; i++)
{
foreach (MDbgThread thread in (IEnumerable)process.Threads)
{
//...
}
process.Go();
Thread.Sleep(1000);
process.AsyncStop().WaitOne();
}

در کل این مثال جای کار زیاد دارد. برای مثال طراحی یک رابط کاربری برای آن و نمایش جزئیات بیشتر. به همین منظور حداقل سه پروژه مشابه را می‌توان نام برد:

مطالب
معرفی پروژه Orchard
معرفی پروژه Orchard:
 سیستم مدیریت محتوای Orchard توسط مایکروسافت در ژانویه سال 2011 همراه با ASP.NET MVC 3, IIS Express, SQL CE 4 ,فریم ورک Web Farm و WebMatrix ارائه شد. هدف تمامی این پروژه‌ها ایجاد قابلیتی برای توسعه آسان برنامه‌های تحت وب در محیط ویندوز بود. همانطور که PHP دارای ابزارهای مناسبی برای این منظور است. با ارائه این ابزارها مایکروسافت درخواست برنامه نویسان را برای ساده سازی تجربه توسعه وب اجابت کرد. پروژه Orchard متعلق به Outercurve Foundation (به ندرت CodePlex Foundation نیز شناخته می‌شود) است که توسط مایکروسافت پشتیبانی می‌شود. Outercurve Foundation یک سازمان غیر انتفاعی است که هدف آن تشویق و حمایت از پروژه‌های متنی بازی نظیر Orchad و یا toolkit معروف ASP.NET MVC یعنی MVC Contrib است. مایکروسافت به صورت رسمی از Orchad پشتیبانی نمی‌کند اما در حال حاضر برنامه نویسانی را جهت توسعه این سیستم استخدام کرده است.

برای پروژه Orchad سه هدف تعیین شده است :
1)فراهم نمودن و به اشتراک گذاری یک مجموعه کامپوننت جهت استفاده در برنامه‌های ASP.NET
2)ساخت تعدادی برنامه‌ی مرجع با استفاده از کامپوننت‌های فوق
3)ساخت انجمن هایی برای پشتیبانی از این کامپوننت‌ها و یا برنامه‌های مرجع

 در حال حاضر Orchard بیشتر به عنوان یک سکو (platform) برای ساخت وب سایت‌های ایجاد محتوی استفاده می‌شود آنچه در Orchard حائز اهمیت است ذکر این نکته است که این سیستم به طور کامل با استفاده از ابزار‌های متن باز نوشته شده است. Orchard از ASP.NET MVC 3.0 به همراه View engine جدید و فوق العاده آن یعنی Razor بهره می‌برد. همچنین این پروژه وابستگی زیادی به دیگر ابزارهای متن باز نظیر NHibernate برای دسترسی به داده‌ها و همچنین Autofac برای dependency injection دارد شایان ذکر است که مجوز استفاده از Orchard تحت لیسانس BSD است.

طبق اعلام وب سایت رسمی این پروژه در عرض حدود یک سالی که از ارائه این CMS می‌گذرد بیش از یک میلیون بار دانلود  و بیش از 300 ماژول و تم برای آن ساخته شده است که در گالری آن در دسترس می‌باشد. Orchard به صورت ریلیز‌های جزئی ارائه می‌شود و جدیدترن نسخه آن در هنگام نوشتن این متن 1.5.1 می‌باشد.

اما چرا به یک CMS دات نتی دیگر نیاز است ؟

تعداد زیادی سیستم‌های مدیریت محتوای تجاری و یا متن باز در طول این سال‌ها با استفاده از دات نت ارائه شده اند. (DotNetNuke (DNN بدون تردید یک از معروفترین و قدرتمندترین آن‌ها است. این CMS در ابتدا با VB.NET نوشته شد و این رویه تا مدت‌ها ادامه داشت تا اینکه در نسخه اخیر به #C تغییر کرد. اگرچه DNN و همچنین پروژه متن باز دیگری به نام Umbraco هر دو محبوب هستند اما با استفاده از WebForm‌ها پیاده سازی شده اند( البته Umbraco در نسخه 5 قصد داشت که از ASP.NET MVC استفاده کند اما علی رغم در دسترس قرار گرفتن این نسخه ظاهرا تیم Umbraco برای تمرکز بیشتر روی نسخه وب فرمی, تصمیم ندارند این پروژه را ادامه دهند.) امروزه وب فرم‌ها همانند گذشته محبوب نیستند به همین دلیل رغبت کمتری برای استفاده از این CMS‌ها  نسبت به قبل وجود دارد. با توجه به شواهد موجود بسیاری از برنامه نویسان دات نتی به سمت ASP.NET MVC مهاجرت کرده اند به همین دلیل سیستم Orchard بر مبنای این تکنولوژی نسبتا جدید دات نت پیاده شده است. با استفاده از Orchard می‌توان یک وب سایت با عملکرد بسیار بالا بدون نوشتن حتی یک خط کد ایجاد نمود. اما مانند هر سیستم مدیریت محتوی دیگری اگر بخواهیم به آن قابلیت هایی را اضافه کنیم که به صورت پیش فرض در آن نیست باید با ساختار آن به خوبی آشنا شویم و همچنین بر ابزارهای مورد نیاز این کار نیز احاطه داشته باشیم. برای دریافت اطلاعات بیشتر می‌توانید به وب سایت رسمی این پروژه در اینجا مراجعه کنید
مطالب
بررسی بهبودهای ProblemDetails در ASP.NET Core 7x
در زمان ارائه‌ی ASP.NET Core 2.1، ویژگی جدیدی به نام [ApiController] ارائه شد که با استفاده از آن، یکسری اعمال توکار جهت سهولت کار با Web API توسط خود فریم‌ورک انجام می‌شوند؛ برای مثال عدم نیاز به بررسی وضعیت ModelState و بررسی خودکار آن با علامتگذاری یک کنترلر به صورت ApiController. یکی دیگر از این ویژگی‌های توکار، تبدیل خروجی تمام status codeهای بزرگتر و یا مساوی 400 یا همان Bad Request، به شیء جدید و استاندارد ProblemDetails است:
{
    "type": "https://example.com/probs/out-of-credit",
    "title": "You do not have enough credit.",
    "detail": "Your current balance is 30, but that costs 50.",
    "instance": "/account/12345/msgs/abc",
    "status": 403,
}
 بازگشت یک چنین خروجی یک‌دست و استانداردی، استفاده‌ی از آن‌را توسط کلاینت‌ها، ساده و قابل پیش‌بینی می‌کند. البته باید درنظر داشت که اگر در این‌حالت، برنامه یک استثنای معمولی را سبب شود، ProblemDetails ای بازگشت داده نمی‌شود. اگر برنامه در حالت توسعه اجرا شود، با استفاده از میان‌افزار app.UseDeveloperExceptionPage، یک صفحه‌ی نمایش جزئیات خطا ظاهر می‌شود و اگر برنامه در حالت تولید و ارائه‌ی نهایی اجرا شود، یک صفحه‌ی خالی (بدون داشتن response body) با status code مساوی 500 بازگشت داده می‌شود. این کمبود ویژه و امکانات سفارشی سازی بیشتر آن، به صورت توکار به ASP.NET Core 7x اضافه شده‌اند و دیگر نیازی به استفاده از کتابخانه‌های ثالث دیگری برای انجام آن نیست.


ProblemDetails بر اساس RFC7807 طراحی شده‌است

RFC7807، قالب استانداردی را برای ارائه‌ی خطاهای HTTP APIها تعریف می‌کند تا نیازی به وجود تعاریف متعددی در این زمینه نباشد و خروجی آن قابل پیش‌بینی و قابل بررسی توسط تمام کلاینت‌های یک API باشد. کلاس ProblemDetails در ASP.NET Core نیز بر همین اساس طراحی شده‌است.
این RFC دو فرمت خروجی را بر اساس مقدار مشخص شده‌ی در هدر Content-Type بازگشت داده شده، مجاز می‌داند:
  • JSON: “application/problem+json” media type
  • XML: “application/problem+xml” media type

که با توجه به این هدر ارسالی، اگر از یک کلاینت از نوع HttpClient استفاده کنیم، می‌توان بر اساس مقدار ویژه‌ی «application/problem+json» تشخیص داد که خروجی API دریافتی، به همراه خطا است و نحوه‌ی پردازش آن به صورت زیر خواهد بود:
var mediaType = response.Content.Headers.ContentType?.MediaType;
if (mediaType != null && mediaType.Equals("application/problem+json", StringComparison.InvariantCultureIgnoreCase))
{
   var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>(null, ct) ?? new ProblemDetails();
   // ...
}
در اینجا بدنه‌ی اصلی شیء ProblemDetails بازگشت داده شده، می‌تواند به همراه اعضای زیر باشد:
- type: یک رشته‌است که به آدرس مستندات HTML ای مرتبط با خطای بازگشت داده شده، اشاره می‌کند.
- title: رشته‌ای است که خلاصه‌ی خطای رخ‌داده را بیان می‌کند.
- detail: رشته‌ای است که توضیحات بیشتری را در مورد خطای رخ‌داده، بیان می‌کند.
- instance: رشته‌ای است که به آدرس محل بروز خطا اشاره می‌کند.
- status: عددی است که بیانگر HTTP status code بازگشتی از سمت سرور است.


البته اگر ویژگی ApiController بر روی کنترلرهای خود استفاده نمی‌کنید، می‌توانید این خروجی را به صورت زیر هم با استفاده از return Problem، تولید کنید:
[HttpPost("/sales/products/{sku}/availableForSale")]
public async Task<IActionResult> AvailableForSale([FromRoute] string sku)
{
   return Problem(
            "Product is already Available For Sale.",
            "/sales/products/1/availableForSale",
            400,
            "Cannot set product as available.",
            "http://example.com/problems/already-available");
}


امکان افزودن اعضای سفارشی به شیء ProblemDetails

امکان بسط این خروجی، با افزودن اعضای سفارشی نیز پیش‌بینی شده‌است. یک نمونه‌ی متداول و پرکاربرد آن، بازگشت خطاهای مرتبط با اعتبارسنجی اطلاعات رسیده‌است:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en
{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "errors": {
        "User": [
            "The user name is not verified."
        ]
    }
}
در اینجا عضو جدید errors را بنابر نیاز این مساله‌ی خاص، مشاهده می‌کنید که در صورت استفاده از ویژگی ApiController بر روی کنترلرهای Web API، به صورت خودکار توسط ASP.NET Core تولید می‌شود و نیازی به تنظیم خاصی و یا کدنویسی اضافه‌تری ندارد. کلاس مخصوص آن نیز ValidationProblemDetails‌ است.


جهت افزودن اعضای سفارشی دیگری به شیء ProblemDetails می‌توان به صورت زیر عمل کرد:
namespace WebApplication.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class DemoController : ControllerBase
    {
        [HttpPost]
        public ActionResult Post()
        {
            var problemDetails = new ProblemDetails
            {
                Detail = "The request parameters failed to validate.",
                Instance = null,
                Status = 400,
                Title = "Validation Error",
                Type = "https://example.net/validation-error",
            };

            problemDetails.Extensions.Add("invalidParams", new List<ValidationProblemDetailsParam>()
            {
                new("name", "Cannot be blank."),
                new("age", "Must be great or equals to 18.")
            });

            return new ObjectResult(problemDetails)
            {
                StatusCode = 400
            };
        }
    }

    public class ValidationProblemDetailsParam
    {
        public ValidationProblemDetailsParam(string name, string reason)
        {
            Name = name;
            Reason = reason;
        }

        public string Name { get; set; }
        public string Reason { get; set; }
    }
}
شیء ProblemDetails، به همراه خاصیت Extensions است که می‌توان به آن یک <Dictionary<string, object را انتساب داد و نمونه‌ای از آن‌را در مثال فوق مشاهده می‌کنید. این مثال سبب می‌شود تا عضو جدیدی با کلید دلخواه invalidParams، به همراه لیستی از name و reasonها به خروجی نهایی اضافه شود. مقدار این کلید، از نوع object است؛ یعنی هر شیء دلخواهی را در اینجا می‌توان تعریف و استفاده کرد.


معرفی سرویس جدید ProblemDetails در دات نت 7

در دات نت 7 می‌توان سرویس‌های جدید ProblemDetails را به نحو زیر به برنامه اضافه کرد:
services.AddProblemDetails();
پس از آن به 3 روش مختلف می‌توان از امکانات این سرویس‌ها استفاده کرد:
الف) با اضافه کردن میان‌افزار مدیریت خطاها
app.UseExceptionHandler();
پس از آن، هر استثنای مدیریت نشده‌ای نیز به صورت یک ProblemDetails ظاهر می‌شود و دیگر همانند قبل، سبب نمایش یک صفحه‌ی خالی نخواهد شد.

ب) با افزودن میان‌افزار StatusCodePages
app.UseStatusCodePages();
در این حالت مواردی که استثناء شمرده نمی‌شوند مانند 404، در صورت بروز رسیدن به یک مسیریابی یافت نشده و یا 405، در صورت درخواست یک HTTP method غیرمعتبر نیز توسط یک ProblemDetails استاندارد مدیریت می‌شوند.

ج) با افزودن میان‌افزار صفحه‌ی استثناءهای توسعه دهنده‌ها
app.UseDeveloperExceptionPage();
به این ترتیب در خروجی ProblemDetails، اطلاعات بیشتری از استثناء رخ‌داده، مانند استک‌تریس آن ظاهر خواهد شد.


امکان بازگشت ساده‌تر یک ProblemDetails سفارشی در دات نت 7

برای سفارشی سازی خروجی ProblemDetails، علاوه بر راه‌حلی که پیشتر در این مطلب مطرح شد، می‌توان در دات نت 7 از روش تکمیلی ذیل نیز استفاده کرد:
builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("MachineName", Environment.MachineName));
به این ترتیب در صورت لزوم می‌توان یک عضو سفارشی سراسری را به تمام اشیاء ProblemDetails برنامه به صورت خودکار اضافه کرد و یا اگر می‌خواهیم این مورد را کمی اختصاصی‌تر کنیم، می‌توان به صورت زیر عمل کرد:

الف) تعریف یک ErrorFeature سفارشی
public class MyErrorFeature
{
    public ErrorType Error  { get; set; }
}
​
public enum ErrorType
{
    ArgumentException
}
در ASP.NET Core می‌توان به شیء HttpContext.Features قابل تنظیم در هر اکشن متدی، اشیاء دلخواهی را مانند شیء سفارشی فوق، اضافه کرد و سپس در قسمت options.CustomizeProblemDetails تنظیماتی که ذکر شد، به دریافت و تنظیم آن، واکنش نشان داد.

ب) تنظیم مقدار ErrorFeature سفارشی در اکشن متدها
    [HttpGet("{value}")]
    public IActionResult MyErrorTest(int value)
    {
        if (value <= 0)
        {
            var errorType = new MyErrorFeature
            {
                Error = ErrorType.ArgumentException
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }
​
        return Ok(value);
    }
پس از تعریف شیءایی که قرار است به HttpContext.Features اضافه شود، اکنون روش تنظیم و مقدار دهی آن‌را در یک اکشن متد، در مثال فوق مشاهده می‌کنید.

ج) واکنش نشان دادن به دریافت ErrorFeature سفارشی
services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
    {
        var MyErrorFeature = ctx.HttpContext.Features.Get<MyErrorFeature>();
​
        if (MyErrorFeature is not null)
        {
            (string Title, string Detail, string Type) details = MyErrorFeature.Error switch
            {
                ErrorType.ArgumentException =>
                (
                    nameof(ArgumentException),
                    "This is an argument-exception.",
                    "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1"
                ),
                _ =>
                (
                    nameof(Exception),
                    "default-exception",
                    "https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1"
                )
            };
​
            ctx.ProblemDetails.Title = details.Title;
            ctx.ProblemDetails.Detail = details.Detail;
            ctx.ProblemDetails.Type = details.Type;
        }
    }
);
پس از تنظیم HttpContext.Features در اکشن متدی، می‌توان در options.CustomizeProblemDetails فوق، توسط متد ctx.HttpContext.Features.Get به آن شیء خاص تنظیم شده، در صورت وجود دسترسی یافت و سپس جزئیات بیشتری را از آن استخراج و مقادیر ctx.ProblemDetails جاری را که قرار است به کاربر بازگشت داده شوند، بازنویسی کرد و یا تغییر داد.
 

امکان تبدیل ساده‌تر اطلاعات استثناءهای سفارشی به یک ProblemDetails سفارشی در دات نت 7

بجای استفاده از تنظیمات services.AddProblemDetails جهت بازنویسی مقدار شیء ProblemDetails بازگشتی، می‌توان جزئیات میان‌افزار app.UseExceptionHandler را نیز سفارشی سازی کرد و به بروز استثناءهای خاصی واکنش نشان داد. برای مثال فرض کنید یک استثنای سفارشی را به صورت زیر طراحی کرده‌اید:
public class MyCustomException : Exception
{
    public MyCustomException(
        string message,
        HttpStatusCode statusCode = HttpStatusCode.BadRequest
    ) : base(message)
    {
        StatusCode = statusCode;
    }
​
    public HttpStatusCode StatusCode { get; }
}
و سپس در اکشن متدی، سبب بروز آن شده‌اید:
    [HttpGet("{value}")]
    public IActionResult MyErrorTest(int value)
    {
        if (value <= 0)
        {
            throw new MyCustomException("The value should be positive!");
        }
​
        return Ok(value);
    }
اکنون می‌توان در میان‌افزار مدیریت استثناءهای برنامه، نسبت به مدیریت این استثناء خاص، واکشن نشان داد و ProblemDetails متناظری را تولید و بازگشت داد:
app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async context =>
    {
        context.Response.ContentType = "application/problem+json";
​
        if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
        {
            var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
            var exceptionType = exceptionHandlerFeature?.Error;
​
            if (exceptionType is not null)
            {
                (string Title, string Detail, string Type, int StatusCode) details = exceptionType switch
                {
                    MyCustomException MyCustomException =>
                    (
                        exceptionType.GetType().Name,
                        exceptionType.Message,
                        "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1",
                        context.Response.StatusCode = (int)MyCustomException.StatusCode
                    ),
                    _ =>
                    (
                        exceptionType.GetType().Name,
                        exceptionType.Message,
                        "https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1",
                        context.Response.StatusCode = StatusCodes.Status500InternalServerError
                    )
                };
​
                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = details.Title,
                        Detail = details.Detail,
                        Type = details.Type,
                        Status = details.StatusCode
                    }
                });
            }
        }
    });
});
​
در اینجا نحوه‌ی کار با سرویس توکار IProblemDetailsService و سپس دسترسی به IExceptionHandlerFeature و استثنای صادر شده را مشاهده می‌کنید. پس از آن بر اساس نوع و اطلاعات این استثناء، می‌توان یک ProblemDetails مخصوص را تولید و در خروجی ثبت کرد.
مطالب
نمایش یک فایل PDF در WinForms ، WPF و سیلورلایت

شاید PDF را بشود تنها فرمت گزارشگیری دانست که همه‌جا و در تمام سیستم عامل‌ها پشتیبانی می‌شود. از ویندوز تا لینوکس از وب تا WPF تا سیلورلایت تا همه جا و از همه مهم‌تر اینکه خروجی آن دقیقا همان چیزی است که کاربر نهایی می‌خواهد: من می‌خوام اون چیزی رو که می‌بینم، دقیقا همان را، بدون کم و کاست و با همان صفحه بندی، بتوانم چاپ کنم.
برای تولید PDF می‌شود از کتابخانه‌ی iTextSharp استفاده کرد اما برای نمایش آن حداقل در ویندوز بهترین راه حل استفاده از COM Components‌ شرکت Adobe است که به همراه برنامه رایگان Adobe PDF reader ارائه می‌شود. در ادامه نحوه‌ی استفاده از این Active-X را بررسی خواهیم کرد.

نمایش PDF در WPF
در تمام حالت‌ها هدف این است که به نحوی به اکتیوایکس شرکت Adobe دسترسی پیدا کنیم؛ یا با اضافه کردن آن به پروژه یا استفاده از امکانات یکپارچه مرورگرها. در WPF از زمان ارائه سرویس پک یک دات نت سه و نیم (به بعد)، کنترل مرورگر وب هم به جمع کنترل‌های قابل استفاده در آن اضافه شده است. در اینجا به سادگی چند سطر زیر می‌شود یک فایل PDF را در WPF نمایش داد:
<Window x:Class="WpfAppTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pdf Report" Height="495" WindowState="Maximized"
WindowStartupLocation="CenterScreen" Width="703">
<Grid>
<WebBrowser x:Name="WebBrowser1"/>
</Grid>
</Window>
و بعد هم در کدهای برنامه تنها کافی است که مقدار Source کنترل WebBrowser را مقدار دهی کرد:
WebBrowser1.Source = new Uri(PdfFilePath);


نمایش PDF در WinForms
اکتیوایکس نمایش دهنده PDF شرکت Adobe اساسا در فایل ذیل قرار گرفته است:
C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll
بنابراین برای استفاده از آن در یک برنامه‌ی WinForms باید مراحل ذیل طی شود:
الف) در VS.NET‌ از طریق منوی Tools گزینه‌ی Choose toolbox items ، برگه‌ی Com components را انتخاب کنید.
ب) سپس گزینه‌ی Adobe PDF reader که به همان مسیر dll فوق اشاره می‌کند را انتخاب نمائید و بر روی دکمه‌ی OK‌ کلیک کنید.
ج) اکنون این کنترل جدید را بر روی فرم برنامه قرار دهید. به صورت خودکار COMReference های متناظر به پروژه اضافه می‌شوند.

اکنون نحوه‌ی استفاده از این شیء COM به همراه آزاد سازی منابع مرتبط به شرح زیر خواهند بود:
using System.Windows.Forms;

namespace WindowsFormsAppTests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
axAcroPDF1.Dispose();
}

void Form1_Load(object sender, System.EventArgs e)
{
axAcroPDF1.LoadFile(PdfFilePath);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}

نمایش PDF در Silverlight

در Silverlight هم از نسخه‌ی 4 به بعد کنترل WebBrowser همانند آنچه که در WPF موجود است، اضافه شده است؛ اما این کنترل فقط در حالت اجرای در خارج از مرورگر برنامه Silverlight در دسترس می‌باشد. بنابراین روش دیگری را باید انتخاب کرد. این روش بر اساس تعامل سیلورلایت با کدهای HTML صفحه کار می‌کند. یک IFrame مخفی را در صفحه بالای شیء مرتبط با سیلورلایت قرار خواهیم داد. سپس در سیلورلایت Src این IFrame را به مسیر فایل PDF تنظیم می‌کنیم و همین. به این ترتیب فایل PDF نمایش داده می‌شود.
این IFrame به صورت زیر در همان صفحه‌ی aspx ایی که object مرتبط با Silverlight نمایش داده می‌شود قرار می‌گیرد:
<iframe id="pdfFrame" style="visibility:hidden; position:absolute"><b>No Content</b></iframe>
<div id="silverlightControlHost">
سپس در کدهای سیلورلایت، ابتدا این IFrame یافت شده:
var iFrame = HtmlPage.Document.GetElementById("pdfFrame");
در ادامه بر اساس اطلاعات مکانی یک Grid ساده به نام pdfHost که در صفحه قرار گرفته، این iFrame بالاتر از سطح Grid (بر اساس z-index تنظیم شده) نمایش داده می‌شود:
var gt = pdfHost.TransformToVisual(Application.Current.RootVisual);
var offset = gt.Transform(new Point(0, 0));
var controlLeft = (int)offset.X;
var controlTop = (int)offset.Y;
iFrame.SetStyleAttribute("left", string.Format("{0}px", controlLeft));
iFrame.SetStyleAttribute("top", string.Format("{0}px", controlTop));
iFrame.SetStyleAttribute("visibility", "visible");
iFrame.SetStyleAttribute("height", string.Format("{0}px", pdfHost.ActualHeight));
iFrame.SetStyleAttribute("width", string.Format("{0}px", pdfHost.ActualWidth));
iFrame.SetStyleAttribute("z-index", "1000");
و در آخر نام فایلی را که می‌خواهیم مشاهده کنیم به یک صفحه‌ی aspx در همان سایت ارسال می‌کنیم:
iFrame.SetProperty("src", "ShowPdf.aspx?file=" + fileName);
کدهای این صفحه در حد یک Response.Redirect ساده برای نمایش دادن فایل pdf در مرورگر کافی هستند. در کل در اینجا سیلورلایت تنها نقش انتخاب فایل را به عهده دارد و کار اصلی را خود مرورگر انجام می‌دهد.

مطالب
لینک‌های هفته‌ی دوم بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

ASP. Net

طراحی و توسعه وب

PHP

سی شارپ

عمومی دات نت

ویندوز
(ایکاش بجای تمام این‌کارها یک سیستم ساده‌تر توسعه‌ی پلاگین برای آن طراحی می‌کردند ... یا به عبارتی یکی از مهم‌ترین دلیل‌های اقبال مردم به فایرفاکس را به صورت بسیار کم رنگی دارد)


مسایل اجتماعی و انسانی برنامه نویسی

متفرقه
مطالب
استفاده از نگارش سوم Google Analytics API در سرویس‌های ویندوز یا برنامه‌های وب
در زمان نگارش این مطلب، آخرین نگارش API مخصوص Google Analytics، نگارش سوم آن است و ... کار کردن با آن دارای مراحل خاصی است که حتما باید رعایت شوند. در غیر اینصورت عملا در یک برنامه‌ی وب یا سرویس ویندوز قابل اجرا نخواهند بود. زیرا در حالت متداول کار با API مخصوص Google Analytics، ابتدا یک صفحه‌ی لاگین به Gmail باز می‌شود که باید به صورت اجباری، مراحل آن را انجام داد تا مشخصات تائید شده‌ی اکانت در حال استفاده‌ی از API، در پوشه‌ی AppData ویندوز برای استفاده‌های بعدی ذخیره شود. این مورد برای یک برنامه‌ی دسکتاپ معمولی مشکل ساز نیست؛ زیرا کاربر برنامه، به سادگی می‌تواند صفحه‌ی مرورگری را که باز شده‌است، دنبال کرده و به اکانت گوگل خود وارد شود. اما این مراحل را نمی‌توان در یک برنامه‌ی وب یا سرویس ویندوز پیگیری کرد، زیرا عموما امکان لاگین از راه دور به سرور و مدیریت صفحه‌ی لاگین به Gmail وجود ندارد یا بهتر است عنوان شود، بی‌معنا است. برای حل این مشکل، گوگل راه حل دیگری را تحت عنوان اکانت‌های سرویس، ارائه داده است که پس از ایجاد آن، یک یک فایل X509 Certificate برای اعتبارسنجی سرویس، در اختیار برنامه نویس قرار می‌گیرد تا بدون نیاز به لاگین دستی به Gmail، بتواند از API گوگل استفاده کند. در ادامه نحوه‌ی فعال سازی این قابلیت و استفاده از آن‌را بررسی خواهیم کرد.


ثبت برنامه‌ی خود در گوگل و انجام تنظیمات آن

اولین کاری که برای استفاده از نگارش سوم Google Analytics API باید صورت گیرد، ثبت برنامه‌ی خود در Google Developer Console است. برای این منظور ابتدا به آدرس ذیل وارد شوید:
سپس بر روی دکمه‌ی Create project کلیک کنید. نام دلخواهی را وارد کرده و در ادامه بر روی دکمه‌ی Create کلیک نمائید تا پروفایل این پروژه ایجاد شود.



تنها نکته‌ی مهم این قسمت، بخاطر سپردن نام پروژه است. زیرا از آن جهت اتصال به API گوگل استفاده خواهد شد.
پس از ایجاد پروژه، به صفحه‌ی آن وارد شوید و از منوی سمت چپ صفحه، گزینه‌ی Credentials را انتخاب کنید. در ادامه در صفحه‌ی باز شده، بر روی دکمه‌ی Create new client id کلیک نمائید.


در صفحه‌ی باز شده، گزینه‌ی Service account را انتخاب کنید. اگر سایر گزینه‌ها را انتخاب نمائید، کاربری که قرار است از API استفاده کند، باید بتواند توسط مرورگر نصب شده‌ی بر روی کامپیوتر اجرا کننده‌ی برنامه، یکبار به گوگل لاگین نماید که این مورد مطلوب برنامه‌های وب و همچنین سرویس‌ها نیست.


در اینجا ابتدا یک فایل مجوز p12 را به صورت خودکار دریافت خواهید کرد و همچنین پس از ایجاد client id، نیاز است، ایمیل آن‌را جایی یادداشت نمائید:


از این ایمیل و همچنین فایل p12 ارائه شده، جهت لاگین به سرور استفاده خواهد شد.
همچنین نیاز است تا به برگه‌ی APIs پروژه‌ی ایجاد شده رجوع کرد و گزینه‌ی Analytics API آن‌را فعال نمود:


تا اینجا کار ثبت و فعال سازی برنامه‌ی خود در گوگل به پایان می‌رسد.


دادن دسترسی به Client ID ثبت شده در برنامه‌ی Google Analytics

پس از اینکه Client ID سرویس خود را ثبت کردید، نیاز است به اکانت Google Analytics خود وارد شوید. سپس در منوی آن، گزینه‌ی Admin را پیدا کرده و به آن قسمت، وارد شوید:


در ادامه به گزینه‌ی User management آن وارد شده و به ایمیل Client ID ایجاد شده در قسمت قبل، دسترسی خواندن و آنالیز را اعطاء کنید:


در صورت عدم رعایت این مساله، کلاینت API، قادر به دسترسی به Google Analytics نخواهد بود.


استفاده از نگارش سوم Google Analytics API در دات نت

قسمت مهم کار، تنظیمات فوق است که در صورت عدم رعایت آن‌ها، شاید نصف روزی را مشغول به دیباگ برنامه شوید. در ادامه نیاز است پیشنیازهای دسترسی به نگارش سوم Google Analytics API را نصب کنیم. برای این منظور، سه بسته‌ی نیوگت ذیل را توسط کنسول پاورشل نیوگت، به برنامه اضافه کنید:
 PM> Install-Package Google.Apis
PM> Install-Package Google.Apis.auth
PM> Install-Package Google.Apis.Analytics.v3
پس از نصب، کلاس GoogleAnalyticsApiV3 زیر، جزئیات دسترسی به Google Analytics API را کپسوله می‌کند:
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Analytics.v3;
using Google.Apis.Analytics.v3.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;

namespace GoogleAnalyticsAPIv3Tests
{
    public class AnalyticsQueryParameters
    {
        public DateTime Start { set; get; }
        public DateTime End { set; get; } 
        public string Dimensions { set; get; } 
        public string Filters { set; get; }
        public string Metrics { set; get; }
    }

    public class AnalyticsAuthentication
    {
        public Uri SiteUrl { set; get; }
        public string ApplicationName { set; get; }
        public string ServiceAccountEmail { set; get; }
        public string KeyFilePath { set; get; }
        public string KeyFilePassword { set; get; }

        public AnalyticsAuthentication()
        {
            KeyFilePassword = "notasecret";
        }
    }

    public class GoogleAnalyticsApiV3
    {
        public AnalyticsAuthentication Authentication { set; get; }
        public AnalyticsQueryParameters QueryParameters { set; get; }

        public GaData GetData()
        {
            var service = createAnalyticsService();
            var profile = getProfile(service);
            var query = service.Data.Ga.Get("ga:" + profile.Id,
                                QueryParameters.Start.ToString("yyyy-MM-dd"),
                                QueryParameters.End.ToString("yyyy-MM-dd"),
                                QueryParameters.Metrics);
            query.Dimensions = QueryParameters.Dimensions;
            query.Filters = QueryParameters.Filters;
            query.SamplingLevel = DataResource.GaResource.GetRequest.SamplingLevelEnum.HIGHERPRECISION;
            return query.Execute();
        }

        private AnalyticsService createAnalyticsService()
        {
            var certificate = new X509Certificate2(Authentication.KeyFilePath, Authentication.KeyFilePassword, X509KeyStorageFlags.Exportable);
            var credential = new ServiceAccountCredential(
                new ServiceAccountCredential.Initializer(Authentication.ServiceAccountEmail)
                {
                    Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
                }.FromCertificate(certificate));

            return new AnalyticsService(new BaseClientService.Initializer
            {
                HttpClientInitializer = credential,
                ApplicationName = Authentication.ApplicationName
            });
        }

        private Profile getProfile(AnalyticsService service)
        {
            var accountListRequest = service.Management.Accounts.List();
            var accountList = accountListRequest.Execute();
            var site = Authentication.SiteUrl.Host.ToLowerInvariant();
            var account = accountList.Items.FirstOrDefault(x => x.Name.ToLowerInvariant().Contains(site));
            var webPropertyListRequest = service.Management.Webproperties.List(account.Id);
            var webPropertyList = webPropertyListRequest.Execute();
            var sitePropertyList = webPropertyList.Items.FirstOrDefault(a => a.Name.ToLowerInvariant().Contains(site));
            var profileListRequest = service.Management.Profiles.List(account.Id, sitePropertyList.Id);
            var profileList = profileListRequest.Execute();
            return profileList.Items.FirstOrDefault(a => a.Name.ToLowerInvariant().Contains(site));
        }
    }
}
در اینجا در ابتدا بر اساس فایل p12 ایی که از گوگل دریافت شد، یک X509Certificate2 ایجاد می‌شود. پسورد این فایل مساوی است با ثابت notasecret که در همان زمان تولید اکانت سرویس در گوگل، لحظه‌ای در صفحه نمایش داده خواهد شد. به کمک آن و همچنین ServiceAccountEmail ایمیلی که پیشتر به آن اشاره شد، می‌توان به AnalyticsService لاگین کرد. به این ترتیب به صورت خودکار می‌توان شماره پروفایل اکانت سایت خود را یافت و از آن در حین فراخوانی service.Data.Ga.Get استفاده کرد.


مثالی از نحوه استفاده از کلاس GoogleAnalyticsApiV3

در ادامه یک برنامه‌ی کنسول را ملاحظه می‌کنید که از کلاس GoogleAnalyticsApiV3 استفاده می‌کند:
using System;
using System.Collections.Generic;
using System.Linq;

namespace GoogleAnalyticsAPIv3Tests
{
    class Program
    {

        static void Main(string[] args)
        {
            var statistics = new GoogleAnalyticsApiV3
            {
                Authentication = new AnalyticsAuthentication
                {
                    ApplicationName = "My Project",
                    KeyFilePath = "811e1d9976cd516b55-privatekey.p12",
                    ServiceAccountEmail = "10152bng4j3mq@developer.gserviceaccount.com",
                    SiteUrl = new Uri("https://www.dntips.ir/")
                },
                QueryParameters = new AnalyticsQueryParameters
                {
                    Start = DateTime.Now.AddDays(-7),
                    End = DateTime.Now,
                    Dimensions = "ga:date",
                    Filters = null,
                    Metrics = "ga:users,ga:sessions,ga:pageviews"
                }
            }.GetData();


            foreach (var result in statistics.TotalsForAllResults)
            {
                Console.WriteLine(result.Key + " -> total:" + result.Value);
            }
            Console.WriteLine();

            foreach (var row in statistics.ColumnHeaders)
            {
                Console.Write(row.Name + "\t");
            }
            Console.WriteLine();

            foreach (var row in statistics.Rows)
            {
                var rowItems = (List<string>)row;
                Console.WriteLine(rowItems.Aggregate((s1, s2) => s1 + "\t" + s2));
            }

            Console.ReadLine();
        }
    }
}

چند نکته
ApplicationName همان نام پروژه‌ای است که ابتدای کار، در گوگل ایجاد کردیم.
KeyFilePath مسیر فایل مجوز p12 ایی است که گوگل در حین ایجاد اکانت سرویس، در اختیار ما قرار می‌دهد.
ServiceAccountEmail آدرس ایمیل اکانت سرویس است که در قسمت ادمین Google Analytics به آن دسترسی دادیم.
SiteUrl آدرس سایت شما است که هم اکنون در Google Analytics دارای یک اکانت و پروفایل ثبت شده‌است.
توسط AnalyticsQueryParameters می‌توان نحوه‌ی کوئری گرفتن از Google Analytics را مشخص کرد. تاریخ شروع و پایان گزارش گیری در آن مشخص هستند. در مورد پارامترهایی مانند Dimensions و Metrics بهتر است به مرجع کامل آن در گوگل مراجعه نمائید:
Dimensions & Metrics Reference

برای نمونه در مثال فوق، تعداد کاربران، سشن‌های آن و همچنین تعداد بار مشاهده‌ی صفحات، گزارشگیری می‌شود.


برای مطالعه بیشتر
Using Google APIs in Windows Store Apps
How To Use Google Analytics From C# With OAuth
Google Analytic’s API v3 with C#
.NET Library for Accessing and Querying Google Analytics V3 via Service Account
Google OAuth2 C#
مطالب
سشن صفر

یکی از تفاوت‌های مهم امنیتی ویندوزهای جدید (از سرویس پک 2 ویندوز 2003 به بعد)، اجرای سرویس‌های ویندوز در جلسه‌ای کاری (سشنی) جدا از جسله کاری برنامه‌های عادی است که توسط کاربران وارد شده به سیستم اجرا می‌شوند. در این ویندوزها، تنها سرویس‌ها هستند که در سشن صفر اجرا می‌شوند و مابقی برنامه‌های سایر کاربران در سشن‌های دیگر.



این برخلاف چیزی است که برای مثال در ویندوز XP وجود دارد؛ یعنی اجرای دوستانه‌ی تمام برنامه‌ها در یک سشن (چون کاربر وارد شده به سیستم نیز در سشن صفر قرار می‌گیرد).



در این حالت برنامه‌های عادی می‌توانند سرویس‌های ویندوز را به راحتی مورد حمله قرار دهند. اطلاعات بیشتر:


مهم‌ترین معنای اجرای ایزوله سرویس‌ها در سشن صفر، به خاطره سپرده شدن سرویس‌هایی است که امکان تعامل با کاربر را داشتند؛ برای مثال سرویس‌هایی که یک رابط کاربری را نمایش می‌دادند (به کمک همان تیک معروف Allow Service to Interact with Desktop در صفحه تنظیمات یک سرویس). زیرا اکنون اولین کاربر وارد شده به سیستم، در سشن یک قرار می‌گیرد (و نه همانند قبل در سشن صفر). به این صورت دیگر به رابط کاربری نمایش داده شده در سشن صفر دسترسی نداشته و چیزی را مشاهده نخواهد کرد.




البته شاید سؤال بپرسید که چه سرویس‌هایی نیاز به نمایش رابط کاربری دارند؟ چون عموما سرویسی که صحیح طراحی شده باشد نیاز به تعامل مستقیم با کاربر را از طریق رابط کاربری ندارد.
و پاسخ این است که خیلی از سرویس‌ها! در بسیاری از برنامه‌های متداول، امکان اجرای به صورت سرویس ویندوز NT پیش بینی نشده است. اگر نیاز باشد تا این نوع برنامه‌ها را به صورت سرویس، برای مثال در یک ویندوز سرور اجرا کرد (تا همیشه در حال اجرا باشند و همچنین با ری استارت شدن سیستم نیز مجددا بدون دخالت کاربر شروع به کار کنند)، در این حالت نیاز خواهد بود تا رابط کاربری آن‌ها نیز نمایش داده شوند.

سؤال: در ویندوزهای جدید برای تعامل با سشن صفر چه باید کرد؟
در ویندوز 2003 (به همراه تمام به روز رسانی‌ها) برای اتصال به سشن صفر می‌توان دستور زیر را در خط فرمان صادر کرد:
Mstsc /console

به این ترتیب امکان اتصال به سشن کنسول یا همان سشن صفر میسر می‌شود.
این پارامتر در ویندوز سرور 2008 ندید گرفته خواهد شد زیرا در این ویندوز امکان نصب یک سرویس در سشن‌هایی بجز سشن صفر نیز پیش بینی شده است؛ هر چند پیش فرض نصب همان سشن صفر است. اما به جهت سازگاری با نگارش‌های قبل این‌بار بجای پارامتر console از پارامتر admin استفاده کنید.

با استفاده از برنامه نویسی چطور؟
روش کار در این مقاله توضیح داده شده است اما سطر اول آن به این نکته تاکید دارد که ... این‌کار را نکنید! سرویسی که درست طراحی شده باشد نیازی به رابط کاربری تعاملی عموما جهت اعمال تنظیمات آن، ندارد.

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه
بله. در یک برنامه‌ی کنسول مبتنی بر دات نت Core قابل استفاده است (این نوع برنامه‌های کنسول هم چندسکویی هستند و تحت لینوکس هم اجرا می‌شوند). همچنین در برنامه‌های یونیورسال ویندوز (UWP)؛ موردی که با EF 6.x میسر نیست. اطلاعات بیشتر
مطالب
تقویم فارسی و سیلورلایت؛ لطفا رای بدهید!

سیلورلایت 4 با پشتیبانی رسمی از زبان‌های راست به چپ و همچنین ارائه‌ی کوهی از قابلیت‌های جدید، کاندید مناسبی برای توسعه‌ی نرم افزارهای تحت وب (و همچنین برنامه‌های Desktop چند سکویی) می‌باشد. اما باید درنظر داشت که تیم آن برای کوچک نگه داشتن حجم افزونه‌ی آن تمامی کلاس‌های موجود در دات نت فریم ورک را به آن اضافه نکرده‌اند و تقویم فارسی نیز از این دست است.
مایکروسافت مدتی است که قسمتی را جهت دریافت بازخورد برنامه نویس‌ها و دریافت نظرات و پیشنهادات آن‌ها در این مورد ایجاد کرده است:


در همین زمینه لطفا به آدرس ذیل مراجعه کرده و برای اضافه شدن تقویم فارسی به صورت رسمی به آن رای بدهید؛ با تشکر!