نظرات اشتراک‌ها
تحریم شکن بگذر!
در کل بنظر من وب سایت جالبی نیست وب سایت دولوپر اندروید
وارد کردم در قسمت مخصوص سایت هایی که پشتیبانی میکنه برای گذر از تحریم پیغام : بررسی و سپس پشتیبانی خواهد شد. متشکریم !  را نشان میدهد.
نظرات مطالب
قابلیت های جدید VisualStudio.NET 2012 - قسمت دوم
کی معلوم می‌شه که آیا  .net 4.5  ویندوز xp را پشتیبانی می‌کند یا خیر؟
آیا بطور قطع دیگه پشتیبانی نمی‌شود و باید با .net 4 ادامه بدیم :( یا نه؟ 
اینجاهم که به رای گذاشتن و تیم تولید زیر بار نرفته!
نظرات مطالب
EF Code First #4
سلام و بسیار ممنون.
مفهومی به نام «Migrations» در EF 4.3 ارائه شده است . آیا این EF 4.3 توسط دات نت 4 پشتیبانی میشود؟
چون در زمینه ی وب ، هاست ها بیشتر از NET 4. را پشتیبانی نمیکنند.
مطالب
React 16x - قسمت 26 - احراز هویت و اعتبارسنجی کاربران - بخش 1 - ثبت نام و ورود به سیستم
می‌خواهیم به برنامه‌ی لیست فیلم‌هایی که تا این قسمت تکمیل کردیم، امکانات جدیدی را مانند ورود به سیستم، خروج از آن، کار با JWT، فراخوانی منابع محافظت شده‌ی سمت سرور، نمایش و یا مخفی کردن المان‌های صفحه بر اساس سطوح دسترسی کاربر و همچنین محافظت از مسیرهای مختلف تعریف شده‌ی در برنامه، اضافه کنیم.
برای قسمت backend، از همان برنامه‌ی تکمیل شده‌ی قسمت قبل استفاده می‌کنیم که به آن تولید مقدماتی JWTها نیز اضافه شده‌است. البته این سری، مستقل از قسمت سمت سرور آن تهیه خواهد شد و صرفا در حد دریافت توکن از سرور و یا ارسال مشخصات کاربر جهت لاگین، نیاز بیشتری به قسمت سمت سرور آن ندارد و تاکید آن بر روی مباحث سمت کلاینت React است. بنابراین اینکه چگونه این توکن را تولید می‌کنید، در اینجا اهمیتی ندارد و کلیات آن با تمام روش‌های پیاده سازی سمت سرور سازگار است (و مختص به فناوری خاصی نیست). پیشنیاز درک کدهای سمت سرور قسمت JWT آن، مطالب زیر هستند:
  1. «معرفی JSON Web Token»
  2. «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» 
  3. «پیاده سازی JSON Web Token با ASP.NET Web API 2.x»
  4. « آزمایش Web APIs توسط Postman - قسمت ششم - اعتبارسنجی مبتنی بر JWT»  


ثبت یک کاربر جدید

فرم ثبت نام کاربران را در قسمت 21 این سری، در فایل src\components\registerForm.jsx، ایجاد و تکمیل کردیم. البته این فرم هنوز به backend server متصل نیست. برای کار با آن هم نیاز است شیءای را با ساختار زیر که ذکر سه خاصیت اول آن اجباری است، به endpoint ای با آدرس https://localhost:5001/api/Users به صورت یک HTTP Post ارسال کنیم:
{
  "name": "string",
  "email": "string",
  "password": "string",
  "isAdmin": true,
  "id": 0
}
در سمت سرور هم در Services\UsersDataSource.cs که در انتهای بحث می‌توانید پروژه‌ی کامل آن‌را دریافت کنید، منحصربفرد بودن ایمیل وارد شده بررسی می‌شود و اگر یک رکورد دو بار ثبت شود، یک BadRequest را به همراه پیام خطایی، بازگشت می‌دهد.

اکنون نوبت به اتصال کامپوننت registerForm.jsx، به سرویس backend است. تا اینجا دو سرویس src\services\genreService.js و src\services\movieService.js را در قسمت قبل، به برنامه جهت کار کردن با endpoint‌های backend server، اضافه کردیم. شبیه به همین روش را برای کار با سرویس سمت سرور api/Users نیز در پیش می‌گیریم. بنابراین فایل جدید src\services\userService.js را با محتوای زیر، به برنامه‌ی frontend اضافه می‌کنیم:
import http from "./httpService";
import { apiUrl } from "../config.json";

const apiEndpoint = apiUrl + "/users";

export function register(user) {
  return http.post(apiEndpoint, {
    email: user.username,
    password: user.password,
    name: user.name
  });
}
توسط متد register این سرویس می‌توانیم شیء user را با سه خاصیت مشخص شده، از طریق HTTP Post، به آدرس api/Users ارسال کنیم. خروجی این متد نیز یک Promise است. در این سرویس، تمام متدهایی که قرار است با این endpoint سمت سرور کار کنند، مانند ثبت، حذف، دریافت اطلاعات و غیره، تعریف خواهند شد.
اطلاعات شیء user ای که در اینجا دریافت می‌شود، از خاصیت data کامپوننت RegisterForm تامین می‌گردد:
class RegisterForm extends Form {
  state = {
    data: { username: "", password: "", name: "" },
    errors: {}
  };
البته اگر دقت کرده باشید، در شیء منتسب به خاصیت data، خاصیتی به نام username تعریف شده‌است، اما در سمت سرور، نیاز است خاصیتی با نام Name را دریافت کنیم. یک چنین نگاشتی در داخل متد register سرویس کاربر، قابل مشاهده‌‌است. در غیراینصورت می‌شد در متد http.post، کل شیء user را به عنوان پارامتر دوم، درنظر گرفت و ارسال کرد.

پس از تعریف userService.js، به registerForm.jsx بازگشته و ابتدا امکانات آن‌را import می‌کنیم:
import * as userService from "../services/userService";
می‌شد این سطر را به صورت زیر نیز نوشت، تا تنها یک متد از ماژول userService را دریافت کنیم:
import { register } userService from "../services/userService";
اما روش as userService * به معنای import تمام متدهای این ماژول است. به این ترتیب نام ذکر شده‌ی پس از as، به عنوان شیءای که می‌توان توسط آن به این متدها دسترسی یافت، قابل استفاده می‌شود؛ مانند فراخوانی متد userService.register. اکنون می‌توان متد doSubmit این فرم را به سرور متصل کرد:
  doSubmit = async () => {
    try {
      await userService.register(this.state.data);
    } catch (ex) {
      if (ex.response && ex.response.status === 400) {
        const errors = { ...this.state.errors }; // clone an object
        errors.username = ex.response.data;
        this.setState({ errors });
      }
    }
  };


مدیریت و نمایش خطاهای دریافتی از سمت سرور

در این حالت برای ارسال اطلاعات یک کاربر، در بار اول، یک چنین خروجی را از سمت سرور می‌توان شاهد بود که id جدیدی را به این رکورد نسبت داده‌است:


اگر مجددا همین رکورد را به سمت سرور ارسال کنیم، اینبار خطای زیر را دریافت خواهیم کرد:


که از نوع 400 یا همان BadRequest است:


بنابراین نیاز است بدنه‌ی response را در یک چنین مواردی که خطایی از سمت سرور صادر می‌شود، دریافت کرده و با به روز رسانی خاصیت errors در state فرم (همان قسمت بدنه‌ی catch کدهای فوق)، سبب درج و نمایش خودکار این خطا شویم:


پیشتر در قسمت بررسی «کار با فرم‌ها» آموختیم که برای مدیریت خطاهای پیش بینی شده‌ی دریافتی از سمت سرور، نیاز است قطعه کدهای مرتبط با سرویس http را در بدنه‌ی try/catch‌ها محصور کنیم. برای مثال در اینجا اگر ایمیل شخصی تکراری وارد شود، سرویس یک return BadRequest("Can't create the requested record.") را بازگشت می‌دهد که در اینجا status code معادل BadRequest، همان 400 است. بنابراین انتظار داریم که خطای 400 را از سمت سرور، تحت شرایط خاصی دریافت کنیم. به همین دلیل است که در اینجا تنها مدیریت status code=400 را در بدنه‌ی catch نوشته شده ملاحظه می‌کنید.
سپس برای نمایش آن، نیاز است خاصیت متناظری را که این خطا به آن مرتبط می‌شود، با پیام دریافت شده‌ی از سمت سرور، مقدار دهی کنیم که در اینجا می‌دانیم مرتبط با username است. به همین جهت سطر errors.username = ex.response.data، کار انتساب بدنه‌ی response را به خاصیت جدید errors.username انجام می‌دهد. در این حالت اگر به کمک متد setState، کار به روز رسانی خاصیت errors موجود در state را انجام دهیم، رندر مجدد فرم، در صف انجام قرار گرفته و در رندر بعدی آن، پیام موجود در errors.username، نمایش داده می‌شود.


پیاده سازی ورود به سیستم

فرم ورود به سیستم را در قسمت 18 این سری، در فایل src\components\loginForm.jsx، ایجاد و تکمیل کردیم که این فرم نیز هنوز به backend server متصل نیست. برای کار با آن نیاز است شیءای را با ساختار زیر که ذکر هر دو خاصیت آن اجباری است، به endpoint ای با آدرس https://localhost:5001/api/Auth/Login به صورت یک HTTP Post ارسال کنیم:
{
  "email": "string",
  "password": "string"
}
با ارسال این اطلاعات به سمت سرور، درخواست Login انجام می‌شود. سرور نیز در صورت تعیین اعتبار موفقیت آمیز کاربر، به صورت زیر، یک JSON Web token را بازگشت می‌دهد:
var jwt = _tokenFactoryService.CreateAccessToken(user);
return Ok(new { access_token = jwt });
یعنی بدنه‌ی response رسیده‌ی از سمت سرور، دارای یک شیء JSON خواهد بود که خاصیت access_token آن، حاوی JSON Web token متعلق به کاربر جاری لاگین شده‌است. در آینده اگر این کاربر نیاز به دسترسی به یک api endpoint محافظت شده‌ای را در سمت سرور داشته باشد، باید این token را نیز به همراه درخواست خود ارسال کند تا پس از تعیین اعتبار آن توسط سرور، مجوز دسترسی به منبع درخواستی برای او صادر شود.

در ادامه برای تعامل با منبع api/Auth/Login سمت سرور، ابتدا یک سرویس مختص آن‌را در فایل جدید src\services\authService.js، با محتوای زیر ایجاد می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/auth";

export function login(email, password) {
  return http.post(apiEndpoint + "/login", { email, password });
}
متد login، کار ارسال ایمیل و کلمه‌ی عبور کاربر را به اکشن متد Login کنترلر Auth، انجام می‌دهد و خروجی آن یک Promise است. برای استفاده‌ی از آن به کامپوننت src\components\loginForm.jsx بازگشته و متد doSubmit آن‌را به صورت زیر تکمیل می‌کنیم:
import * as auth from "../services/authService";

class LoginForm extends Form {
  state = {
    data: { username: "", password: "" },
    errors: {}
  };

  // ...

  doSubmit = async () => {
    try {
      const { data } = this.state;
      const {
        data: { access_token }
      } = await auth.login(data.username, data.password);
      console.log("JWT", access_token);
      localStorage.setItem("token", access_token);
      this.props.history.push("/");
    } catch (ex) {
      if (ex.response && ex.response.status === 400) {
        const errors = { ...this.state.errors };
        errors.username = ex.response.data;
        this.setState({ errors });
      }
    }
  };
توضیحات:
- ابتدا تمام خروجی‌های ماژول authService را با نام شیء auth دریافت کرده‌ایم.
- سپس در متد doSubmit، اطلاعات خاصیت data موجود در state را که معادل فیلدهای فرم لاگین هستند، به متد auth.login برای انجام لاگین سمت سرور، ارسال کرده‌ایم. این متد چون یک Promise را باز می‌گرداند، باید await شود و پس از آن متد جاری نیز باید به صورت async معرفی گردد.
- همانطور که عنوان شد، خروجی نهایی متد auth.login، یک شیء JSON دارای خاصیت access_token است که در اینجا از خاصیت data خروجی نهایی دریافت شده‌است.
- سپس نیاز است برای استفاده‌های آتی، این token دریافتی از سرور را در جایی ذخیره کرد. یکی از مکان‌های متداول اینکار، local storage مرورگرها است (اطلاعات بیشتر).
- در آخر کاربر را توسط شیء history سیستم مسیریابی برنامه، به صفحه‌ی اصلی آن هدایت می‌کنیم.
- در اینجا قسمت catch نیز ذکر شده‌است تا خطاهای حاصل از return BadRequestهای دریافتی از سمت سرور را بتوان ذیل فیلد نام کاربری نمایش داد. روش کار آن، دقیقا همانند روشی است که برای فرم ثبت یک کاربر جدید استفاده کردیم.

اکنون اگر برنامه را ذخیره کرده و اجرا کنیم، توکن دریافتی، در کنسول توسعه دهندگان مرورگر لاگ شده و سپس کاربر به صفحه‌ی اصلی برنامه هدایت می‌شود. همچنین این token ذخیره شده را می‌توان در ذیل قسمت application->storage آن نیز مشاهده کرد:



لاگین خودکار کاربر، پس از ثبت نام در سایت

پس از ثبت نام یک کاربر در سایت، بدنه‌ی response بازگشت داده شده‌ی از سمت سرور، همان شیء user است که اکنون Id او مشخص شده‌است. بنابراین اینبار جهت ارائه‌ی token از سمت سرور، بجای response body، از یک هدر سفارشی در فایل Controllers\UsersController.cs استفاده می‌کنیم (کدهای کامل آن در انتهای بحث پیوست شده‌است):
var jwt = _tokenFactoryService.CreateAccessToken(user);
this.Response.Headers.Add("x-auth-token", jwt);



در ادامه در کدهای سمت کلاینت src\components\registerForm.jsx، برای استخراج این هدر سفارشی، اگر شیء response دریافتی از سرور را لاگ کنیم:
const response = await userService.register(this.state.data);
console.log(response);
یک چنین خروجی را به همراه دارد که در آن، هدر سفارشی ما درج نشده‌است و فقط هدر content-type در آن مشخص است:


برای اینکه در کدهای سمت کلاینت بتوان این هدر سفارشی را خواند، نیاز است هدر مخصوص access-control-expose-headers را نیز به response اضافه کرد:
var jwt = _tokenFactoryService.CreateAccessToken(data);
this.Response.Headers.Add("x-auth-token", jwt);
this.Response.Headers.Add("access-control-expose-headers", "x-auth-token");
به این ترتیب وب سرور برنامه، هدر سفارشی را که قرار است برنامه‌ی کلاینت به آن دسترسی پیدا کند، مجاز اعلام می‌کند. اینبار اگر خروجی دریافتی از Axios را لاگ کنیم، در لیست هدرهای آن، هدر سفارشی x-auth-token نیز ظاهر می‌شود:


اکنون می‌توان این هدر سفارشی را در متد doSubmit کامپوننت RegisterForm، از طریق شیء response.headers خواند و در localStorage ذخیره کرد. سپس کاربر را توسط شیء history سیستم مسیریابی، به ریشه‌ی سایت هدایت نمود:
class RegisterForm extends Form {
  // ...

  doSubmit = async () => {
    try {
      const response = await userService.register(this.state.data);
      console.log(response);
      localStorage.setItem("token", response.headers["x-auth-token"]);
      this.props.history.push("/");
    } catch (ex) {
      if (ex.response && ex.response.status === 400) {
        const errors = { ...this.state.errors }; // clone an object
        errors.username = ex.response.data;
        this.setState({ errors });
      }
    }
  };

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-26-backend.zip و sample-26-frontend.zip
مطالب
سشن صفر

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



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



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


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




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

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

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

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

مطالب
پیاده سازی Row Level Security در Entity framework
در این مقاله قصد داریم به صورت عملی row level security را در زبان #C و Entity framework پیاده سازی نماییم. اینکار باعث خواهد شد، پروژه refactoring آسان‌تری داشته باشد، همچنین باعث کاهش کد‌ها در سمت لایه business می‌گردد و یا اگر از DDD استفاده میکنید، در سرویس‌های خود به صورت چشم گیری کد‌های کمتر و واضح‌تری خواهید داشت. در مجموع راه حل‌های متنوعی برای پیاده سازی این روش ارائه شده است که یکی از آسان‌‌ترین روش‌های ممکن برای انجام اینکار استفاده‌ی درست از interface‌ها و همچنین بحث validation آن در سمت Generic repository میباشد.
فرض کنید در سمت مدل‌های خود User و Post را داشته باشیم و بخواهیم بصورت اتوماتیک رکورد‌های Post مربوط به هر User بارگذاری شود بطوریکه دیگر احتیاجی به نوشتن شرط‌های تکراری نباشد و در صورتیکه آن User از نوع Admin بود، همه‌ی Postها را بتواند ببیند. برای اینکار یک پروژه‌ی Console Application را در Visual studio به نام "Console1" ساخته و بصورت زیر عمل خواهیم کرد:

ابتدا لازم است Entity framework را توسط Nuget packages manager دانلود نمایید. سپس به پروژه‌ی خود یک فولدر جدید را به نام Models و درون آن ابتدا یک کلاس را به نام User اضافه کرده و بدین صورت خواهیم داشت :
using System;

namespace Console1.Models
{
    public enum UserType
    {
        Admin,
        Ordinary
    }
    public class User
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }

        public UserType Type { get; set; }

    }
}  

UserType نیز کاملا مشخص است؛ هر User نقش Admin یا Ordinary را می‌تواند داشته باشد.

نوبت به نوشتن اینترفیس IUser میرسد. در همین پوشه‌ای که قرار داریم، آن را پیاده سازی مینماییم:

namespace Console1.Models
{
    public interface IUser
    {
        int UserId { get; set; }

        User User { get; set; }
    }
}

هر entity که با User ارتباط دارد، باید اینترفیس فوق را پیاده سازی نماید. حال یک کلاس دیگر را به نام Post در همین پوشه درست کرده و بدین صورت پیاده سازی مینماییم.

using System.ComponentModel.DataAnnotations.Schema;

namespace Console1.Models
{
    public class Post : IUser
    {
        public int Id { get; set; }

        public string Context { get; set; }

        public int UserId { get; set; }

        [ForeignKey(nameof(UserId))]
        public User User { get; set; }

    }
}

واضح است که relation از نوع one to many برقرار است و هر User میتواند n تا Post داشته باشد.

خوب تا اینجا کافیست و میخواهیم مدل‌های خود را با استفاده از EF به Context معرفی کنیم. میتوانیم در همین پوشه کلاسی را به نام Context ساخته و بصورت زیر بنویسیم

using System.Data.Entity;

namespace Console1.Models
{
    public class Context : DbContext
    {
        public Context() : base("Context")
        {
        }

        public DbSet<User> Users { get; set; }

        public DbSet<Post> Posts { get; set; }
    }
}

در اینجا مشخص کرده‌ایم که دو Dbset از نوع User و Post را داریم. بدین معنا که EF دو table را برای ما تولید خواهد کرد. همچنین نام کلید رشته‌ی اتصالی به دیتابیس خود را نیز، Context معرفی کرده‌ایم.

خوب تا اینجا قسمت اول پروژه‌ی خود را تکمیل کرده‌ایم. الان میتوانیم با استفاده از Migration دیتابیس خود را ساخته و همچنین رکوردهایی را بدان اضافه کنیم. در Package Manager Console خود دستور زیر را وارد نمایید:

enable-migrations

به صورت خودکار پوشه‌ای به نام Migrations ساخته شده و درون آن Configuration.cs قرار می‌گیرد که آن را بدین صورت تغییر میدهیم:

namespace Console1.Migrations
{
    using Models;
    using System.Data.Entity.Migrations;

    internal sealed class Configuration : DbMigrationsConfiguration<Console1.Models.Context>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(Console1.Models.Context context)
        {

            context.Users.AddOrUpdate(x => x.Id,
              new User { Id = 1, Name = "aaa", Age = 30, Type = UserType.Admin },
              new User { Id = 2, Name = "bbb", Age = 20, Type = UserType.Ordinary },
              new User { Id = 3, Name = "ccc", Age = 25, Type = UserType.Ordinary }
            );

            context.Posts.AddOrUpdate(x => x.Id,
                new Post { Context = "ccc 1", UserId = 3 },
                new Post { Context = "bbb 1", UserId = 2 },
                new Post { Context = "bbb 2", UserId = 2 },
                new Post { Context = "aaa 1", UserId = 1 },
                new Post { Context = "bbb 3", UserId = 2 },
                new Post { Context = "ccc 2", UserId = 3 },
                new Post { Context = "ccc 3", UserId = 3 }
            );

            context.SaveChanges();
        }
    }
}

در متد seed، رکورد‌های اولیه را به شکل فوق وارد کرده ایم (رکورد‌ها فقط به منظور تست میباشند*). در کنسول دستور Update-database را ارسال کرده، دیتابیس تولید خواهد شد.

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


حال میخواهیم به پیاده سازی بخش اصلی این مقاله یعنی repository که از Row Level Security پشتیبانی میکند بپردازیم. در ریشه‌ی پروژه‌ی خود پوشه‌ای را به نام Repository ساخته و درون آن کلاسی را به نام GenericRepository میسازیم. پروژه‌ی شما هم اکنون باید ساختاری شبیه به این را داشته باشد.

GenericRepository.cs را اینگونه پیاده سازی مینماییم

using Console1.Models;
using System;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;

namespace Console1.Repository
{
    public interface IGenericRepository<T>
    {
        IQueryable<T> CustomizeGet(Expression<Func<T, bool>> predicate);
        void Add(T entity);
        IQueryable<T> GetAll();
    }

    public class GenericRepository<TEntity, DbContext> : IGenericRepository<TEntity>
        where TEntity : class, new() where DbContext : Models.Context, new()
    {

        private DbContext _entities = new DbContext();

        public IQueryable<TEntity> CustomizeGet(Expression<Func<TEntity, bool>> predicate)
        {
            IQueryable<TEntity> query = _entities.Set<TEntity>().Where(predicate);
            return query;
        }

        public void Add(TEntity entity)
        {
            int userId = Program.UserId; // یوزد آی دی بصورت فیک ساخته شده
                            // اگر از آیدنتیتی استفاده میکنید میتوان آی دی و هر چیز دیگری که کلیم شده را در اختیار گرفت

            if (typeof(IUser).IsAssignableFrom(typeof(TEntity)))
            {
                ((IUser)entity).UserId = userId;
            }

            _entities.Set<TEntity>().Add(entity);
        }

        public IQueryable<TEntity> GetAll()
        {
            IQueryable<TEntity> result = _entities.Set<TEntity>();

            int userId = Program.UserId; // یوزد آی دی بصورت فیک ساخته شده
                            // اگر از آیدنتیتی استفاده میکنید میتوان آی دی و هر چیز دیگری که کلیم شده را در اختیار گرفت

            if (typeof(IUser).IsAssignableFrom(typeof(TEntity)))
            {
                User me = _entities.Users.Single(c => c.Id == userId);
                if (me.Type == UserType.Admin)
                {
                    return result;
                }
                else if (me.Type == UserType.Ordinary)
                {
                    string query = $"{nameof(IUser.UserId).ToString()}={userId}";
                    
                    return result.Where(query);
                }
            }
            return result;
        }
        public void Commit()
        {
            _entities.SaveChanges();
        }
    }
}
توضیح کد‌های فوق

1) یک اینترفیس Generic را به نام IGenericRepository داریم که کلاس GenericRepository قرار است آن را پیاده سازی نماید.

2) این اینترفیس شامل متدهای CustomizeGet است که فقط یک predicate را گرفته و خیلی مربوط به این مقاله نیست (صرفا جهت اطلاع). اما متد Add و GetAll بصورت مستقیم قرار است هدف row level security را برای ما انجام دهند.

3) کلاس GenericRepository دو Type عمومی را به نام TEntity و DbContext گرفته و اینترفیس IGenericRepository را پیاده سازی مینماید. همچنین صریحا اعلام کرده‌ایم TEntity از نوع کلاس و DbContext از نوع Context ایی است که قبلا نوشته‌ایم.

4) پیاده سازی متد CustomizeGet را مشاهده مینمایید که کوئری مربوطه را ساخته و بر میگرداند.

5) پیاده سازی متد Add بدین صورت است که به عنوان پارامتر، TEntity را گرفته (مدلی که قرار است save شود). بعد مشاهد میکنید که من به صورت hard code به UserId مقدار داده‌ام. قطعا میدانید که برای این کار به فرض اینکه از Asp.net Identity استفاده میکنید، میتوانید Claim آن Id کاربر Authenticate شده را بازگردانید.

با استفاده از IsAssignableFrom مشخص کرده‌ایم که آیا TEntity یک Typeی از IUser را داشته است یا خیر؟ در صورت true بودن شرط، UserId را به TEntity اضافه کرده و بطور مثال در Service‌های خود نیازی به اضافه کردن متوالی این فیلد نخواهید داشت و در مرحله‌ی بعد نیز آن را به entity_ اضافه مینماییم.

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

6) به جالبترین متد که GetAll میباشد میرسیم. ابتدا کوئری را از آن Entity ساخته و در مرحله‌ی بعد مشخص مینماییم که آیا TEntity یک Typeی از IUser میباشد یا خیر؟ در صورت برقرار بودن شرط، User مورد نظر را یافته در صورتیکه Typeی از نوع Admin داشت، همه‌ی مجموعه را برخواهیم گرداند (Admin میتواند همه‌ی پست‌ها را مشاهده نماید) و در صورتیکه از نوع Ordinary باشد، با استفاده از dynamic linq، کوئری مورد نظر را ساخته و شرط را ایجاد می‌کنیم که UserId برابر userId مورد نظر باشد. در این صورت بطور مثال همه‌ی پست‌هایی که فقط مربوط به user خودش میباشد، برگشت داده میشود.

نکته: برای دانلود dynamic linq کافیست از طریق nuget آن را جست و جو نمایید: System.Linq.Dynamic

و اگر هم از نوع IUser نبود، result را بر میگردانیم. بطور مثال فرض کنید مدلی داریم که قرار نیست security روی آن اعمال شود. پس کوئری ساخته شده قابلیت برگرداندن همه‌ی رکورد‌ها را دارا میباشد. 

7) متد Commit هم که پرواضح است عملیات save را اعمال میکند.


قبلا در قسمت Seed رکوردهایی را ساخته بودیم. حال میخواهیم کل این فرآیند را اجرا نماییم. Program.cs را از ریشه‌ی پروژه‌ی خود باز کرده و اینگونه تغییر میدهیم:

using System;
using Console1.Models;
using Console1.Repository;
using System.Collections.Generic;
using System.Linq;

namespace Console1
{
    public class Program
    {
        public static int UserId = 1; //fake userId
        static void Main()
        {
            GenericRepository<Post, Context> repo = new GenericRepository<Post, Context>();

            List<Post> posts = repo.GetAll().ToList();

            foreach (Post item in posts)
                Console.WriteLine(item.Context);

            Console.ReadKey();
        }
    }
}

همانطور که ملاحظه می‌کنید، UserId به صورت fake ساخته شده است. آن چیزی که هم اکنون در دیتابیس رفته، بدین صورت است که UserId = 1 برابر Admin و بقیه Ordinary میباشند. در متد Main برنامه، یک instance از GenericRepository را گرفته و بعد با استفاده از متد GetAll و لیست کردن آن، همه‌ی رکورد‌های مورد نظر را برگردانده و سپس چاپ مینماییم. در صورتی که UserId برابر 1 باشد، توقع داریم که همه‌ی رکورد‌ها بازگردانده شود:

حال کافیست مقدار userId را بطور مثال تغییر داده و برابر 2 بگذاریم. برنامه را اجرا کرده و مشاهد می‌کنیم که با تغییر یافتن userId، عملیات مورد نظر متفاوت می‌گردد و به صورت زیر خواهد شد:

میبینید که تنها با تغییر userId رفتار عوض شده و فقط Postهای مربوط به آن User خاص برگشت داده میشود.


از همین روش میتوان برای طراحی Repositoryهای بسیار پیچیده‌تر نیز استفاده کرد و مقدار زیادی از validationها را به طور مستقیم بدان واگذار نمود!

دانلود کد‌ها در Github

مطالب
Ef 6 و Ngen : شروعی سریعتر برای برنامه های مبتنی بر Entity Framework
تولید کد Native زمانی اتفاق می‌افتد که کامپایلر JIT، کد اسمبلی‌های MSIL را به کدهای Native در ماشین محلی کامپایل می‌کند و این عمل بلافاصله قبل از اجرای متد برای اولین بار اتفاق می‌افتد. این کد به صورت موقتی بوده و در حافظه‌ای که برای پردازش در نظر گرفته شده ذخیره می‌شود و در پایان هر پردازش توسط سیستم عامل ویرایش می‌شود. کد Native به ازای هر بار شروع یک پردازش تولید می‌شود. ابزار Native Image Generator یا همان Ngen اقدام به تولید کد Native با استفاده از کامپایلر JIT نموده و آن را در هارد دیسک ذخیره می‌نماید. زمانیکه برنامه نیازمند یک اسمبلی CLR است، به جای بارگذاری خود اسمبلی، ایمیج کد Native آن بارگذاری می‌شود. به این نکته نیز توجه داشته باشید که CLR اطلاعاتی در مورد اینکه کدام اسمبلی، ایمیج کد Native است و این ایمیج در کجا و در چه زمانی تهیه شده است، دارد. کد Native باعث بهبود استفاده از حافظه می‌شود، زمانیکه یک اسمبلی بین پروسس‌ها به اشتراک گذاشته شده‌است. تا قبل از EF6 کتابخانه‌های هسته‌ای EF در زمان اجرا جزئی از دات نت فریمورک بودند و تولید کد Native آنها به صورت اتوماتیک انجام می‌شد. اما از نسخه 6، تمامی این کتابخانه‌ها در داخل پکیج Nuget آن ترکیب شده‌اند . پس برای تولید کد Native مربوط به فایل EntityFramework.dll نیازمند ابزار Ngen هستیم.
1- ابتدا یک برنامه‌ی ساده کنسول ویندوز ساخته و از Package Manager Console دستور Install-package entityframework را اجرا نموده تا پکیج Ef به برنامه اضافه گردد.
using System;
using System.Data.Entity;

namespace UsingNgen
{

    public class NgenDbContex : DbContext
    { }

    class Program
    {
        static void Main()
        {
            var nGenCtx = new NgenDbContex();
            Console.WriteLine("Press a key to exit...");
            Console.ReadKey();
        }
    }


}
حال کد ساده بالا را به برنامه اضافه می‌کنیم و برنامه را Build میکنیم.
2- برای ثبت جزئیات اجرای برنامه از ابزار Windows Performance Recorder که جزئی از ویندوز می‌باشد، استفاده می‌کنیم. کافیست عبارت WPR را در نوار جستجوی ویندوز تایپ کنید تا این ابزار در دسترس قرار گیرد. 



برای ضبط جزئیات، روی دکمه‌ی Start کلیک کنید و به محل ذخیره‌ی فایل اجرایی حاصل از Build ویژوال استودیو رفته و آن را اجرا کنید. بعد از اتمام اجرا، جزئیات را ذخیره نمایید.

بعد از ذخیره فایل، در پنجره بالا دکمه‌ای به نام Open in WPA ظاهر می‌شود. WPA مخفف Windows Performance Analyzer می‌باشد. آن را کلیک کنید تا محیط آنالایزر باز شود.

حال در سمت چپ این پنجره انواع آنالایزرها را مشاهده می‌کنید. روی آنالایزر Computation کلیک کنید و از زیرمجموعه‌ی آن، CPU Usage را انتخاب کنید. آمار مربوط به برنامه خودمان را در تصویر بالا مشاهده می‌کنید. کل برنامه 164 میلی ثانیه زمان برده و فایل Clr.dll حدود 47 میلی ثانیه و یک فایل clrjit.dll نیز برای تولید کد JIT وجود دارد. حال برای تسریع در عمل شروع، از تکنیک Ngen به صورت زیر استفاده می‌کنیم.

3- دوباره به نوار جستجوی ویندوز رفته و ابزار Developer Command Prompt for VsXXXX را با امتیاز دسترسی از نوع Admin اجرا کنید. XXXX نسخه‌ی ویژوال استودیو می‌باشد.

حال به محل ذخیره فایل اجرایی برنامه رفته و دستور Ngen Install EntityFramework.dll را تایپ کنید تا یک ایمیج کد Native از entityframework.dll ساخته شود. دوباره ابزار Windows Performance Recorder را لود کرده و روی دکمه Start کلیک کنید و فایل اجرایی برنامه را اجرا نمایید. پس از اتمام عملیات ثبت جزئیات، آن را در Windows Performance Analyzer باز نمایید.

همانطور که مشاهده می‌کنید کل برنامه ما 89 میلی ثانیه زمان برده و Clr.dll 29 ثانیه و به جای clrjit.dll فایل EntityFramework به صورت native تولید شده است.

مطالب
توسعه کنترلر و مدل در F# MVC4
در پست قبلی با F# MVC4 Template آشنا شدید. در این پست به توسعه کنترلر و مدل در قالب مثال خواهم پرداخت. برای شروع ابتدا یک پروژه همانند مثال ذکر شده در پست قبلی ایجاد کنید. در پروژه #C ساخته شده که صرفا برای مدیریت View‌ها است یک View جدید به صورت زیر ایجاد نمایید:
@model IEnumerable<FsWeb.Models.Book>
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" 
        href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
</head>
 
<body>
    <div data-role="page" data-theme="a" id="booksPage">
        <div data-role="header">
            <h1>Guitars</h1>
        </div>
        <div data-role="content">
        <ul data-role="listview" data-filter="true" data-inset="true"> 
            @foreach(var x in Model) {
                <li><a href="#">@x.Name</a></li>
            }
        </ul>
        </div>
    </div>
    <script src="http://code.jquery.com/jquery-1.6.4.min.js">
    </script>
    <script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js">
    </script>

    <script>
        $(document).delegate("#bookPage", 'pageshow', function (event) {
            $("div:jqmData(role='content') > ul").listview('refresh');
        });
    </script>
</body>
</html>
از آنجا که هدف از این پست آشنایی با بخش #F پروژه‌های وب است در نتیجه نیازی به توضیح کد‌های بالا دیده نمی‌شود. 
برای ساخت کنترلر جدید، در پروژه #F ساخته شده یک Source File ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
namespace FsWeb.Controllers

open System.Web.Mvc
open FsWeb.Models

[<HandleError>]
type BooksController() =
    inherit Controller()
    member this.Index () =   
        seq { yield  Book(Name = "My F# Book")
              yield Book(Name = "My C# Book") }
        |> this.View
در کد‌های بالا ابتدا یک کنترلر به نام BookController ایجاد کردیم که از کلاس Controller ارث برده است(با استفاده از inherit).  سپس یک تابع به نام Index داریم(به عنوان Action مورد نظر در کنترلر) که آرایه ای از کتاب‌ها به عنوان پارامتر به تابع View می‌فرستد.( توسط اپراتور Pipe - <|). در نهایت دستور this.View  معادل فراخوانی اکشن ()View در پروژه‌های #C است که View متناظر با اکشن را فراخوانی می‌کند. همان طور که ملاحظه می‌نمایید بسیار شبیه به پیاده سازی #C است.
اما نکته ای که در مثال بالا وجود دارد این است که دو نمونه از نوع Book را برای ساخت seq وهله سازی می‌کند. در نتیجه باید Book Type را به عنوان مدل تعریف کنیم. به صورت زیر:
namespace FsWeb.Models
 type Book = { Id : Guid; Name : string }
البته در F# 3.0 امکانی فراهم شده است به نام Auto-Properties که شبیه تعریف خواص در #C است. در نتیجه می‌توان تعریف بالا را به صورت زیر نیز بازنویسی کرد:
namespace FsWeb.Models 
type Book() = member val Name = "" with get, set
Attribute‌ها مدل
اگر همچون پروژه‌های #C قصد دارید با استفاده از Attribute‌ها مدل خود را اعتبارسنجی نمایید می‌توانید به صورت زیر اقدام نمایید:
open System.ComponentModel.DataAnnotations
 type Book() = [<Required>] member val Name = "" with get, set
هم چنین می‌توان Attribute‌های مورد نظر برای مدل EntityFramework را نیز اعمال نمود(نظیر Key):
namespace FsWeb.Models 
open System 
open System.ComponentModel.DataAnnotations 
type Book() = [<Key>] member val Id = Guid.NewGuid() with get, set 
[<Required>] member val Name = "" with get, set

نکته: دستور open معادل با using در #C است.
در پست بعدی برای تکمیل مثال جاری، روش طراحی Repository با استفاده از EntityFramework بررسی خواهد شد.
اشتراک‌ها
Entity Framework Core 5.0 Preview 4 منتشر شد
// Configure database precision/scale in model
modelBuilder
    .Entity<Blog>()
    .Property(b => b.Numeric)
    .HasPrecision(16, 4);

// Specify SQL Server index fill factor
modelBuilder
    .Entity<Customer>()
    .HasIndex(e => e.Name)
    .HasFillFactor(90);
Entity Framework Core 5.0 Preview 4 منتشر شد
اشتراک‌ها
تغییر نام رسمی ASP.NET 5 و EF 7

Reintroducing ASP.NET Core 1.0 and .NET Core 1.0

ASP.NET 5 is now ASP.NET Core 1.0.
.NET Core 5 is now .NET Core 1.0.
Entity Framework 7 is now Entity Framework Core 1.0 or EF Core 1.0 colloquially.
 

تغییر نام رسمی ASP.NET 5 و EF 7