نظرات مطالب
ASP.NET MVC #12
نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery 
- محل تعریف اسکریپت‌ها باید در فایل Layout باشد و بهتر است از bundling موجود استفاده کنید.
+ مسیردهی صحیح فایل‌ها در ASP.NET MVC با توجه به مسایل مسیریابی آن باید توسط Url.Content صورت گیرد:
src='@Url.Content("~/Contents/Scripts/jQuery.js")'
البته Razor نگارش 2 به بعد با روش زیر هم سازگار است:
src='~/Contents/Scripts/jQuery.js'
مطالب
استفاده از چند فرم در کنار هم در ASP.NET MVC

اجرای این نوع صفحات کار سختی نیست؛ با کمی جستجو در اینترنت مثلا در اینجا میتوانید چیزهای خوبی پیدا کنید. اما متاسفانه اکثر مثال‌ها چیزی شبیه قرار دادن پارشال "ورود اعضا" در کنار پارشال "ثبت نام" هستند. حتما متوجه شده‌اید که معمولا این دو صفحه پس از  PostBack به صفحه‌ای جدید هدایت میشوند و یا در بهترین حالت به کمک Ajax ، پس از انجام عملیات، پیامی به کاربر نمایش میدهیم.

در این مقاله سعی شده روشی برای ایجاد چند فرم در یک View توضیح داده شود با این شرط که: 

اولا : از Ajax یا هلپر ایجکسی استفاده نکنیم.

ثانیا : پس از post-back، عملیات Redirect را انجام ندهیم و صفحه جاری را حفظ کنیم؛ چه قرار باشد همه چیز درست انجام شده باشد و چه مشکلی پیش آمده باشد و پیام خطایی در کنار فیلد‌ها نمایش داده شود. 

 در این روش به این نکته توجه شده که هر مدل پس از Post-back حفظ شود و مستقل از دیگری رفتار کند. مثلا اگر یکی از فرم‌ها ناقص پر شد و دکمه‌ی ارسال آن فشرده شد، پس از Post-back، فقط و فقط اجزای همین فرم Validate شود و فرم دوم بدون تغییر باقی بماند. 

ویوی زیر را در نظر بگیرید. در layout، دو پارشال، به کمک اکشن‌متد فراخوانی شده‌اند:

ViewModelهای مرتبط با این دو بخش به شکل زیر هستند : 

ContactVM .cs  

public class ContactVM
    {
        [Display(Name = "نام")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string Name { get; set; }

              [EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string EmailAddress { get; set; }

        [Display(Name = "متن پیام")]
        [Required(ErrorMessage = "حرفی برای گفتن ندارید؟")]
        public string Description { get; set; }

        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        [Display(Name = "حاصل جمع")]
        public string Captcha { get; set; }
    }

SubscriberVM .cs

    public class SubscriberVM
    {   
        /*[RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "آدرس ایمیل صحیح نیست")]*/
          [EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")] /*.Net4.5*/
        [Display(Name = "ایمیل")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string Email { get; set; }

        [Display(Name = "وضعیت")]
        public bool IsActive { get; set; }    
    }

در Layout، دو اکشن متد صدا زده شده‌اند که وظیفه ارسال ویوهای هر کدام به Layout را به عهده دارند :

        <div class="row footerclass">
            <div class="col-md--6">
                @Html.Action("Subscribers", "Home")
            </div>
            <div class="col-md-6">
                @Html.Action("Contact", "Home")
            </div>

        </div>

اکشن متدهای این دو پارشال به شکل زیر هستند :

public ActionResult Contact()
        {
            return PartialView("_Contact", model);
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]

        public ActionResult Contact(ContactVM model)
        {
              if (ModelState.IsValid)
                {
//Do Something                    
                }
            return PartialView("_Contact", model);
        }

        public ActionResult Subscribers()
        {
            return PartialView("_Subscribers");
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Subscribers(SubscriberVM model)
        {
                if (ModelState.IsValid)
                {
//Do Something
                }
            }
            return PartialView("_Subscribers",model);
        }

و اما ویوهایی که قرار است نمایش داده شوند:

Contact.Cshtml

@model IrsaShop.Models.ViewModel.ContactVM


<span></span><span>تماس با ما</span>
<hr />

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <div>
        @Html.TextBoxFor(m => m.Name, new { @class = "form-control", @id = "name", @name = "name", placeholder = "نام" })
        @Html.ValidationMessageFor(m => m.Name)
    </div>
    <div>
        @Html.TextBoxFor(m => m.EmailAddress, new { @class = "form-control", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr" })
        @Html.ValidationMessageFor(m => m.EmailAddress)
    </div>
    <div>
        @Html.TextAreaFor(model => model.Description, new { @class = "form-control", @id = "message", @name = "message", placeholder = "پیام", @style = "max-width: 100%;height: 90px;" })
    </div>
    <div>
        <input type="button" value="" id="refresh" />
        <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" />
    </div>
    <div>
        @Html.TextBoxFor(model => model.Captcha, new { @class = "form-control", placeholder = "حاصل جمع؟" })
        @Html.ValidationMessageFor(model => model.Captcha)

    </div>
    <div>
        <input type="submit" value="ارسال" name="submitValue" />
    </div>
}

_Subscriber.Csh tml 

@model IrsaShop.Models.SubscriberVM

<span></span><span>خبرنامه</span>
<hr/>

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <div>

        <div>
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control right-buffer top-buffer pull-right", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr;width: 50%", @required = "required" })
       @*     <button type="submit" name="submitValue">ثبت ایمیل</button>*@
            <input type="submit" value="ثبت ایمیل" name="submitValue" />
        </div>
        
    </div>
   @Html.ValidationMessageFor(m => m.Email,"",new { @class = "right-buffer pull-right"})
   
}

نکته اول : هیچ نوع ورودی برای Html.BeginForm در نظر گرفته نشده است. اگر اکشن متدی را برای صدا زدن در این بخش در نظر بگیرید، هنگام Postback به مشکل برخورد خواهید کرد؛ چون آدرس آن اکشن متد به شکل صریح در آدرس مرورگر فراخوانی میشود و پارشال ما پس از Post-back به تنهایی و بدون Layout نمایش داده خواهد شد. اسم بردن از اکشن متد وقتی کارساز است که آن اکشن متد قرار باشد یک Redirect انجام دهد ولی هدف ما این است که صفحه را از دست ندهیم و پیام‌های خطای ModelState را در همان صفحه قبل و پس از Post-back ببینیم و همچنین پس از انجام عملیات (مثلا ارسال پیام) همین صفحه نمایش داده شود. 

نکته دوم : نکته اول یک مشکل دارد! اگر به شکل صریح اکشن متد مربوط به Post-back مشخص نشود، بطور اتوماتیک تمامی اکشن متدهایی که ویژگی [HttpPost] دارند اجرا خواهند شد. این یعنی هر دو اکشن متد Contact و Subscriber اجرا می‌شوند و بنابر آنچه در اکشن متدها نوشته‌ایم هر دو ModelState بررسی می‌شود که این هدف ما نیست. مثلا فرم سمت چپ را تکمیل کرده ایم و دکمه "ثبت ایمیل" را فشار داده‌ایم و صفحه Postback می‌شود و با اینکه ایمیل در بانک ثبت شده اما فرم سمت راستی با خطا ظاهر میشود که چرا فیلدها خالی هستند!؟ 

برای حل این مشکل کافیست خاصیت name مربوط به دکمه‌ها را به شکل یک ورودی برای اکشن متدها بفرستیم و بر اساس وضعیت آن تنها state مدل مورد نظر خودمان را بررسی کنیم. پس اصلاح زیر را برای اکشن متدهای دارای ویژگی [HttpPost] انجام میدهیم.

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]

        public ActionResult Contact(ContactVM model, , string submitValue)
        {
   if (submitValue == "ارسال") 
                {
                 if (ModelState.IsValid)
                {
//Do Something                    
                }
}   else
                {
                         ModelState.Clear();
                }        
            return PartialView("_Subscribers", model);
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
public ActionResult Subscribers(SubscriberVM model, string submitValue)
        {
             if (submitValue == "ثبت ایمیل") 
            {
if (ModelState.IsValid)
                {
//Do Something
                }
}
            else
            {
                ModelState.Clear();
            }
            return PartialView("_Subscribers");
        }

نکته سوم : در این روش سعی کنید از ViewModel  استفاده کنید و سعی کنید ویو مدل‌ها پراپرتی‌های با نام یکسان نداشته باشند. مثلا پراپرتی Email  در ویو مدل‌ها نام‌های متفاوتی داشته باشند (مثل EmailAddress  ، Email  ، ContactMail  و ...). با اینکار در زمان Postback  احتمال اینکه فیلدهای مشترک اتوماتیک پر شده به ما نمایش داده باشند صفر خواهد شد.

نکته چهارم : حواستان باشد پس از انجام عملیات مرتبط با هر فرم در اکشن متد مربوط به آن (مثلا ارسال ایمیل، ثبت در بانک یا ...) در صورتی که عملیات با موفقیت انجام شد حتما ModelState  را clear کنید. با اینکار پس از Post-back  فیلدهای پارشال‌ها خالی میشوند.

نکته پنجم : میتوانید به سادگی مدیریت خطا را به کمک جی کوئری انجام دهید؛ مثلا فرض کنید میخواهیم اگر ایمیل کاربر برای دریافت خبرنامه با موفقیت ثبت شد، پیامی مبنی بر موفقیت برای وی بفرستیم؛ اکشن متد HttPost مربوط به  Subscriber  را به شکل زیر تکمیل میکنیم : 

[HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Subscribers(SubscriberVM model, string submitValue)
        {
            if (submitValue == "ثبت ایمیل")
            {                
                if (ModelState.IsValid)
                {
                    Subscriber mail = new Subscriber() { Email = model.EmailSubscriber, IsActive = true };
                    context.Subscribers.Add(mail);
                    context.SaveChanges();
                    ViewBag.info = "ایمیل شما با موفقیت ثبت شد.";
                    ViewBag.color = "alert-success";
                    ModelState.Clear();
                }
            }
            else
            {
                ModelState.Clear();
            }
            
            return PartialView("_Subscribers ");
        }

در انتهای پارشال _Subscriber هم چند خط کد زیر را مینویسیم :

@if (!String.IsNullOrEmpty(ViewBag.info))
{
    <div id="info" style="position: fixed; bottom: 0; right: 0; margin-right: 1%;">

        <div class="alert @ViewBag.color alert-dismissable">
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            <strong> @ViewBag.info</strong>
        </div>

    </div>
    <script>
        $(function () {
            $("#info").fadeOut(15000);
        });
    </script>
}


نتیجه این خواهد بود که پس از PostBack در صورت موفقیت تصویر زیر را خواهیم دید و 15 ثانیه المان سبزرنگ بوت استرپِ زیر نمایش داده خواهد شد.

این روش نوعی مدیریت میان اکشن متدهای دارای ویژگی HttpPost است و همانطور که گفتیم به علت اینکه پس از Post-Back نیاز به ساختار به هم نخورده‌ی صفحه‌ی قبلی داریم، نمیتوانستیم به شکل صریح، اکشن متد برای Html.BeginForm تعریف کنیم تا این دردسر‌ها را نداشته باشیم.

حین نوشتن این مقاله به علت وجود if ‌های تو در تو، امیدوار بودم که روش‌های بهتری برای اینکار موجود باشند و هنوز هم امیدوارم نظرات شما چنین چیزی را نشان دهد. 

نظرات مطالب
CheckBoxList در ASP.NET MVC
- کد کاملت باید باشه تا بتونم دیباگش کنم. این توضیحات رو جایی ارسال کنید، تصور نکنید که یک نفر مثلا یک ساعتی سعی در بازسازی مثال شما خواهد کرد. بعد سعی می‌کند تا خطای شما را به نحوی باز تولید کند و بعد هم مثلا یک مقاله جهت توضیح جزئیات آن در نصف روز منتشر می‌کند. این امر هیچ وقت رخ نخواهد داد. برای پاسخ دادن به یک سؤال، سرعت بازسازی آن مهم است.
- اگر نال دریافت می‌کنی ممکن است اطلاعات شما در یک فرم صحیح که به کنترلر اشاره می‌کند قرار ندارد یا حتی ممکن است نام‌های دیگری در سمت کاربر رندر شده باشند. هر دو مورد را با استفاده از نکات مطلب «نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery » می‌توانید بررسی کنید. به این ترتیب مشخص میشه اصلا چه اطلاعاتی در برگه شبکه این افزونه زمان ارسال به سرور لاگ شده. این را بررسی کنید و سپس با ساختار داده کنترلر خودتون مقایسه‌اش کنید.
به صورت خلاصه، برگه network افزونه فایرباگ را در حین ارسال اطلاعات به سرور بررسی کنید. ساختار اطلاعات آن چیست؟ آیا تطابقی با پارامترهای کنترلر شما دارد یا خیر؟ مثلا من در مثال فوق نوشتم result. این مورد به این معنا نیست که خروجی نهایی HTML کار شما هم حتما result نام دارد (خصوصا اگر خودکار تولید می‌شود). بسته به نام واقعی عناصر انتخابی، نام این پارامتر متغیر خواهد بود. اما اصول نهایی یکی است.
- ضمنا روش دوم یافتن نام واقعی پارامترهای ارسالی، استفاده از روش قدیمی Request.Form در یک اکشن متد هست. این خاصیت دریافتی را دیباگ کنید تا به نام‌های واقعی برسید.
مطالب
آماده سازی Jasmine برای پروژه های Asp.Net MVC
با گسترش روز افزون برنامه‌های تحت وب، نیاز به یک سری ابزار برای تست و اطمینان از نحوه عملکرد صحیح کدهای نوشته شده احساس می‌شود. Jasmine یکی از این ابزار‌های قدرتمند برای تست کد‌های JavaScript است.
چندی پیش در سایت جاری چند مقاله خوب توسط یکی از دوستان درباره Qunit منتشر شد. Qunit یک ابزار قدرتمند و مناسب برای تست کد‌های جاوااسکریپت است و در اثبات صحت این گفته همین کافیست که بدانیم برای تست کد‌های نوشته شده در پروژه‌های متن بازی هم چون Backbone.Js و JQuery از این فریم ورک استفاده شده است. اما به احتمال قوی در ذهن شما این سوال مطرح شده است که خب! در صورت آشنایی با Qunit چه نیاز به یادگیری Jasmine یا خدای نکرده Mocha و FuncUnit است؟ هدف صرفا معرفی یک ابزار غیر برای تست کد است نه مقایسه و نتیجه گیری برای تعیین میزان برتری این ابزارها. اصولا مهم‌ترین دلیل برای انتخاب، علاوه بر امکانات و انعطاف پذیری، فاکتور راحتی و آسان بودن در هنگام استفاده است که به صورت مستقیم به شما و تیم توسعه نرم افزار بستگی دارد.

اما به عنوان توسعه دهنده نرم افزار که قرار است از این ابزار استفاده کنیم بهتر است با تفاوت‌ها و شباهت‌های مهم این دو فریم ورک آشنا باشیم:

»Jasmine یک فریم ورک تست کدهای جاوا اسکریپ بر مبنای Behavior-Driven Development است در حالی که Qunit بر مبنای Test-Driven Development است و همین مسئله مهم‌ترین تفاوت بین این دو فریم ورک می‌باشد.
»اگر قصد دارید که از Qunit نیز به روش BDD استفاده نمایید باید از ترکیب Pavlov به همراه Qunit استفاده کنید.
»Jasmine از مباحث مربوط به Spies و Mocking به خوبی پشتیبانی می‌کند ولی این امکان به صورت توکار در Qunit فراهم نیست. برای اینکه بتوانیم این مفاهیم را در Qunit پیاده سازی کنیم باید از فریم ورک‌های دیگر نظیر SinonJS به همراه Qunit استفاده کنیم.
»هر دو فریم ورک بالا به سادگی و راحتی کار معروف هستند 
»تمام موارد مربوط به الگوهای Matching در هر دو فریم ورک به خوبی تعبیه شده است
» هر دو فریم ورک بالا از مباحث مربوط به Asynchronous Testing  برای تست کد‌های Ajax ای به خوبی پشتیبانی می‌کنند.

بررسی چند مفهوم
قبل از شروع، بهتر است که با چند مفهوم کلی و در عین حال مهم این فریم ورک آشنا شویم
describe('JavaScript addition operator', function () {
 it('adds two numbers together', function () {
  expect(1 + 2).toEqual(3);
 });
});


در کد بالا یک نمونه از تست نوشته شده با استفاده از Jasmine را مشاهده می‌کنید. دستور describe  برای تعریف یک تابع تست مورد استفاده قرار می‌گیرد که دارای دو پارامتر ورودی است. ابتدا یک نام را به این تست اختصاص دهید(بهتر است که این عنوان به صورت یک جمله قابل فهم باشد). سپس یک تابع به عنوان بدنه تست نوشته می‌شود. به این تابع Spec گفته می‌شود.
در تابع it کد بالا شما می‌توانید کد‌های مربوط بدنه توابع تست خود را بنویسید. برای پیاده سازی Assert در توابع تست مفهوم expectation‌ها وجود دارد. در واقع expect برای بررسی مقادیر حقیقی با مقادیر مورد انتظار مورد استفاده قرار می‌گیرد و شامل مقادیر true یا false خواهد بود.
برای Setup و Teardown توابع تست خود باید از توابع beforeEach و afterEach که بدین منظور تعبیه شده اند استفاده کنید.
describe("A spec (with setup and tear-down)", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  afterEach(function() {
    foo = 0;
  });

  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });

  it("can have more than one expectation", function() {
    expect(foo).toEqual(1);
    expect(true).toEqual(true);
  });
});
کاملا واضح است که در تابع beforeEach مجموعه دستورالعمل‌های مربوط به setup تست وجود دارد. سپس دو تابع it برای پیاده سازی عملیات Assertion نوشته شده است. در پایان هم دستورات تابع afterEach ایجاد می‌شوند.

اگر در کد تست خود قصد دارید که یک تابع describe یا it را غیر فعال کنید کافیست یک x به ابتدای آن‌ها اضافه کنید و دیگر نیاز به هیچ کار اضافه دیگری برای comment کردن کد نیست.
xdescribe("A spec", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  xit("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });
});
توابع describe و it بالا در هنگام تست نادیده گرفته می‌شوند و خروجی آن‌ها مشاهده نخواهد شد.

درادامه قصد پیاده سازی یک مثال  را با استفاده از Jasmine و RequireJs در پروژه Asp.Net MVC دارم.
برای شروع آخرین نسخه Jasmine را از اینجا دریافت نمایید. یک پروژه Asp.Net MVC به همراه پروژه تست به صورت Empty ایجاد کنید(در هنگام ایجاد پروژه، گزینه create unit test را انتخاب نمایید). فایل دانلود شده را unzip نمایید و دو پوشه lib و spec ،به همراه فایل specRunner.html را در پروژه تست خود کپی نمایید. 
  • فولدر lib شامل فایل‌ها کد‌های Jasmine برای setup و tear down و spice و تست کد‌های شما می‌باشد.
  • فایل specRunner.html به واقع یک فایل برای نمایش فایل‌های تست و همچنین نمایش نتیجه تست است.
  • فولدر spec نیز شامل کد‌های Jasmine برای کمک به نوشتن تست می‌باشد.

در این مثال قصد داریم فایل‌های player.js و song.js که به عنوان نمونه به همراه این فریم ورک قرار دارد را در قالب یک پروژه MVC به همراه RequireJs، تست نماییم. در نتیجه این فایل‌ها را از فولدر src انتخاب نمایید و آن‌ها را در قسمت Scripts پروژه اصلی خود کپی کنید(ابتدا بک پوشه به نام App بسازید و فایل‌ها را در آن قرار دهید)

برای استفاده از requireJs باید دستور define را در ابتدا این فایل‌ها اضافه نماییم. در نتیجه فایل‌های Player.js و Song.js را باز کنید و تغییرات زیر را در ابتدای این فایل‌ها اعمال نمایید.

Song.js

define(function () {
    function Song() {
    }

    Song.prototype.persistFavoriteStatus = function (value) {
        // something complicated
        throw new Error("not yet implemented");
    };
});
Player.js
define(function () {
    function Player() {
    }
    Player.prototype.play = function (song) {
        this.currentlyPlayingSong = song;
        this.isPlaying = true;
    };

    Player.prototype.pause = function () {
        this.isPlaying = false;
    };

    Player.prototype.resume = function () {
        if (this.isPlaying) {
            throw new Error("song is already playing");
        }

        this.isPlaying = true;
    };

    Player.prototype.makeFavorite = function () {
        this.currentlyPlayingSong.persistFavoriteStatus(true);
    };
});
حال فایل SpecRunner.html را بازکنید و کد‌های مربوط به تگ script که به مسیر اصلی فایل‌های تست  اشاره می‌کند را Comment نمایید و به جای آن تگ Script مربوط به RequireJs را اضافه نمایید. برای پیکر بندی RequireJs باید از baseUrl و paths استفاده کرد.

baseUrl در پیکر بندی requireJs به مسیر فایل‌های پروژه که در پروژه اصلی MVC قرار دارد اشاره می‌کند. paths برای تعیین مسیر فایل‌های تست که در پوشه spec در پروژه تست قرار دارد اشاره می‌کند. اگر دقت کرده باشید به دلیل اینگه تگ‌های script مربوط به لود فایل‌های SpecHelper.js و PlayerSpec.js به صورت comment در آمده اند در نتیجه این فایل‌ها لود نخواهند شد و خروجی مورد نظر مشاهده نمی‌شود. در این جا باید از مکانیزم AMD موجود در RequireJs استفاده نماییم و فایل‌های مربوطه را لود کنیم. برای این کار نیاز به اضافه کردن دستور require در ابتدای تگ script به صورت زیر در این فایل است. در نتیجه فایل‌های PlayerSpec و SpecHelper نیز توسط RequireJs لود خواهند شد.



نیاز به یک تغییر کوچک دیگر نیز وجود دارد. فایل PlayerSpec را باز نمایید و وابستگی فایل‌های آن را تعیین نمایید. از آن جا که این فایل برای تست فایل‌های Player , Song ایجاد شده است در نتیجه باید از define برای تعیین این وابستگی‌ها استفاده نماییم.

یادآوری:

»دستور describe در فایل بالا برای تعریف تابع تست است. همان طور که می‌بینید بک نام به آن داده می‌شود به همراه بدنه تابع تست. 

»دستور beforeEach برای آماده سازی مواردی است که قصد داریم در تست مورد استفاده قرار گیرند. همانند متد‌های Setup در UnitTest.

» دستور expect نیز معادل Assert در UnitTest است و برای بررسی صحت عملکرد تست نوشته می‌شود.

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


 

 
مطالب
یکپارچه سازی CKEditor با Lightbox
در یک پروژه من احتیاج داشتم تا عکس هایی که کاربر از طریق ckeditor آپلود می‌کنه ، به صورت خودکار با lightbox  یکپارچه شه و به صورت گالری عکس نمایش داده شود.
همان طور که می‌دونید مکانیزم عملکرد اکثر پلاگین‌های lightbox  به صورت زیر است:
<a href="orginal size of image directory"><img src="thumb(smaller size) image directory" /></a>

خب معلوم میشه که آدرس عکسی که به صورت کوچیک)اصطلاحا بند انگشتی) به نمایش در میاد باید داخل تگ img ، وآدرس عکس با سایز اصلی ، داخل تگ a قرار بگیره.

پس تقریبا معلوم شد که چه باید بکنیم.

ابتدا باید عکسی که آپلود شده را به صورت خودکار به اندازه کوچکتر تغییر ابعاد داده و سپس آدرس عکس تغییر ابعاد داده شده ، را به عنوان آدرس عکس آپلود شده به ckeditor باز گردانیم  که در شکل زیر آن را نشان داده ام:

ولی  ما فقط  آدرس  عکس تغییر ابعاد داده شده متعلق به تگ img  را ، به ckeditor  داده ایم و برای اینکه با lightbox  به خوبی کار کند، احتیاج داریم که آدرس عکس با سایز اصلی را داخل تگ  a  قرار دهیم، که در ckeditor می‌توانیم از قسمت پیوندها این کار را انجام دهیم، و همه‌ی این عملیات باید به صورت خودکار انجام شود.


خب تا اینجا خلاصه ای از آنچه باید انجام شود را گفتم.

در کل منطقی که باید پیاده شود بدین گونه است:

1-  پیاده سازی ckeditor
2-  فعال کردن قسمت آپلود عکس ckeditor
3-  آپلود کردن عکس و تغییر اندازه آن به کمک  کلاس WebImage
4-  برگرداندن آدرس عکس‌های آپلود شده به ckeditor و روش‌های پیاده سازی آن

نکات:
الف) من در اینجا از ASP.NET MVC استفاده می‌کنم .شما  نیز می‌توانید منطق پیاده سازی شده را به راحتی و با کمی تغییرات به پروژه خود اعمال کنید.
ب) کدهایی که من نوشتم 100% بهینه نیستند و کاملا هم بدون اشکال نیستند ولی  نیازهای شما را برآورده می‌کند.
ج)آدرس دادن فایل‌های من کاملا صحیح نیستند و بهتر است شما از T4MVC استفاده کنید.

مرحله اول: دانلود و پیاده سازی ckeditor در صفحه

ابتدا به سایت ckeditor.com مراجعه  و از قسمت دانلود، آن را دانلود کرده و سپس از حالت فشرده درآورده، در فولدری مثلا به نام Scripts در پروژه‌ی خود بریزید.
همان طور که می‌دانید ckeditor با یک textarea  یکپارچه میشود ، که من ساده‌ترین آن را برای شما شرح می‌دهم.
اول از همه شما باید یک فایل جاوا اسکریپت متعلق به ckeditor  را در صفحه ای که از آن می‌خواهید استفاده کنید به صفحه ضمیمه کنید. نام این فایل ckeditor.js هست که در فایل دانلودی در داخل پوشه‌ی ckeditor دیده میشود.
 
<script src="/Scripts/ckeditor/ckeditor.js" type="text/javascript"></script>
 نکته: بهتر است که این ارجاع را قبل از بسته شدن تگ body انجام دهید یعنی درست قبل از </body>.
خب فرض کنید که یک textarea به شکل زیر داریم. برای اینکه این textarea  با ckeditor  یکپارچه شود کافیست که class آن را بر روی ckeditor  قرار دهید.
<textarea id="content" name="content" class="ckeditor" ></textarea>
نکته: حتما برای textarea خود id را نیز تعیین کنید ، چون در غیر این صورت  ckeditor کار نخواهد کرد.

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

مرحله دوم: فعال سازی آپلود عکس در ckeditor

از شواهد این طور به نظر میاد که به صورت پیشفرض این امکان غیر فعال است و باید به صورت دستی آن را فعال کنیم.
 برای این کار کافیست که از مستندات ckeditor  کمک بگیریم.
خب یکی از راحت‌ترین این روش‌ها این است که قبل از بسته شدن تگ بادی فایل جاوا اسکریپت زیر را بنویسیم:
 
<script type="text/javascript">
        CKEDITOR.replace( 'content' , { filebrowserImageUploadUrl: '/Admin/UploadImage' } );
</script>
نکته اول: این خط کدی که نوشتیم مرحله اول را باطل می‌کنه یعنی احتیاج نبود که مرحله اول را طی کنیم و می‌توانستیم مستقیما از همین مرحله شروع کنیم.
توضیحات:
آرگومان اول CKEDITOR.replace که در اینجا content  است ، در واقع  id همان textarea ای هست که می‌خواهیم ckeditor  روی آن اعمال شود.
آرگومان دوم نام کنترلر و اکشن را برای آپلود فایل مشخص می‌کنه.(از منطق ASP.Net MVC استفاده کردم)
خب تا اینجا اگر تست بگیرید میبینید که قسمت زیر برایتان فعال شده.

مرحله سوم: آپلود کردن عکس و تغییر اندازه آن به کمک  کلاس WebImage

برای این که ببینم که این فرم آپلود ckeditor ، چه پارامترهایی را به اکشن متد من ارسال می‌کنه ، از فایرباگ کمک گرفتم:

همان طور که می‌یبینید به خوبی آدرس post  شدن اطلاعات به اکشن متدی که من برایش مشخص کردم را فهمیده.
اما سه پارامتر دیگر نیز به اکشن متد ما ارسال می‌کنه: CKEditor و CKeditorFuncNum و langCod
برای اینکه با این پارامتر‌های ارسالی بیشتر آشنا شوید ، توصیه می‌کنم این صفحه را ببینید.
آنچه که از این پارامتر‌ها برای ما مهم هست ، من در اکشن متد تعریفی خود لحاظ کرده ام.

خب همون طور که یپش از این گفته بودم ما نیاز داریم که عکس آپلود شده را به ابعاد کوچکتر تغییر اندازه داده  و اصطلاحا از آن به عنوان تصویر بند انگشتی استفاده کنیم. آدرس این عکس کوچک شده ، همانیست که در آدرس تگ img  قرار می‌گیره و در ابتدا به کاربر نمایش داده میشه.
برای انجام این عمل من کلاسی را برای کار کردن با عکس برایتان معرفی می‌کنم،  به نام WebImage  که  در داخل فضای نامی System.Web.Helpers  قرار گرفته است.
از طریق این کلاس می‌توان کلیه عملیات دریافت فایل آپلود شده،  ویرایش ، تغییر اندازه ، چرخاندن ، بریدن و حتی watermark کردن و در نهایت ذخیره عکس را به آسانی انجام داد.
من کدهای متد UploadImage را برایتان قرار می‌دهم که زیاد هم بهینه نیست و سپس برایتان توضیح میدهم.

public ActionResult UploadImage(string CKEditorFuncNum, string CKEditor, string langCode)
        {
            string message;  // message to display   when file upload successfully(optional)
            string thumbPath = "";  // the directory for thumb file that should resize
            var db = new MyDbContext();  // make new instance from my context
            // here logic to upload image
            // and get file path of the image
            var file = WebImage.GetImageFromRequest(); // get the uploaded file from request
            var ext = Path.GetExtension(file.FileName);  // get the path of file
            //get the file name without extension
            var fileName = Path.GetFileNameWithoutExtension(file.FileName);
            //add time to file name to avoid same name file overwrite, and then add extension to it
            fileName += DateTime.Now.ToFileTime() + ext;
            //choose the path for the original size of image
            var path = Path.Combine(Server.MapPath("~/Content/UploadedImages/Default"), fileName);
            file.Save(path);  //save the original size of the image
            db.Images.Add(new Image { RealName = file.FileName, FileName = fileName, UploadDate = DateTime.Now }); // save image info to db
            db.SaveChanges();  // submit changes to db
            string defaultPath = "/Content/UploadedImages/Default/" + fileName;  //path for original size of //images
            if (file.Width > 400)  // if width of image bigger than 400 px do resize
            {
                file.Resize(400, 400, true);  //resize the image , third argument is aspect ratio
                string thumbName = "Thumb-" + fileName;  // resized image name
                //path for resized image file
                string path2 = Server.MapPath("~/Content/UploadedImages/Thumbs/" + thumbName);
                thumbPath = "/Content/UploadedImages/Thumbs/" + thumbName;
                //save resized image file
                file.Save(path2);
            }
            else
            {
                thumbPath = defaultPath;  // if the size not bigger than 400px the thumb, path = default path
            }
            // passing message success/failure
            message = "Image was saved correctly";
            // since it is an ajax request it requires this string
            //java script that return files path to ckeditor
            string output = @"<script>window.parent.CKEDITOR.tools.callFunction(" + CKEditorFuncNum + ", \"" + thumbPath + "\", \"" + message + "\");window.parent.document.getElementById('cke_145_textInput').value='" + defaultPath + "';window.parent.document.getElementById('cke_125_textInput').value=0;</script>";
            return Content(output);
        }


توضیحات:
ابتدا یه رشته  به نام message  در نظر گرفتم ، برای هنگامی که آپلود شد، ckeditor به کاربر نشان بده.
سپس منطق من به این صورت بوده که مسیری برای ذخیره سازی فایل‌های تغییر اندازه داده شده ، و نیز مسیری برای فایل‌های با اندازه اصلی در نظر گرفتم.
همچنین من در اینجا من از بانک اطلاعاتی برای ذخیره سازی اطلاعاتی از عکس استفاده کردم،  که در اینجا بحث اصلی ما نیست.
سپس به کمک WebImage.GetImageFromRequest فایل آپلود شده را دریافت کردم.این متد به اندازه کافی باهوش هست که بفهمد ، چه فایلی آپلود شده.
سپس پسوند فایل را از نام فایل جدا کردم، و تاریخ کنونی را به شکل رشته در آورده  و به انتهای نام عکس اضافه کرده تا از تکراری نبودن نام عکس‌ها مطمئن باشم.
سپس پسوند فایل را نیز دوباره به نام فایل اضافه کردم و به کمک متد Save کلاس WebImage عکس را ذخیره کردم.
سپس چک کردم که اگر عرض عکس بیشتر از 400  پیکسل هست ، آن  را تغییر اندازه  بده و ذخیره کنه، و در غیر این صورت آدرس عکسی که قرار بود تغییر اندازه داده بشه با آدرس عکس اصلی یکی میشه.


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

window.parent.CKEDITOR.tools.callFunction(" + CKEditorFuncNum + ", \"" + thumbPath + "\", \"" + message + "\");

در حقیقت ما در اینجا ما از api‌های ckeditor و همچنین پارامترهای ارسالی از طرف ckeditor  استفاده کردیم ، تا قسمت آدرس عکس را با آدرس عکس تغییر اندازه داده شده و کوچک شده پر می‌کنیم.
سوال؟
حالا چگونه قسمت پیوند را پر کنیم؟ این  را دیگر من پیدا نکردم ، تا دست به دامن دوست  و یا شایدم دشمن قدیمیمون جاوا اسکریپت شدم.
اول رفتم به کمک فایرباگ دیدم که id  فیلد پیوند‌ها چیه؟


همان طور که معلومه  id این فیلد cke_145_textInput  هست و به کمک یه خط js می‌توان این فیلد را با آدرس عکس آپلود شده با سایز اصلی پر کرد.
اولش من این را نوشتم:

document.getElementsById("cke_145_textInput").value = defaultPath;

اما بازم js  شروع کرد به بدقلق شدن. هرچی توی کنسول دیباگش کردم خطای null بودن را میداد. بعد از یه ساعت سرو کله زدن و تقریبا ناامید شدن ، چشمم به قسمت اول کد api خود ادیتور که اولش را با window.parent شروع کرده  افتاد ، و من هم کد خودم را به شکل زیر تغییر دادم:

window.parent.document.getElementsById("cke_145_textInput").value = defaultPath;

موفقیت در این قسمت از کد باعث شد که من دست به کد‌تر شوم و مشکل border عکس ها، که به صورت  دیفالت در IE  یا همون دشمن همیشگی وجود داره ، را حل کنم ومقدار border را به صورت پیش فرض صفر کنم. 

window.parent.document.getElementsById('cke_125_textInput').value=0;

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

$(".container [href$='.jpg']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });
$(".container [href$='.png']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });
$(".container [href$='.gif']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });

فرض کنید div ی که متن و عکس‌های ما را شامل میشه ، کلاسش container  باشه . با کمک [href$='.jpg']  می‌توان گفت هر لینکی که،  پسوند فایلی که به آن اشاره می‌کند، .jpg هست،  ویژگی colorbox  را به خود بگیرید.


یک پیشنهاد برای تشکیل گالری عکس:
همان طور که من در بالا اشاره کردم ، rel را بر روی  gal قرار دادم، تا هر تگی که ویژگی  rel را داشته باشد، تشکیل یک گروه برای گالری عکس را بدهد.
برای اینکه  بتوانیم این ویژگی را به عناصر مورد نظر خود اعمال کنیم ، بازم دست به دامان jQuery می‌شویم:

$(".container [href$='.jpg']").attr("rel", "gal");

خب مثل اینکه دیگر کار تمام شده و امیدوارم برای شما مفید بوده باشه.
موفق باشید... 

اشتراک‌ها
قدم به قدم آموزش Angular 2، TypeScript و ASP.NET Core

این لینک به صورت قدم به قدم آموزش داده است که چطور می‌توانید با تکنولوژی‌های  Angular 2، TypeScript و ASP.NET Core  برنامه تولید کنید. بنابراین با خواندن این پست و انجام مراحل شما یک محصول کامل خواهید نوشت.
 

قدم به قدم آموزش  Angular 2، TypeScript و ASP.NET Core
نظرات مطالب
وضعیت فناوری‌های مرتبط با دات نت از دیدگاه مرگ و زندگی!
سلام، من داخل ایران زندگی می‌کنم ولی در کل یک سری ویدیو در مورد ASP.NET MVC هست که اکثر تهیه کنندگان آن‌ها افرادی هستند که مشغول تولید نرم افزار برای دولت آمریکا هستند و الان کارشون رو به MVC ارتقاء دادند (از لابلای صحبت‌ها قابل استخراج است): (+)
نظرات مطالب
مروری بر کاربردهای Action و Func - قسمت چهارم
این تکنیک و مقاله، یکی از مطالب Must Read سال هست. به شخصه از این تکنیک در توسعه‌ی کامپوننت‌های ASP.NET MVC استفاده می‌کنم. کلاً تکنیک Fluent که برادر نصیری فعلاً در دو مقاله به اون پرداختند، انعطاف پذیری بسیاری به برنامه‌ها میده. مثلاً شبیه سازی روال RowDataBound کنترل GridView در Web Forms، در بستر MVC با استفاده از یک Action.
به نظر من کمبودی که ASP.NET MVC در حال حاضر داره، داشتن مجموعه ای غنی از کامپوننت‌های توکار هست که فکر می‌کنم در نسخه‌های آینده، مایکروسافت این نقیصه رو بر طرف می‌کنه، شاید با مشارکت شرکت‌های دیگه مثل Telerik. 
پاسخ به بازخورد‌های پروژه‌ها
مشکل در IDENTITY و سیستم کاربران
- لطفا از این قسمت برای انجمن پرسش و پاسخ استفاده نکنید. این سایت، انجمن عمومی نیست.
- قسمت پروژه‌ها فقط مرتبط هست به مشکلات پروژه‌ها و هیچ هدف دیگری ندارد. لطفا رعایت کنید.
عدم رعایت این مساله در آینده، سبب حذف شما از سایت خواهد شد.
سایت ما هدف تبدیل شدن به انجمن عمومی پرسش و پاسخ را ندارد. از روز اول نداشته‌است. 

- این پروژه از روش دات نت 4 استفاده می‌کند. به عبارتی از ASP.NET Identity نوشته شده برای دات نت 4.5 به بعد کمک نگرفته‌است و از روش Forms authentication استفاده می‌کند. اطلاعات بیشتر
- برای استفاده از کلاس‌های شخصی در ASP.NET Identity به این مقاله مراجعه کنید.