مطالب
جمع آوری آمار لینک‌های خروجی از سایت توسط Google analytics

چندی قبل مطلب کوتاهی را در مورد Google analytics نوشتم. در حین جستجو درباره‌ی jQuery در وب، به نحوه ردیابی لینک‌های خروجی از سایت توسط Google analytics برخوردم که نحوه پیاده سازی آن به صورت زیر است.
بدیهی است قبل از هر کاری باید اسکریپت مربوط به Google analytics را به انتهای صفحه و جایی که تگ body بسته می‌شود اضافه کنید (قابل دریافت درقسمت Add Website Profile . شماره این اسکریپت برای هر پروفایلی که ایجاد می‌کنید متفاوت است).
سپس:
الف) افزودن ارجاعی از کتابخانه jQuery به هدر صفحه که آن‌را در مطلب شمسی کردن تاریخ بلاگر ملاحظه کردید.
ب) افزودن چند سطر زیر به هدر صفحه
<script type="text/javascript">
$(document).ready(function() {
$("a").click(function() {
var $a = $(this);
var href = $a.attr("href");

// see if the link is external
if ( (href.match(/^http/)) && (! href.match(document.domain)) ) {

// if so, register an event
var category = "outgoing";
var event = "click";
var label = href;

pageTracker._trackPageview('/outgoing/' + href);
pageTracker._trackEvent(category, event, href);
}
});
});
</script>

البته اگر قبلا اسکریپت شمسی کردن تاریخ بلاگر را اضافه کرده بودید فقط محتویات تابع document.ready را باید اضافه کنید (جهت مشاهده نمونه اعمال شده، روی صفحه جاری کلیک راست کنید و سورس صفحه را مشاهده نمائید).

توضیحاتی در مورد کد فوق:
این اسکریپت به روال رخ داد گردان onclick هر لینکی که به خارج از سایت ختم می‌شود (مثلا لینک به یک فایل یا یک سایت خارجی (خارج از سایت))، به صورت خودکار تابع trackPageview مربوط به Google analytics را اضافه می‌کند. این کار تاثیری در عملکرد سایت ندارد و کاربر چیزی را متوجه نخواهد شد، اما به این طریق لینک‌های خروجی در آمار Google analytics ظاهر می‌شوند (مطابق تصاویر زیر).





از این پس آمار تمام لینک‌های خروجی از سایت ، متمایز شده با outgoing ، جمع آوری و نمایش داده خواهند شد.

امکانات بیشتری مانند event tracking نیز قرار است به Google analytics اضافه شود که هنوز در مرحله آزمایشی است و بر روی تمامی اکانت‌ها فعال نشده است.

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت سوم - رهاسازی منابع سرویس‌های IDisposable
یکی از پرکاربردترین اینترفیس‌های NET.، اینترفیس IDisposable است. عموما کلاس‌هایی که ارجاعی را به منابع غیر مدیریت شده مانند فایل‌ها و سوکت‌ها داشته باشند، این اینترفیس را پیاده سازی می‌کنند. garbage collector به صورت خودکار حافظه‌ی اشیاء مدیریت شده یا دات نتی را رها می‌کند؛ اما چیزی را در مورد منابع غیر مدیریت شده نمی‌داند. به همین جهت پیاده سازی اینترفیس IDisposable روشی را جهت پاکسازی این منابع به garbage collector معرفی می‌کند.


رفتار IoC Container توکار ASP.NET Core با سرویس‌های IDisposable

ASP.NET Core به همراه یک IoC Container توکار ارائه می‌شود و اگر سرویسی با طول عمرTransient و یا Scoped به آن معرفی شود و همچنین این سرویس اینترفیس IDisposable را نیز پیاده سازی کند، کار dispose خودکار آن در پایان درخواست جاری صورت می‌گیرد و نیازی به تنظیمات اضافه‌تری ندارد. در اینجا سرویس‌هایی با طول عمر Singleton نیز در پایان کار برنامه، زمانیکه خود ServiceProvider به پایان کارش می‌رسد، dispose خواهند شد.
البته این مورد یک شرط را نیز به همراه دارد: کار وهله سازی سرویس‌های درخواستی باید توسط خود این IoC Container مدیریت شود تا در پایان کار بداند چگونه آن‌ها را Dispose کند.

یک مثال: بررسی Dispose شدن خودکار یک سرویس IDisposable
namespace CoreIocServices
{
    public interface IMyDisposableService
    {
        void Run();
    }

    public class MyDisposableService : IMyDisposableService, IDisposable
    {
        private readonly ILogger<MyDisposableService> _logger;

        public MyDisposableService(ILogger<MyDisposableService> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _logger.LogInformation("+ {0} was created", this.GetType().Name);
        }

        public void Run()
        {
            _logger.LogInformation("Running MyDisposableService!");
        }

        public void Dispose()
        {
            _logger.LogInformation("- {0} was disposed!", this.GetType().Name);
        }
    }
}
سرویس ساده‌ی فوق، اینترفیس IDisposable را پیاده سازی می‌کند و با استفاده از ILogger، پیام‌هایی را در زمان ایجاد و Dipose آن در پنجره کنسول و یا دیباگ نمایش خواهد داد.
اگر این سرویس را به یک برنامه‌ی ASP.NET Core معرفی کنیم:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IMyDisposableService, MyDisposableService>();
و سپس به نحو متداولی از آن در یک کنترلر استفاده کنیم:
namespace CoreIocSample02.Controllers
{
    public class HomeController : Controller
    {
        private readonly IMyDisposableService _myDisposableService;

        public HomeController(IMyDisposableService myDisposableService)
        {
            _myDisposableService = myDisposableService;
        }

        public IActionResult Index()
        {
            _myDisposableService.Run();
            return View();
        }
در اینجا منظور از نحو‌ه‌ی متداول، همان تزریق در سازنده‌ی کلاس و درخواست وهله‌ای از این سرویس از IoC Container است؛ بجای ایجاد مستقیم آن.
در ادامه با اجرای برنامه، اگر به لاگ‌های آن دقت کنیم، این خروجی قابل مشاهده خواهد بود:
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "Index", controller = "Home"}. Executing action CoreIocSample02.Controllers.HomeController.Index (CoreIocSample02)
info: CoreIocServices.MyDisposableService[0]
      + MyDisposableService was created
.
.
.  
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'CoreIocSample02.Controllers.HomeController.Index (CoreIocSample02)'
info: CoreIocServices.MyDisposableService[0]
      - MyDisposableService was disposed!
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 1316.4719ms 200 text/html; charset=utf-8
در ابتدای اجرای درخواست، پیام MyDisposableService was created ظاهر شده‌است (پیام صادر شده‌ی از سازنده‌ی سرویس) و جائیکه پیام Executed endpoint یا پایان درخواست جاری لاگ شده، بلافاصله پیام MyDisposableService was disposed نیز مشاهده می‌شود که از متد Dispose سرویس درخواستی صادر شده‌است.
بنابراین IoC Container، به صورت خودکار، کار Dispose این سرویس IDisposable را نیز انجام داده‌است.


Dispose خودکار وهله‌هایی که توسط IoC Container ایجاد نشده‌اند

اگر ایجاد اشیاء از نوع IDisposable را خودتان و خارج از دید IoC Container توکار ASP.NET Core انجام می‌دهید، از مزیت پاکسازی خودکار منابع توسط آن‌ها در پایان درخواست محروم خواهید شد، اما ... برای رفع این مشکل نیز متد context.Response.RegisterForDispose پیش بینی شده‌است. اگر شیءای از نوع IDisposable را توسط این متد به ASP.NET Core معرفی کنید، در پایان درخواست به صورت خودکار Dispose خواهد شد.
یک مثال: فرض کنید یک StreamWriter را داخل یک میان‌افزار ایجاد کرده‌اید، اما آن‌را Dispose نکرده‌اید:
namespace CoreIocSample02
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                var writer = File.CreateText(Path.GetTempFileName());
                context.Response.RegisterForDispose(writer);
                context.Items["filewriter"] = writer;
                await writer.WriteLineAsync("some important information");
                await writer.FlushAsync();
                await next();
            });
در این مثال، شیء writer به context.Items انتساب داده شده‌است تا در سایر قسمت‌های pipeline جاری نیز قابل دسترسی باشد. یعنی از آن می‌توان داخل یک اکشن متد نیز استفاده کرد. نکته‌ی مهم آن، معرفی این شیء به متد context.Response.RegisterForDispose است که سبب خواهد شد پس از پایان کار درخواست، به صورت خودکار writer را Dispose کند.
اکنون در ادامه، در اکشن متد WriteLog یک کنترلر دلخواه، کار ثبت وقایع با دریافت این writer از HttpContext.Items قابل انجام است؛ چون هنوز طول عمر درخواست جاری پایان نیافته و شیء writer به صورت خودکار Dispose نشده‌است:
namespace CoreIocSample02.Controllers
{
    public class HomeController : Controller
    {
        public async Task<IActionResult> WriteLog()
        {
            var writer = HttpContext.Items["filewriter"] as StreamWriter;
            if (writer != null)
            {
                await writer.WriteLineAsync("more important information");
                await writer.FlushAsync();
            }
            return View();
        }
 
روش صحیح Dispose اشیایی با طول عمر Scoped، در خارج از طول عمر یک درخواست ASP.NET Core

زمانیکه به صورت متداولی از سیستم تزریق وابستگی‌های ASP.NET Core استفاده می‌کنیم، به ازای هر درخواست HTTP رسیده، یک Scope از نوع IServiceScopeFactory ایجاد می‌شود و با پایان درخواست، این Scope نیز Dispose خواهد شد. به این ترتیب هر سرویس ایجاد شده‌ی درون این Scope نیز Dispose می‌شود؛ کاری شبیه به عملیات زیر:
using(var scope = serviceProvider.CreateScope())
{
   var provider = scope.ServiceProvider;
   var resolvedService = provider.GetRequiredService(someType);
   // Use resolvedService...
}
در این بین سرویس‌های Singleton به هیچ Scope ای منتسب نمی‌شوند و طول عمر آن‌ها توسط root container مدیریت می‌شود و زمانیکه این ServiceProvider یا root container به پایان کار خودش برسد، با dispose شدن آن، سرویس‌های Singleton آن نیز dispose خواهند شد.

مشکل! اگر از سرویس فرضی IOperationScoped با طول عمر Scoped در متدهای مختلف کلاس آغازین برنامه استفاده کنیم (مانند DbContext برنامه)، طول عمری را که دریافت خواهیم کرد singleton خواهد بود و نه Scoped؛ چون درون یک scopeFactory.CreateScope ایجاد شده‌ی به صورت خودکار توسط یک درخواست قرار نداریم. بنابراین هر درخواست وهله‌ای از سرویس IOperationScoped با طول عمر Scoped، تنها همان وهله‌ی ابتدایی آن‌را باز می‌گرداند و singleton رفتار می‌کند؛ چون scope ایی ایجاد و تخریب نشده‌است.
در یک چنین مواردی، برای اطمینان حاصل کردن از dispose شدن سرویس در پایان کار، نیاز است مراحل ایجاد scope و dispose آن‌را به صورت دستی به نحو ذیل مدیریت کنیم:
public void Configure(IApplicationBuilder app, 
                      ILoggerFactory loggerFactory, 
                      IServiceScopeFactory scopeFactory) 
{
   using (var scope = scopeFactory.CreateScope()) 
   { 
     var initializer = scope.ServiceProvider.GetService<IOperationScoped>(); 
     initializer.SeedAsync().Wait(); 
   } 
}


Dispose کردن سرویس‌های IDisposable در برنامه‌های Console

اگر همین سرویس IMyDisposableService را در مثال برنامه‌ی کنسول قسمت اول استفاده کنیم:
var myDisposableService = serviceProvider.GetService<IMyDisposableService>();
myDisposableService.Run();
در پایان کار برنامه، شاهد پیام MyDisposableService was disposed نخواهیم بود. به همین جهت در اینجا نیز می‌توانیم شبیه به کاری که در ASP.NET Core در پشت صحنه رخ می‌دهد، عمل کنیم:
در برنامه‌ی کنسول، کار ایجاد serviceProvider را خودمان انجام دادیم:
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
متد BuildServiceProvider خروجی از نوع کلاس ServiceProvider را دارد؛ با این امضاء:
namespace Microsoft.Extensions.DependencyInjection
{
    public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback
    {
        public void Dispose();
        public object GetService(Type serviceType);
    }
}
همانطور که مشاهده کنید، کلاس ServiceProvider نیز اینترفیس IDisposable را پیاده سازی می‌کند. بنابراین برای آزاد سازی صحیح منابع وابسته‌ی به آن، باید متد Dispose آن‌را نیز فراخوانی کرد:
namespace CoreIocSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
            using (var serviceProvider = serviceCollection.BuildServiceProvider())
            {
                var myDisposableService = serviceProvider.GetService<IMyDisposableService>();
                myDisposableService.Run();

                var testService = serviceProvider.GetService<ITestService>();
                testService.Run();
            }
        }
در اینجا serviceProvider را داخل یک using statement قرار داده‌ایم. اینبار اگر برنامه را اجرا کنیم، پس از پایان کار برنامه، پیام MyDisposableService was disposed نیز ظاهر می‌شود. ServiceProvider ایجاد شده یا همان root container، در زمان Dispose، تمام اشیایی را هم که توسط آن مدیریت شده‌اند و نیاز به Dispose دارند، Dispose می‌کند.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-02.zip
اشتراک‌ها
دیالوگ های بوت استراپ با استفاده از jquery

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

سورس در گیت هاب 

نسخه MVC آن 

دیالوگ های بوت استراپ با استفاده از jquery
مطالب
اتصال Node.js به SQL Server با استفاده از Edge.js
اگر خواسته باشید که با استفاده از Node.js به SQL Server متصل شوید، احتمالا متوجه شده‌اید ماژولی که مایکروسافت منتشر کرده است، ناقص بوده و به صورت پیش نمایش است که بسیاری از ویژگی‌ها و مسائل مهم، در آن در نظر گرفته نشده است.

یکی دیگر از ماژول‌هایی که امکان اتصال Node.js را به SQL Server ممکن می‌کند، Edge.js است. Edge.js یک ماژول Node.js است که امکان اجرای کدهای دات نت را در همان پروسه توسط Node.js فراهم می‌کند. این مسئله، توسعه دهندگان Node.js را قادر می‌سازد تا از فناوری‌هایی که به صورت سنتی استفاده‌ی از آنها سخت یا غیر ممکن بوده است را به راحتی استفاده کنند. برای نمونه:
  • SQL Server
  • Active Directory
  • Nuget packages
  • استفاده از سخت افزار کامپیوتر (مانند وب کم، میکروفن و چاپگر)


نصب Node.js

اگر Node.js را بر روی سیستم خود نصب ندارید، می‌توانید از اینجا آن را دانلود کنید. بعد از نصب برای اطمینان از کارکرد آن، command prompt را باز کرده و دستور زیر را تایپ کنید:

node -v
شما باید نسخه‌ی نصب شده‌ی Node.js را مشاهده کنید.

ایجاد پوشه پروژه

سپس پوشه‌ای را برای پروژه Node.js خود ایجاد کنید. مثلا با استفاده از command prompt و دستور زیر:

md \projects\node-edge-test1
cd \projects\node-edge-test1

نصب Edge.js

Node با استفاده از package manager خود دانلود و نصب ماژول‌ها را خیلی آسان کرده است. برای نصب، در command prompt عبارت زیر را تایپ کنید:

npm install edge
npm install edge-sql
فرمان اول باعث نصب Edge.js و دومین فرمان سبب نصب پشتیبانی از SQL Server می‌شود.

Hello World

ایجاد یک فایل متنی با نام server.js و نوشتن کد زیر در آن:
var edge = require('edge');

// The text in edge.func() is C# code
var helloWorld = edge.func('async (input) => { return input.ToString(); }');

helloWorld('Hello World!', function (error, result) {
    if (error) throw error;
    console.log(result);
});
حالا برای اجرای این Node.js application از طریق command prompt کافی است به صورت زیر عمل کنید:
node server.js
همانطور که مشاهده می‌کنید "!Hello World" در خروجی چاپ شد.

ایجاد پایگاه داده تست

در مثال‌های بعدی، نیاز به یک پایگاه داده داریم تا query‌ها را اجرا کنیم. در صورتی که SQL Server بر روی سیستم شما نصب نیست، می‌توانید نسخه‌ی رایگان آن را از اینجا دانلود و نصب کنید. همچنین SQL Management Studio Express را نیز نصب کنید.

  1. در SQL Management Studio، یک پایگاه داده را با نام node-test با تنظیمات پیش فرض ایجاد کنید.
  2. بر روی پایگاه داده node-test راست کلیک کرده و New Query را انتخاب کنید.
  3. اسکریپت زیر را copy کرده و در آنجا paste کنید، سپس بر روی Execute کلیک کنید.
IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('SampleUsers')) BEGIN; DROP TABLE SampleUsers; END; GO

CREATE TABLE SampleUsers ( Id INTEGER NOT NULL IDENTITY(1, 1), FirstName VARCHAR(255) NOT NULL, LastName VARCHAR(255) NOT NULL, Email VARCHAR(255) NOT NULL, CreateDate DATETIME NOT NULL DEFAULT(getdate()), PRIMARY KEY (Id) ); GO

INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Orla','Sweeney','nunc@convallisincursus.ca','Apr 13, 2014');
INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Zia','Pickett','porttitor.tellus.non@Duis.com','Aug 31, 2014');
INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Justina','Ayala','neque.tellus.imperdiet@temporestac.com','Jul 28, 2014');
INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Levi','Parrish','adipiscing.elit@velarcueu.com','Jun 21, 2014');
INSERT INTO SampleUsers(FirstName,LastName,Email,CreateDate) VALUES('Pearl','Warren','In@dignissimpharetra.org','Mar 3, 2014');
نتیجه‌ی اجرای کد بالا، ایجاد جدولی با نام SampleUsers و درج 5 رکورد در آن می‌شود.

تنظیمات ConnectionString

قبل از استفاده از Edge.js با SQL Server، باید متغیر محیطی (environment variable) با نام EDGE_SQL_CONNECTION_STRING را تعریف کنید.

set EDGE_SQL_CONNECTION_STRING=Data Source=localhost;Initial Catalog=node-test;Integrated Security=True
این متغیر تنها برای command prompt جاری تعریف شده است و با بستن آن از دست می‌رود. در صورتیکه از Node.js Tools for Visual Studio استفاده می‌کنید، نیاز به ایجاد یک متغیر محیطی دائمی و راه اندازی مجدد VS دارید. همچنین در صورتیکه بخواهید متغیر محیطی دائمی ایجاد کنید، فرمان زیر را اجرا کنید:
SETX EDGE_SQL_CONNECTION_STRING "Data Source=localhost;Initial Catalog=node-test;Integrated Security=True"


روش اول: اجرای مستقیم SQL Server Query در Edge.js

فایلی با نام server-sql-query.js را ایجاد کرده و کد زیر را در آن وارد کنید:

var http = require('http');
var edge = require('edge');
var port = process.env.PORT || 8080;

var getTopUsers = edge.func('sql', function () {/*
    SELECT TOP 3 * FROM SampleUsers ORDER BY CreateDate DESC
*/});

function logError(err, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write("Error: " + err);
    res.end("");
}    

http.createServer(function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html' });

    getTopUsers(null, function (error, result) {
        if (error) { logError(error, res); return; }
        if (result) {
            res.write("<ul>");
            result.forEach(function(user) {
                res.write("<li>" + user.FirstName + " " + user.LastName + ": " + user.Email + "</li>");
            });
            res.end("</ul>");
        }
        else {
        }
    });
}).listen(port);
console.log("Node server listening on port " + port);
سپس با استفاده از command prompt، فرمان زیر را اجرا کنید:
node server-sql-query.js
حال مرورگر خود را باز و سپس آدرس http://localhost:8080 را باز کنید. در صورتی که همه چیز به درستی انجام گرفته باشد لیستی از 3 کاربر را خواهید دید.

روش دوم: اجرای کد دات نت برای SQL Server Query

Edge.js تنها از دستورات Update، Insert، Select و Delete پشتیبانی می‌کند. در حال حاضر از store procedures و مجموعه‌ای از کد SQL پشتیبانی نمی‌کند. بنابراین، اگر چیزی بیشتر از عملیات CRUD می‌خواهید انجام دهید، باید از دات نت برای این کار استفاده کنید.

یادتان باشد، همیشه async

مدل اجرایی Node.js به صورت یک حلقه‌ی رویداد تک نخی است. بنابراین این بسیار مهم است که کد دات نت شما به صورت async باشد. در غیر اینصورت یک فراخوانی به دات نت سبب مسدود شدن و ایجاد خرابی در Node.js می‌شود.

ایجاد یک Class Library

اولین قدم، ایجاد یک پروژه Class Library در Visual Studio که خروجی آن یک فایل DLL است و استفاده از آن در Edge.js است. پروژه Class Library با عنوان EdgeSampleLibrary ایجاد کرده و فایل کلاسی با نام Sample1 را به آن اضافه کنید و سپس کد زیر را در آن وارد کنید:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace EdgeSampleLibrary
{
     public class Sample1
    {
        public async Task<object> Invoke(object input)
        {
            // Edge marshalls data to .NET using an IDictionary<string, object>
            var payload = (IDictionary<string, object>) input;
            var pageNumber = (int) payload["pageNumber"];
            var pageSize = (int) payload["pageSize"];
            return await QueryUsers(pageNumber, pageSize);
        }

        public async Task<List<SampleUser>> QueryUsers(int pageNumber, int pageSize)
        {
            // Use the same connection string env variable
            var connectionString = Environment.GetEnvironmentVariable("EDGE_SQL_CONNECTION_STRING");
            if (connectionString == null)
                throw new ArgumentException("You must set the EDGE_SQL_CONNECTION_STRING environment variable.");

            // Paging the result set using a common table expression (CTE).
            // You may rather do this in a stored procedure or use an 
            // ORM that supports async.
            var sql = @"
DECLARE @RowStart int, @RowEnd int;
SET @RowStart = (@PageNumber - 1) * @PageSize + 1;
SET @RowEnd = @PageNumber * @PageSize;

WITH Paging AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY CreateDate DESC) AS RowNum,
            Id, FirstName, LastName, Email, CreateDate
    FROM    SampleUsers
)
SELECT  Id, FirstName, LastName, Email, CreateDate
FROM    Paging
WHERE   RowNum BETWEEN @RowStart AND @RowEnd
ORDER BY RowNum;
";
            var users = new List<SampleUser>();

            using (var cnx = new SqlConnection(connectionString))
            {
                using (var cmd = new SqlCommand(sql, cnx))
                {
                    await cnx.OpenAsync();

                    cmd.Parameters.Add(new SqlParameter("@PageNumber", SqlDbType.Int) { Value = pageNumber });
                    cmd.Parameters.Add(new SqlParameter("@PageSize", SqlDbType.Int) { Value = pageSize });

                    using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
                    {
                        while (await reader.ReadAsync())
                        {
                            var user = new SampleUser
                            {
                                Id = reader.GetInt32(0), 
                                FirstName = reader.GetString(1), 
                                LastName = reader.GetString(2), 
                                Email = reader.GetString(3), 
                                CreateDate = reader.GetDateTime(4)
                            };
                           users.Add(user);
                        }
                    }
                }
            }
            return users;
        } 
    }

    public class SampleUser
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public DateTime CreateDate { get; set; }
    }
}
سپس ذخیره و کامپایل کنید. فایل DLL خروجی که در مسیر
[project]/bin/Debug/EdgeSampleLibrary.dll
قرار دارد را در پوشه‌ی پروژه Node کپی کنید. فایل جدیدی را با نام server-dotnet-query.js در پروژه Node ایجاد کنید و کد زیر را در آن وارد کنید:
var http = require('http');
var edge = require('edge');
var port = process.env.PORT || 8080;

// Set up the assembly to call from Node.js 
var querySample = edge.func({ assemblyFile: 'EdgeSampleLibrary.dll', typeName: 'EdgeSampleLibrary.Sample1', methodName: 'Invoke' });

function logError(err, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write("Got error: " + err); res.end(""); }

http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/html' });

    // This is the data we will pass to .NET
    var data = { pageNumber: 1, pageSize: 3 };

    // Invoke the .NET function
    querySample(data, function (error, result) {
        if (error) { logError(error, res); return; }
        if (result) {
            res.write("<ul>");
            result.forEach(function(user) {
                res.write("<li>" + user.FirstName + " " + user.LastName + ": " + user.Email + "</li>");
            });
            res.end("</ul>");
        }
        else {
            res.end("No results");
        }
    });

}).listen(port);

console.log("Node server listening on port " + port);
سپس از طریق command prompt آن را اجرا کنید:
node server-dotnet-query.js
حال مرورگر خود را باز کرده و به آدرس http://localhost:8080 بروید. در صورتیکه همه چیز به درستی انجام گرفته باشد، لیستی از 3 کاربر را خواهید دید. مقادیر pageNumber و pageSize را در فایل جاوااسکریپت تغییر دهید و تاثیر آن را بر روی خروجی مشاهده کنید.
 
نکته: برای ایجاد pageNumber و pageSize داینامیک با استفاده از ارسال مقادیر توسط QueryString، می‌توانید از ماژول connect استفاده کنید.
مطالب
رشته‌ها در ES 6

در بیشتر زبان‌های برنامه‌نویسی قابلیتی تحت عنوان String Interpolation وجود دارد. منظور، فرآیند جایگزین کردن مقادیر، با یکسری placeholder درون یک رشته است. در نسخه‌های قبلی جاوا اسکریپت محدودیت‌هایی در استفاده از رشته‌ها وجود داشت و امکان انجام این کار به صورت توکار مهیا نبود. یعنی برای پیاده‌سازی این قابلیت می‌توانستیم با تغییر prototype شیء String و یا روش‌های دیگری این‌حالت را پیاده‌سازی کنیم (+):

// First, checks if it isn't implemented yet.
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) { 
      return typeof args[number] != 'undefined'
        ? args[number]
        : match
      ;
    });
  };
}
"Hello, {0}, I'm a simple {1}, Today is: {2}".format("World", "String", new Date());

// Output

"Hello, World, I'm a simple String, Today is: Tue Dec 29 2015 10:21:10 GMT+0330 (Iran Standard Time)"

اما در ES 6 با کمک قابلیتی تحت عنوان template string این محدودیت‌ها به طور قابل ملاحظه‌ایی کاهش پیدا کرده است. در واقع یک template string، یک رشته‌ی جاوا اسکریپتی است که به جای (" ") و یا (' ') درون دو کاراکتر (` `) یا به اصطلاح back-tick character محصور خواهد شد. این ویژگی در سناریوهای مختلفی کاریرد دارد. از این ویژگی می‌توانیم جهت الحاق رشته‌ها استفاده کنیم. به عنوان مثال می‌توانیم کد زیر را:

let category = "music";
let id = 2112;

let url = "http://apiserver/" + category + "/" + id;

با کمک template string به اینصورت بازنویسی کنیم:

let category = "music";
let id = 2112;

let url = `http://apiserver/${category}/${id}`;

و یا می‌توانیم مثال ابتدای مطلب را به اینصورت بازنویسی کنیم:

console.log(`Hello, ${"World"}, I'm a simple ${"String"}, Today is: ${new Date()}`);

همانطور که عنوان شد برای استفاده از این قابلیت باید رشته‌ی موردنظر را درون دو کاراکتر (` `) قرار دهیم. سپس درون این کاراکترها می‌توانیم literal text و همچنین یکسری placeholder جهت جایگزین کردن با مقادیر و عبارات موردنظر داشته باشیم. این placeholder‌ها نیز با استفاده از سینتکس { }$ قابل تعریف هستند.  لازم به ذکر است که عبارت موردنظرمان را باید درون دو علامت { } بنویسیم. مقادیر درون این دو علامت می‌توانند هر عبارت معتبر جاوا اسکریپتی باشند: 

let a = 5;
let b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

در کد فوق متغیرهای a و b درون placeholder‌های مربوطه جایگزین خواهند شد. همانطور که مشاهده می‌کنید، این سینتکس نسبت به سینتکس + که برای الحاق رشته‌ها قبلاً مورد استفاده قرار می‌گرفت خیلی بهتر و خواناتر است.

به صورت خلاصه:

  • کد درون placeholder می‌تواند هر عبارت جاوا اسکریپتی باشد.
  • اگر مقدار درون placeholder یک رشته نباشد٬ توسط متد toString به رشته تبدیل خواهد شد.
  • اگر بخواهید درون template string از یک کاراکتر backtick استفاده کنید٬ می‌توانید به این صورت عمل کنید: 
`\``

// یا

"`"
در واقع می‌توانید توسط یک بک‌اسلش ار کارکترهای back tick و $ صرفنظر کنید.

  Multiline Strings 

console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

همانطور که مشاهده می‌کنید، template string از متن‌های چندخطی نیز به خوبی پشتیبانی می‌کند. به عنوان مثال اگر رشته‌ی فوق را درون گیومه می‌نوشتیم می‌بایستی از سینتکس + برای الحاق دو خط فوق استفاده می‌کردیم:

console.log("string text line 1\n"+
"string text line 2");
// "string text line 1
// string text line 2"

محدودیت‌های template strings
  • به صورت خودکار کارکترهای خاص را برای شما escape نمی‌کند (جهت جلوگیری از آسیب‌پذیری‌های XSS).
  • به صورت کامل از کتابخانه‌هایی جهت اعمال internationalization پشتیبانی نمی‌کند.
  • جایگزینی برای کتابخانه‌هایی مانند  Mustache و  Nunjucks نیست.
ES 6 قابلیت دیگری تحت عنوان tagged templates جهت رفع محدودیت‌های فوق در اختیارمان قرار می‌دهد. سینتکس آن نیز خیلی ساده است. کافی است قبل از کارکتر back-tick یک tag نوشته شود. قبل از توضیح این قابلیت مثال زیر را در نظر بگیرید:
var x = 1;
var y = 3;
var result = upper `${x} + ${y} is ${x+y}`;

console.log(result);

// Output
// 1 + 3 IS 4
همانطور که مشاهده می‌کنید متغیرهای x و y و همچنین مجموع آنها را درون رشته‌ی فوق قرار داده‌ایم. اما نکته‌ایی که در اینجا وجود دارد این است که مقدار خروجی دقیقاً معادل template نیست؛ زیرا در خروجی، is به صورت حروف بزرگ نمایش داده شده است. دلیل آن نیز این است که قبل از شروع کاراکتر back-tick، از یک تگ با نام upper استفاده کرده‌ایم. در واقع یک تگ چیزی بیشتر از یک تابع نیست که در ادامه پیاده‌سازی آن را مشاهده خواهید کرد:
let upper = function(strings, ...values){
  let result = "";
  for(var i = 0; i < strings.length; i++){
    result += strings[i];
    if(i < values.length){
      result += values[i];
    }
  }
  return result.toUpperCase();
};
تابع فوق دو پارامتر را از ورودی دریافت خواهد کرد: به اولین پارامتر parsed template string گفته می‌شود و مقدار آن متن parse شده درون کاراکتر‌های back-tick است. به پارامتر دوم نیز rest parameter گفته می‌شود که در واقع یک آرایه از مقادیر placeholder هایمان است. در نتیجه مقادیر این دو پارامتر به صورت زیر خواهد بود:
strings = ["", " + ", " is ", ""];
values  = [1, 3, 4];
درون تابع با مقادیر فوق می‌توانیم کارهای مختلفی را انجام دهیم. به عنوان مثال در اینجا ایجاد همان رشته؛ اما اینبار به صورت upper case.
در نتیجه با استفاده از این قابلیت می‌توانیم تگ‌های سفارشی زیادی را ایجاد کنیم. به عنوان مثال می‌توانیم تگی را ایجاد کنیم که تمپلیتی را دریافت کرده و آن را به HTML encoded تبدیل کند و در این‌حالت به ما در جلوگیری از حملات XSS و همچنین رفع محدویت‌هایی که در template strings داشتیم کمک خواهد کرد.

یک مثال عملی
می‌خواهیم یک tag template ایجاد کنیم که به انتهای اعداد درون یک تملپت، مقدار "تومان" را اضافه کرده و خود عدد را نیز به صورت سه رقم سه رقم جدا کند. می‌خواهیم رشته‌ی زیر همراه با مقادیر آن:
var name = "سیروان عفیفی";
var price = 150000;
var text = withToman `${name} با تشکر از خرید شما, مبلغ قابل پرداخت: ${price}`;
alert(text);
در خروجی اینچنین نمایش داده شود:

کدهای تگ withToman نیز به اینصورت میباشد:
function withToman(strings, ...values) {
  return strings.reduce( function (s, v, idx) {
    if(idx > 0) {
      if(typeof values[idx - 1] == "number") {
        s += `${values[idx - 1].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} تومان`
      }
      else {
        s += values[idx -1];
      }
    }
    return s + v;
  }, "");
}
همچنین در حالت پیشرفته‌تری می‌توان از این قابلیت جهت ایجاد یک DSL یا (Domain Specific Languages) ایده گرفت.
مطالب دوره‌ها
تزریق خودکار وابستگی‌ها در ASP.NET Web API به همراه رها سازی خودکار منابع IDisposable
در انتهای مطلب « تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET MVC » اشاره‌ای کوتاه به روش DependencyResolver توکار Web API شد که این روش پس از بررسی‌های بیشتر (^ و ^) به دلیل ماهیت service locator بودن آن و همچنین از دست دادن Context جاری Web API، مردود اعلام شده و استفاده از IHttpControllerActivator توصیه می‌گردد. در ادامه این روش را توسط Structure map 3 پیاده سازی خواهیم کرد.

پیش نیازها
- شروع یک پروژه‌ی جدید وب با پشتیبانی از Web API
- نصب دو بسته‌ی نیوگت مرتبط با Structure map 3
 PM>install-package structuremap
PM>install-package structuremap.web

پیاده سازی IHttpControllerActivator توسط Structure map

using System;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using StructureMap;

namespace WebApiDISample.Core
{
    public class StructureMapHttpControllerActivator : IHttpControllerActivator
    {
        private readonly IContainer _container;
        public StructureMapHttpControllerActivator(IContainer container)
        {
            _container = container;
        }

        public IHttpController Create(
                HttpRequestMessage request,
                HttpControllerDescriptor controllerDescriptor,
                Type controllerType)
        {
            var nestedContainer = _container.GetNestedContainer();
            request.RegisterForDispose(nestedContainer);
            return (IHttpController)nestedContainer.GetInstance(controllerType);
        }
    }
}
در اینجا نحوه‌ی پیاده سازی IHttpControllerActivator را توسط StructureMap ملاحظه می‌کنید.
نکته‌ی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب می‌شود تا کلیه کلاس‌های IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.


معرفی StructureMapHttpControllerActivator به برنامه

فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http;
using System.Web.Http.Dispatcher;
using StructureMap;
using WebApiDISample.Core;
using WebApiDISample.Services;

namespace WebApiDISample
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // IoC Config
            ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>());

            var container = ObjectFactory.Container;
            GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container));


            // Web API routes
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}
 در ابتدا تنظیمات متداول کلاس‌ها و اینترفیس‌ها صورت می‌گیرد. سپس نحوه‌ی معرفی  StructureMapHttpControllerActivator را به GlobalConfiguration.Configuration.Services مخصوص Web API ملاحظه می‌کنید. این مورد سبب می‌شود تا به صورت خودکار کلیه وابستگی‌های مورد نیاز یک Web API Controller به آن تزریق شوند.


تهیه سرویسی برای آزمایش برنامه

namespace WebApiDISample.Services
{
    public interface IEmailsService
    {
        void SendEmail();
    }
}

using System;

namespace WebApiDISample.Services
{
    /// <summary>
    /// سرویسی که دارای قسمت دیسپوز نیز هست
    /// </summary>
    public class EmailsService : IEmailsService, IDisposable
    {
        private bool _disposed;

        ~EmailsService()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void SendEmail()
        {
            //todo: send email!
        }

        protected virtual void Dispose(bool disposeManagedResources)
        {
            if (_disposed) return;
            if (!disposeManagedResources) return;

            //todo: clean up resources here ...

            _disposed = true;
        }
    }
}
در اینجا یک سرویس ساده ارسال ایمیل را بدون پیاده سازی خاصی مشاهده می‌کنید.
نکته‌ی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شده‌است). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.


استفاده از سرویس تعریف شده در یک Web API Controller

using System.Web.Http;
using WebApiDISample.Services;

namespace WebApiDISample.Controllers
{
    public class ValuesController : ApiController
    {
        private readonly IEmailsService _emailsService;
        public ValuesController(IEmailsService emailsService)
        {
            _emailsService = emailsService;
        }

        // GET api/values/5
        public string Get(int id)
        {
            _emailsService.SendEmail();
            return "_emailsService.SendEmail(); called!";
        }
    }
}
در اینجا مثال ساده‌ای را از نحوه‌ی تزریق سرویس ارسال ایمیل را در ValuesController مشاهده می‌کنید.
تزریق وهله‌ی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت می‌گیرد.

فراخوانی متد Get آن‌را نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2>

@section scripts
{
    <script type="text/javascript">
        $(function () {
            $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) {
                alert(data);
            });
        });
    </script>
}
درون متد Get کنترلر، یک break point قرار دهید. همچنین داخل متد Dispose لایه سرویس نیز جهت بررسی بیشتر یک break point قرار دهید.
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهله‌ی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی می‌شود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
WebApiDISample.zip 
مطالب
آشنایی با Virtual Directory
نمیدانم آیا تا به حال برای شما پیش آمده است که بخواهید اطلاعاتتان را در جایی غیر از زیرشاخه‌های wwwroot ذخیره نمایید یا خیر؟
یادم هست برای یکی از مشتریانم یک سرور خریده بودیم که دو پارتیشن داشت و آن موقع به ذهنم خطور کرد که اگه بخواهم مثلا فایل‌های سیستم مدیریتی را داخل یک پارتیشن دیگر قرار بدهم چگونه انجام می‌شود؛ چطوری میتوانم به مکانی غیر از شاخه‌ی wwwroot، عمل mappath را انجام بدم؟ چگونه میتوانم یک لینک مستقیم، به مکانی دیگر داشته باشم؟
جواب تمام این سوالات در امکانی از IIS به اسم virtual Directory بود. در این روش ما یک مکان فیزیکی را به iis معرفی می‌کنیم و به آن یک نام مجازی میدهیم که از آن در برنامه استفاده خواهیم کرد.
در iis، در بخش sites، همه سایت‌های ایجاد شده بر روی IIS لیست می‌شوند. یک context menu بر روی سایت مورد نظر خود باز کنید و گزینه‌ی add virtual directory انتخاب کنید. یک نام مجازی به آن بدهید و مکان مورد نظر را انتخاب کنید و کادر را تایید کنید.

برای ویرایش آن یعنی تغییر مسیر فیزیکی هم میتوان از طریق مسیر زیر عمل کرد

Right Click On virtual Directory>Manage Virtual Directory >Advanced Settings

از این پس در IIS دسترسی به پوشه، از طریق ~/media میسر هست؛ ولی بسیاری از ما برای تست، برنامه را از طریق IIS Express اجرا و تست می‌کنیم. پس بهتر هست این گزینه در آنجا هم مورد بررسی قرار بگیرد.

برای دسترسی به کانفیگ iis express عموما مسیرهای زیر معتبر هستند:

%userprofile%\documents\iisexpress\config\applicationhost.config
%userprofile%\my documents\iisexpress\config\applicationhost.config

فایل applicationhost.config  فایلی است که قرار است تغییر بدهیم.

ولی اگر مسیرهای بالا راهگشا نبود، برای پیدا کردن فایل‌های کانفیگ می‌توانید از طریق آیکن IIS Express که حین اجرای پروژه در notification area باز می‌شود نیز اقدام کرد.

یک context menu از طریق این آیکن باز کرده و گزینه‌ی show all applications را انتخاب کنید. لیست تمامی سایت‌های در حال اجرا نمایش داده می‌شود. یکی را انتخاب کنید تا در زیر اطلاعات نمایش یابد. در قسمت کانفیگ، آدرس فایل کانفیگ داده شده است و مسیر نیز مشخص است. با کلیک بر روی آن فایل applicationhost.config باز می‌شود.

فایل مورد نظر را باز کنید. این فایل را می‌توان با نوت پد یا یک xml editor گشود. در آن یک تگ sites وجود دارد که تمامی پروژه‌های تحت وبی را که تا الان دارید، درون خودش ذخیره کرده‌است. به ازای هر سایت یک تگ site هست و خصوصیات هر کدام، داخل این تگ قرار گرفته‌است. اگر دقت کنید هر پروژه شما یا همان تگ site، شامل تگ زیر می‌باشد:

<application path="/" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="E:\website\oscar\panel\Oscar_Manager\Oscar_Manager" />
                </application>

در اینجا خود IIS Express اقدام به ساخت یک دایرکتوری مجازی که همان مسیر ذخیره پروژه هست کرده. برای معرفی دایرکتوری مجازی جدید، یک کپی از تگ application را ایجاد کنید.

برای مثال من قصد دارم یک دایرکتوری مجازی به اسم media بسازم تا تصاویر و دیگر فایل‌های چندرسانه ای را در آن ذخیره کنم و محل فیزیکی آن نیز D:\testvd می‌باشد. پس تگ کپی شده را به نحو زیر تغییر می‌دهم:

 <application path="/media" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="d:\testvd" />
                </application>

بنابراین در کل، تگ site من به شکل زیر تغییر پیدا می‌کند:

<site name="Oscar_Manager" id="23">
                <application path="/" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="E:\website\oscar\panel\Oscar_Manager\Oscar_Manager" />
                </application>
 <application path="/media" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="d:\testvd" />
                </application>
                <bindings>
                    <binding protocol="http" bindingInformation="*:1844:localhost" />
                </bindings>
            </site>

الان مسیر مجازی ما ساخته شده است. برای تست صحت این کار، یک فایل تصویری را در آن قرار می‌دهم و در کنترلر مورد نظر، این کد را می‌نویسم تا یک تصویر به سمت کلاینت در یک virtual directory ارسال شود.

var dir = Server.MapPath("~/media");
            var path = System.IO.Path.Combine(dir, "1.jpg");
            return base.File(path, "image/jpeg");

کنترلر را صدا زده تا نتیجه کار را ببنید.

همانطور که حتما متوجه شدید IIS Express محیط GUI ندارد. البته مدتی است افزونه‌ای برای این کار محیا شده ولی بیشتر کاربرد آن ایجاد یک سایت جدید و اجرا و توقف IIS می‌باشد که می‌توانید آن را از اینجا  دریافت نمایید.

نکته:البته بنده این نکته را تایید نمی‌کنم ولی شنیده‌ام که در نسخه‌های بالاتر ویژوال استادیو با راست کلیک بر روی نام پروژه، گزینه Use IIS Express وجود دارد که به یک محیط گرافیکی ختم می‌شود و از آنجا که بنده نسخه 2012 را دارم این مورد را تست نکردم.


ایجاد virtual Directory از طریق Appcmd

دسترسی به appcmd از طریق مسیر زیر امکان پذیر است:

%systemroot%\system32\inetsrv\AppCmd.exe

این دستور به تمامی اشیاء سرور، از قبیل سایت‌ها و اپلیکیشن‌ها دسترسی دارد و میتواند هر نوع متدی را بر روی اشیاء سرور، از قبیل ثبت، ویرایش و حذف را انجام دهد.

یکی از این دستورات، برای ساخت Virtual Directory استفاده می‌شود:

APPCMD add vdir /app.name:"default we site/" /path:/vdir1 /physicalPath:d:\testvd

سوییچ /app برای نام وب سایت یا پروژه است و حتما در انتهای نام، علامت / قرار گیرد. سوییچ بعدی هم که /path هست برای دادن مسیر مجازی و سوییچ آخری هم آدرس محل فزیکی است. بعد از اجرای دستور، پیام زیر نمایش داده می‌شود:

VDIR object "Default Web Site/vdir1" added

پنجره commandprompt باید به صورت Run a Administrator باز شده باشد.

برای تغییر محل فیزیکی یک virtual directory میتوان از دستور زیر استفاده کرد:

appcmd.exe set vdir "Default Web Site/vdir1/" -physicalPath:"D:\Files"

از این پس مسیر فیزیکی این آدرس مجازی مسیر D:\Files خواهد بود.

البته مباحث پیشرفته‌تری چون application pool‌ها نیز وجود دارد که از حوصله این مقاله خارج هست و در وقت و مقاله دیگر بررسی خواهیم کرد.

برای ارسال دستور به IIS Express هم از طریق مسیر زیر appcmd را باز کنید:

%ProgramFiles%\IIS Express\appcmd.exe

امکان ایجاد virtual directory عموما در سرورهای مجازی و اختصاصی در پنل مربوطه نیز وجود دارد.

ساخت virtual Directory در وب سایت پنل 

ساحت virtual Directory در پلسک 

مطالب
xamarin.android قسمت سوم
Theme
برای اینکه بتوانیم ظاهر گرافیکی layout‌ها را کنترل نماییم، از Theme که مجموعه‌ای از styleهای گرافیکی می‌باشد، استفاده می‌کنیم. در اندروید مجموعه‌ای از تم‌های از پیش ساخته شده که به آنها Builtin Theme نیز گفته می‌شود می‌توانیم استفاده کنیم. تم‌ها ظاهر گرافیکی کلیه کنترل‌های Layout را با نام‌های زیر، کنترل می‌کنند:
statusBarColor
textColorPrimary
colorAccent
ColorPrimary
WindowBackground

اگر ساختار زیر را در یک صفحه استاندارد برنامه‌های موبایل را در نظر بگیریم، styleها هر بخش، یک نام منحصر به فرد دارد:



اگر بخواهیم از style‌های از پیش طراحی شده‌ی اندروید استفاده نماییم، ابتدا میتوانیم در صفحه‌ی layout در زامارین، style مربوطه را از بخش Theme استفاده کرده و نتیجه را مشاهده کنیم. ولی تغییر style سبب تغییر layout در زمان اجرا نمی‌شود. هرگاه بخواهیم از styleهای استاندارد یا builtin اندروید استفاده نماییم، در Activity توسط خصوصیت Theme با فرمت:
[Activity(Theme = "@style/NameThem")]
تم را به‌عنوان تم داخلی وسپس نام کامل تم را می‌نویسیم.
 
CustomTheme
در طراحی فرم‌ها ممکن است بخواهیم از یک استایل خاص builtin استفاده کنیم؛ ولی ممکن است بعضی از استایل‌های آن را استفاده نکنیم، مانند تمی که از قبل استفاده شده‌است، از روش زیر استفاده می‌کنیم:
- بر روی دایرکتوری value راست کلیک میکنیم. گزینه add new item را انتخاب و یک فایل xmlfile را با نام style ایجاد میکنیم.
- style‌های جدید منابع application می‌باشند که در بخش resource از آن‌ها استفاده میکنیم. هر استایل جدید را توسط Style Tag مشخص میکنیم و در خصوصیت Name، نام Style را مشخص میکنیم.
ممکن است در یک Style نتوانیم و یا نخواهیم تمامی Style‌های مورد نیاز را تامین کنیم. از این رو توسط Parent، یک StyleBuition تعریف نموده که این Styleها از آن مشتق می‌شوند. اگر در Theme جدید گزینه‌ها مشخص شوند، تم اصلی تغییر نمی‌کند. در غیر اینصورت تمامی گزینه‌های تعریف شده در تم جدید از ThemParent مقدار دهی می‌شود.
توسط item می‌توان یک style را تعریف نمود. در Activity توسط
 [Activity(Theme = "@style/NameThem")]
می‌توانیم استایل سفارشی شده خودمان را مشخص کنیم.

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <style name="MainTheme" parent="Theme.AppCompat.Light.DarkActionBar">
     <item name="windowNoTitle">true</item>
     <item name="windowActionBar">false</item>
  </style>
</resources>
 
  
Navigation Menu
کتابخانه متریال دیزاین کتابخانه جدیدی می‌باشد که به اندروید اضافه شده‌است. توسط  آن می‌توانیم کنترل‌های جدید را با استایل‌های جدید برای Appهای اندروید، تولید کنیم. ابتدا نیاز به نصب Component‌های ذیل در زامارین می‌باشد.
 
استفاده که از کتابخانه متریال دیزاین
برای اینکه بتوانیم Navigation menu را ایجاد کنیم، باید از نیوگت، کتابخانه‌های Appcompat و Designlibrary را انتخاب و نصب نماییم و اگر نگارش ویژوال استودیوی شما 15.7.3باشد، از ابتدا بصورت اتوماتیک نصب شده است و احتیاجی به این مراحل نمی‌باشد. بدیهی است در زمان نصب باید از نرم افزار‌های تحریم گذر نیز استفاده کرد.
  
ساخت Menu
NavigationMenu، منوی اصلی منو می‌باشد که با Swipping از گوشه راست به چپ، باز یا بسته می‌شود یا با کلیک بر روی دکمه‌ی Menu بر روی Toolbar، منو را باز یا بسته میکند.
قدم اول: نصب منو
منظور همان اضافه کردن کتابخانه‌ها است.
قدم دوم:
ابتدا باید گزینه‌های منو را در یک فایل xml تعریف نمود. هر گزینه‌ی آن از دو بخش متن اصلی منو و ID منو تشکیل شده‌است.
بر روی دایرکتوری Resource راست کلیک کرده و یک دایرکتوری یا پوشه را به نام Menu ایجاد می‌کنیم. بر روی دایرکتوری منو، راست کلیک کرده و یک فایل Xml را به آن اضافه می‌کنیم. برای آنکه بتوانیم در این فایل دستورات ساخت منو را نوشته و به نحوی که توسط اندروید قابل خواندن و تبدیل به منو باشد، ساختار منو را از آدرس زیر ویژوال استودیو دانلود میکنیم.
 <menu xmlns:android="http://schemas.android.com/apk/res/android">

توسط Group مجموعه گزینه‌های منو را را معرفی میکنیم.
هر گزینه در منو ی اصلی توسط یک آیتم مشخص میشود. برای آنکه هنگام کلیک بر روی هر گزینه از طریق برنامه نویسی بتوانیم گزینه انتخاب شده را شناسایی کنیم، یک آی دی منحصر بفرد را به هر گزینه اختصاص میدهیم. زمانیکه بر روی یک گزینه کلیک میشود، توسط این ID‌ها میتوانیم شناسایی کنیم کدام گزینه انتخاب شده‌است.
خصوصیت Android :Title متن اصلی منو را مشخص میکند.
 <?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <group android:checkableBehavior="single">
     <item android:id="@+id/menuItemHome" android:title="صفحه اصلی"></item>
     <item android:id="@+id/menuItemInsertProduct" android:title="ورود کالا جدید" ></item>
     <item android:id="@+id/menuItemListProduct" android:title="مشاهده کالاها"></item>
     <item android:id="@+id/menuItemExit"  android:title="خروج"></item>
  </group>
</menu>

سپس باید در Layout مورد نظر همانند صفحه Main، ساختار اصلی برنامه شامل Toolbar و Menu را بصورت زیر تعریف نماییم:
<android.support.v7.widget.Toolbar
  android:layout_width="match_parent"
  android:id="@+id/toolbar1"
  android:background="#33B86C"
  android:minHeight="?android:attr/actionBarSize"
  android:layout_height="wrap_content">
</android.support.v7.widget.Toolbar>

ساختار منو به صورت زیر است:
<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <group android:checkableBehavior="single">
    <item android:id="@+id/menuItemHome" android:title="صفحه اصلی"></item>
    <item android:id="@+id/menuItemInsertProduct" android:title="ورود کالا جدید" ></item>
    <item android:id="@+id/menuItemListProduct" android:title="مشاهده کالاها"></item>
    <item android:id="@+id/menuItemExit"  android:title="خروج"></item>
  </group>
</menu>
در Linearlayout ریشه، گزینه Fitssystemwindow را true میکنیم که سایز linearlayout را با سایز موبایل جدید اندازه می‌کند. سپس از toolbox، کنترل Toolbarرا به پنجره اضافه میکنیم که در بالای صفحه قرار می‌گیرد.
toolbar اضافه شده، toolbar استاندارد قبل از متریال دیزاین میباشد. در واقع toolbar اول، Toolbar استاندارد اندروید می‌باشد. برای آنکه از Toolbar متریال دیزاین استفاده کنیم، کنترل‌های متریال دیزاین در بخش supportlibrary اضافه میشود و Toolbar متریال دیزاین را اضافه میکنیم. علامت ؟ یعنی اینکه می‌خواهیم از اندازه سیستمی استفاده کنیم. اگر بخواهیم حداقل سایز Toolbarبر اساس پیش فرض در دستگاه‌های مختلف باشد، از علامت Android :attr ? استفاده میکنیم. اگر بخواهیم حداقل ارتفاع پیشنهادی اندروید در هر موبایل متصل شود، از خصوصیت Action Bar Size  استفاده میکنیم. این خصوصیت زمانی عمل میکند که Height  آن Wrapcontent باشد.
گذاشتن دکمه منو: برای آنکه بتوانیم دکمه منو را به Toolbar اضافه کنیم، از دکمه Image Button استفاده میکنیم که یک دکمه‌ی معمولی می‌باشد ولی خلاصه‌ی آن عکس است. در خصوصیت Back ground دکمه، بصورت زیر نام فایل آیکن منو را در دایرکتوری Drawable، مشخص میکنیم و خصوصیت src آن‌را null می‌کنیم تا تصویری بجز تصویر انتخابی نباشد.
برای آنکه بتوانیم پنجره اصلی منو را به صورتیکه دارای قابلیت حرکت به راست و چپ باشد، ایجاد کنیم، از کنترلی به‌نام  Drowerlayout استفاده میکنیم که بر روی صفحه قرار میگیرد. DrawerLayout در linearlayout ریشه قرار میگیرد و یا بعد از ToolBar و حتما باید خصوصیت fitsystemwindow کنترل Drawer را True کنیم. جهت نمایش گزینه‌های اصلی در Drawer از کنترل NavigationٰView استفاده می‌کنیم.
گزینه‌های منو در کنترلی به نام Navigationview قرار دارد. این کنترل باید در Drawerlayout قرار گیرد. توسط فضای نام منو، محل فایل xml را که منو درون آن قرار گرفته است، مشخص می‌کنیم. آدرس این دستور در این مسیر می‌باشد:
 xmlns:app="http://schemas.android.com/apk/res-auto"
Layout gravity  آن را end  قرار میدهیم که از سمت راست قرار بگیرد. Fit system Window را هم True میکنیم تا گزینه‌های داخل آن‌را هم Fit کند. Theme باید از نوع تم‌های متریال دیزاین و با کلمه Them . App Compact. ligth.NoActionBar باشد. برای آنکه اکتیویتی‌ها، متریال دیزاین را ساپورت کنند، میتوان از کلاس  App compact Activity  استفاده کنیم. Tool bar بصورت پیش فرض لیبل اکتیویتی را نشان می‌دهد و دستور زیر عنوان Toolbar را حذف میکند.
 SupportActionBar.SetDisplayShowTitleEnabled(false);
 
مدیریت گزینه‌های منو
به محض انتخاب یک گزینه درون NavigationView، رخدادی به نام NavigationItemSelected صادر می‌شود که توسط آن میتوانیم گزینه‌ی انتخاب شده را از طریق برنامه نویسی مدیریت کنیم. این کنترل در Android.Support.V7.Widget و NameSpace بالا قرار میگیرد. سپس یک رخ‌داد گردان را با نام navigationItemSelected پیاده سازی می‌کنیم. اطلاعات مربوط به گزینه‌ی انتخاب شده، در پارامتر دوم از این تابع NavigationView.NavigationItemSelectedEventArgs ذخیره می‌شود. ID، آیتم انتخاب شده در فایل Menu را باز می‌گرداند.
        var navigationview = this.FindViewById<NavigationView>(Resource.Id.navigationView1);
        navigationview.NavigationItemSelected += Navigationview_NavigationItemSelected;
        private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
        {
            Intent intent = null;
            switch (e.MenuItem.ItemId)
            {
                case Resource.Id.menuItemHome:

                    break;
                case Resource.Id.menuItemExit:
                    Finish();
                    break;
                case Resource.Id.menuItemInsertProduct:

                    break;
                case Resource.Id.menuItemListProduct:

                    break;
            }
        }
 
مدیریت اکتیویتی‌ها توسط Menu
با انتخاب گزینه Menu باید اکتیویتی مربوطه انتخاب شود. بنابراین برای هر گزینه‌ی منو یک Layout و اکتیویتی را ایجاد می‌کنیم و اجرا میکنیم. ولی در اکتیویتی جدید Toolbar وجود ندارد.
 
تکنیک ادغام:
برای آنکه در Layoutهای مختلف، تولبار و منو و یا هر View دیگری را بصورت مشترک استفاده کنیم، یک فایل xml را به دایرکتوری Layout اضافه می‌کنیم. دستور Merge میتواند تمام layoutها را به درون layoutهای دیگر مانند home,insert ادغام و یا تزریق کند. جهت استفاده از Merge در layoutهای دیگر نیاز به Id منحصر به فرد می‌باشد.
<?xml version="1.0" encoding="utf-8" ?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbarlayout">
    <android.support.v7.widget.Toolbar android:layout_width="match_parent" android:id="@+id/toolbar1" android:background="#33B86C" android:minHeight="?android:attr/actionBarSize" android:layout_height="wrap_content">
        <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageButton1" android:background="@drawable/mainmenu" android:layout_gravity="end" />
    </android.support.v7.widget.Toolbar>
    <android.support.v4.widget.DrawerLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/drawerLayout1" android:fitsSystemWindows="true">
        <android.support.design.widget.NavigationView android:minWidth="25px" android:minHeight="25px" android:layout_width="200dp" android:layout_height="match_parent" android:layout_gravity="end" app:menu="@menu/menu" android:id="@+id/navigationView1" android:fitsSystemWindows="true" />
    </android.support.v4.widget.DrawerLayout>
</merge>

در اکتیویتی‌های دیگر باید Toolbar و مدیریت گزینه‌های منو با کد‌های مشابه Main انجام شود.
        private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
        {
            Intent intent = null;
            switch (e.MenuItem.ItemId)
            {
                case Resource.Id.menuItemHome:
                    intent = new Intent(this, typeof(MainActivity));
                    break;
                case Resource.Id.menuItemExit:
                    Finish();
                    break;
                case Resource.Id.menuItemInsertProduct:
                    intent = new Intent(this, typeof(InsertActivity));
                    break;
                case Resource.Id.menuItemListProduct:
                    intent = new Intent(this, typeof(ListProductsActivity));
                    break;
            }
            if (intent != null) { }
        }
بنابراین دستورات xmlTollbar  و darawer layout در تمامی Layoutها و دستورات سی شارپ، کنترل کننده Toolbar و منو در تمامی اکتیویتی‌ها تکرار شده‌اند.
 
حل مشکلات Layout
یک فایل Xml را به Layout  اضافه می‌کنیم و درون آن Tag merge و کد‌های مشترک Drawer out و Toolbar را داخل تگ Merge اضافه می‌کنیم. جهت استفاده از کدهای (مقدار فایل ایکس ام ال ساخته شده که Tag merge داخل آن است)  Merge، در layout های دیگر، از دستور Include  استفاده می‌کنیم.
نام لی‌آوت را در خصوصیت Layout اضافه می‌کنیم. برای آنکه کد‌های سی شارپ کنترل کننده‌ی Toolbar و Menu چندین Toolbar وجود دارد که در یکی از آن‌ها یک کلاس واسط از کلاس app compat Activity  را به ارث میبریم. تابع Protected را از آن بازنویسی کرده و تمام کد‌های مدیریت Toolbar و منو را در آن پیاده سازی می‌کنیم. تمام اکتیویتی‌های برنامه را از این کلاس به ارث می‌بریم. بنابراین تابع InitieToolbar به تمامی فرزندان نیز به ارث برده می‌شود. در زمان اجرای دستورات، this ، اکتیویتی جاری می‌باشد.
    public class BaseAcitivity : AppCompatActivity
    {
        protected void InitieToolbar()
        {
            var toolbar = this.FindViewById<widgetV7.Toolbar>(Resource.Id.toolbar1);
            this.SetSupportActionBar(toolbar);
            //SupportActionBar.SetDisplayShowTitleEnabled(false);
            var imagebutton = toolbar.FindViewById<ImageButton>(Resource.Id.imageButton1);
            imagebutton.Click += Imagebutton_Click;
            var navigationview = this.FindViewById<NavigationView>(Resource.Id.navigationView1);
            navigationview.NavigationItemSelected += Navigationview_NavigationItemSelected;
        }

        private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
        {
            Intent intent = null;
            switch (e.MenuItem.ItemId)
            {
                case Resource.Id.menuItemHome:
                    intent = new Intent(this, typeof(MainActivity));
                    break;
                case Resource.Id.menuItemExit:
                    Finish();
                    break;
                case Resource.Id.menuItemInsertProduct:
                    intent = new Intent(this, typeof(InsertActivity));
                    break;
                case Resource.Id.menuItemListProduct:
                    intent = new Intent(this, typeof(ListProductsActivity));
                    break;
            }
            if (intent != null)
                StartActivity(intent);
        }

        private void Imagebutton_Click(object sender, EventArgs e)
        {
            var drawerlayout = this.FindViewById<DrawerLayout>(Resource.Id.drawerLayout1);
            if (drawerlayout.IsDrawerOpen(Android.Support.V4.View.GravityCompat.End) == false)
            {
                drawerlayout.OpenDrawer(Android.Support.V4.View.GravityCompat.End);
            }
            else
            {
                drawerlayout.CloseDrawer(Android.Support.V4.View.GravityCompat.End);
            }
        }
    }
 
اگر بخواهیم یک تم در تمامی اکتیویتی‌ها  به صورت سراسری استفاده شود، از فایل تنظمیات اندروید بنام AndroidManifest در دایرکتوری Properties استفاده می‌کنیم و در  بخش Application Theme، نام تم را مشخص میکنیم:
 android:theme="@style/Theme.AppCompat.Light.NoActionBar"
 

ساخت TabPage
پیشنیاز: نصب کتابخانه‌های متریال دیزاین همانند قبل و طبق ورژن Sdk نصب شده
اگر بخواهیم چندین صفحه را بر روی یکدیگر Stack و یا Overload نماییم، از Tabpage استفاده می‌کنیم. صفحاتی‌که از TabPage استفاده می‌کنند، با انگشت جابجا میشوند و همانند برنامه‌ی واتساپ Fragment می‌باشند و هر Fragment دارای layout و اکتیویتی مربوط به خود می‌باشد. معماری layout آن بصورت زیر است:


ToolBar، در بالای فرم قرار می‌گیرد. TabLayou که بصورت TabPage آن‌ها را به عهده دارد. Viewpager مدیریت Layout‌ها را به هنگام Swipe یا جابجایی به عهده دارد.
یک layout را برای Toolbar قرار می‌دهیم. سپس Layout اصلی main را طراحی میکنیم. پس از اضافه کردن ToolBar، ابزار TabLayout را در بخش SupportLibrary متریال دیزاین انتخاب و در صفحه می‌کشیم. TabLayout در پایین Toolbar قرار می‌گیرد و با انتخاب رنگ یکسان برای هر دو، متصل و یکنواخت به نظر می‌رسد. سپس از Layout از Toolbar آیتم ViewPager را بر روی صفحه قرار می‌دهیم. اگر LayoutWeight آن را یک قرار دهیم، تمام ارتفاع صفحه را به ما تخصیص می‌دهد. زمانیکه در TabLayout تب‌ها جابجا می‌شوند یا بر روی یک آیکن کلیک می‌شود، صفحه مربوطه در بخش ViewPager به کاربر نمایش داده میشود. هر Page یک فرگمنت می‌باشد. به ازای هر فرگمنت یک Layout به دایرکتوری layout اضافه کرده و به ازای هر layoutFragment یک Activity Fragment را اضافه می‌کنیم. یک اکتیویتی از نوع را Android.Support.v4.AppFragment ایجاد میکنیم.
    public class Fragment1 : Android.Support.V4.App.Fragment
    {
        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
        }
        public override View OnCreateView(LayoutInflater inflater,
      ViewGroup container, Bundle savedInstanceState)
        {
            return inflater.Inflate(Resource.Layout.FragmentLayout1, container, false);
        }
    }
ابتدا باید viewpager در Layout اصلی را پیدا کرده و با دستور زیر به Tablayout متصل کنیم:
 var tablayout = FindViewById<Android.Support.Design.Widget.TabLayout>(Resource.Id.tabLayout1);
var viewpager = FindViewById<ViewPager>(Resource.Id.viewPager1);
tablayout.SetupWithViewPager(viewpager);
زمانیکه آیکن را در TabLayout انتخاب میکنیم یا با انگشت Swipe میکنیم، به ترتیب بین صفحات که از Position صفحه آغاز شده‌اند، حرکت می‌کنیم، باید فرگمنت همان Position را نشان دهیم و این مدیریت توسط بخشی به‌نام Adapter انجام میشود. یک Adapter را به کنترلر اضافه میکنیم و از کلاس Fragment pager adapter به ارث می‌بریم. بر روی کلاس Fragment pager adapter ، دکمه‌های کنترل و نقطه را میزنیم و سپس کلاس را پیاده سازی می‌کنیم. در این حالت دو تابع را به ما می‌دهد: تابع Get item .count مجددا بر روی کلاس پدر راست کلیک میکنیم. در تابع کانت تعداد کل صفحه‌ها را (Layout ها) را انتخاب میکنیم. هرگاه از یک صفحه به صفحه‌ی دیگری انتقال پیدا کنیم، موقعیت صفحه جدیدی که از یک شروع میشود را به تابع Get Item بر اساس موقعیت Object  از fragment مربوطه new کرده و بعنوان خروجی باز می‌گرداند.
    class TabFragmentAdapter : FragmentPagerAdapter
    {
        public TabFragmentAdapter(FragmentManager fm) : base(fm)
        {
        }
        public override int Count => 3;
        public override Fragment GetItem(int position)
        {
            switch (position)
            {
                case 0: return new Fragment1();
                case 1: return new Fragment2();
                case 2: return new Fragment3();
                default: return new Fragment1();
            }
        }


        //int f1() { return 100; }
        //int f1 => 100;
    }
و در اکتیویتی اصلی، کد زیر را برای Load فرگمنت‌ها نیز قرار می‌دهیم:
 viewpager.Adapter = new TabFragmentAdapter(this.SupportFragmentManager);
  
آیکن برای TabPage
سپس اگر بخواهیم آیکن‌های Tab را به ترتیب تعریف کنیم، از تابع Gettabat استفاده میکنیم. پارامتر ورودی آن موقعیت Tab page میباشد و Set icon هم آیکن‌های دایرکتوری Drawable را انتخاب میکند.
 tablayout.GetTabAt(0).SetIcon(Resource.Drawable.iconCall);
 

نمایش متن همراه با عکس
 اگر بخواهیم آیکن‌های تب پیج را سفارشی کنیم، از Layout استفاده میکنیم که عرض و ارتفاع آن wrap Content  باشند و درون آن یک Text view که معادل Lable میباشند، قرار میدهند:
 View iconlayout1 = LayoutInflater.Inflate(Resource.Layout.custom_TabIconLayout, null);
var txt = iconlayout1.FindViewById<TextView>(Resource.Id.tabTextIcon);
txt.Text = "تماس";
txt.SetCompoundDrawablesWithIntrinsicBounds(Resource.Drawable.iconCall, 0, 0, 0);
tablayout.GetTabAt(0).SetCustomView(iconlayout1);

کدهای مطلب جاری برای دریافت: Navigation-TabPage-samples.zip
مطالب
مهاجرت داده عضویت و پروفایل از Universal Providers به ASP.NET Identity
در این مقاله مهاجرت داده‌های سیستم عضویت، نقش‌ها و پروفایل‌های کاربران که توسط Universal Providers ساخته شده اند به مدل ASP.NET Identity را بررسی می‌کنیم. رویکردی که در این مقاله استفاده شده و قدم‌های لازمی که توضیح داده شده اند، برای اپلیکیشنی که با SQL Membership کار می‌کند هم می‌توانند کارساز باشند.

با انتشار Visual Studio 2013، تیم ASP.NET سیستم جدیدی با نام ASP.NET Identity معرفی کردند. می‌توانید  در این لینک  بیشتر درباره این انتشار بخوانید. 

در ادامه مقاله قبلی تحت عنوان  مهاجرت از SQL Membership به ASP.NET Identity ، در این پست به مهاجرت داده‌های یک اپلیکیشن که از مدل Providers برای مدیریت اطلاعات کاربران، نقش‌ها و پروفایل‌ها استفاده می‌کند به مدل جدید ASP.NET Identity می‌پردازیم. تمرکز این مقاله اساسا روی مهاجرت داده‌های پروفایل کاربران خواهد بود، تا بتوان به سرعت از آنها در اپلیکیشن استفاده کرد. مهاجرت داده‌های عضویت و نقش ها، شبیه پروسه مهاجرت SQL Membership است. رویکردی که در ادامه برای مهاجرت داده پروفایل‌ها دنبال شده است، می‌تواند برای اپلیکیشنی با SQL Membership نیز استفاده شود.

بعنوان یک مثال، با اپلیکیشن وبی شروع می‌کنیم که توسط Visual Studio 2012 ساخته شده و از مدل Providers استفاده می‌کند. پس از آن یک سری کد برای مدیریت پروفایل ها، ثبت نام کاربران، افزودن اطلاعات پروفایل به کاربران و مهاجرت الگوی دیتابیس می‌نویسیم و نهایتا اپلیکیشن را بروز رسانی می‌کنیم تا برای استفاده از سیستم Identity برای مدیریت کاربران و نقش‌ها آماده باشد. و بعنوان یک تست، کاربرانی که قبلا توسط Universal Providers ساخته شده اند باید بتوانند به سایت وارد شوند، و کاربران جدید هم باید قادر به ثبت نام در سایت باشند.

سورس کد کامل این مثال را می‌توانید از  این لینک  دریافت کنید.



خلاصه مهاجرت داده پروفایل ها

قبل از آنکه با مهاجرت‌ها شروع کنیم، بگذارید تا نگاهی به تجربه مان از ذخیره اطلاعات پروفایل‌ها در مدل Providers بیاندازیم. اطلاعات پروفایل کاربران یک اپلیکیشن به طرق مختلفی می‌تواند ذخیره شود. یکی از رایج‌ترین این راه ها، استفاده از تامین کننده‌های پیش فرضی است که بهمراه Universal Providers منتشر شدند. بدین منظور انجام مراحل زیر لازم است
  1. کلاس جدیدی بسازید که دارای خواصی برای ذخیره اطلاعات پروفایل است.
  2. کلاس جدیدی بسازید که از 'ProfileBase' ارث بری می‌کند و متدهای لازم برای دریافت پروفایل کاربران را پیاده سازی می‌کند.
  3. استفاده از تامین کننده‌های پیش فرض را، در فایل web.config فعال کنید. و کلاسی که در مرحله 2 ساختید را بعنوان کلاس پیش فرض برای خواندن اطلاعات پروفایل معرفی کنید.


اطلاعات پروفایل‌ها بصورت serialized xml و binary در جدول 'Profiles' ذخیره می‌شوند.


پس از آنکه به سیستم ASP.NET Identity مهاجرت کردیم، اطلاعات پروفایل  deserialized شده و در قالب خواص کلاس User ذخیره می‌شوند. هر خاصیت، بعدا می‌تواند به  یک ستون در دیتابیس متصل شود. مزیت بدست آمده این است که مستقیما از کلاس User به اطلاعات پروفایل دسترسی داریم. ناگفته نماند که دیگر داده‌ها serialize/deserialize هم نمی‌شوند.


شروع به کار

در Visual Studio 2012 پروژه جدیدی از نوع ASP.NET 4.5 Web Forms application بسازید. مثال جاری از یک قالب Web Forms استفاده می‌کند، اما می‌توانید از یک قالب MVC هم استفاده کنید.

پوشه جدیدی با نام 'Models' بسازید تا اطلاعات پروفایل را در آن قرار دهیم.

بعنوان یک مثال، بگذارید تا تاریخ تولد کاربر، شهر سکونت، قد و وزن او را در پروفایلش ذخیره کنیم. قد و وزن بصورت یک کلاس سفارشی (custom class) بنام 'PersonalStats' ذخیره می‌شوند. برای ذخیره و بازیابی پروفایل ها، به کلاسی احتیاج داریم که 'ProfileBase' را ارث بری می‌کند. پس کلاس جدیدی با نام 'AppProfile' بسازید.

public class ProfileInfo
{
    public ProfileInfo()
    {
        UserStats = new PersonalStats();
    }
    public DateTime? DateOfBirth { get; set; }
    public PersonalStats UserStats { get; set; }
    public string City { get; set; }
}

public class PersonalStats
{
    public int? Weight { get; set; }
    public int? Height { get; set; }
}

public class AppProfile : ProfileBase
{
    public ProfileInfo ProfileInfo
    {
        get { return (ProfileInfo)GetPropertyValue("ProfileInfo"); }
    }
    public static AppProfile GetProfile()
    {
        return (AppProfile)HttpContext.Current.Profile;
    }
    public static AppProfile GetProfile(string userName)
    {
        return (AppProfile)Create(userName);
    }
}

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

<profile defaultProvider="DefaultProfileProvider" enabled="true"
    inherits="UniversalProviders_ProfileMigrations.Models.AppProfile">
  <providers>
    .....
  </providers>
</profile>

برای دریافت اطلاعات پروفایل از کاربر، فرم وب جدیدی در پوشه Account بسازید و آنرا 'AddProfileData.aspx' نامگذاری کنید.

<h2> Add Profile Data for <%# User.Identity.Name %></h2>
<asp:Label Text="" ID="Result" runat="server" />
<div>
    Date of Birth:
    <asp:TextBox runat="server" ID="DateOfBirth"/>
</div>
<div>
    Weight:
    <asp:TextBox runat="server" ID="Weight"/>
</div>
<div>
    Height:
    <asp:TextBox runat="server" ID="Height"/>
</div>
<div>
    City:
    <asp:TextBox runat="server" ID="City"/>
</div>
<div>
    <asp:Button Text="Add Profile" ID="Add" OnClick="Add_Click" runat="server" />
</div>

کد زیر را هم به فایل code-behind اضافه کنید.

protected void Add_Click(object sender, EventArgs e)
{
    AppProfile profile = AppProfile.GetProfile(User.Identity.Name);
    profile.ProfileInfo.DateOfBirth = DateTime.Parse(DateOfBirth.Text);
    profile.ProfileInfo.UserStats.Weight = Int32.Parse(Weight.Text);
    profile.ProfileInfo.UserStats.Height = Int32.Parse(Height.Text);
    profile.ProfileInfo.City = City.Text;
    profile.Save();
}

دقت کنید که فضای نامی که کلاس AppProfile در آن قرار دارد را وارد کرده باشید.

اپلیکیشن را اجرا کنید و کاربر جدیدی با نام 'olduser' بسازید. به صفحه جدید 'AddProfileData' بروید و اطلاعات پروفایل کاربر را وارد کنید.

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

مهاجرت الگوی دیتابیس

برای اینکه دیتابیس فعلی بتواند با سیستم ASP.NET Identity کار کند، باید الگوی ASP.NET Identity را بروز رسانی کنیم تا فیلدهای جدیدی که اضافه کردیم را هم در نظر بگیرد. این کار می‌تواند توسط اسکریپت‌های SQL انجام شود، باید جداول جدیدی بسازیم و اطلاعات موجود را به آنها انتقال دهیم. در پنجره 'Server Explorer' گره 'DefaultConnection' را باز کنید تا جداول لیست شوند. روی Tables کلیک راست کنید و 'New Query' را انتخاب کنید.

اسکریپت مورد نیاز را از آدرس https://raw.github.com/suhasj/UniversalProviders-Identity-Migrations/master/Migration.txt دریافت کرده و آن را اجرا کنید. اگر اتصال خود به دیتابیس را تازه کنید خواهید دید که جداول جدیدی اضافه شده اند. می‌توانید داده‌های این جداول را بررسی کنید تا ببینید چگونه اطلاعات منتقل شده اند.

مهاجرت اپلیکیشن برای استفاده از ASP.NET Identity

پکیج‌های مورد نیاز برای ASP.NET Identity را نصب کنید:
  • Microsoft.AspNet.Identity.EntityFramework
  • Microsoft.AspNet.Identity.Owin
  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security.Facebook
  • Microsoft.Owin.Security.Google
  • Microsoft.Owin.Security.MicrosoftAccount
  • Microsoft.Owin.Security.Twitter
اطلاعات بیشتری درباره مدیریت پکیج‌های NuGet از اینجا قابل دسترسی هستند.

برای اینکه بتوانیم از الگوی جاری دیتابیس استفاده کنیم، ابتدا باید مدل‌های لازم ASP.NET Identity را تعریف کنیم تا موجودیت‌های دیتابیس را Map کنیم. طبق قرارداد سیستم Identity کلاس‌های مدل یا باید اینترفیس‌های تعریف شده در Identity.Core dll را پیاده سازی کنند، یا می‌توانند پیاده سازی‌های پیش فرضی را که در Microsoft.AspNet.Identity.EntityFramework وجود دارند گسترش دهند. ما برای نقش ها، اطلاعات ورود کاربران و claim‌ها از پیاده سازی‌های پیش فرض استفاده خواهیم کرد. نیاز به استفاده از یک کلاس سفارشی User داریم. پوشه جدیدی در پروژه با نام 'IdentityModels' بسازید. کلاسی با نام 'User' در این پوشه بسازید و کد آن را با لیست زیر تطابق دهید.
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using UniversalProviders_ProfileMigrations.Models;

namespace UniversalProviders_Identity_Migrations
{
    public class User : IdentityUser
    {
        public User()
        {
            CreateDate = DateTime.UtcNow;
            IsApproved = false;
            LastLoginDate = DateTime.UtcNow;
            LastActivityDate = DateTime.UtcNow;
            LastPasswordChangedDate = DateTime.UtcNow;
            Profile = new ProfileInfo();
        }

        public System.Guid ApplicationId { get; set; }
        public bool IsAnonymous { get; set; }
        public System.DateTime? LastActivityDate { get; set; }
        public string Email { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public bool IsApproved { get; set; }
        public bool IsLockedOut { get; set; }
        public System.DateTime? CreateDate { get; set; }
        public System.DateTime? LastLoginDate { get; set; }
        public System.DateTime? LastPasswordChangedDate { get; set; }
        public System.DateTime? LastLockoutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public System.DateTime? FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public System.DateTime? FailedPasswordAnswerAttemptWindowStart { get; set; }
        public string Comment { get; set; }
        public ProfileInfo Profile { get; set; }
    }
}
دقت کنید که 'ProfileInfo' حالا بعنوان یک خاصیت روی کلاس User تعریف شده است. بنابراین می‌توانیم مستقیما از کلاس کاربر با اطلاعات پروفایل کار کنیم.

محتویات پوشه‌های IdentityModels  و IdentityAccount  را از آدرس  https://github.com/suhasj/UniversalProviders-Identity-Migrations/tree/master/UniversalProviders-Identity-Migrations  دریافت و کپی کنید. این فایل‌ها مابقی مدل ها، و صفحاتی برای مدیریت کاربران و نقش‌ها در سیستم جدید ASP.NET Identity هستند.


انتقال داده پروفایل‌ها به جداول جدید

همانطور که گفته شد ابتدا باید داده‌های پروفایل را deserialize کرده و از فرمت xml خارج کنیم، سپس آنها را در ستون‌های جدول AspNetUsers ذخیره کنیم. ستون‌های جدید در مرحله قبل به دیتابیس اضافه شدند، پس تنها کاری که باقی مانده پر کردن این ستون‌ها با داده‌های ضروری است. بدین منظور ما از یک اپلیکیشن کنسول استفاده می‌کنیم که تنها یک بار اجرا خواهد شد، و ستون‌های جدید را با داده‌های لازم پر می‌کند.

در solution جاری یک پروژه اپلیکیشن کنسول بسازید.

آخرین نسخه پکیج Entity Framework را نصب کنید. همچنین یک رفرنس به اپلیکیشن وب پروژه بدهید (کلیک راست روی پروژه و گزینه 'Add Reference').

کد زیر را در کلاس Program.cs وارد کنید. این قطعه کد پروفایل تک تک کاربران را می‌خواند و در قالب 'ProfileInfo' آنها را serialize می‌کند و در دیتابیس ذخیره می‌کند.

public class Program
{
    var dbContext = new ApplicationDbContext();
    foreach (var profile in dbContext.Profiles)
    {
        var stringId = profile.UserId.ToString();
        var user = dbContext.Users.Where(x => x.Id == stringId).FirstOrDefault();
        Console.WriteLine("Adding Profile for user:" + user.UserName);
        var serializer = new XmlSerializer(typeof(ProfileInfo));
        var stringReader = new StringReader(profile.PropertyValueStrings);
        var profileData = serializer.Deserialize(stringReader) as ProfileInfo;
        if (profileData == null)
        {
            Console.WriteLine("Profile data deserialization error for user:" + user.UserName);
        }
        else
        {
            user.Profile = profileData;
        }
    }
    dbContext.SaveChanges();
}

برخی از مدل‌های استفاده شده در پوشه 'IdentityModels' تعریف شده اند که در پروژه اپلیکیشن وبمان قرار دارند، بنابراین افزودن فضاهای نام مورد نیاز فراموش نشود.


کد بالا روی دیتابیسی که در پوشه App_Data وجود دارد کار می‌کند، این دیتابیس در مراحل قبلی در اپلیکیشن وب پروژه ایجاد شد. برای اینکه این دیتابیس را رفرنس کنیم باید رشته اتصال فایل app.config اپلیکیشن کنسول را بروز رسانی کنید. از همان رشته اتصال web.config در اپلیکیشن وب پروژه استفاده کنید. همچنین آدرس فیزیکی کامل را در خاصیت 'AttachDbFilename' وارد کنید.


یک Command Prompt باز کنید و به پوشه bin اپلیکیشن کنسول بالا بروید. فایل اجرایی را اجرا کنید و نتیجه را مانند تصویر زیر بررسی کنید.

در پنجره Server Explorer جدول 'AspNetUsers' را باز کنید. حال ستون‌های این جدول باید خواص کلاس مدل را منعکس کنند.


کارایی سیستم را تایید کنید

با استفاده از صفحات جدیدی که برای کار با ASP.NET Identity پیاده سازی شده اند سیستم را تست کنید. با کاربران قدیمی که در دیتابیس قبلی وجود دارند وارد شوید. کاربران باید با همان اطلاعات پیشین بتوانند وارد سیستم شوند. مابقی قابلیت‌ها را هم بررسی کنید. مثلا افزودن OAuth، ثبت کاربر جدید، تغییر کلمه عبور، افزودن نقش ها، تخصیص کاربران به نقش‌ها و غیره.

داده‌های پروفایل کاربران قدیمی و جدید همگی باید در جدول کاربران ذخیره شده و بازیابی شوند. جدول قبلی دیگر نباید رفرنس شود.
مطالب
مسیریابی در AngularJs #بخش سوم
در بخش‌های پیشین ( بخش اول و بخش دوم) به خوبی با اصول و روش مسیریابی (Routing) در AngularJS آشناشدیم. در این بخش می‌خواهم به برخی جزئیات درباره مسیریابی بپردازم.
اولین موضوع، تغییراتی است که از نسخه 1.2 به بعد در روش استفاده از سرویس مسیریابی در AngularJS بوجود آمده است. از نسخه 1.2 سرویس مسیریابی از هسته اصلی AngularJS خارج شد و برای استفاده از امکانات این سرویس باید  فایل angular-route.js و یا angular-route.min.js را به صفحه خود بیفزاییم:  
<script src="~/Scripts/angular.min.js"></script>
<script src="~/Scripts/angular-route.min.js"></script>
سپس باید هنگام تعریف ماژول، ngRoute را به عنوان وابستگی تزریق کنیم: 
var app = angular.module("mainApp", ['ngRoute']);
روش Controller as در AngularJS که از نسخه 1.2 به بعد امکان استفاده از آن وجود دارد قبلا معرفی شده است. با پاس کردن خصوصیت controllerAs به متد when می‌توان از viewای استفاده کرد که در آن از این روش استفاده شده است.
.when('/controllerAS', {
    controller:      'testController',
    controllerAs:    'tCtrl',
    template:        '<div>{{tCtrl.Title}}</div>'
})
 باقی ماجرا مانند گذشته است. 
موضوع دیگری که پرداختن به آن می‌تواند مفید باشد، بررسی بیشتر متد when است. وقتی در متد config ماژول از routeProvider$ استفاده می‌کنیم، داریم سرویس route$ را تنظیم، مقداردهی اولیه، و نمونه گیری می‌کنیم. درواقع با استفاده از متدهای when و otherwise داریم سرویس route$ را مقداردهی اولیه می‌کنیم (برای آشنایی با تقاوت factory، service و provider کلیک کنید ). خوب! جریان این مقادیری که به عنوان پارامتر به این متدها پاس می‌کنیم چیست؟
متد when به این صورت تعریف شده است:
when(string path, object route)

 پارامتر path در بخش‌های قبل به اندازه کافی معرفی شده است. پارامتر route یک شی است شامل اطلاعاتی که با تطبیق آدرس صفحه با پارامتر path، به  route.current$  مقداردهی می‌شود (حالا باید متوجه شده باشید که روال افزودن داده‌های سفارشی به سیستم مسیریابی و دسترسی به آن‌ها که در بخش دوم مطرح شد به چه شکل کار می‌کند). این شی می‌تواند خصوصیات از قبل تعریف شده‌ای داشده باشد که در ادامه آن‌ها را مرور می‌کنیم:
controller: می‌تواند یک رشته شامل نام کنترلر از قبل تعریف شده، یا یک تابع به عنوان تابع کنترلر باشد.
controllerAs: رشته‌ای شامل نام مستعار کنترلر.
template: رشته‌ای شامل قالب html، و یا تابعی که قالب html را بازمی‌گرداند. این خصوصیت بر templateUrl اولویت دارد. اگر مقدار این خصوصیت یک تابع باشد، routeParams$ به عنوان پارامتر ورودی به آن پاس می‌شود.
templateUrl: رشته‌ای شامل مسیر فایل قالب html، و یا تابعی که این رشته را بازمی‌گرداند. اگر مقدار این خصوصیت یک تابع باشد، routeParams$ به عنوان پارامتر ورودی به آن پاس می‌شود.
redirectTo: مقداری برای به روز رسانی  location$، و فراخوانی روال مسیر یابی. این مقدار می‌تواند یک رشته، و یا تابعی که یک رشته را بازمی‌گرداند باشد. اگر مقدار این خصوصیت یک تابع باشد، این پارامترها به آن پاس می‌شود:
  •  routeParams$ برای دسترسی به پارامترهای آمده در آدرس صفحه جاری.
  • ()location.path$ جاری به صورت یک رشته.
  • ()location.search$ جاری به صورت یک شی.
caseInsensitiveMatch: یک مقدار منطقی است که مشخص می‌کند بزرگ و کوچک بودن حروف در تطبیق آدرس صفحه با پارامتر route در نظر گرفته بشود یا نه. مقدار پیشفرض این خصوصیت false است. یعنی در همه مثالهایی که تا کنون زده شده، اگر بزرگ و کوچه بودن حروف آدرس صفحه با مقدار مشخص شده برای پارامتر route متفاوت باشد، روال مسیریابی انجام نخواهد شد. برای رفع این مشکل کافی است مقدار این خصوصیت را true قرار دهیم. برای مثال، مسیر 'controllerAS/' که بالاتر تعریف کرده‌ایم را درنظر بگیرید. اگر www.mySite.com/#/ControllerAS را وارد کنیم، هیچ اتفاقی نخواهد افتاد و در واقع این آدرس با route مشخص شده تطبیق پیدا نمی‌کند. اگر بخواهیم کوچک و بزرگ بودن حروف در نظر گرفته نشود، کافیست به این ترتیب عمل کنیم:
.when('/controllerAS', {
    controller:      'testController',
    controllerAs:    'tCtrl',
    template:        '<div>{{tCtrl.Title}}</div>',
    caseInsensitiveMatch: true
})
resolve: نگاشتی از وابستگی‌هایی که می‌خواهیم به کنترلر تزریق شود. قبلا مفهوم promise توضیح داده شده است . اگر هر یک از این وابستگی‌ها یک promise باشد، مسیریاب تا resolve شدن همه آن‌ها یا reject شدن یکی از آن‌ها منتظر می‌ماند. در صورتی که همه promiseها resolve شوند، رخداد routeChangeSuccess$، و در صورتی که یکی از آن‌ها reject شود رخداد routeChangeError$ اجرا می‌شود. یکی از کاربردهای resolve زمانیست که بخواهید جلوی تغییر محتویات صفحه، پیش از بارگذاری داده‌ای که از سمت سرور درخواست کرده‌اید را بگیرید. 
$routeProvider
    .when('/resolveTest',
        {
            resolve: {
                // این وابستگی بلافاصله بازمی‌گردد
                person: function () {
                    return {
                        name: "Hamid Saberi",
                        email: "Hamid.Saberi@Gmail.com"
                    }
                },
                // بازمی‌گرداند promise این وابستگی یک
                // شدن آن به تاخیر می‌افتد resolve پس تغییر مسیر تا 
                currentDetails: function ($http) {
                    return $http({
                        method: 'Get',
                        url: '/current_details'
                    });
                },
                //می‌توانیم از یک وابستگی در وابستگی دیگر استفاده کنیم
                facebookId: function ($http, currentDetails) {
                    $http({
                        method: 'GET',
                        url: 'http://facebook.com/api/current_user',
                        params: {
                            email: currentDetails.data.emails[0]
                        }
                    })
                },
                // بارگذاری فایل‌های اسکریپت مورد نیاز
                fileDeps:function($q, $rootScope){
                    var deferred = $q.defer();
                    var dependencies =
                    [
                        'controllers/AboutViewController.js',
                        'directives/some-directive.js'
                    ];
 
                    //$Script.js بارگذاری وابستگی‌ها با استفاده از 
                    $script(dependencies, function(){
                        // همه وابستگی‌ها بارگذاری شده اند
                        $rootScope.$apply(function(){
                            deferred.resolve();
                        });
                    });
 
                    return deferred.promise;
                }
            },
            controller: function ($scope, person, currentDetails, facebookId) {
                this.Person = person;
            },
            controllerAs: 'rtCtrl',
            template: '<div>{{rtCtrl.Person.name}}</div>',
            caseInsensitiveMatch: true
        })