نظرات مطالب
افزودن خودکار کلاس‌های تنظیمات نگاشت‌ها در EF Code first
از AppDomain برای یافتن اسمبلی مدنظر استفاده کنید:
  var enumerable = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(assembly => assembly.GetTypes())
        .Select(type => type.Namespace)
        .Distinct()
        .Where(name => name != null &&
                       name == "DomainClasses.Mappings");
مطالب
اتصال به بانک اراکل بدون نیاز به نصب oracleclient و یا استفاده از کنترلهای Devart
گام اول: دانلود پکیج odp.net از وبسایت اراکل(Free).
گام دوم: فایلهای
 
size  filename 
 922kb  oci.dll
 1,386kb  Oracle.DataAccess.dll
 1,256kb  orannzsbb11.dll
 127,196kb  oraociei11.dll
 344kb  OraOps11w.dll
در کنار app کپی کنید 
گام سوم: ایجاد connectionstring به صورت زیر:
 Dim oradb As String = "Data Source=(DESCRIPTION=(ADDRESS_LIST=" _
                  + "(ADDRESS=(PROTOCOL=TCP)(HOST=YourServer)(PORT=YourPort)))" _
                  + "(CONNECT_DATA=(SERVER=DEDICATED)(SID=ORCL)));" _
                  + "User Id=UserName;Password=Password;"
        Dim conn As New OracleConnection(oradb)
        Try
            conn.Open()
        Catch ex As Exception
            Throw
        End Try
موفق باشید
نظرات مطالب
LocalDB چیست؟
- یعنی حالت سرور داشته باشد و بتوان به آن مستقیما از راه دور متصل شد. مثل SQL Server کامل.
- هاست اشتراکی باید این بانک اطلاعاتی را پیشتر نصب کرده باشد.
مانند SQL Server CE نیست که با توزیع چند DLL مشکل آن برطرف شود و نیازی به نصب نداشته باشد.
نظرات مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت سوم
راه حل: نباید این‌کار را انجام دهید.
علت:
- اگر افزونه‌ای قرار هست برنامه‌ی اصلی را تغدیه کند - مثلا اعتبارسنجی - نام آن افزونه نیست و نباید به صورت افزونه تعریف شود. برنامه‌ی اصلی بجز بارگذاری افزونه‌ها هیچ کار دیگری قرار نیست با جزئیات آن‌ها به صورت مستقیم انجام دهد.
- اگر افزونه‌ای وابسته‌است به افزونه‌ی دیگر، نام اینکار افزونه نویسی نیست.
- شما قبل از اینکه بخواهید وارد این مبحث شوید، نیاز است کمی در مورد برنامه‌های افزونه پذیر موجود (در حالت کلی) مطالعه کنید و بررسی کنید که مثلا اگر یک برنامه‌ی پخش music افزونه پذیر است، افزونه‌ی A آن که توسط فرد X تهیه شده، آیا قرار است از امکانات افزونه‌ی B که توسط فرد Y تهیه شده‌است، استفاده کند؟ چنین کاری اساسا بی‌مفهوم است و طراحی افزونه پذیر نام ندارد. آیا افزونه‌ی A فایرفاکس از افزونه‌ی B آن استفاده می‌کند و به آن وابسته‌‌است؟ خیر.
- اگر قرار هست افزونه‌ها به یک سری اطلاعات مشترک دسترسی پیدا کنند، این اطلاعات باید مشترک باشند و مستقل از هر کدام از افزونه‌ها.

در مثالی که ارائه شد، اگر هدف کوئری گرفتن از لیست خبرهای یک کاربر است، این کار فقط باید در افزونه‌ی News انجام شود (چون اگر قرار باشد سایر افزونه‌ها به ریز اطلاعات news دسترسی داشته باشند که ضرورتی به افزونه تعریف کردن آن نبود) و به این صورت:
 var userNewsList =  _news.Include(x=>x.User).Where(x=>x.UserId == 1).ToList();
نظرات مطالب
اثر وجود سشن بر پردازش موازی در ASP.NET

سلام؛  اگر بخواهیم از سشن استفاده نکنیم، برای نگهداری اطلاعات کسی که لاگین کرده چه راهی وجود داره؟ مثلاً UserId یا LoginId که با اون بشه به UserId رسید.

مطالب
کار با Docker بر روی ویندوز - قسمت هفتم - مدیریت اجرای چندین کانتینر به هم وابسته
تا اینجا نحوه‌ی اجرای برنامه‌ها، وب سرورها و حتی بانک‌های اطلاعاتی را توسط داکر بررسی کردیم. در این قسمت می‌خواهیم یک برنامه و بانک اطلاعاتی مخصوص آن‌را داخل یک کانتینر اجرا کنیم و برای این منظور از ابزار ساده کننده‌ی docker-compose استفاده خواهیم کرد.


docker-compose چیست؟

فرض کنید برنامه‌ی ما، از یک قسمت منطق خود برنامه و قسمت دیگر بانک اطلاعاتی آن تشکیل شده‌است. در این حالت برای توزیع آن توسط کانتینرها، نیاز به دو کانتینر مجزا خواهد بود؛ یکی برای برنامه و دیگری برای بانک اطلاعاتی:
dockerrun --name db`
-d `
-p 3306:3306 `
-e MYSQL_ROOT_PASSWORD=my-secret-pw `
-v db:/var/lib/mysql`
mysql

dockerinspect db # extract ipaddress

dockerrun --name web `
-d `
-p 8080:80 `
-e MY_DB_PORT=3306 `
-e MY_DB_HOST=? `
-v /my/php/app:/usr/share/nginx/html `
nginx
تمام این دستورات را به همراه یک ` نیز مشاهده می‌کنید. این روشی است که از آن برای چندسطری کردن دستورات در PowerShell استفاده می‌شود.
- دستور اول مطابق توضیحات قسمت قبل، یک بانک اطلاعاتی MySQL را در پس زمینه، با نام db که در آن، پورت 3306 میزبان به پورت 3306 کانتینر نگاشت شده‌است و همچنین بانک اطلاعاتی آن در یک volume نامدار به نام db با مسیر نگاشت شده‌ی به /var/lib/mysql/ داخل کانتینر ایجاد می‌شود، اجرا می‌کند.
- دستور دوم کار استخراج اطلاعات این کانتینر را انجام می‌دهد که شامل آدرس IP آن نیز می‌باشد. از این IP در برنامه‌ی وب استفاده خواهیم کرد.
- دستور سوم مطابق توضیحات قسمت پنجم، یک وب سرور nginx را برای هاست یک برنامه‌ی PHP که در آن پورت 8080 میزبان به پورت 80 کانتینر نگاشت شده‌است و همچنین فایل‌های آن از مسیر /my/php/app/ میزبان به مسیر /usr/share/nginx/html/ داخل کانتینر نگاشت و تامین می‌شوند، اجرا می‌کند. در اینجا از پارامتر e برای تعریف یک سری متغیر محیطی مانند شماره‌ی پورت و IP کانتینر اجرا کننده‌ی mysql، استفاده شده‌است.

در این مثال دو کانتینر به هم وابسته را اجرا کرده‌ایم و برای اجرای کانتینر دوم، نیاز است حداقل IP کانتینر اول را دانست و در قسمت MY_DB_HOST مقدار دهی کرد. روش دیگری نیز برای مدیریت ساده‌تر اجرای چندین کانتینر به هم وابسته توسط ابزاری به نام docker-compose وجود دارد. اگر از Dockerfile (که آن‌را در قسمت پنجم معرفی کردیم) جهت ایجاد Imageهای سفارشی بکار می‌رود، فایل docker-compose.yml، کار خودکار سازی ایجاد و اجرای چندین کانتینر را انجام می‌دهد که با قالب YAML تعریف می‌شود:
version: '2'
services:
    db:
         image: mysql
         ports:
             -3306:3306
         environment:
             -MYSQL_ROOT_PASSWORD=my-secret-pw
         volumes:
             -db:/var/lib/mysql

    web:
         image: nginx
         ports:
             -8080:80
         environment:
             -MY_DB_PORT=3306
             -MY_DB_HOST=db
         volumes:
             -/my/php/app:/usr/share/nginx/html
همانطور که مشاهده می‌کنید، هرچند قالب آن اندکی متفاوت شده‌است، اما در اصل با دستورات docker ای که در ابتدا معرفی کردیم، تفاوتی ندارد. محتوای فوق را در یک فایل متنی ویژه به نام docker-compose.yml ذخیره و آن‌را به ابزار خط فرمان دیگری به نام docker-compose معرفی می‌کنیم.
در ابتدای این فایل، شماره نگارش قالب YAML مورد استفاده، مشخص شده‌است. در این نگارش، به کانتینرها، services گفته می‌شود که در اینجا دو سرویس db و web را مشاهده می‌کنید. در فایل‌های yml، فضاهای خالی و indentations مهم هستند و بر این اساس است که کانتینرها و سپس مشخصات این کانتینرها، تمیز داده می‌شوند.


راه اندازی TeamCity به کمک فایل docker-compose.yml آن

در اینجا محتویات فایل docker-compose.yml مخصوص راه اندازی TeamCity را مشاهده می‌کنید که از سه کانتینر تشکیل شده‌است و از بانک اطلاعاتی postgres استفاده می‌کند:
version: '2'
services:
    teamcity:
        image: sjoerdmulder/teamcity
        ports:
            - 8111:8111
    teamcity-agent:
        image: sjoerdmulder/teamcity-agent
        environment:
            - TEAMCITY_SERVER=http://teamcity:8111
    postgres:
        image: postgres
        environment:
            - POSTGRES_DB=teamcity
در این تنظیمات، پورت 8111 میزبان به پورت 8111 کانتینر teamcity نگاشت شده‌است. در ادامه teamcity-agent نیاز به IP این کانتینر را دارد (یک build-server است). زمانیکه چندین کانتینر را توسط فایل docker-compose.yml راه اندازی می‌کنیم، داکر یک شبکه‌ی ایزوله (private network) را نیز برای اینکار مهیا می‌کند. داخل این شبکه‌ی ایزوله، یک DNS سرور توکار نیز وجود دارد که امکان دسترسی به کانتینرهای مختلف را از طریق نام کانتینرهای آن‌ها میسر می‌کند. به همین جهت است که مقدار TEAMCITY_SERVER، به http://teamcity:8111 تنظیم شده‌است و دقیقا از نام کانتینر teamcity برای یافتن IP آن استفاده می‌کند. همچنین باید دقت داشت در این آدرس، عدد 8111 به پورت داخل کانتینر teamcity اشاره می‌کند و نه به پورت میزبان. کانتینرها از طریق آدرس IP و پورت خودشان با هم در تماس هستند. پورت میزبان 8111، صرفا جهت فراخوانی teamcity از طریق سیستم میزبان مشخص شده‌است.


در ادامه برای کار با آن، ابتدا این محتویات را به صورت یک فایل متنی docker-compose.yml ذخیره کنید. سپس از طریق خط فرمان به پوشه‌ی آن وارد شده و دستور docker-compose up را صادر کنید. docker-compose یکی دیگر از ابزارهای خط فرمان نصب شده‌ی به همراه داکر است و پارامتر up آن کار راه اندازی و اجرای کانتینرهای ذکر شده‌ی در فایل yml موجود را انجام می‌دهد. نام پوشه‌ای که این فایل در آن قرار دارد، به عنوان نام پروژه‌ی مشترک بین این کانتینرها در گزارشات آن مورد استفاده قرار می‌گیرد.
پس از صدور این فرمان، ابتدا تمام imageهای ذکر شده‌ی در فایل yml دریافت می‌شوند (سه image در اینجا) و هر سه کانتینر راه اندازی می‌شوند. اکنون می‌توان در سیستم میزبان به آدرس http://localhost:8111 مراجعه کرد و از برنامه‌ی teamcity استفاده نمود. البته صفحه‌ی ابتدایی آن کار تنظیمات بانک اطلاعاتی آن‌را انجام می‌دهد و جائیکه در مورد database type سؤال می‌پرسد می‌توان postgres را انتخاب کرد و سپس در ذیل آن مقدار database host را نیز postgres وارد می‌کنیم. علت آن‌را نیز پیشتر توضیح دادیم. postgres در اینجا نام کانتینر نیز هست و ذکر نام آن، با ذکر IP مرتبط با آن کانتینر، یکی است. نام بانک اطلاعاتی را teamcity وارد کنید (مطابق تنظیمات فایل yml فوق) و نام کاربری آن نیز postgres است؛ بدون کلمه‌ی عبور. البته می‌شد در فایل yml فوق، متغیر محیطی POSTGRES_PASSWORD=xyz را نیز تنظیم کرد و سپس از آن در اینجا استفاده نمود.


docker-compose و ایجاد شبکه‌های ایزوله

توسط دستور docker network ls می‌توان لیست شبکه‌های مجازی ایجاد شده‌ی توسط docker را مشاهده کرد (و همچنین سایر network adapters موجود). اگر این دستور را اجرا کنید، کارت شبکه‌ی مجازی متناظر با شبکه‌ی خصوصی teamcity_default را که پیشتر در مورد آن توضیح داده شد، می‌توانید مشاهده کنید. این teamcity در اینجا همان نام پروژه و یا در اصل نام پوشه‌ای است که فایل docker-compose را از آنجا اجرا کردیم.
برای دریافت اطلاعات بیشتری در مورد این کارت شبکه‌ی به خصوص، می‌توان دستور docker network inspect teamcity_default را صادر کرد. یکی از قسمت‌های خروجی این گزارش، لیست کانتینرهایی است که هم اکنون به این شبکه متصل هستند؛ که در اینجا teamcity و بانک اطلاعاتی آن است.
مزیت ایجاد یک شبکه‌ی خصوصی مخصوص کانتینرهای به هم پیوسته، علاوه بر سادگی تشکیل فایل docker-compose آن‌ها با اشاره‌ی به نام کانتینرها، بجای ذکر مستقیم آدرس IP هر کدام، ایزوله ساختن این شبکه، از شبکه‌ی پیش‌فرض docker و بالا بردن میزان امنیت سایر کانتینرهایی است که هم اکنون از آن شبکه استفاده می‌کنند.


docker-compose و ایجاد DNS Server توکار

همانطور که عنوان شد، در این شبکه‌ی خصوصی ویژه‌ی کانتینرهای به هم پیوسته که توسط docker-compse اجرا و مدیریت شده‌اند، می‌توان از نام containerها بجای آدرس IP آن‌ها استفاده کرد و این مورد با وجود یک DNS Server توکار در این شبکه میسر شده‌است. برای آزمایش بیشتر این قابلیت، ابتدا دستور docker ps را صادر می‌کنیم تا نام کانتینرهای در حال اجرا را بدست بیاوریم. سپس سعی می‌کنیم پروسه‌ی bash shell داخل کانتینر بانک اطلاعاتی را اجرا کنیم:
docker ps
docker exec -it teamcity_postgres_1 bash
#exit
استفاده از docker exec یک روش است برای دسترسی به shell این کانتینر در حال اجرا؛ روش دیگر، استفاده از خود docker-compse می‌باشد که در این حالت، کار با آن ساده‌تر است:
 docker-compose exec postgres bash
در اینجا نیازی به ذکر سوئیچ it نیست؛ چون مقدار پیش‌فرض آن است و همچنین دیگر نیازی به اجرای docker ps برای یافتن نام کانتینری هم نیست و مستقیما می‌توان از نام‌های سرویس‌هایی که در فایل docker-compose.yml تعریف شده‌اند، استفاده کرد.
پس از دسترسی به شل، دستور زیر را اجرا کنید:
#ping teamcity
#exit
مشاهده خواهید کرد که این کانتینر می‌تواند کانتینر دیگری را صرفا با ذکر نام آن، ping کند.

یک نکته: اگر بخواهیم از وضعیت بانک‌های اطلاعاتی postgres توسط برنامه‌ی psql آن گزارش بگیریم نیز روش اجرای آن به همین صورت است:
docker-compose exec postgres psql -U postgres
postgres=#\l
postgres=#\q


اتصال یک کانتینر خارج از شبکه‌ی مجازی ایجاد شده‌ی توسط docker-compose به آن

فرض کنید می‌خواهید کانتینر کم حجم لینوکس alpine را اجرا کنید و توسط آن به شبکه‌ی مجازی ایجاد شده‌ی توسط docker-compose متصل شوید. روش آن به صورت زیر است:
 docker run --name apline -it --rm --net teamcity_default alpine sh
در اینجا توسط سوئیچ net، می‌توان نام شبکه‌ی مجازی را که نیاز است به آن متصل شویم، ذکر کرد. نام teamcity_default نیز از طریق اجرای دستور docker netwrok ls بدست آمده‌است.
این دستور، کانتینر لینوکس alpine را در حالت interactive جهت اجرای shell آن، راه اندازی می‌کند. سپس به شبکه‌ی مجازی teamcity_default متصل می‌شود. برای آزمایش این اتصال، در این shell راه اندازی شده، دستور ping teamcity را می‌توان صادر کرد. همچنین از داخل کانتینر teamcity نیز می‌توان این کانتینر را با نام آن ping کرد.


راه اندازی مجدد کانتینرها توسط docker-compose

اگر دستور docker-compose ps را دقیقا در پوشه‌ای که فایل yml آن قرار دارد اجرا کنیم، می‌توان گزارشی را صرفا از وضعیت کانتینرهای مرتبط با این فایل yml بدست آورد. دستور docker ps، لیست وضعیت تمام کانتینرهای در حال اجرای موجود را بر می‌گرداند. اکنون فرض کنید یکی از کانتینرهای اجرای شده‌ی توسط docker-compose، دیگر در حال اجرا نیست. برای راه اندازی مجدد آن می‌توان از دستور docker-compose start teamcity-agent استفاده کرد. همچنین دستور docker-compose logs teamcity-agent، لیست آخرین لاگ‌های مرتبط با یک کانتینر را بر می‌گرداند که می‌تواند برای رفع اشکال بسیار مفید باشد.


حذف کانتینرهای به هم پیوسته‌ی ایجاد شده‌ی توسط docker-compose

در ذیل ابتدا یک سری دستور را جهت مشاهده‌ی وضعیت سیستم مشاهده می‌کنید. سپس دستور docker-compose stop، کار متوقف کردن کانتینرهای مرتبط با فایل yml آن‌را انجام می‌دهد. دستور docker-compose rm -v، علاوه بر حذف این کانتینرها، volumeهای بانک‌های اطلاعاتی مرتبط را نیز حذف می‌کند. در آخر دستور docker-compose down، شبکه‌ی مجازی مرتبط را نیز حذف خواهد کرد. سپس مجددا از وضعیت سیستم گزارش گیری شده‌است.
 docker ps
docker-compose ps
docker volume ls
docker network ls

docker-compose stop
docker-compose rm -v
docker-compose down

docker ps -a
docker volume ls
docker network ls


اجرای پروژه‌ی ASP.NET Core Music Store توسط docker-compose

پروژه‌ی معروف Music Store مایکروسافت را به همراه فایل docker-compose.windows.yml آن، در اینجا می‌توانید مشاهده کنید. محتوای این فایل نیز به صورت زیر است:
version: '3'
services:
  db:
    image: microsoft/mssql-server-windows-developer
    environment:
      sa_password: "Password1"
      ACCEPT_EULA: "Y"
    ports:
      - "1433:1433" # REMARK: This is currently required, needs investigation
    healthcheck:
      test: [ "CMD", "sqlcmd", "-U", "sa", "-P", "Password1", "-Q", "select 1" ]
      interval: 1s
      retries: 30
web:
    build:
      context: .
      dockerfile: Dockerfile.windows
    environment:
      - "Data:DefaultConnection:ConnectionString=Server=db,1433;Database=MusicStore;User Id=sa;Password=Password1;MultipleActiveResultSets=True"
    depends_on:
      - db
    ports:
      - "5000:5000"
در اینجا تعریف دو کانتینر را مشاهده می‌کنید:
- کانتینر db که بر اساس image مخصوص mssql-server-windows-developer راه اندازی می‌شود. تنظیمات آن نیز بسیار شبیه به مطلب «کار با Docker بر روی ویندوز - قسمت ششم - کار با بانک‌های اطلاعاتی درون Containerها» است که پیشتر در مورد آن بحث کردیم.
- کانتینر web آن که از فایل Dockerfile.windows برای build سپس publish و در آخر run خودکار این برنامه‌ی ASP.NET Core، کمک می‌گیرد. در اینجا context به پوشه‌ی جاری اشاره می‌کند. در قسمت تنظیمات بانک اطلاعاتی آن، استفاده‌ی از نام کانتینر db را در قسمت رشته‌ی اتصالی مشاهده می‌کنید. قسمت depends_on آن ترتیب اجرای این کانتینرها را مشخص می‌کند. یعنی ابتدا باید کانتینر db اجرا شود و سپس web.
محتوای فایل Dockerfile.windows آن نیز به صورت زیر است که بر اساس دستورات NET Core CLI. تهیه شده‌است:
FROM microsoft/dotnet-nightly:2.0-sdk-nanoserver
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
ENV NUGET_XMLDOC_MODE skip
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
RUN New-Item -Path \MusicStore\samples\MusicStore -Type Directory
WORKDIR app
ADD samples/MusicStore/MusicStore.csproj samples/MusicStore/MusicStore.csproj
ADD build/dependencies.props build/dependencies.props
ADD NuGet.config .
RUN dotnet restore --runtime win10-x64 .\samples\MusicStore
ADD samples samples
RUN dotnet publish --output /out --configuration Release --framework netcoreapp2.0 --runtime win10-x64 .\samples\MusicStore
FROM microsoft/dotnet-nightly:2.0-runtime-nanoserver
WORKDIR /app
COPY --from=0 /out .
EXPOSE 5000
ENV ASPNETCORE_URLS http://0.0.0.0:5000
CMD dotnet musicstore.dll
روش اجرای آن‌را نیز به صورت زیر بیان کرده‌است:
docker-compose -f .\docker-compose.windows.yml build
docker-compose -f .\docker-compose.windows.yml up
البته باید دقت داشت که اجرای فرمان up، به صورت خودکار کار build را هم انجام می‌دهد. پس از اجرای آن، برنامه را بر روی پورت 5000 می‌توانید مشاهده کنید.
نظرات مطالب
گذری بر مفاهیم relationship
سلام،
برای این منظور باید از recursive cte کمک گرفت.
فرض کنید درختی به شکل زیر داریم:
/*         A
         /   \
        B     C
        |    /|\
        D   E F G
        |
        H
        
*/

و هدف بدست آوردن تمام زیر شاخته‌های گره A است.
ابتدا باید تمام گره هایی که مقدار گره پدرشان برابر با A است را بدست بیاریم یعنی گره‌های B و C
حالا باید تمام گره هایی که گره پدرشان B و یا C است را بدست بیاریم یعنی گره‌های D E F G
و در مرحله بعد باید تمام گره هایی را بدست بیاریم که گره پدرشان برابر با یکی از مقادیر بدست آمده در مرحله قبل (یعنی D E F G) یعنی H

این الگوریتم را توسط Recursive CTE پیاده میکنیم:
declare @t table
(
   id char(1) primary key not null,
   pid char(1) null --references @t
);

insert @t
values ('A', null), ('B','A'),('C','A'),
('D','B'), ('H','D'),('E','C'),('F','C'),('G','C');

with cte as 
(
select id
from @t 
where pid = 'A'

union all

select t.id
from cte c
join @t t
on t.pid = c.id
)
select * from cte

موفق باشید

پاسخ به بازخورد‌های پروژه‌ها
ارسال پارامترهای اختیاری به دستور Sql و مشکل با دستور is null
- با درایور SQLite امتحان شد و مشکلی مشاهده نشد.
- بنابراین این مورد بستگی دارد به توانایی‌های موتور و همچنین درایور بانک اطلاعاتی مورد استفاده (به نظر SQL-CE است) و یک مساله‌ی عمومی نیست.
- برای حل این مشکل در SQL-CE از روش زیر استفاده کنید:
 ... WHERE f.Bar = @bar OR cast(@bar AS varchar(4000)) IS NULL
مطالب
Blazor 5x - قسمت 23 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 3 - کار با نقش‌های کاربران
در قسمت قبل، روش یکپارچه سازی context مربوط به ASP.NET Core Identity را با یک برنامه‌ی Blazor Server، بررسی کردیم. در این قسمت می‌خواهیم محدود کردن دسترسی‌ها را بر اساس نقش‌های کاربران و همچنین کدنویسی مستقیم، بررسی کنیم.


کار با Authentication State از طریق کدنویسی

فرض کنید در کامپوننت HotelRoomUpsert.razor نمی‌خواهیم دسترسی‌ها را به کمک اعمال ویژگی attribute [Authorize]@ محدود کنیم؛ می‌خواهیم اینکار را از طریق کدنویسی مستقیم انجام دهیم:
// ...

@*@attribute [Authorize]*@


@code
{
    [CascadingParameter] public Task<AuthenticationState> AuthenticationState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationState;
        if (!authenticationState.User.Identity.IsAuthenticated)
        {
            var uri = new Uri(NavigationManager.Uri);
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}");
        }
        // ...
- در اینجا در ابتدا اعمال ویژگی Authorize را کامنت کردیم.
- سپس یک پارامتر ویژه را از نوع CascadingParameter، به نام AuthenticationState تعریف کردیم. این خاصیت از طریق کامپوننت CascadingAuthenticationState که در قسمت قبل به فایل BlazorServer.App\App.razor اضافه کردیم، تامین می‌شود.
- در آخر در روال رویدادگردان OnInitializedAsync، بر اساس آن می‌توان به اطلاعات User جاری وارد شده‌ی به سیستم دسترسی یافت و برای مثال اگر اعتبارسنجی نشده بود، با استفاده از NavigationManager، او را به صفحه‌ی لاگین هدایت می‌کنیم.
- در اینجا روش ارسال آدرس صفحه‌ی فعلی را نیز مشاهده می‌کنید. این امر سبب می‌شود تا پس از لاگین، کاربر مجددا به همین صفحه هدایت شود.

authenticationState، امکانات بیشتری را نیز در اختیار ما قرار می‌دهد؛ برای مثال با استفاده از متد ()authenticationState.User.IsInRole آن می‌توان دسترسی به قسمتی را بر اساس نقش‌های خاصی محدود کرد.


ثبت کاربر ادمین Identity

در ادامه می‌خواهیم دسترسی به کامپوننت‌های مختلف را بر اساس نقش‌ها، محدود کنیم. به همین جهت نیاز است تعدادی نقش و یک کاربر ادمین را به بانک اطلاعاتی برنامه اضافه کنیم. برای اینکار به پروژه‌ی BlazorServer.Common مراجعه کرده و تعدادی نقش ثابت را تعریف می‌کنیم:
namespace BlazorServer.Common
{
    public static class ConstantRoles
    {
        public const string Admin = nameof(Admin);
        public const string Customer = nameof(Customer);
        public const string Employee = nameof(Employee);
    }
}
علت قرار دادن این کلاس در پروژه‌ی Common، نیاز به دسترسی به آن در پروژه‌ی اصلی Blazor Server و همچنین در پروژه‌ی سرویس‌های برنامه‌است. فضای نام این کلاس را نیز در فایل imports.razor_ قرار می‌دهیم.

سپس در فایل BlazorServer.App\appsettings.json، مشخصات ابتدایی کاربر ادمین را ثبت می‌کنیم:
{
  "AdminUserSeed": {
    "UserName": "vahid@dntips.ir",
    "Password": "123@456#Pass",
    "Email": "vahid@dntips.ir"
  }
}
جهت دریافت strongly typed این تنظیمات در برنامه، کلاس معادل AdminUserSeed را به پروژه‌ی Models اضافه می‌کنیم:
namespace BlazorServer.Models
{
    public class AdminUserSeed
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}
که به صورت زیر در فایل BlazorServer.App\Startup.cs به سیستم تزریق وابستگی‌های برنامه معرفی می‌شود:
namespace BlazorServer.App
{
    public class Startup
    {

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions<AdminUserSeed>().Bind(Configuration.GetSection("AdminUserSeed"));
            // ...
اکنون می‌توان سرویس افزودن نقش‌ها و کاربر ادمین را در پروژه‌ی BlazorServer.Services تکمیل کرد:
using System;
using System.Linq;
using System.Threading.Tasks;
using BlazorServer.Common;
using BlazorServer.DataAccess;
using BlazorServer.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace BlazorServer.Services
{
    public class IdentityDbInitializer : IIdentityDbInitializer
    {
        private readonly ApplicationDbContext _dbContext;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly IOptions<AdminUserSeed> _adminUserSeedOptions;

        public IdentityDbInitializer(
            ApplicationDbContext dbContext,
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager,
            IOptions<AdminUserSeed> adminUserSeedOptions)
        {
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager));
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _adminUserSeedOptions = adminUserSeedOptions ?? throw new ArgumentNullException(nameof(adminUserSeedOptions));
        }

        public async Task SeedDatabaseWithAdminUserAsync()
        {
            if (_dbContext.Roles.Any(role => role.Name == ConstantRoles.Admin))
            {
                return;
            }

            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Admin));
            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Customer));
            await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Employee));

            await _userManager.CreateAsync(
                new IdentityUser
                {
                    UserName = _adminUserSeedOptions.Value.UserName,
                    Email = _adminUserSeedOptions.Value.Email,
                    EmailConfirmed = true
                },
                _adminUserSeedOptions.Value.Password);

            var user = await _dbContext.Users.FirstAsync(u => u.Email == _adminUserSeedOptions.Value.Email);
            await _userManager.AddToRoleAsync(user, ConstantRoles.Admin);
        }
    }
}
این سرویس، با استفاده از دو سرویس توکار UserManager و RoleManager کتابخانه‌ی Identity، ابتدا سه نقش ادمین، مشتری و کارمند را ثبت می‌کند. سپس بر اساس اطلاعات AdminUserSeed تعریف شده، کاربر ادمین را ثبت می‌کند. البته این کاربر در این مرحله، یک کاربر معمولی بیشتر نیست. در مرحله‌ی بعد است که با انتساب نقش ادمین به او، می‌توان کاربر او را بر اساس این نقش ویژه، شناسایی کرد. کلاس‌های IdentityRole و IdentityUser، کلاس‌های پایه‌ی نقش‌ها و کاربران کتابخانه‌ی Identity هستند.

پس از تعریف این سرویس، نیاز است آن‌را به سیستم تزریق وابستگی‌های برنامه اضافه کرد:
namespace BlazorServer.App
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IIdentityDbInitializer, IdentityDbInitializer>();
            // ...
مرحله‌ی آخر، اعمال و اجرای سرویس IIdentityDbInitializer، در زمان آغاز برنامه‌است و محل توصیه شده‌ی آن، در متد Main برنامه‌ی اصلی، پیش از اجرای برنامه‌است. به همین جهت، نیاز است BlazorServer.DataAccess\Utils\MigrationHelpers.cs را به صورت زیر ایجاد کرد:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Polly;

namespace BlazorServer.DataAccess.Utils
{
    public static class MigrationHelpers
    {
        public static void MigrateDbContext<TContext>(
                this IServiceProvider serviceProvider,
                Action<IServiceProvider> postMigrationAction
                ) where TContext : DbContext
        {
            using var scope = serviceProvider.CreateScope();
            var scopedServiceProvider = scope.ServiceProvider;
            var logger = scopedServiceProvider.GetRequiredService<ILogger<TContext>>();
            using var context = scopedServiceProvider.GetService<TContext>();

            logger.LogInformation($"Migrating the DB associated with the context {typeof(TContext).Name}");

            var retry = Policy.Handle<Exception>().WaitAndRetry(new[]
                {
                    TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)
                });

            retry.Execute(() =>
                {
                    context.Database.Migrate();
                    postMigrationAction(scopedServiceProvider);
                });

            logger.LogInformation($"Migrated the DB associated with the context {typeof(TContext).Name}");
        }
    }
}
در مورد این متد و استفاده از Polly جهت تکرار عملیات شکست خورده پیشتر در مطلب «اضافه کردن سعی مجدد به اجرای عملیات Migration در EF Core» بحث شده‌است.
کار متد الحاقی فوق، دریافت یک IServiceProvider است که به سرویس‌های اصلی برنامه اشاره می‌کند. سپس بر اساس آن، یک Scoped ServiceProvider را ایجاد می‌کند تا درون آن بتوان با Context برنامه در طی مدت کوتاهی کار کرد و در پایان آن، سرویس‌های ایجاد شده را Dispose کرد.
در این متد ابتدا Database.Migrate فراخوانی می‌شود تا اگر مرحله‌ای از Migrations برنامه هنوز به بانک اطلاعاتی اعمال نشده، کار اجرا و اعمال آن انجام شود. سپس یک متد سفارشی را از فراخوان دریافت کرده و اجرا می‌کند. برای مثال توسط آن می‌توان IIdentityDbInitializer در فایل BlazorServer.App\Program.cs به صوت زیر فراخوانی کرد:
public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    host.Services.MigrateDbContext<ApplicationDbContext>(
     scopedServiceProvider =>
            scopedServiceProvider.GetRequiredService<IIdentityDbInitializer>()
                                 .SeedDatabaseWithAdminUserAsync()
                                 .GetAwaiter()
                                 .GetResult()
    );
    host.Run();
}
تا اینجا اگر برنامه را اجرا کنیم، سه نقش پیش‌فرض، به بانک اطلاعاتی برنامه اضافه شده‌اند:


و همچنین کاربر پیش‌فرض سیستم را نیز می‌توان مشاهده کرد:


که نقش ادمین و کاربر پیش‌فرض، به این صورت به هم مرتبط شده‌اند (یک رابطه‌ی many-to-many برقرار است):



محدود کردن دسترسی کاربران بر اساس نقش‌ها

پس از ایجاد کاربر ادمین و تعریف نقش‌های پیش‌فرض، اکنون محدود کردن دسترسی به کامپوننت‌های برنامه بر اساس نقش‌ها، ساده‌است. برای این منظور فقط کافی است لیست نقش‌های مدنظر را که می‌توانند توسط کاما از هم جدا شوند، به ویژگی Authorize کامپوننت‌ها معرفی کرد:
@attribute [Authorize(Roles = ConstantRoles.Admin)]
و یا از طریق کدنویسی به صورت زیر نیز قابل اعمال است:
    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationState;
        if (!authenticationState.User.Identity.IsAuthenticated ||
            !authenticationState.User.IsInRole(ConstantRoles.Admin))
        {
            var uri = new Uri(NavigationManager.Uri);
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}");
        }


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-23.zip
نظرات مطالب
بررسی علت CPU Usage بالای برنامه در حال اجرا
سوال شما مرتبط با بحث CPU Usage نیست...
- در سرور 32 بیتی شما نمی‌تونید استفاده مناسبی از سخت افزاری که تهیه کردید بکنید. نیاز است به سرور 64 بیتی نقل مکان کنید. یعنی هزینه کردید اما ... از آن استفاده بهینه‌ای نمی‌کنید.
- تابع SQL ، برای تبدیل تاریخ میلادی به شمسی وجود دارد (بگردید در انجمن‌ها هست). از همان تابع در کوئری‌های خودتون قبل از ارسال به گزارشات استفاده کنید. یعنی دیتا از همان اول تاریخ شمسی داشته باشد. به این صورت نیازی به استفاده از افزونه دیگری نیست.
- IIS یک قسمت دارد به نام Application pool . اولا به ازای هر برنامه بهتر است یک Application pool جدا درست کنید. این حالت دقیقا مثل اجرای هر سایت در یک پروسه جدا است. مثلا IE8 رو دیدید که هر tab آن در یک پروسه جدا اجرا می‌شود، یا مثلا مرورگر کروم هم به همین صورت است. Application pool در IIS هم دقیقا همین معنا را دارد. عادت متداول، استفاده از یک Application pool به ازای تمام سایت‌ها است که غلط است. یعنی زمانیکه این توانایی هست چرا استفاده نمی‌کنید؟ ثانیا در همین Application pool می‌تونید تنظیم کنید که اگر میزان مصرف حافظه برنامه منتسب، مثلا به 600 مگ رسید لطفا خودت به صورت خودکار اون رو recycle کن تا مشکلات کمبود حافظه رخ ندهد.
- از برنامه‌های dotTrace و Ants memory profiler هم می‌تونید استفاده کنید. این‌ها قابلیت دیباگ پروسه‌های مرتبط با IIS رو هم دارند و دقیقا گزارشات آماری پیشرفته‌ای می‌تونند از وضعیت مصرف حافظه قسمت‌های مختلف برنامه در اختیار شما قرار بدن: (+) و (+)