مطالب
T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
تعریف ActionLink زیر را درنظر بگیرید:
@Html.ActionLink("text", "Index", "Home")

پارامترهای دوم و سوم آن که به نام‌های یک اکشن متد و کنترلر آن اشاره می‌کنند، توسط رشته‌ها تعریف شده‌اند. مشکلاتی هم که با رشته‌ها در حالت کلی وجود دارند به شرح زیر است:
الف) می‌توان نام کنترلر یا نام متد را در برنامه تغییر داد. به این ترتیب تمام ActionLink هایی که در برنامه به این کنترلر اشاره می‌کردند از کار می‌افتند (تکرار رشته‌ها به علاوه refactoring friendly نبودن آن‌ها).
ب) برای نوشتن رشته‌ها intellisense کارآیی ندارد.
ج) امکان بروز اشتباهات تایپی در این بین بسیار زیاد است.

راه حل متداولی که برای حل این نوع مشکلات وجود دارد، تعریف یک کلاس عمومی و معرفی رشته‌ها به صورت فیلدهایی ثابت در آن‌ها می‌باشند و سپس استفاده از این فیلدها بجای استفاده مستقیم از رشته‌ها.
و ... چقدر خوب می‌شد اگر ابزاری وجود می‌داشت که کلاس‌های کنترلرهای ما را آنالیز می‌کرد و خودش این ثوابت رشته‌ای را از آن‌ها استخراج و کلاس‌های عمومی یاد شده را تشکیل می‌داد!
خوشبختانه نیازی به اختراع مجدد چرخ نیست و اینکار توسط پروژه‌ی سورس بازی به نام T4MVC انجام شده است. برای دریافت آن به سایت زیر مراجعه نمائید:
این پروژه توسط David Ebbo از اعضای تیم ASP.NET MVC تهیه شده است.

پس از دریافت پروژه، تنها به دو فایل زیر از آن نیاز داریم:
T4MVC.tt و T4MVC.tt.settings.t4

دو فایل فوق را به درون پوشه‌ای از پروژه جاری MVC خود کپی کنید (مثلا یک پوشه T4MVC را ایجاد و این دو فایل را به آن اضافه کنید). بلافاصله این فایل‌های t4 وارد عمل شده و کلاس‌های کنترلرها، Viewها، تصاویر و غیره را آنالیز و ... ثوابت رشته‌ای معادل آن‌ها را تولید می‌کنند.
اکنون برای استفاده از این کلاس‌های تولید شده می‌توان به صورت زیر عمل کرد:

اصلاح ActionLink‌ها و حذف رشته‌های موجود در آن‌ها

اینبار بجای اینکه بنویسیم:
@Html.ActionLink("text", "Index", "Home")
می‌توان نوشت:
@Html.ActionLink("text", result: MVC.Home.Index())
و یا:
@Html.ActionLink("text", MVC.Home.ActionNames.Index, MVC.Home.Name)

برای دسترسی به امکانات آن با نام کلاس MVC شروع می‌کنیم و سپس برای مثال به نام کنترلر Home رسیده و توسط ActionNames آن به تمام اکشن متدهای موجود در آن می‌توان دسترسی داشت.
البته این پروژه بسیار فراتر از تولید فیلدهای strongly typed معادل رشته‌ها است. همانطور که ملاحظه می‌کنید، یک سری overload را هم به متدهای پیش فرض ASP.NET MVC اضافه کرده است و حتی بجای معرفی رشته معادل اکشن متد Index، خود این اکشن متد را می‌توان معرفی کرد (آرگومانی از نوع ActionResult را هم اضافه کرده است). نمونه دیگر آن به نحو زیر می‌تواند باشد:

@Url.Action(result: MVC.Article.Delete())
که بجای مورد متداول ذیل قابل استفاده است:
@Url.Action("Delete", "Article")


امکان معرفی بهتر نام Partial Viewها

برای مثال اگر پیشتر یک Partial View را به این شکل تعریف می‌کردید:
@{ Html.RenderPartial("_ViewPage1"); }
اکنون امکان معرفی آن به نحو زیر فراهم شده است:
@{ Html.RenderPartial(MVC.Home.Views._ViewPage1); }
همچنین تمام این کلاس‌ها در کنترلرها نیز قابل دسترسی هستند:
return PartialView(Views._ViewPage1);
اینجا دیگر نیازی به ذکر کلاس MVC نبوده و می‌توان کار را با کلاس جدید Views شروع کرد. یا اگر نیازی به ذکر نام اکشن متدی در کنترلر جاری بود می‌توان از ActionNames مستقیما استفاده کرد.
و یا بجای:
return RedirectToAction(actionName: "Index", controllerName: "Menu");
می‌توان نوشت:
return RedirectToAction(actionName: MVC.Menu.ActionNames.Index, controllerName: MVC.Menu.Name);
و یا حتی به صورت خلاصه‌تر به نحو زیر:
return RedirectToAction(result: MVC.Menu.Index());
این overload جدیدی است که با T4MVC به پروژه اضافه می‌شود و یک Action Result را می‌تواند بپذیرد. به این ترتیب به صورت خودکار نام کنترلر و متد Index آن تنها در یک پارامتر لحاظ می‌شوند. اگر نیاز به ارسال پارامتری هم به متد Index وجود داشت، همینجا می‌توان اینکار را انجام داد (بجای استفاده از anonymously typed objects متداول).

و یا بجای مسیر دهی به شکل زیر:
return PartialView("~/Views/CommentsArchive/_LatestCommentsInfo.cshtml", data);
می‌توان نوشت:
return PartialView(MVC.CommentsArchive.Views._LatestCommentsInfo, data);
همچنین برای مسیر دهی viewهای قرار گرفته در پوشه shared می‌توان از MVC.Shared.Views شروع کرد.


امکان معرفی بهتر عناصر استاتیک سایت

این مورد نیز بسیار جالب توجه است. توسط کلاس Links آن می‌توان به محتویات استاتیک (تصاویر، فایل‌های css و غیره) پوشه‌های Content و Scripts هم دسترسی یافت و حتی این موارد را نیز refactor کرد:

<img src="@Links.Content.Images.arrow_right_png" alt="arrow" />
<script src="@Links.Scripts.jquery_1_5_1_min_js" type="text/javascript"></script>


امکان تعریف بهتر پارامترها و مقادیر route

بجای اینکه  routeValues را همانند سابق با anonymously typed objects مقدار دهی کنیم:
Html.ActionLink(linkText: "عنوان",
                       actionName: "Index",
                       controllerName: "Comments",
                       routeValues: new
                       {
                            userName = @Model.FriendlyName
                       },
                       htmlAttributes: null))
اینبار می‌توان نوشت:
Html.ActionLink(linkText: "عنوان",
                       result: MVC.Comments.Index(userName: @Model.FriendlyName)
                       htmlAttributes: null))
در overload جدیدی که ملاحظه می‌کنید، هم دسترسی به متد Index یک کنترلر از نوع strongly typed است و هم امکان تعریف پارامترهای آن به نحو منطقی‌تری فراهم شده است (متد Comments.Index واقعا وجود خارجی داشته و پارامتری از نوع userName را می‌پذیرد).


چند نکته جانبی
-این ابزار بر اساس Reflection کار می‌کند (البته فقط در حین تشکیل خودکار کلاس‌های مورد نیاز؛ وگرنه ثوابتی را که ایجاد می‌کند کامپایل شده و در زمان اجرا سرباری را به برنامه اضافه نمی‌کنند). بنابراین اگر کلاسی به پروژه اضافه شده است، کامپایل کردن آن‌را فراموش نکنید.
-اگر تغییری در فایل‌های View، در تعداد و نام آن‌ها صورت گرفت، روی فایل T4MVC.tt کلیک راست کرده و گزینه‌ی اجرای آن‌را انتخاب کنید. پس از این‌کار، مجددا کامپایل پروژه را فراموش نکنید.
-در فایل T4MVC.tt.settings.t4 یک سری تنظیمات پیش فرض قرار دارند. برای مثال اگر علاقمندید که به این فایل‌های تولید شده خودکار، فضای نام سفارشی خاصی را اضافه کنید می‌شود آرایه ReferencedNamespaces آن‌را مقدار دهی کرد.
- overloadهای جدید ActionResult دار آن نسبت به نمونه‌های استاندارد موجود، بسیار منطقی‌تر به نظر می‌رسند.
- توضیحات کامل امکانات T4MVC را در مستندات رسمی آن می‌توانید مطالعه کنید.


و ... اگر یک مدت با آن کار کنید خواهید گفت: «من قبلا چطور با ASP.NET MVC کار می‌کردم؟!»

مطالب
React reconciliation
در پروژه‌های React، نقطه‌ی آغازین فرآیند rendering، قطعه کد زیر میباشد که درون فایل index.js قرار دارد:
ReactDOM.render(<App />, document.getElementById('root'));
 
توسط متد ReactDOM.render یک وهله از کامپوننت App ایجاد شده و منجر به فراخوانی متد render کامپوننت مربوطه خواهد شد. درون متد ذکر شده ممکن است چندین کامپوننت تعریف شده باشند. در نتیجه به ازای هر کامپوننت، متد render متناظر با آن فراخوانی خواهد شد. در نهایت یک ساختار سلسله مراتبی از عناصر HTML تشکیل شده و توسط آرگومان دوم متد فوق، درون عنصری با آی‌دی root درج خواهند شد. 
برای مثال کامپوننت‌های زیر را در نظر بگیرید که هر کدام درون فایل مربوط به خودشان هستند:
// List.js
import React, { Component } from 'react';
import { ActionButton } from './ActionButton';
export class List extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: [
                { id: 1, title: "Item 1" },
                { id: 2, title: "Item 2" },
                { id: 3, title: "Item 3" },
                { id: 4, title: "Item 4" },
                { id: 5, title: "Item 5" },
            ]
        };
    }

    reverse = () => {
        this.setState({ items: this.state.items.reverse() });
    }

    render() {
        console.log("Render List Component");
        return (
            <div className="list-container">
                <ActionButton callback={this.reverse} />
                <ul>
                    {this.state.items.map(item => {
                        return <li key={item.id}>
                            {item.title}
                        </li>
                    })}
                </ul>
            </div>
        );
    }
}

// ActionButton.js
import React, { Component } from 'react';
export class ActionButton extends Component {
    render() {
        console.log("Render ActionButton Component");
        return (
            <button onClick={this.props.callback}>Click me</button>
        );
    }
}
در نهایت درون کامپوننت App.js نیز همچین ساختاری خواهیم داشت:
import React from 'react';
import './App.css';
import { List } from './List';

function App() {
  console.log("Render App Component");
  return (
    <div className="App">
      <h1>Reconciliation Process</h1>
      <List />
    </div>
  );
}

export default App;

 
درون متد render هر کدام از کامپوننت‌های فوق یک console.log نوشته شده است. با اجرای برنامه خروجی زیر در کنسول مرورگر قابل مشاهده است:
Render App Component
Render List Component
Render ActionButton Component

دلیل آن نیز این است که با اجرای اپلیکیشن، React از تمامی کامپوننت‌ها درخواست فراخوانی متد renderشان را خواهد کرد. به محض اینکه محتوای HTML درون صفحه نمایش داده شد، اپلیکیشن در وضعیتی تحت عنوان reconciled قرار خواهد گرفت؛ در این مرحله، خروجی نمایش داده شده با state کامپوننت‌ها سازگار است. React منتظر خواهد بود تا تغییری رخ دهد که در بیشتر اپلیکیشن‌ها این تغییر توسط کاربر انجام خواهد گرفت که در نهایت منجر به فراخوانی متد setState میشود. متد setState در واقع state dataی یک کامپوننت را بروزرسانی میکند؛ اما اینکار کامپوننت را stale یا کهنه میکند. یعنی محتوای HTMLی نمایش داده شده به کاربر قدیمی خواهد شد و با تنها یک رخداد ممکن است چندین state data تغییر کنند. این کار باعث خواهد شد که متد render برای تمامی کامپوننت‌های تغییر کرده فراخوانی شود. اما از آنجائیکه بروزرسانی DOM عملی هزینه‌بر است، در نتیجه React محتوای قبلی (کش شده با عنوان Virtual DOM) را با محتوای جدید مقایسه میکند تا کمترین میزان بروزرسانی DOM را داشته باشد. به این فرآیند Reconciliation گفته میشود.
برای درک بهتر این فرآیند، درون کامپوننت List یک آی‌دی به عنصر ul اضافه خواهیم کرد:
<ul id="list">
    {this.state.items.map(item => {
        return <li key={item.id}>
            {item.title}
        </li>
    })}
</ul>

اکنون در حالیکه پروژه در حال اجرا است، کد زیر را درون کنسول مرورگر اجرا کنید:
document.getElementById("list").classList.add("message")

همانطور که مشخص است کد فوق کلاسی با نام message را به عنصر ul اضافه کرده است:

.message {
  border: 1px solid green;
  padding: 2rem;
}


اکنون وقتی بر روی دکمه Click me کلیک کنیم، محتوای درون کامپوننت فوق تغییر پیدا میکند، اما عنصر ul همچنان دارای کلاس message است؛ دلیل آن نیز همانطور که عنوان شد این است که React محتوای تولید شده توسط کامپوننت List را با Virtual DOM خودش مقایسه میکند و چون از لحاظ ساختار DOM با هم برابر هستند تغییری در ساختار خروجی کامپوننت ایجاد نمیکند و فقط قسمت‌هایی را که تغییر کرده‌اند، بروزرسانی خواهد کرد.


اکنون کامپوننت فوق را اینگونه تغییر خواهیم داد:

import React, { Component } from 'react';
import { ActionButton } from './ActionButton';
export class List extends Component {
    constructor(props) {
        // as before
    }

    reverse = () => {
        this.setState({ items: this.state.items.reverse(), wrapInDiv: true });
    }

    generateElement = () => {
        const list = <ul id="list">
            {this.state.items.map(item => {
                return <li key={item.id}>
                    {item.title}
                </li>
            })}
        </ul>;

        return this.state.wrapInDiv ? <div>{list}</div> : list;
    }

    render() {
        console.log("Render List Component");
        return (
            <div className="list-container">
                <ActionButton callback={this.reverse} />
                {this.generateElement()}
            </div>
        );
    }
}

  در اینجا اگر مجدداً کلاس ذکر شده را به عنصر ul اضافه کنیم و سپس بر روی دکمه کلیک کنیم، خواهیم دید که کلاس message از عنصر ul حذف خواهد شد. دلیل آن نیز این است که ساختار HTML با Virtual DOM یکی نیست و React کامپوننت موردنظر را با تغییرات جدید مجدداً رندر خواهد کرد. 

نکته: برای مشاهده تغییرات DOM می‌توانید قابلیت Paint flashing را در مرورگر Chrome از قسمت Developer Tool > More tools > Rendering فعال کنید و تغییرات را به صورت visual ببینید.
مطالب
افزودن تصدیق ایمیل به ASP.NET Identity در MVC 5
در پست قبلی نحوه سفارشی کردن پروفایل کاربران در ASP.NET Identity را مرور کردیم. اگر بیاد داشته باشید یک فیلد آدرس ایمیل به کلاس کاربر اضافه کردیم. در این پست از این فیلد استفاده میکنیم تا در پروسه ثبت نام ایمیل‌ها را تصدیق کنیم. بدین منظور پس از ثبت نام کاربران یک ایمیل فعالسازی برای آنها ارسال می‌کنیم که حاوی یک لینک است. کاربران با کلیک کردن روی این لینک پروسه ثبت نام خود را تایید می‌کنند و می‌توانند به سایت وارد شوند. پیش از تایید پروسه ثبت نام، کاربران قادر به ورود نیستند.


در ابتدا باید اطلاعات کلاس کاربر را تغییر دهید تا دو فیلد جدید را در بر گیرد. یک فیلد شناسه تایید (confirmation token) را ذخیره می‌کند، و دیگری فیلدی منطقی است که مشخص می‌کند پروسه ثبت نام تایید شده است یا خیر. پس کلاس ApplicationUser  حالا باید بدین شکل باشد.
public class ApplicationUser : IdentityUser
{
    public string Email { get; set; }
    public string ConfirmationToken { get; set; }
    public bool IsConfirmed { get; set; }
}
اگر پیش از این کلاس ApplicationUser را تغییر داده اید، باید مهاجرت‌ها را فعال کنید و دیتابیس را بروز رسانی کنید. حالا می‌توانیم از این اطلاعات جدید در پروسه ثبت نام  استفاده کنیم و برای کاربران ایمیل‌های تاییدیه را بفرستیم.
private string CreateConfirmationToken()
{
    return ShortGuid.NewGuid();
}
 
private void SendEmailConfirmation(string to, string username, string confirmationToken)
{
    dynamic email = new Email("RegEmail");
    email.To = to;
    email.UserName = username;
    email.ConfirmationToken = confirmationToken;
    email.Send();
}
 
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        string confirmationToken = CreateConfirmationToken();
        var user = new ApplicationUser()
        {
            UserName = model.UserName,
            Email = model.Email,
            ConfirmationToken = confirmationToken, 
                IsConfirmed = false };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            SendEmailConfirmation(model.Email, model.UserName, confirmationToken);
            return RedirectToAction("RegisterStepTwo", "Account");
        }
        else
        {
            AddErrors(result);
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}
برای تولید شناسه‌های تایید (tokens) از کلاسی بنام ShortGuid استفاده شده است. این کلاس یک مقدار GUID را encode می‌کند که در نتیجه آن مقدار خروجی کوتاه‌تر بوده و برای استفاده در URL‌ها ایمن است. کد این کلاس را از این وبلاگ گرفته ام. پس از ایجاد حساب کاربری باید شناسه تولید شده را به آن اضافه کنیم و مقدار فیلد IsConfirmed را به false تنظیم کنیم. برای تولید ایمیل‌ها من از Postal استفاده می‌کنم. Postal برای ساختن ایمیل‌های دینامیک شما از موتور Razor استفاده می‌کند. می‌توانید ایمیل‌های ساده (plain text) یا HTML بسازید، عکس و فایل در آن درج و ضمیمه کنید و امکانات بسیار خوب دیگر. اکشن متد RegisterStepTwo تنها کاربر را به یک View هدایت می‌کند که پیامی به او نشان داده می‌شود.
بعد از اینکه کاربر ایمیل را دریافت کرد و روی لینک تایید کلیک کرد به اکشن متد RegisterConfirmation باز می‌گردیم.
private bool ConfirmAccount(string confirmationToken)
{
    ApplicationDbContext context = new ApplicationDbContext();
    ApplicationUser user =  context.Users.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
    if (user != null)
    {
        user.IsConfirmed = true;
        DbSet<ApplicationUser> dbSet = context.Set<ApplicationUser>();
        dbSet.Attach(user);
        context.Entry(user).State = EntityState.Modified;
        context.SaveChanges();
 
        return true;
    }
    return false;
}
 
[AllowAnonymous]
public ActionResult RegisterConfirmation(string Id)
{
    if (ConfirmAccount(Id))
    {
        return RedirectToAction("ConfirmationSuccess");
    }
    return RedirectToAction("ConfirmationFailure");
}
متد ConfirmAccount سعی می‌کند کاربری را در دیتابیس پیدا کند که شناسه تاییدش با مقدار دریافت شده از URL برابر است. اگر این کاربر پیدا شود، مقدار خاصیت IsConfirmed را به true تغییر می‌دهیم و همین مقدار را به تابع باز می‌گردانیم. در غیر اینصورت false بر می‌گردانیم. اگر کاربر تایید شده است، می‌تواند به سایت وارد شود. برای اینکه مطمئن شویم کاربران پیش از تایید ایمیل شان نمی‌توانند وارد سایت شوند، باید اکشن متد Login را کمی تغییر دهیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null && user.IsConfirmed)
        {
            await SignInAsync(user, model.RememberMe);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}
تنها کاری که می‌کنیم این است که به دنبال کاربری می‌گردیم که فیلد IsConfirmed آن true باشد. اگر مقدار این فیلد false باشد کاربر را به سایت وارد نمی‌کنیم و پیغام خطایی نمایش می‌دهیم.
همین. این تمام چیزی بود که برای اضافه کردن تصدیق ایمیل به اپلیکیشن خود نیاز دارید. از آنجا که سیستم ASP.NET Identity با Entity Framework مدیریت می‌شود و با مدل Code First ساخته شده، سفارشی کردن اطلاعات کاربران و سیستم عضویت ساده‌تر از همیشه است.



توضیحاتی درباره کار با Postal

اگر به متد SendEmailConfirmation دقت کنید خواهید دید که آبجکتی از نوع Email می‌سازیم (که در اسمبلی‌های Postal وجود دارد) و از آن برای ارسال ایمیل استفاده می‌کنیم. عبارت "RegEmail" نام نمایی است که باید برای ساخت ایمیل استفاده شود. این متغیر از نوع dynamic است، مانند خاصیت ViewBag. بدین معنا که می‌توانید مقادیر مورد نظر خود را بصورت خواص دینامیک روی این آبجکت تعریف کنید. از آنجا که Postal از موتور Razor استفاده می‌کند، بعدا در View ایمیل خود می‌توانید به این مقادیر دسترسی داشته باشید.
در پوشه Views پوشه جدیدی بنام Emails بسازید. سپس یک فایل جدید با نام RegEmail.cshtml در آن ایجاد کنید. کد این فایل را با لیست زیر جایگزین کنید.
To: @ViewBag.To
From: YOURNAME@gmail.com
Subject: Confirm your registration

Hello @ViewBag.UserName,
Please confirm your registration by following the link bellow.

@Html.ActionLink(Url.Action("RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }), "RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }, null)
این فایل، قالب ایمیل‌های شما خواهد بود. ایمیل‌ها در حال حاظر بصورت plain text ارسال می‌شوند. برای اطلاعات بیشتر درباره ایمیل‌های HTML و امکانات پیشرفته‌تر به سایت پروژه Postal  مراجعه کنید.

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

  • ViewBag.To آدرس ایمیل گیرنده را نشان می‌دهد.
  • ViewBag.UserName نام کاربر جاری را نمایش می‌دهد.
  • ViewBag.ConfirmationToken شناسه تولید شده برای تایید کاربر است.
در این قالب لینکی به متد RegisterConfirmation در کنترلر Account وجود دارد که شناسه تایید را نیز با پارامتری بنام id انتقال می‌دهد.

یک فایل ViewStart.cshtml_ هم در این پوشه بسازید و کد آن را با لیست زیر جایگزین کنید.
@{ Layout = null; /* Overrides the Layout set for regular page views. */ }
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
ممنون بابت پاسخگویی.
فرض کنید در SampleController، اکشنی به نام Edit وجود دارد که سبب نمایش فرم ویرایش میشود که این فرم حاوی دراپ دانی می‌باشد که  اطلاعات خود رو از اکشنی مجزا به نام GetData در همان کنترلر به صورت ajax دریافت میکند.اگر بخواهیم با روش فعلی به این موارد دسترسی دهیم به حالت زیر خواهیم رسید:
 مشاهده فرم ویرایش   SampleController:Edit  ✓ 
 مشاهده محتویات دراپ دان  SampleController:GetData  ✓ 
در این حالت صفحه بدون مشکل لود شده و محتویات دراپ دان نمایش داده میشود.حالتی که تنها اکشن  index به calim افزوده شود در این حالت صفحه بارگذاری میشود اما محتویات دراپ دان به دلیل اینکه در Calim وجود ندارد لود نخواهد شد و درخواست ajax با خطای 401 مواجه میشود. 
روشی وجود دارد که با انتخاب اکشن Edit تمامی اکشن‌های مورد نیاز در فرم ویرایش نیز به Calim اضافه گردد؟
نظرات اشتراک‌ها
چگونه به مخازن کد GitHub، ابزار بررسی کیفیت کدهای CodeQL را اضافه کنیم؟
به صورت خلاصه، دو فایل «codeql.yml » و « codeql-config.yml » را به پوشه‌ی github/workflows. اضافه کنید. نتیجه‌ی آن در برگه‌ی security، فقط برای صاحب مخزن کد قابل مشاهده خواهد بود:

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

  اگر خواستید از موردی صرفنظر کند، rule id فوق را به فایل codeql-config.yml، اضافه کنید. 
نظرات مطالب
یکپارچه سازی سیستم اعتبارسنجی ASP.NET MVC با Kendo UI validator
مراجعه کنید به مطلب «اعتبار سنجی ورودی‌های کاربر در Kendo UI». در انتهای مطلب در مورد «اعتبارسنجی سفارشی در DataSource»  بحث شده‌است. Kendo UI Grid هم اطلاعات اعتبارسنجی فیلدهای خودش را از data source دریافت می‌کند و ... اصول طراحی اعتبارسنجی آن، هیچ تفاوتی با نکته‌ی عنوان شده ندارد (یک custom rule سفارشی را به نام remote، دقیقا مانند همین مثال می‌توانید اضافه کنید).
بازخوردهای پروژه‌ها
چند پیشنهاد به همراه پروژه
با سلام و عرض خسته نباشید خدمت دوست گرامی و عزیز
من تغییراتی در پروژه شما دادم که در ذیل توضیح دادم
خوشحال می‌شوم بررسی فرمایید.
باتشکر

تغییر در نوع چینش models
افزودن PlUploader برای آپلود فایل
تغییر در Partial به نام _breadCrumb
امکان چند انتخاب هم زمان - و همچنین حذف  چند مورد هم زمان
باز شدن پوشه‌ها و دانلود شدن فایل‌ها با دابل کلیک
تغییرات مختصر در style
تغییر در ساختار لینک برگشت -> انتقال از model به view و پیاده سازی با jquery به دلیل سهولت بیشتر
 سوال؟
چرا پوشه سرویس را در app-code نبردید؟

MvcFileManager.rar