مطالب
شروع کار با ASP.NET Web API 2
HTTP تنها برای به خدمت گرفتن صفحات وب نیست. این پروتکل همچنین پلتفرمی قدرتمند برای ساختن API هایی است که سرویس‌ها و داده را در دسترس قرار می‌دهند. این پروتکل ساده، انعطاف پذیر و در همه جا حاضر است. هر پلتفرمی که فکرش را بتوانید بکنید کتابخانه ای برای HTTP دارد، بنابراین سرویس‌های HTTP می‌توانند بازه بسیار گسترده ای از کلاینت‌ها را پوشش دهند، مانند مرورگرها، دستگاه‌های موبایل و اپلیکیشن‌های مرسوم دسکتاپ.

ASP.NET Web API فریم ورکی برای ساختن API‌های وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر می‌گرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.


یک پروژه Web API بسازید

در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.

در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.

می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.

افزودن یک مدل

یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی می‌کند. ASP.NET Web API می‌تواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمت‌های دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی داده‌ها را بخواند، می‌تواند آبجکت شما را deserialize کند. اکثر کلاینت‌ها می‌توانند XML یا JSON را تفسیر کنند. بعلاوه کلاینت‌ها می‌توانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.

بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.

کلاس جدیدی در پوشه Models ایجاد کنید.

نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.

namespace ProductsApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

افزودن یک کنترلر

در Web API کنترلر‌ها آبجکت هایی هستند که درخواست‌های HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت می‌کنند. ما کنترلری خواهیم ساخت که می‌تواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلر‌های MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق می‌شوند.

کنترلر جدیدی در پوشه Controllers ایجاد کنید.

در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.

در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.

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

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;

namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }
}
برای اینکه مثال جاری را ساده نگاه داریم، محصولات مورد نظر در یک آرایه استاتیک ذخیره شده اند. مسلما در یک اپلیکیشن واقعی برای گرفتن لیست محصولات از دیتابیس یا منبع داده ای دیگر کوئری می‌گیرید.

کنترلر ما دو متد برای دریافت محصولات تعریف می‌کند:

  • متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر می‌گرداند.
  • متد GetProductById سعی می‌کند محصولی را بر اساس شناسه تعیین شده پیدا کند.

همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ می‌دهند:

 URI  Controller Method
api/products/  GetAllProducts
 api/products/id/  GetProductById

برای اطلاعات بیشتر درباره نحوه نگاشت درخواست‌های HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.


فراخوانی Web API با جاوا اسکریپت و jQuery

در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی می‌کند. برای ارسال درخواست‌های آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.

در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.

در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.

حال محتوای این فایل را با لیست زیر جایگزین کنید.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Product App</title>
</head>
<body>

  <div>
    <h2>All Products</h2>
    <ul id="products" />
  </div>
  <div>
    <h2>Search by ID</h2>
    <input type="text" id="prodId" size="5" />
    <input type="button" value="Search" onclick="find();" />
    <p id="product" />
  </div>

  <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
  <script>
    var uri = 'api/products';

    $(document).ready(function () {
      // Send an AJAX request
      $.getJSON(uri)
          .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
              // Add a list item for the product.
              $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
          });
    });

    function formatItem(item) {
      return item.Name + ': $' + item.Price;
    }

    function find() {
      var id = $('#prodId').val();
      $.getJSON(uri + '/' + id)
          .done(function (data) {
            $('#product').text(formatItem(data));
          })
          .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
          });
    }
  </script>
</body>
</html>
راه‌های مختلفی برای گرفتن jQuery وجود دارد، در این مثال از Microsoft Ajax CDN استفاده شده. می‌توانید این کتابخانه را از http://jquery.com دانلود کنید و بصورت محلی استفاده کنید. همچنین قالب پروژه‌های  Web API این کتابخانه را به پروژه نیز اضافه می‌کنند.


گرفتن لیستی از محصولات

برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.

تابع getJSON یک درخواست آژاکسی ارسال می‌کند. پاسخ دریافتی هم آرایه ای از آبجکت‌های JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا می‌شود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی می‌کنیم.

$(document).ready(function () {
    // Send an AJAX request
    $.getJSON(apiUrl)
        .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
                // Add a list item for the product.
                $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
        });
});

گرفتن محصولی مشخص

برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.

function find() {
    var id = $('#prodId').val();
    $.getJSON(apiUrl + '/' + id)
        .done(function (data) {
            $('#product').text(formatItem(data));
        })
        .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
        });
}
برای این کار هنوز از getJSON برای ارسال درخواست آژاکسی استفاده می‌کنیم، اما اینبار شناسه محصول را هم به آدرس درخواستی اضافه کرده ایم. پاسخ دریافتی از این درخواست، اطلاعات یک محصول با فرمت JSON است.


اجرای اپلیکیشن

اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.

برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.

اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر می‌گرداند.


استفاده از F12 برای مشاهده درخواست‌ها و پاسخ ها

هنگام کار با سرویس‌های HTTP، مشاهده‌ی درخواست‌های ارسال شده و پاسخ‌های دریافتی بسیار مفید است. برای اینکار می‌توانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر می‌توانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر می‌کند. می‌توانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.

به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، می‌توانید header‌ها و بدنه درخواست‌ها و پاسخ‌ها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header داده‌ها را با فرمت "application/json" درخواست کرده است.

اگر روی برگه Response body کلیک کنید، می‌توانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیت‌های مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار می‌توانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواست‌های جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما می‌دهد.


قدم‌های بعدی

  • برای یک مثال کامل از سرویس‌های HTTP که از عملیات POST,PUT و DELETE پشتیبانی می‌کند به این لینک مراجعه کنید.
  • برای اطلاعات بیشتر درباره طراحی واکنش گرا در کنار سرویس‌های HTTP به این لینک مراجعه کنید، که اپلیکیشن‌های تک صفحه ای (SPA) را بررسی می‌کند.
مطالب
نوشتن Middleware سفارشی در ASP.NET Core
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 3 - Middleware چیست؟» با اصول مقدماتی Middlewareها آشنا شدیم. همچنین در مطلب «آشنایی با OWIN و بررسی نقش آن در ASP.NET Core» یک مثال سفارشی از آن‌ها، بررسی شد. در اینجا می‌خواهیم نکات بیشتری را در مورد تهیه‌ی Middlewareهای سفارشی بررسی کنیم.


تفاوت بین متدهای app.Use  و  app.Run در چیست؟

Middlewareها به همان ترتیبی که در متد Configure کلاس آغازین برنامه معرفی می‌شوند، اجرا خواهند شد؛ اما نکته‌ی مهم اینجا است که middleware ایی که توسط متد app.Use تعریف می‌شود، می‌تواند middleware بعدی ثبت شده‌را، فراخوانی کند؛ اما app.Run خیر. برای درک بهتر این مفهوم، به مثال زیر دقت کنید:
using Microsoft.AspNetCore.Http;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, before next()</div>");
 
            await next();
 
            await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, after next()</div>");
        });
 
        app.Run(async context =>
        {
            await context.Response.WriteAsync("<div>Inside middleware-2 defined using app.Run</div>");
        });
 
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, before next()</div>");
 
            await next();
 
            await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, after next()</div>");
        });
اگر در این حالت برنامه را اجرا کنیم، چنین خروجی را مشاهده خواهیم کرد:


همانطور که در تصویر نیز مشخص است، ابتدا کدهای پیش از فراخوانی دلیگیت next میان‌افزار اول اجرا شده‌است. سپس باتوجه به فراخوانی دلیگیت next، کدهای دومین میان‌افزار ثبت شده، فراخوانی گردیده‌است و سپس کدهای پس از فراخوانی دلیگیت next میان‌افزار اول، اجرا شده‌اند.
این دلیگیت در اصل یک چنین امضایی را دارد:
 public delegate Task RequestDelegate(HttpContext context);
در اینجا چون میان‌افزار دوم از نوع app.Run است و قابلیت فراخوانی دلیگیت next را ندارد، از نوع terminal یا خاتمه دهنده به‌شمار آمده و دیگر میان افزار بعدی ثبت شده، یعنی میان‌افزار سوم، اجرا نخواهد شد و کار پردازش برنامه در همین مرحله به پایان می‌رسد.

باید دقت داشت که فراخوانی دلیگیت next در میان‌افزارهای از نوع app.Use الزامی نبوده و اگر اینکار انجام نشود، بین app.Run و app.Use تفاوتی نخواهد بود و هر دو terminal به حساب می‌آیند.


تفاوت بین متدهایapp.Map  و  app.MapWhen در چیست؟

متد app.Map در صورت برآورده شدن شرطی، سبب اجرای میان‌افزاری مشخص می‌شود (امکان اجرای غیر خطی میان‌افزارها).
فرض کنید قطعه کد زیر را پس از اولین app.Use مثال فوق قرار داده‌ایم:
app.Map("/dnt", appBuilder =>
{
    appBuilder.Run(async context =>
    {
        await context.Response.WriteAsync(@"<div>Inside Map(/dnt) --> Run</div>");
    });
});
در این حالت اگر برنامه را اجرا کنیم، خروجی جدیدی را مشاهده نخواهیم کرد و خروجی حاصل دقیقا مانند تصویر مثال فوق است. اما اگر آدرس ویژه‌ی dnt/ درخواست شود (الگوی تطابق اولین پارامتر متد Map)، آنگاه میان افزار ثبت شده‌ی app.Run ویژه‌ی این حالت خاص، اجرا می‌شود:


در اینجا چون app.Run داخلی فراخوانی شده، از نوع terminal است، دیگر میان افزارهای پس از آن اجرا نشده‌اند. بدیهی است در اینجا نیز می‌توان به هر تعدادی که نیاز است میان افزارهای جدیدی را به appBuilder متد app.Map اضافه کرد.

پارامتر اول متد Map برای تطابق با الگوهایی خاص و مشخص، مناسب است. اما در اگر در اینجا نیاز به اطلاعات بیشتری از HttpContext جاری داشته باشیم، می‌توانیم از متد app.MapWhen استفاده کنیم که اولین پارامتر آن یک دلیگیت است که HttpContext را در اختیار استفاده کننده قرار می‌دهد و اگر در نهایت true را دریافت کند، سبب اجرای میان افزارهای قسمت appBuilder آن خواهد شد:
app.MapWhen(context =>
{
    return context.Request.Query.ContainsKey("dnt");
},
appBuilder =>
{
    appBuilder.Run(async context =>
    {
        await context.Response.WriteAsync(@"<div>Inside MapWhen(?dnt) --> Run</div>");
    });
});
در این مثال، شرط ارائه شده‌ی در پارامتر اول، اندکی پیچید‌ه‌تر است از حالت app.Map. در اینجا مشخص شده‌است که اگر آدرس دریافتی از کاربر، دارای کوئری استرینگی به نام dnt بود، آنگاه میان افزار(های) ارائه شده‌ی در قسمت appBuilder، اجرا شود. برای نمونه درخواست آدرس ذیل، سبب فراخوانی appBuilder.Run ذکر شده می‌شود:
 http://localhost:7742/?dnt=true



نظم بخشیدن به تعاریف میان‌افزارها

متدهای app.Run و app.Use و امثال آن‌ها برای تعریف سریع میان افزارها مناسب هستند. اما اگر بخواهیم کدهای کلاس آغازین برنامه را اندکی خلوت کرده و به تعاریف میان‌افزارها نظم ببخشیم، می‌توان کدهای آن‌ها را به کلاس‌هایی با امضایی خاص منتقل کرد:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public class MyMiddleware1
    {
        private readonly RequestDelegate _next;
 
        public MyMiddleware1(RequestDelegate next)
        {
            _next = next;
        }
 
        public async Task Invoke(HttpContext context)
        {
                    context.Response.ContentType = "text/html";
                    context.Response.StatusCode = 200;
            await context.Response.WriteAsync("<div>Hello from MyMiddleware1.</div>");
            await _next.Invoke(context);
            await context.Response.WriteAsync("<div>End of action.</div>");
        }
    } 
}
در اینجا نحوه‌ی تعریف یک کلاس میان‌افزار سفارشی را مشاهده می‌کنید. دو پارامتر context و next ایی را که در متد app.Use مشاهده کردید، دراینجا به نحو واضح‌تری مشخص شده‌اند. دلیگیت next اشاره کننده‌ی به اجرای میان افزار بعدی، در سازنده‌ی کلاس این میان افزار تزریق شده‌است. همچنین در اینجا می‌توان سرویس‌هایی را که به IoC Container توکار ASP.NET Core معرفی کرده‌ایم نیز تزریق کنیم و از این لحاظ محدودیتی ندارد (و همچنین امضای آن می‌تواند کاملا متغیر باشد). قسمت اصلی اجرایی این میان افزار، متد Invoke آن است که اطلاعات HttpContext جاری را در اختیار مصرف کننده قرار می‌دهد.
در اینجا نیز اگر دلیگیت next_ فراخوانی نشود، این میان‌افزار سبب خاتمه‌ی اجرای پردازش درخواست جاری می‌گردد.

 مرحله‌ی بعد، روش معرفی این میان‌افزار تعریف شده، به لیست میان‌افزارهای موجود است. برای این منظور می‌توان متد app.UseMiddleware را به صورت مستقیم در کلاس آغازین برنامه فراخوانی کرد و یا مرسوم است اگر کتابخانه‌ای را طراحی کرده‌اید، به نحو ذیل متد الحاقی خاصی را برای آن تدارک دید:
using Microsoft.AspNetCore.Builder;

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
    {
        app.UseMiddleware<MyMiddleware1>();
        return app;
    }
}
سپس مصرف کننده تنها باید این متد را در کلاس آغازین برنامه فراخوانی کند:
public void Configure(IApplicationBuilder app)
{
    app.UseMyMiddleware();
مطالب
کنترل شرایط تاثیرگذار بر روی یک نقش در ASP.NET MVC
در سایت جاری، مباحث زیادی در مورد دسترسی یک نقش به اکشن متدها مطرح شده است. در این مقالات یاد گرفته‌ایم اگر اکشن متدی به ویژگی Authorization مزین گردد، دسترسی این اکشن متد تنها به کاربران لاگین شده خلاصه شده و اگر پارامتر Roles را با نام نقش‌ها مقداردهی کنیم، تنها کاربرانی که آن نقش را دارند، به این اکشن متد دسترسی خواهند داشت. ولی گاهی اوقات شرایطی ایجاد میشود که مشخص نیست این نقش در حال حاضر باید دسترسی به اکشن متد مورد نظر را داشته باشد یا خیر؛ این مثال را در نظر بگیرید:
در یک سیستم حسابداری یا هر سیستم مشابهی، موقعی که یک سند در سیستم درج میشود، نقش‌های زیادی میتوانند درگیر این عمل باشند:
Create می تواند یک سند حسابداری را ایجاد کند.
Edit میتواند یک سند حسابداری را ویرایش کند.

کاربری که نقش edit را دارد توانایی ویرایش اسناد درج شده در سیستم را دارد ولی اسناد حسابداری تنها در همان روز ثبت، امکان ویرایش دارند و پس از پایان ساعاتی کاری یا از روز بعد دیگر قابل ویرایش نبوده و نباید کسی توانایی ویرایش آن را داشته باشد. به همین دلیل، در این نقطه زمانی، دیگر کاربر Edit نباید بتواند این اکشن متد را صدا بزند. به همین علت باید دسترسی او را بر روی سندهای ماقبل امروز بست.

با نگاهی بر روی بسیاری از کدهای نرم افزارهای مختلف که نوشته شده‌اند میتواند دریافت بسیاری از افراد در اکشن مربوطه یا هر دو اکشن Get و Post یک شرط قرار داده و با بررسی اینکه سند متعلق به امروز نیست پیامی را به کاربر نمایش میدهند؛ ولی این نکته باعث میشود که اکشن متد مربوطه تا حدی اولین اصل از اصول Solid یعنی اصل تک مسئولیتی را زیر سوال ببرد. ما در اینجا قصد داریم این موضوع بررسی را به خارج از اکشن متد مربوطه برده و قبل از رسیدن به اکشن متد مورد نظر جلوی آن را بگیریم.

با گردآوری و جمع بندی مطالب سایت تاکنون می‌توان تکه کد زیر را نوشت: 
در سایت جاری مباحث فیلترها (^ و ^) به طور مکرر مورد بررسی قرار گرفته‌است. طبق منابع ذکر شده یکی از این اکشن متدها ActionFilter بوده که دو متد زیر را در اختیار ما قرار می‌دهد:
// Called after the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called before an action method executes
void OnActionExecuting(ActionExecutingContext filterContext)
اولین متد بعد از اجرای اکشن متد و دومی قبل از آن صدا زده میشود. از آنجا که ما نیاز داریم کاربر قبل از رسیدن به اکشن متد مسدود گردد؛ متد OnActionExecuting را بازنویسی میکنیم:
    public class BlockDocument : ActionFilterAttribute
    {
        public Func<IDocumentServices> _documentServices { get; set; };

        public string ParamName;
      
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {           
            var id = 0;
            if (filterContext.ActionParameters.ContainsKey(ParamName))
            {
                id =(int) (filterContext.ActionParameters[ParamName]??0);
            }
            if (IsInvalid(id))
            {
                return;
            } 
            var document = _documentServices().GetDocument(id);
            if (document.InserTime.IsPassed())
            {
                filterContext.Result = new HttpStatusCodeResult(403);
            }
        }
    }
در اینجا با استفاده از تزریق وابستگی‌ها و یک FilterProvider سفارشی، سرویس DocumentServices را در یک Func که باعث تاخیر در بارگذاری سرویس تا زمان استفاده از آن می‌گردد قرار میدهیم. سپس پارامتری که کد یا مشخصه‌ای از سند را داراست از ورودی دریافت کرده و بررسی می‌کنیم که آدرس مربوطه شامل این مشخصه میباشد یا خیر. در صورتیکه مشخصه صحت داشته باشد، سند را از طریق سرویس واکشی کرده و اگر زمان درج سند از محدوده زمانی خارج شده باشد، کاربر را به صفحه 403 هدایت میکنیم.
[BlockDocument(ParamName = "id")]
public ActionResult EditDocument(int id)
{
            //...
}
در نهایت اکشن متد مربوطه را به فیلتر نوشته شده مزین میکنیم.
نظرات مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
یک نکته تکمیلی: ارسال آرگومان‌های سفارشی به متدهای متناظر با begin، ‏failure، ‏complete و success

مثال اول: قصد داریم شناسه فرم جاری را نیز به عنوان آرگومان به متد handleFormFaild ارسال کنیم. برای این منظور می‌بایست به شکل زیر عمل کنید:
function handleFormFailed(xhr, status, error, formId) {
};

data-ajax-failure="handleFormFailed(xhr, status, error, 'role-form')"

مثال دوم: قصد داریم عملیات حذف یک رکورد را با استفاده از یک لینک Ajaxای با متد POST انجام دهیم. برای این منظور ‌می‌بایست شناسه رکورد مورد نظر را به شکل زیر به data ارسالی به سرور اضافه کنیم:
function handleDeleteLinkBegin(xhr, id, settings)
{
    var token = $('input[name=__RequestVerificationToken]').val();
    settings.data =
      settings.data + '&Id=' + id + '&__RequestVerificationToken=' + token;
}

<a href="#" data-ajax="true" data-ajax-method="POST" 
   data-ajax-begin="handleDeleteLinkBegin(xhr, '1', arguments[1])"
   data-ajax-url="/Roles/Delete"> 
   Delete
</a>


مطالب
پشتیبانی از کشیدن و رها کردن در Angular 7
یکی از قابلیت‌هایی که به Angular 7 اضافه شده‌است، پشتیبانی از Drag and Drop می‌باشد. برای استفاده از این قابلیت، در ابتدا لازم است Angular CLI را بروز رسانی کنیم و بعد از آن پکیج @angular/cdk  را نصب و ماژول DragDropModule را در فایل app.module.ts، بخش import  اضافه کنیم:
npm install @angular/cdk

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [ AppComponent ],
  imports: [
    BrowserModule,
    FormsModule,
    DragDropModule
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {}

cdkDrag
با استفاده از دایرکتیو cdkDrag، یک آیتم قابلیت جابجایی را پیدا می‌کند و به صورت پیش فرض این دایرکتیو اجازه جابجایی آیتم را در تمامی جهات می‌دهد:
<div cdkDrag>
  I'm Draggable
</div>

در صورتیکه قصد داشته باشید جابجایی را بر اساس محور x یا y، محدود کنید، از دایرکتیو cdkDragLockAxis استفاده می‌شود که می‌تواند مقدار x یا y را نیز بپذیرد:
<div cdkDrag cdkDragLockAxis="x">
  I'm Draggable
</div>

در ادامه قصد پیاده سازی مثال زیر را داریم :

در ابتدا یک مدل را ایجاد می‌کنیم: 

export interface Todo {
    title: string;
    type?: string;
}


فایل app.component.ts 

export class AppComponent implements OnInit {

  public title = 'Darg and drop';
  public model: Todo;

  public todo: Todo[];
  public done: Todo[];
  public cancelled: Todo[];


  ngOnInit(): void {
    this.setDefalutValue();
  }

  addItem(form, $event: Event) {
    $event.preventDefault();
    if (form.valid) {
      if (this.model.type === 'todo') {
        this.todo.push({ title: this.model.title });
      } else {
        this.done.push({ title: this.model.title });
      }
    } else {
      alert('فرم معتبر نمی‌باشد . عنوان را وارد نمایید');
    }
  }

  drop(event: CdkDragDrop<Todo[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
    }
  }



  private setDefalutValue() {
    this.todo = [
      { title: 'خرید مواد غذایی' },
      { title: 'رفتن به خانه' },
      { title: 'خوابیدن' }
    ];

    this.done = [
      { title: 'بیدار شدن' },
      { title: 'مسواک زدن' },
      { title: 'دوش گرفتن' },
      { title: 'چک کردن ایمیل' }
    ];

    this.cancelled = [];

    this.model = {
      title: null,
      type: 'todo'
    };

  }

}


در اینجا 3 آرایه یکی برای to-do  و یکی برای Done  و دیگری برای Cancelled  ایجاد می‌کنیم و به هر کدام تعدادی آیتم را اضافه می‌کنیم.

addItem : زمانیکه فرم را submit می‌کنیم اجرا می‌شود . 


drop : زمانی اجرا می‌شود که یک آیتم را Drag and Drop کنیم. در ایجا شرط event.previousContainer === event.container  زمانی درست است که جابجایی در درون یک لیست باشد و هدف در این صورت، مرتب سازی است .

moveItemInArray : ایندکس آیتم‌ها را در همان لیست تغیر می‌دهد (مرتب سازی).

transferArrayItem: آیتم را از یک لیست حذف و به لیست دیگری اضافه می‌کند.


فایل app.component.html

  <div>
    <!-- فرم -->
    <div>
      <fieldset>
        <legend>
          اضافه کردن آیتم جدید
        </legend>
        <form #form="ngForm" (submit)="addItem(form,$event)">

          <label></label>
          <input type="text" required name="title" #name="ngModel" [(ngModel)]="this.model.title">
          <label></label>

          <select required name="type" #type="ngModel" [(ngModel)]="this.model.type">
            <option value="todo">
              انجام دادن
            </option>
            <option value="done">
              انجام شده
            </option>
          </select>

          <input type="submit" value="ذخیره">
        </form>
      </fieldset>
    </div>

    <!-- آیتم‌ها -->
    <div>
      <fieldset>
        <legend>
          لیست آیتم‌ها </legend>

        <div>
          <!-- انجام دادن -->
          <div>
            <p>
              انجام دادن
            </p>
            <div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, cancelledList]"
              (cdkDropListDropped)="drop($event)">
              <div *ngFor="let item of todo" cdkDrag>
                <p> {{ item.title | titlecase }} </p>
              </div>
            </div>
          </div>

          <!-- انجام شده -->
          <div>
            <p>
              انجام شده
            </p>

            <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[todoList, cancelledList]"
              (cdkDropListDropped)="drop($event)">
              <div *ngFor="let item of done" cdkDrag>
                <p> {{ item.title | titlecase }} </p>
              </div>
            </div>

          </div>

          <!-- انجام نشده -->
          <div>
            <p>
              انجام نشده
            </p>
            <div cdkDropList #cancelledList="cdkDropList" [cdkDropListData]="cancelled" [cdkDropListConnectedTo]="[todoList, doneList]"
              (cdkDropListDropped)="drop($event)">
              <div *ngFor="let item of cancelled" cdkDrag>
                <p> {{ item.title | titlecase }} </p>
              </div>
            </div>
          </div>

        </div>

      </fieldset>
    </div>
  </div>


در ابتدا یک فرم داریم که در اینجا همه چیز مشخص است ( فرم‌های مبتنی بر قالب‌ها در Angular )

در ادامه 3 container را ایجاد می‌کنیم یکی برای to-do  و یکی برای Done  و در آخر یکی برای Cancelled

container  ایجاد شده برای  to-do

<div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, cancelledList]"
     (cdkDropListDropped)="drop($event)">
   <div *ngFor="let item of todo" cdkDrag>
      <p> {{ item.title | titlecase }} </p>
   </div>
</div>


توضیحات: 

cdkDropList یک container می‌باشد، برای آیتم‌هایی که قرار است Drag and Drop شوند. 

todoList #:

    id مربوط به container را مشخص می‌کند.

cdkDropListConnectedTo:

 id مربوط به container های دیگری که می‌تواند آیتم های container جاری را بپذیرد.

cdkDropListData مشخص کنند منبع داده است.

cdkDropListDropped:  این رویداد زمانی اجرا می‌شود که Drag and Drop برای یک آیتم انجام شود.

cdkDrag:  برای اینکه آیتم‌های درون یک container قابلیت Drag and Drop را داشته باشند، این دایرکتیو را  اضافه می‌کنیم.


تمام ! 


DEMO 

مطالب
نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery
هر از چندگاهی سؤال «این مثال jQuery رو نمی‌تونم اجرا یا باز سازی کنم» در این سایت یا سایت‌های مشابه تکرار می‌شوند. بنابراین بهتر است نحوه عیب یابی برنامه‌های ASP.NET مبتنی بر jQuery را یکبار با هم مرور کنیم. در اینجا، مثال تهیه یک Image Slider را که پیشتر در سایت مطرح شده است، به نحوی دیگر بررسی خواهیم کرد:
1) فراموش می‌کنیم تا اسکریپت اصلی jQuery را به درستی پیوست و مسیردهی کنیم.
2) مسیر Generic handler دیگری را ذکر می‌کنیم.
3) مسیرهای تصاویری را که Image slider باید نمایش دهد، کاملا بی‌ربط ذکر می‌کنیم.
4) خروجی JSON نامربوطی را بازگشت می‌دهیم.
5) یکبار هم یک استثنای عمدی دستی را در بین کدها قرار خواهیم داد.

و ... بعد سعی می‌کنیم با استفاده از Firebug عیوب فوق را یافته و اصلاح کنیم؛ تا به یک برنامه قابل اجرا برسیم.


معرفی برنامه‌ای که کار نمی‌کند!

یک برنامه ASP.NET Empty web application را آغاز کنید. سپس سه پوشه Scripts، Content و Images را به آن اضافه نمائید. در این پوشه‌ها، اسکریپت‌های نمایش دهنده تصاویر، Css آن و تصاویری که قرار است نمایش داده شوند، قرار می‌گیرند:


سپس یک فایل default.aspx و یک فایل OrbitHandler.ashx را نیز به پروژه با محتویات ذیل اضافه کنید: (در این دو فایل، 5 مورد مشکل ساز یاد شده لحاظ شده‌اند)
محتویات فایل OrbitHandler.ashx.cs مطابق کدهای ذیل است:
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Script.Serialization;

namespace OrbitWebformsTest
{
    public class Picture
    {
        public string Title { set; get; }
        public string Path { set; get; }
    }

    public class OrbitHandler : IHttpHandler
    {
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = /*"Images/" + name*/ name,
                    Title = name
                });
            }

            return results;
        }

        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = /*new JavaScriptSerializer().Serialize(items)*/ string.Empty;
            throw new InvalidDataException("همینطوری");
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }

        public bool IsReusable
        { 
            get { return false; } 
        }
    }
}
در اینجا جهت سهولت دموی برنامه (و همچنین امکان باز تولید آن توسط خوانندگان)، از بانک اطلاعاتی استفاده نشده و عمدا از یک لیست جنریک تشکیل شده در حافظه کمک گرفته شده است. تصاویر برنامه در پوشه Images واقع در ریشه سایت، قرار دارند. بنابراین توسط متد PicturesDataSource، فایل‌های این پوشه را یافته و مطابق ساختار کلاس Picture بازگشت می‌دهیم. نهایتا این اطلاعات به ظاهر قرار است با فرمت JSON بازگشت داده شوند تا بتوان نتیجه را توسط افزونه Orbit استفاده کرد.

همچنین کدهای صفحه ASPX ایی که قرار است (به ظاهر البته) از این Generic handler استفاده کند به نحو ذیل است:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="OrbitWebformsTest._default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Content/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.Path + '" alt="' + b.Title + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"
            });
        });
    </script>
</body>
</html>
خوب! اگر پروژه را اجرا کنیم، کار نمی‌کند. یک مستطیل مشکی رنگ در کنار صفحه ظاهر شده و همین! حالا چکار باید کرد؟


مراحل عیب یابی برنامه‌ای که کار نمی‌کند!

ابتدا برنامه را در فایرفاکس باز کرده و سپس افزونه Firebug را با کلیک بر روی آیکن آن، بر روی سایت فعال می‌کنیم. سپس یکبار بر روی دکمه F5 کلیک کنید تا مجددا مراحل بارگذاری سایت تحت نظر افزونه Firebug فعال شده، طی شود.


اولین موردی که مشهود است، نمایش عدد 3، کنار آیکن فایرباگ می‌باشد. این عدد به معنای وجود خطاهای اسکریپتی در کدهای ما است.
برای مشاهده این خطاها، بر روی برگه Console آن کلیک کنید: 


بله. مشخص است که مسیر دهی فایل jquery-1.5.1.min.js صحیح نبوده و همین مساله سبب بروز خطاهای اسکریپتی گردیده است. برای اصلاح آن سطر زیر را در برنامه تغییر دهید:
 <script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
پیشتر پوشه Script ذکر شده بود که باید تبدیل به Scripts شود.

مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید. اینبار در برگه Console و یا در برگه شبکه فایرباگ، خطای یافت نشدن Generic handler نمایان می‌شوند:


برای رفع آن به فایل default.aspx مراجعه و بجای معرفی Handler.ashx، نام OrbitHandler.ashx را وارد کنید.
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید.


اگر به برگه کنسول دقت کنیم، بروز استثناء در کدها تشخیص داده شده و همچنین در برگه Response پاسخ دریافتی از سرور، جزئیات صفحه خطای بازگشتی از آن نیز قابل بررسی و مشاهده است.
اینبار به فایل OrbitHandler.ashx.cs مراجعه کرده و سطر throw new InvalidDataException را حذف می‌کنیم. در ادامه برنامه را کامپایل و مجددا اجرا خواهیم کرد.



با اجرای مجدد سایت، تبادل اطلاعات صحیحی با فایل OrbitHandler.ashx برقرار شده است، اما خروجی خاصی قابل مشاهده نیست. بنابراین بازهم سایت کار نمی‌کند.
برای رفع این مشکل، متد ProcessRequest را به نحو ذیل تغییر خواهیم داد:
        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = new JavaScriptSerializer().Serialize(items);            
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }
برنامه را کامپایل کرده و اجرا می‌کنیم. برنامه اجرا می‌شود، اما باز هم کار نمی‌کند. مشکل از کجاست؟


بله. تمام تنظیمات به نظر درست هستند، اما در برگه شبکه فایرباگ تعدادی خطای 404 و یا «یافت نشد»، مشاهده می‌شوند. مشکل اینجا است که مسیرهای بازگشت داده شده توسط متد Directory.GetFiles، مسیرهای مطلقی هستند؛ مانند c:\path\images\01.jpg و جهت نمایش در یک وب سایت مناسب نمی‌باشند. برای تبدیل آن‌ها به مسیرهای نسبی، اینبار کدهای متد تهیه منبع داده را به نحو ذیل ویرایش می‌کنیم:
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = "Images/" + name,
                    Title = name
                });
            }

            return results;
        }
در این کدها فقط قسمت Path ویرایش شده است تا به مسیر پوشه Images واقع در ریشه سایت اشاره کند.
اینبار اگر برنامه را اجرا کنیم، بدون مشکل کار خواهد کرد.

بنابراین در اینجا مشاهده کردیم که اگر «برنامه‌ای مبتنی بر jQuery کار نمی‌کند»، چگونه باید قدم به قدم با استفاده از فایرباگ و امکانات آن، به خطاهایی که گزارش می‌دهد و یا مسیرهایی را که یافت نشد بیان می‌کند، دقت کرد تا بتوان برنامه را عیب یابی نمود.


سؤال مهم: اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب می‌شوند؟

اگر به برگه اسکریپت‌ها در کنسول فایرباگ مراجعه کنیم، امکان قرار دادن breakpoint بر روی سطرهای کدهای جاوا اسکریپتی نمایش داده شده نیز وجود دارد:


در اینجا همانند VS.NET می‌توان برنامه را در مرورگر اجرا کرده و تگ‌های تصویر پویای تولید شده را پیش از اضافه شدن به صفحه، مرحله به مرحله بررسی کرد. به این ترتیب بهتر می‌توان دریافت که آیا src بازگشت داده شده از سرور فرمت صحیحی دارد یا خیر و آیا به محل مناسبی اشاره می‌کند یا نه. همچنین در برگه HTML آن، عناصر پویای اضافه شده به صفحه نیز بهتر مشخص هستند:

نظرات مطالب
شروع کار با Apache Cordova در ویژوال استودیو #4
سلام وقتی توی تیکه کد بالا میخوام از JqueryMobile با AngularJS بصورت MVC استفاده کنم
JqueryMobile کار نمیکنه و همه چیز ساده میشه
مثلاً این قسمت
   <div data-role= "content" >
<p>سلام من محتوای اولین برنامه هستم</p>
</div>
بشه
   <div data-role= "content" ng-view="" > </div>
دیگه هر چیزی داخل این Div نوشته بشه بصورت Tag عادی باهاش برخورد میشه و JqueryMobile نمیشه
مثل عکس زیر که با کدش معلومه یه textbox ساده درج شده

 

مطالب
معرفی پروژه فروشگاهی Iris Store
پروژه IrisStore، یک سیستم فروشگاهی متن باز برای راه اندازی فروشگاه‌های اینترنتی کوچک است که سورس آن را می‌توانید از آدرس زیر دریافت کنید و برای اجرای آن نیاز به VS 2015 دارید (به دلیل استفاده‌ی از قابلیت‌های جدید زبان سی‌شارپ):
 
https://github.com/MehdiSaeedifar/IrisStore
 
همچنین نمونه‌ی آنلاین آن‌را می‌توانید در فروشگاه آیریس مشاهده کنید.
 

در ادامه برخی از قابلیت‌های این سیستم را مشاهده می‌کنید:
 

جست و جو با قابلیت دسته بندی نتایج

 
به هنگام جست و جو، لیستی از موارد پیشنهادی به صورت دسته بندی شده نمایش داده می‌شود.



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



نمایش نمودار تغییرات قیمت
 
امکان نمایش نمودار تغییرات قیمت کالا در بازه‌ی زمانی نیز پیش بینی شده است.

   
ویرایش اطلاعات به صورت inline
 
امکان ویرایش قیمت و تاریخ به صورت inline وجود دارد.



   

مدیریت تصاویر کالا

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


   

قابلیت‌های دیگر:

 
- مدیریت تصاویر اسلایدشو و تغییر ترتیب آن‌ها از طریق کشیدن و رها کردن (drag & drop)
- تعریف برگه و تغییر ترتیب نمایش آن‌ها از طریق کشیدن و رها کردن
- امکان ارسال پست
- تعریف دسته بندی
- مدیریت کاربران
- تعریف تنظیمات سایت
- نمایش کالا و پست‌های مشابه

کارهایی که باید انجام شود:

 
- پیاده سازی سبد خرید و خرید آنلاین
 

تصویر پنل مدیریت

 

تصویر صفحه‌ی اصلی:



همچنین به راحتی می‌توان با طراحی قالب جدیدی، از این سیستم برای کاری غیر از فروشگاه اینترنتی استفاده کرد؛ سایت‌های زیر نمونه‌های آنلاین دیگری از این سیستم هستند:

- http://www.petrapars.ir
- http://www.ava-tarh.ir
  
در نهایت فهرستی از کتاب خانه‌ها و فناوری‌های استفاده شده و همچنین مقالات مرتبط با این پروژه را قرار داده‌ام.

کتابخانه‌ها و فریم ورک‌های سمت سرور:

 فناوری یا کتابخانه   توضیحات  
مقالات مرتبط
 ASP.NET MVC 5.x
 فریم ورک و موتور اصلی سایت
-ASP.NET MVC
-How to handle repeating form fields in ASP MVC
-How to dynamically (via AJAX) add new items to a bound list model, in ASP MVC.NET  
 Entity Framework 6.x
 فریم ورک دسترسی به داده
-Entity framework code-first
-Update One-to-Many Entity using DBContext 
-مدیریت اطلاعات وابسته به زمان در بانک‌های اطلاعاتی رابطه‌ای
EFSecondLevelCache
کش سطح دوم EF 6
 -بازنویسی سطح دوم کش برای Entity framework 6  
 AutoMapper
 نگاشت اطلاعات یک شی به شی دیگر به صورت خودکار  - دوره AutoMapper
- خودکارسازی فرآیند نگاشت اشیاء در AutoMapper  
 StructureMap
 تزریق وابستگی‌ها
-EF Code First #12  
 MvcCheckBoxList
 اضافه کردن CheckBoxList  به HtmlHelper 

 DNTScheduler
 برای انجام کارهای زمان بندی شده
-انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
 Lucene.Net
 موتور جستجوی سایت  -جستجوی سریع و پیشرفته با لوسین Lucene.net  
 AspNet.Identity
 سیستم مدیریت کاربران
-اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity  
 ELMAH.MVC
 کتابخانه ثبت وقایع و خطا‌های سیستم  -معرفی ELMAH
 PagedList
 نمایش اطلاعات به صورت صفحه بندی شده

PersianDateTime
جایگزینی است برای System.DateTime برای تاریخ‌های شمسی
-PersianDateTime جایگزینی برای System.DateTime
T4MVC
تعاریف Strongly typed مسیرها 
-T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
Dynamic LINQ
نوشتن کوئری‌های LINQ به صورت رشته ای
-انتخاب پویای فیلد‌ها در LINQ
-فعال سازی و پردازش جستجوی پویای jqGrid در ASP.NET MVC 

کتابخانه‌های جاوا اسکریپتی سمت کلاینت:
 
 فناوری یا کتابخانه  
  توضیحات     مقالات مرتبط 
 jQuery  کتاب خانه‌ی پایه جاوا اسکرپتی سایت
 -آموزش (jQuery) جی کوئری
-آموزش JQuery Plugin و مباحث پیشرفته جی کوئری  
 
 jQuery UI  ویجت‌های رابط کاربری
- نمایش رکوردها به ترتیب اولویت به کمک jQuery UI sortable در ASP.NET MVC
- jQuery UI Sortable
-Categorized search result with jQuery UI Autocomplete
- jQuery UI Slider
-rtl jQuery UI Slider
-jquery UI Sortable with table and tr width  
jQuery Validation اعتبار سنجی سمت کلاینت
-مشکل اعتبار سنجی jQuery validator در Bootstrap tabs
-نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Popover به کمک Twitter bootstrap
toastr نمایش پیام و اطلاع رسانی

PersianDatePicker یک DatePicker شمسی کم حجم 
-PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
CKEDITOR ادیتور متن
-استفاده از ادیتور CKEditor در صفحات ASP.NET
-یکپارچه سازی CKEditor با Lightbox
Roxy Fileman مدیریت فایل ها  -افزونه مدیریت فایل‌های رایگان Roxy FileMan برای TinyMce و CkEditor  
Magnific Popup نمایش عکس‌ها به صورت پاپ آپ

Select2 تغییر شکل drop down list‌ها برای انتخاب گزینه‌ها

jqGrid v4.6 نمایش اطلاعات در قالب جدول
- آموزش jqGrid
Bootstrap Star Rating امتیاز دهی ستاره ای
-پیاده سازی امتیاز دهی ستاره‌ای به مطالب به کمک jQuery در ASP.NET MVC
jQuery File Upload Plugin آپلود فایل به صورت AJAX ای

HIGHCHARTS نمایش نمودار

jQuery Number Plugin برای فرمت کردن اعداد

X-editable ویرایش اطلاعات به صورت inline
-قابل ویرایش کننده‌ی فوق العاده x-editable ؛ قسمت اول  
bootstrap-confirmation نمایش فرم تایید در قالب popover

PathJS برای تغییر URL صفحه برای اعمال Ajax ای 
-پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC  

فریمورک‌های CSS:
 
فناوری یا کتابخانه
 توضیحات  
 مقالات مرتبط  
 Bootstrap 3.x 
 فریم ورک پایه ای css سایت
 - Bootstrap 3 RTL Theme
- Twitter Bootstrap
-سازگارسازی کلاس‌های اعتبارسنجی Twitter Bootstrap 3 با فرم‌های ASP.NET MVC 
-ساخت قالب‌های نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC
-نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC
 AdminLTE 
 قالب مدیریت سایت
 - نسخه راستچین شده AdminLTE 2.2.1
Animate.css   انیمیشن‌های css3 سایت

Font Awesome   پک آیکون‌های برداری

Awesome Bootstrap Checkbox   زیبا سازی چک باکس ها

فونت فارسی وزیر   قلم فارسی



لطفا برای طرح سؤالات و پیشنهادات خود و جهت مدیریت بهتر آن‌ها، از قسمت اختصاصی این پروژه در سایت استفاده نمائید.
نظرات مطالب
Ajax.BeginForm و ارسال فایل به سرور در ASP.NET MVC
با سلام
من یک فرم دارم که در اون کاربر باید دو عکس وارد کنید به صورت زیر
 <div>
        <div style="margin: 2px 4px !important">
            <div>
                عکس 1
                <input type="file" name="imageSrc" id="imageSrc" />
                @*@Html.Kendo().Upload().Name("imageSrc").Multiple(false)*@
                @Html.ValidationMessageFor(model => model.Image)
            </div>
        </div>

    </div>
    <div>
        <div style="margin: 2px 4px !important">
            <div>
                عکس 2
                <input type="file" name="imageSrc2" id="imageSrc2" />
                @*@Html.Kendo().Upload().Name("imageSrc2").Multiple(false)*@
                @Html.ValidationMessageFor(model => model.Image)
            </div>
        </div>

آیا می‌تونم این دو فایل رو با هم آپلود کنم؟ چون در مثال شما  fileElementId: 'Image1' ,  فقط نام یک کنترل را می‌گیرد.
ممنون