مسیرراه‌ها
ASP.NET MVC
              اشتراک‌ها
              گوگل کد به زودی از دسترس خارج خواهد شد

              When we started the Google Code project hosting service in 2006, the world of project hosting was limited. We were worried about reliability and stagnation, so we took action by giving the open source community another option to choose from. Since then, we’ve seen a wide variety of better project hosting services such as GitHub and Bitbucket bloom. Many projects moved away from Google Code to those other systems. To meet developers where they are, we ourselves migrated nearly a thousand of our own open source projects from Google Code to GitHub. 

              گوگل کد به زودی از دسترس خارج خواهد شد
              اشتراک‌ها
              آموزش ساخت یک بلاگ با Blazor .Net 8

              In this video we will build a complete full stack Blog Web App using the new Blazor Web App with SSR with .Net 8 | Step by Step | From Scratch to End You will learn: New Static Server Side Rendering with Blazor, QuickGrid, Enhanced Navigation, Enhanced Forms, Stream Rendering, Image Preview & Upload, Identity Authentication, Interactive Server Render Mode for Admin Panel, and much more  

              آموزش ساخت یک بلاگ با  Blazor .Net 8
              مطالب
              ساخت یک سایت ساده‌‌ی نمایش لیست فیلم با استفاده از Vue.js - قسمت دوم
              در قسمت قبلی نحوه کانفیگ اولیه برنامه را به همراه نصب پلاگین‌های مورد نیاز، بررسی نمودیم؛ در ادامه قصد داریم تا چندین کامپوننت , ^ را برای نمایش لیست فیلمها، جزییات فیلم و جستجو، به برنامه اضافه کنیم و به هر کدام یک route را نیز انتساب دهیم. از کامپوننت‌ها برای بخش بندی قسمتهای مختلف سایت استفاده میکنیم. هر بخش برای دریافت و نمایش اطلاعاتی خاص مورد استفاده قرار میگیرد. بر خلاف Angular که به‌راحتی با دستور زیر میتوان برای آن یک کامپوننت ایجاد نمود و هر بخشی (css,js,ts,html) را در یک فایل جداگانه قرار داد:
              ng generate component [name]
              یا
              ng g c [name]
              در Vue.js هنوز امکان اینکه بتوان از طریق cli  یک کامپوننت را ایجاد کرد، فراهم نشده‌است. البته پکیج‌هایی برای اینکار تدارک دیده شده‌اند، ولی در این مقاله به‌صورت دستی اینکار انجام میشود و از Single File Component استفاده میکنیم. بصورت پیش فرض برنامه ایجاد شده vue.js دارای یک کامپوننت با نام HelloWorld.vue  در پوشه components  می‌باشد ( چیزی شبیه Hello Dolly در Wordpress)؛ آن را حذف میکنیم و محتویات فایل App.vue را مطابق زیر تغییر میدهیم ( قسمت import کردن کامپوننت HelloWorld.vue را حذف میکنیم) 
              <template>
                <v-app>
                  <v-toolbar app>
                    <v-toolbar-title>
                      <span>Vuetify</span>
                      <span>MATERIAL DESIGN</span>
                    </v-toolbar-title>
                    <v-spacer></v-spacer>
                    <v-btn
                      flat
                      href="https://github.com/vuetifyjs/vuetify/releases/latest"
                      target="_blank"
                    >
                      <span>Latest Release</span>
                    </v-btn>
                  </v-toolbar>
              
                  <v-content>
                    <HelloWorld/>
                  </v-content>
                </v-app>
              </template>
              
              <script>
              
              
              export default {
                name: 'App',
                components: {
                  
                },
                data () {
                  return {
                    //
                  }
                }
              }
              </script>
               در پوشه components، سه کامپوننت را با نام‌های LatestMovie.vue ، Movie.vue و SearchMovie.vue ایجاد کنید.
              محتویات LatestMovie.vue
              <template>
              
                <v-container v-if="loading">
                  <div>
                    <v-progress-circular
                      indeterminate
                      :size="150"
                      :width="8"
                      color="green">
                    </v-progress-circular>
                  </div>
                </v-container>
              
                <v-container v-else grid-list-xl>
                  <v-layout wrap>
                    <v-flex xs4
                      v-for="(item, index) in wholeResponse"
                      :key="index"
                      mb-2>
                      <v-card>
                        <v-img
                          :src="item.Poster"
                          aspect-ratio="1"
                        ></v-img>
              
                        <v-card-title primary-title>
                          <div>
                            <h2>{{item.Title}}</h2>
                            <div>Year: {{item.Year}}</div>
                            <div>Type: {{item.Type}}</div>
                            <div>IMDB-id: {{item.imdbID}}</div>
                          </div>
                        </v-card-title>
              
                        <v-card-actions>
                          <v-btn flat
                            color="green"
                            @click="singleMovie(item.imdbID)"
                            >View</v-btn>
                        </v-card-actions>
              
                      </v-card>
                    </v-flex>
                </v-layout>
                </v-container>
              </template>
              
              <script>
              import movieApi from '@/services/MovieApi'
              
              export default {
                data () {
                  return {
                    wholeResponse: [],
                    loading: true
                  }
                },
                mounted () {
                  movieApi.fetchMovieCollection('indiana')
                    .then(response => {
                      this.wholeResponse = response.Search
                      this.loading = false
                    })
                    .catch(error => {
                      console.log(error)
                    })
                },
                methods: {
                  singleMovie (id) {
                    this.$router.push('/movie/' + id)
                  }
                }
              }
              </script>
              
              <style scoped>
                .v-progress-circular
                  margin: 1rem
              </style>

              محتویات Movie.vue
              <template>
              
                <v-container v-if="loading">
                  <div>
                      <v-progress-circular
                        indeterminate
                        :size="150"
                        :width="8"
                        color="green">
                      </v-progress-circular>
                    </div>
                </v-container>
              
                <v-container v-else>
                  <v-layout wrap>
                    <v-flex xs12 mr-1 ml-1>
                      <v-card>
                        <v-img
                          :src="singleMovie.Poster"
                          aspect-ratio="2"
                        ></v-img>
                        <v-card-title primary-title>
                          <div>
                            <h2>{{singleMovie.Title}}-{{singleMovie.Year}}</h2>
                            <p>{{ singleMovie.Plot}} </p>
                            <h3>Actors:</h3>{{singleMovie.Actors}}
                             <h4>Awards:</h4> {{singleMovie.Awards}}
                             <p>Genre: {{singleMovie.Genre}}</p>
                          </div>
                        </v-card-title>
                        <v-card-actions>
                          <v-btn flat color="green" @click="back">back</v-btn>
                        </v-card-actions>
                      </v-card>
                    </v-flex>
                  </v-layout>
              
                  <v-layout row wrap>
                    <v-flex xs12>
                      <div>
                      <v-dialog
                        v-model="dialog"
                        width="500">
                        <v-btn
                          slot="activator"
                          color="green"
                          dark>
                          View Ratings
                        </v-btn>
                        <v-card>
                          <v-card-title
                           
                            primary-title
                          >
                            Ratings
                          </v-card-title>
                          <v-card-text>
                            <table style="width:100%" border="1" >
                              <tr>
                                <th>Source</th>
                                <th>Ratings</th>
                              </tr>
                              <tr v-for="(rating,index) in this.ratings" :key="index">
                                <td align="center">{{ratings[index].Source}}</td>
                                <td align="center"><v-rating :half-increments="true" :value="ratings[index].Value"></v-rating></td>
                              </tr>
                            </table>
                          </v-card-text>
                          <v-divider></v-divider>
                          <v-card-actions>
                            <v-spacer></v-spacer>
                            <v-btn
                              color="primary"
                              flat
                              @click="dialog = false"
                            >
                              OK
                            </v-btn>
                          </v-card-actions>
                        </v-card>
                      </v-dialog>
                    </div>
                    </v-flex>
                  </v-layout>
                </v-container>
              </template>
              
              <script>
              import movieApi from '@/services/MovieApi'
              export default {
                props: ['id'],
              
                data () {
                  return {
                    singleMovie: '',
                    dialog: false,
                    loading: true,
                    ratings: ''
                  }
                },
              
                mounted () {
                  movieApi.fetchSingleMovie(this.id)
                    .then(response => {
                      this.singleMovie = response
                      this.ratings = this.singleMovie.Ratings
                      this.ratings.forEach(function (element) {
                        element.Value = parseFloat(element.Value.split(/\/|%/)[0])
                        element.Value = element.Value <= 10 ? element.Value / 2 : element.Value / 20
                      }
                      )
                      this.loading = false
                    })
                    .catch(error => {
                      console.log(error)
                    })
                },
                methods: {
                  back () {
                    this.$router.push('/')
                  }
                }
              }
              
              </script>
              
              <style scoped>
                .v-progress-circular
                  margin: 1rem
              </style>

              محتویات SearchMovie.vue 
              <template>
              
                <v-container v-if="loading">
                  <div>
                    <v-progress-circular
                      indeterminate
                      :size="150"
                      :width="8"
                      color="green">
                    </v-progress-circular>
                  </div>
                </v-container>
              
                <v-container v-else-if="noData">
                  <div>
                  <h2>No Movie in API with {{this.name}}</h2>
                  </div>
                </v-container>
              
                <v-container v-else grid-list-xl>
                  <v-layout wrap>
                    <v-flex xs4
                      v-for="(item, index) in movieResponse"
                      :key="index"
                      mb-2>
                      <v-card>
                        <v-img
                          :src="item.Poster"
                          aspect-ratio="1"
                        ></v-img>
              
                        <v-card-title primary-title>
                          <div>
                            <h2>{{item.Title}}</h2>
                            <div>Year: {{item.Year}}</div>
                            <div>Type: {{item.Type}}</div>
                            <div>IMDB-id: {{item.imdbID}}</div>
                          </div>
                        </v-card-title>
              
                        <v-card-actions>
                          <v-btn flat
                            color="green"
                            @click="singleMovie(item.imdbID)"
                            >View</v-btn>
                        </v-card-actions>
              
                      </v-card>
                    </v-flex>
                </v-layout>
                </v-container>
              </template>
              
              <script>
              // در همه کامپوننتها جهت واکشی اطلاعات ایمپورت میشود
              import movieApi from '@/services/MovieApi'
              
              export default {
                // route پارامتر مورد استفاده در 
                props: ['name'],
                data () {
                  return {
                    // آرایه ای برای دریافت فیلمها
                    movieResponse: [],
                    // جهت نمایش لودینگ در زمان بارگذاری اطلاعات
                    loading: true,
                    // مشخص کردن آیا اطللاعاتی با سرچ انجام شده پیدا شده یا خیر
                    noData: false
                  }
                },
                // تعریف متدهایی که در برنامه استفاده میکنیم
                methods: {
                  // این تابع باعث میشود که 
                  // route
                  // تعریف شده با نام
                  // Movie
                  // فراخوانی شود و آدرس بار هم تغییر میکنید به آدرسی شبیه زیر
                  // my-site/movie/tt4715356 
                  singleMovie (id) 
                    this.$router.push('/movie/' + id)
                  },
              
                  fetchResult (value) {
                    movieApi.fetchMovieCollection(value)
                      .then(response => {
                        if (response.Response === 'True') {
                          this.movieResponse = response.Search
                          this.loading = false
                          this.noData = false
                        } else {
                          this.noData = true
                          this.loading = false
                        }
                      })
                      .catch(error => {
                        console.log(error)
                      })
                  }
                },
                // جز توابع
                // life cycle
                // vue.js
                // میباشد و زمانی که تمپلیت رندر شد اجرا میشود
                // همچنین با هر بار تغییر در 
                // virtual dom
                // این تابع اجرا میشود
                mounted () {
                  this.fetchResult(this.name)
                },
                // watch‌ها // کار ردیابی تغییرات را انجام میدهند و به محض تغییر مقدار  پراپرتی 
                // name
                // کد مورد نظر در بلاک زیر انجام میشود
                watch: {
                  name (value) {
                    this.fetchResult(value)
                  }
                }
              }
              </script>
              
              <style scoped>
                .v-progress-circular
                  margin: 1rem
              </style>

              توضیحی درباره کدهای بالا
              برای درخواستهای ا‌‌‌‌‌‌‌‌ی‌جکس از axios استفاده میکنیم و با توجه به اینکه در این برنامه سه کامپوننت داریم، باید در هر کامپوننت axios را import کنیم:
              import axios from 'axios'
              لذا (DRY) یک فولدر را بنام service در پوشه src  ایجاد میکنیم. یک فایل جاوااسکریپتی را نیز با نام دلخواهی در آن ایجاد و فقط یکبار axios را در آن  import میکنیم و توابع مورد نیاز را در آنجا مینویسیم (هر چند راه‌های بهتر دیگری هم برای کار با axios هست که در حیطه مقاله جاری نیست).

              محتویات فایل MovieApi.js در پوشه service
              import axios from 'axios'
              
              export default {
              
                fetchMovieCollection (name) {
                  return axios.get('&s=' + name)
                    .then(response => {
                      return response.data
                    })
                },
              
                fetchSingleMovie (id) {
                  return axios.get('&i=' + id)
                    .then(response => {
                      return response.data
                    })
                }
              }
              فایل main.js برنامه را بشکل زیر تغییر میدهیم و با استفاده از تنظیماتی که برای axios وجود دارد، آدرس baseURL آن را به ازای نمونه وهله سازی شده‌ی vue برنامه، تنظیم میکنیم.
              axios.defaults.baseURL = 'http://www.omdbapi.com/?apikey=b76b385c&page=1&type=movie&Content-Type=application/json'

              فایل  index.js درون پوشه router را باز میکنیم و محتویات آن را بشکل زیر تغییر می‌دهیم:
              import Vue from 'vue'
              import VueRouter from 'vue-router'
              // برای رجیستر کردن کامپوننت‌ها در بخش روتر، آنها را ایمپورت میکنیم
              import LatestMovie from '@/components/LatestMovie'
              import Movie from '@/components/Movie'
              import SearchMovie from '@/components/SearchMovie'
              
              Vue.use(VueRouter)
              
              export default new VueRouter({
                routes: [
                  {
                    // مسیری هست که برای این کامپوننت در نظر گرفته شده(صفحه اصلی)بدون پارامتر
                    path: '/',
                    // نام روت
                    name: 'LatestMovie',
                    // نام کامپوننت مورد نظر
                    component: LatestMovie
                  },
                  {
                    // پارامتری هست که به این کامپوننت ارسال میشه id
                    // برای دستیابی به این کامپوننت نیاز هست با آدرسی شبیه زیر اقدام کرد
                    // my-site/movie/tt4715356
                    path: '/movie/:id',
                    name: 'Movie',
                    // در کامپوننت جاری یک پراپرتی وجود دارد 
                    //id که میتوان با نام 
                    // به آن دسترسی پیدا کرد
                    props: true,
                    component: Movie
                  },
                  {
                    path: '/search/:name',
                    name: 'SearchMovie',
                    props: true,
                    component: SearchMovie
                  }
                ],
                // achieve URL navigation without a page reload
                // When using history mode, the URL will look "normal," e.g. http://oursite.com/user/id. Beautiful!
                // در آدرس # قرار نمیگیرد
                mode: 'history'
              })

              در برنامه ما سه کامپوننت وجود دارد. ما برای هر کدام یک مسیر و نام را برای route آنها تعریف میکنیم، تا بتوانیم با آدرس مستقیم، آنها را فراخوانی کنیم و با دکمه‌های back و forward مرورگر کار کنیم.


              نکته:  برای اجرای برنامه و دریافت پکیج‌های مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید: 
              npm install
              نظرات مطالب
              احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور
              یک نکته‌ی تکمیلی: بهبود کنترل نمایش و مخفی سازی قسمت‌های مختلف

              یک روش «نمایش و یا مخفی کردن قسمت‌های مختلف صفحه بر اساس نقش‌های کاربر وارد شده‌ی به سیستم» را در مطلب جاری مطالعه کردید. روش دیگر اینکار، تهیه‌ی یک دایرکتیو و سپس اعمال آن به المان‌های مختلف صفحه است. به علاوه با توجه به اینکه Auth Service ما رخ‌داد خروج کاربر را نیز گزارش می‌کند، روش ارائه شده‌ی در اینجا نیاز به اندکی بهبود هم دارد:
                ngOnInit() {
                  this.isAdmin = this.authService.isAuthUserInRole("Admin");
                  this.isUser = this.authService.isAuthUserInRole("User");
                }
              نتیجه‌ی این بررسی، حتی با خروج کاربر نیز تغییری نخواهد کرد و ثابت است. بنابراین بهتر است مشترک this.authService.authStatus شد و نسبت به رخ‌دادهای صادر شده‌ی توسط سرویس اعتبارسنجی، همانند کامپوننت هدر، واکنش نشان داد.
              برای پیاده سازی آن و همچنین کپسوله سازی این عملیات تکراری، دایرکتیو جدیدی را در مسیر src\app\shared\directives\is-visible-for-auth-user.directive.ts ایجاد می‌کنیم:
              import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core";
              import { Subscription } from "rxjs/Subscription";
              
              import { AuthService } from "../../core/services/auth.service";
              
              @Directive({
                selector: "[isVisibleForAuthUser]"
              })
              export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy {
              
                private subscription: Subscription;
              
                @Input() isVisibleForRoles: string[];
              
                constructor(private elem: ElementRef, private authService: AuthService) { }
              
                ngOnDestroy(): void {
                  this.subscription.unsubscribe();
                }
              
                ngOnInit(): void {
                  this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status));
                  this.changeVisibility(this.authService.isAuthUserLoggedIn());
                }
              
                private changeVisibility(status: boolean) {
                  const isInRoles = !this.isVisibleForRoles ? true : this.authService.isAuthUserInRoles(this.isVisibleForRoles);
                  this.elem.nativeElement.style.display = isInRoles && status ? "" : "none";
                }
              }
              در اینجا علاوه بر بررسی isAuthUserLoggedIn و isAuthUserInRoles، نسبت به تغییرات this.authService.authStatus نیز واکنش نشان داده می‌شود.

              سپس تعریف آن‌را به قسمت‌های declarations و exports مربوط به SharedModule اضافه می‌کنیم:
              import { IsVisibleForAuthUserDirective } from "./directives/is-visible-for-auth-user.directive";
              
              @NgModule({
                declarations: [
                  IsVisibleForAuthUserDirective
                ],
                exports: [
                  IsVisibleForAuthUserDirective
                ]
              })
              export class SharedModule {}

              اکنون ماژول Dashboard برای استفاده‌ی از این امکانات تنها کافی است SharedModule را دریافت کند (یا هر ماژول دیگری نیز به همین ترتیب است):
              import { SharedModule } from "../shared/shared.module";
              
              @NgModule({
                imports: [
                  SharedModule
                ]
              })
              export class DashboardModule { }

              پس از آن برای مخفی سازی یک المان از دید کاربران وارد نشده‌ی به سیستم، فقط کافی است دایرکتیو isVisibleForAuthUser را به المان اعمال کنیم:
              <div class="alert alert-info" isVisibleForAuthUser>
                    Is-Visible-For-AuthUser
              </div>
              و یا اگر نیاز به اعمال نقش‌ها نیز وجود داشت می‌توان از خاصیت isVisibleForRoles آن استفاده کرد:
              <div class="alert alert-success" isVisibleForAuthUser [isVisibleForRoles]="['Admin','User']">
                    Is-Visible-For-Roles = ['Admin','User']
              </div>

              خلاصه‌ی این تغییرات به کدهای نهایی این سری اعمال شده‌اند.
              مطالب
              زیرنویس فارسی ویدئوهای مقدمات AngularJS - قسمت اول
              سایت pluralsight یک دوره آموزشی با عنوان AngularJS Fundamentals تهیه کرده است، که به آموزش مقدمات AngularJS و اینکه چگونه می‌توانیم برنامه هایی با قابلیت تست پذیری، SPA و به سبک MVC بنویسیم، می‌پردازد.
              فعلاً قسمت اول این مجموعه زیرنویس شده است که از اینجا  قابل دریافت می‌باشد، جهت مشاهده ویدئوها نیز پیشنهاد می‌شود از برنامه KMPlayer استفاده کنید.
              لیست ویدئوهای قسمت اول این مجموعه به شرح زیر است :
              Course Introduction
              Module Introduction
              Introduction to Angular
              Angular Architecture
              Demo: Hello World in Angular
              The Angular Event Reg Application
              Angular Seed
              Summary
              6 قسمت دیگر از این مجموعه باقیمانده است، که بعد از آماده شدن به همین ترتیب به صورت یک پست در سایت ارائه خواهد شد. اگر مایل به همکاری بودید در قسمت پروژه‌های سایت می‌توانید اقدام کنید.
              برای تهیه زیرنویس‌ها هم از برنامه Subtitle Tools استفاده میکنم، البته ظاهراً خود ویدئوها دارای زیرنویس انگلیسی هستند که رایگان نیستند.