مطالب
WCF Method Overloading
تشریح مسئله : چگونه متد‌های سرویس WCF را Overload کنیم.

نکته : آشنایی با مفاهیم اولیه WFC برای فهم بهتر مفاهیم الزامی است.

همانطور که می‌دانیم امکان Overload کردن متد‌ها در سرویس‌های WCF وجود ندارد. یعنی نمی‌توان 2 متد با نام و پارامتر‌های متفاوت داشت. به مثال زیر دقت کنید.
ابتدا یک Contract به صورت زیر تعریف کنید
 [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        int Sum( int number1, int number2 );

        [OperationContract]
        float Sum( float number1, float number2 );
    }
در Contract بالا دو متد با یک نام ولی آرگومان‌های متفاوت داریم. حالا یک سرویس برای این Contract می‌نویسیم.
   public class SampleService : ISampleService
    {
        public int Sum( int number1, int number2 )
        {
            return number1 + number1;
        }

        public float Sum( float number1, float number2 )
        {
            return number1 + number1;
        }
    }
اگر پروژه را کامپایل کنید پروژه بدون هیچ گونه مشکلی کامپایل خواهد شد. ولی اگر قصد استفاده از این سرویس را داشته باشیم با خطا روبرو خواهیم شد. از روش AddServiceReference استفاده کنید و سرویس مورد نظر را سمت کلاینت اضافه کنید. با خطای زیر روبرو خواهید شد.
Cannot have two operations in the same contract with the same name, 
methods Sum and Sum in type Service.ISampleService violate this rule. 
You can change the name of one of the operations by changing the method name 
or by using the Name property of OperationContractAttribute.
در این خطا به صورت کامل روش حل این مسئله گفته شده است. برای حل این مسئله باید از خاصین Name در OperationContractAttribute استفاده کرد. Contract بالا را به صورت زیر تغییر دهید.
[ServiceContract]
    public interface ISampleService
    {
        [OperationContract( Name = "SumByIntNumbers" )]
        int Sum( int number1, int number2 );

        [OperationContract( Name = "SumByFloatNumbers" )]
        float Sum( float number1, float number2 );
    }
حال اگر سرویس مورد نظر را به پروژه سمت کلاینت اضافه کنیم دو متد با نام‌های SumByIntNumbers و SumByFloatNumbers خواهیم داشت. البته اگر از روش Self Hosted استفاده کنیم دقیقا دو متد با نام Sum خواهیم داشت و Overloading را سمت سرور و کلاینت خواهیم داشت ولی در روش IIS Hosting و استفاده از AddServiceReference  از خاصیت Name برای این کار استفاده میشود.

موفق باشید.
مطالب
نمایش بلادرنگ اعلامی به تمام کاربران در هنگام درج یک رکورد جدید
در ادامه می‌خواهیم اعلام عمومی نمایش افزوده شدن یک پیام جدید را بعد از ثبت رکوردی جدید، به تمامی کاربران متصل به سیستم ارسال کنیم. پیش نیاز مطلب جاری موارد زیر می‌باشند:
namespace ShowAlertSignalR.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public float Price { get; set; }
        public Category Category { get; set; }

    }

    public enum Category
    {
        [Display(Name = "دسته بندی اول")]
        Cat1,
        [Display(Name = "دسته بندی دوم")]
        Cat2,
        [Display(Name = "دسته بندی سوم")]
        Cat3
    }
}
در اینجا مدل ما شامل عنوان، توضیح، قیمت و یک enum برای دسته‌بندی یک محصول ساده می‌باشد.
کلاس context نیز به صورت زیر می‌باشد:
namespace ShowAlertSignalR.Models
{
    public class ProductDbContext : DbContext
    {
        public ProductDbContext() : base("productSample")
        {
            Database.Log = sql => Debug.Write(sql);
        }
        public DbSet<Product> Products { get; set; }
    }
}
همانطور که در ابتدا عنوان شد، می‌خواهیم بعد از ثبت یک رکورد جدید، پیامی عمومی به تمامی کاربران متصل به سایت نمایش داده شود. در کد زیر اکشن متد Create را مشاهده می‌کنید: 
[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(product);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(product);
        }
می‌توانیم از ViewBag برای اینکار استفاده کنیم؛ به طوریکه یک پارامتر از نوع bool برای متد Index تعریف کرده و سپس مقدار آن را درون این شیء ViewBag انتقال دهیم، این متغییر بیانگر حالتی است که آیا اطلاعات جدیدی برای نمایش وجود دارد یا خیر؟ بنابراین اکشن متد Index را به اینصورت تعریف می‌کنیم:
public ActionResult Index(bool notifyUsers = false)
        {
            ViewBag.NotifyUsers = notifyUsers;
            return View(db.Products.ToList());
        }
در اینجا مقدار پیش‌فرض این متغیر، false می‌باشد. یعنی اطلاعات جدیدی برای نمایش موجود نمی‌باشد. در نتیجه اکشن متد Create را به صورتی تغییر می‌دهیم که بعد از درج رکورد موردنظر و هدایت کاربر به صفحه‌ی Index، مقدار این متغییر به true تنظیم شود:
[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(product);
                db.SaveChanges();
                return RedirectToAction("Index", new { notifyUsers = true });
            }

            return View(product);
        }
قدم بعدی ایجاد یک هاب SignalR می‌باشد:
namespace ShowAlertSignalR.Hubs
{
    public class NotificationHub : Hub
    {
        public void SendNotification()
        {
            Clients.Others.ShowNotification();
        }
    }
}
در ادامه کدهای سمت کلاینت را برای هاب فوق، داخل ویوی Index اضافه می‌کنیم:
@section scripts
{
    
    <script src="~/Scripts/jquery.signalR-2.0.2.min.js"></script>
    <script src="~/signalr/hubs"></script>
    <script>

        var notify = $.connection.notificationHub;
        notify.client.showNotification = function() {
            $('#result').append("<div class='alert alert-info alert-dismissable'>" +
                "<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>&times;</button>" +
            "رکورد جدیدی هم اکنون ثبت گردید، برای مشاهده آن صفحه را بروزرسانی کنید" + "</div>");
        };
        $.connection.hub.start().done(function() {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
        });
    </script>
}
همانطور که در کدهای فوق مشاهده می‌کنید، بعد از اینکه اتصال با موفقیت برقرار شد (درون متد done) شرط چک کردن متغییر NotifyUsers را بررسی کرده‌ایم. یعنی در این حالت اگر مقدار آن true بود، متد درون هاب را فراخوانی کرده‌ایم. در نهایت پیام به یک div با آی‌دی result اضافه شده است.
لازم به ذکر است برای حالت‌های حذف و به‌روزرسانی نیز روال کار به همین صورت می‌باشد.
سورس مثال جاری : ShowAlertSignalR.zip
مطالب
ساخت یک سایت ساده‌‌ی نمایش لیست فیلم با استفاده از 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
نظرات مطالب
ASP.NET MVC #23
- گزینه‌ی «"uncheck “Verify that file exists» را هم امتحان کنید.
- این سؤال خارج از بحث است. بازگرداندن View هیچ ارتباطی به مسیریابی ندارد. فقط کافی است بنویسید:
return View("~/Views/....مسیر کامل فایل", model);
تولید URLهای خودکار بر اساس اطلاعات مسیریابی در Viewهای برنامه، توسط متدهای توکار ActionLink و امثال آن انجام می‌شود.
- تمام خطاهای مدیریت نشده‌ی برنامه‌های وب در لاگ ویندوز ثبت می‌شوند. آن‌ها را بررسی کنید. همچنین ELMAH را هم نصب کنید تا خطاها را برای بررسی بیشتر لاگ کند.
- روش‌های قدیمی را با MVC کار نکنید. صفحه‌ی اول سایت، همان صفحه‌ای است که در مسیریابی پیش فرض تعریف شده‌است. یعنی همان اکشن متد Index در کنترلر Home، به همراه View ایی که مد نظر شما است.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 5
پس از ایجاد متدها، نوبت به تغییرات App.Config می‎رسد. هرچند خود Visual Studio برای کلاس پیش‌گزیده‌ی خود تنظیماتی را در App.Config افزوده است ولی چنان‎چه در در خاطر دارید ما آن فایل‎ها را حذف کردیم و فایل‎های جدیدی به جای آن افزودیم. از این رو مراحل زیر را انجام دهید:
1- فایل App.Config را از Solution Explorer باز کنید.
2- به جای عبارت MyNewsWCFLibrary.Service1 در قسمت Service Name این عبارت را بنویسید: MyNewsWCFLibrary.MyNewsService
3- در قسمت BaseAddress عبارت Design_Time_Addresses را حذف کنید.
4- در قسمت BaseAddress شماره پورت را به 8080 تغییر دهید.
5- در قسمت BaseAddress به جای Service1 بنویسید: MyNewsService
6- در قسمت endpoint به جای عبارت MyNewsWCFLibrary.IService1 بنویسید: MyNewsWCFLibrary.IMyNewsService 
در پایان تگ Service در App.Config باید همانند کد زیر باشد:
   <services>
      <service name="MyNewsWCFLibrary.MyNewsService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/MyNewsWCFLibrary/MyNewsService/" />
          </baseAddresses>
        </host>
        <!-- Service Endpoints -->
        <!-- Unless fully qualified, address is relative to base address supplied above -->
        <endpoint address="" binding="basicHttpBinding" contract="MyNewsWCFLibrary.IMyNewsService">
          <!-- 
              Upon deployment, the following identity element should be removed or replaced to reflect the 
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
              automatically.
          -->
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <!-- Metadata Endpoints -->
        <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
        <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
تغییرات را ذخیره کنید و پروژه را اجرا کنید. باید پنجره‌ای شبیه به پنجره‌ی زیر نشان داده شود:

در صورت مشاهده پیام خطا، ویژوال استودیو را ببندید و این‌بار به صورت Run as administrator باز کنید.

برای نمونه روی متد AddCategory کلیک کنید. در پنجره نشان داده شده همانند شکل در برابر فیلد CatName مقداری وارد کنید و روی دکمه Invoke کلیک کنید. متد مورد نظر اجرا شده و مقداری که وارد کرده ایم در پایگاه داده‌ها ذخیره می‌شود. مقداری که در قسمت پایین دیده می‌شود خروجی متد است که در اینجا شناسه رکورد درج‌شده است.

بار دیگر برای مشاهده رکورد درج‌شده روی متد GetAllCategory کلیک کنید. به علت این‌که این متد ورودی ندارد در قسمت بالا چیزی نشان داده نمی‌شود. روی دکمه Invoke کلیک کنید. با پیغام خطای زیر روبه‌رو خواهید شد:

افزودن ویژگی Virtual به tblNews و tblCategory در بخش دوم  خواندید؛ باعث می‌شود که Entity Framework در هنگام اجرا کلاس‌هایی با عنوان "پروکسی‌های پویا" به کلاس‌های Address و Customer بیفزاید و بنابراین قابلیت Lazy Loading برای این کلاس‌ها در زمان اجرای برنامه فراهم می‌گردد. 

ولی با افزودن پروکسی‌های پویا به کلاس‌های ما، این کلاس‌ها قابلیت انتقال خود از طریق سرویس‌های WCF را از دست می‌دهند زیرا پروکسی‌های پویا به طور پیش‌گزیده قابلیت سریالایز و دیسریالایز شدن را ندارند!

خوشبختانه می‌توانیم این ویژگی را در کلاس DBContext غیرفعال کنیم. برای این منظور قالب سازنده‌ی آن یا MyNewsModel.Context.tt را از Solution Explorer باز کنید و کد زیر را در آن پیدا کنید:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {

سپس در ادامه‌ی آن کدغیرفعال‌کردن پروکسی پویا را به این شکل بنویسید:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {
      Configuration.ProxyCreationEnabled = false;

اکنون اگر فایل را ذخیره کنیم سپس فایل MyNewsModel.Context.cs را از Solution Explorer باز کنید؛ خواهید دید که این خط کد در جای خود قرارگرفته است.

بار دیگر پروژه را اجرا کنید روی متد GetAllCategory کلیک کنید. این بار اگر دکمه Invoke را بفشارید با همانند شکل زیر را خواهید دید:

در بخش ششم پیرامون ارتباط جدول‌های tblNews و tblCategory و نمایش محتویات وابسته جدول خبر به دسته و تنظیمات آن در t4 و کلاس Service

در بخش هفتم پیرامون میزبانی WCFLibrary در یک Web Application

و در بخش هشتم پیرامون ایجاد یک برنامه‌ی ویندوزی جهت استفاده از سرویس‌های WCF خواهم نوشت. 

نظرات مطالب
نحوه کار با ftp - بخش اول
امکان دیباگ کردن ریز فعالیت‌های System.Net به صورت زیر وجود دارد:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.diagnostics>
   <trace autoflush="true" />
   <sources>
     <source name="System.Net">
       <listeners>
         <add name="MyTraceFile"/>
       </listeners>
     </source>
   </sources>

   <sharedListeners>
     <add
       name="MyTraceFile"
       type="System.Diagnostics.TextWriterTraceListener"
       initializeData="System.Net.trace.log"
               />
   </sharedListeners>

   <switches>
     <add name="System.Net" value="Verbose" />
   </switches>

 </system.diagnostics>
</configuration>
که به فایل app.config و یا web.config قابل افزودن است. به این صورت کلیه هدرهای ارسالی به سرور و ریز فعالیت‌های انجام شده در پشت صحنه‌، در فایلی به نام System.Net.trace.log برای شما ثبت خواهد شد. 
مطالب
آشنایی با Feature Toggle - بخش اول
فرض کنید میخواهید برای بخش‌هایی از نرم افزاری که طراحی کرده‌اید ، امکانی را در نظر بگیرید که بتوانید زمانیکه نرم افزار در حال استفاده‌است، قابلیت‌هایی از آن‌را فعال یا غیرفعال نمایید؛ بدون اینکه نرم افزار از دسترس خارج شود. Feature Toggle که تحت عنوان Feature Flag هم شناخته می‌شود همین امکان را برای ما به ارمغان می‌آورد و ما را قادر می‌سازد تا قابلیت‌هایی را از نرم افزار، فعال یا غیرفعال کنیم، بدون اینکه نیاز باشد نرم افزار از دسترس مشتریان خارج شود و یا نیاز باشد نسخه‌ی جدیدی از نرم افزار  منتشر شود. برای مثال قابلیت ثبت نام کاربران را در بازه‌های خاصی غیرفعال کنیم و یا فرض کنید قابلیت جدیدی به نرم افزار اضافه کرده‌اید و میخواهید بعد از پابلیش، در یک بازه زمانی که نرم افزار شما بازدید کننده‌های کمتری دارد، آن‌را موقتا فعال کنید، نتیجه خروجی را ببینید و سپس آن را غیر فعال نمایید. در ادامه این مقاله سعی خواهیم کرد ابتدا با یک مثال ساده با این قابلیت آشنا شویم و سپس به معرفی یکی از کتابخانه‌های محبوب در این زمینه بپردازیم.
Feature Toggle چیزی بیشتر از یک دستور IF نیست، اگر شرط مورد نظر برقرار بود، کد را اجرا میکند، در غیر اینصورت از اجرای آن بخش صرف نظر میکند.
IF (currentYear<2023){
alert('Wear a mask!');
}
در قطعه کد فوق، سال جاری را چک کرده‌ایم و گفته‌ایم اگر سال جاری کمتر از سال 2023 بود، به بازدید کننده یک پیغام را نمایش دهیم. حال فرض کنید بیماری کرونا، پیش از سال 2023 از بین برود، ولی طبق این شرط همچنان پیغام به کاربران نمایش داده میشود. میتوانیم فعال و غیر فعال بودن نمایش این پیغام را یا از دیتابیس و یا از فایل appsetting.json  بخوانیم که در این حالت  به صورت زیر می‌باشد :
var showCoronaAlert=_cofiguration.GetValue<bool>("Features:showCoronaAlert"); // or read this from Database
If(showCoronaAlert){
alert(Wear a amask!);
}
در این روش بجای اینکه تاریخ را چک کنیم و بر اساس آن تصمیم بگیریم که آیا پیغامی نمایش داده شود یا نه، وضعیت نمایش آن را از فایل تنظیمات و یا دیتابیس خوانده‌ایم. در این حالت دیگر نیازی به تغییر و انتشار نسخه‌ی جدیدی از نرم افزار نیست و فقط کافی‌است مقدار مربوط به نمایش پیغام را در دیتابیس و یا فایل تنظیمات، به روزسانی نماییم.

 کتابخانه  Microsoft.FeatureManagement
کتابخانه  Microsoft.FeatureManagement  توسط تیم اژور پیاده سازی و نوشته شده‌است و برای خواندن اطلاعات، از همان IConfiguration استفاده میکند که ما را قادر می‌سازد تنظیمات را از منابع مختلفی بخوانیم  و همچنین  قابلیت‌های آن فراتر از تنظیم یک مقدار با true/false می‌باشد که در ادامه با بعضی از آنها آشنا خواهیم شد.
ابتدا نیاز هست این کتابخانه را به صورت زیر نصب نماییم :
Install-Package Microsoft.FeatureManagement

سپس نیاز هست در متد ConfigureService، سرویس مربوطه را اضافه نماییم :
using Microsoft.FeatureManagement;
public void ConfigureServices(IServiceCollection services)
{
    services.AddFeatureManagement();
}

این کتابخانه به صورت پیش فرض، اطلاعات feature‌ها را از بخشی (section) تحت عنوان FeatureManagement  از فایل appsetting.json می‌خواند. پس نیاز داریم این بخش را در appsetting.json تعریف نماییم  ( لیست تمامی قابلیت‌هایی را که قصد داریم به صورت داینامیک فعال/غیرفعال کنیم، در این بخش اضافه خواهیم کرد):
"FeatureManagement": {
   
}
اگر تمایل داشتید از اسم دیگری برای بخش تنظیمات، در فایل appsetting. json  استفاده نمایید، می‌توانید به صورت زیر این کار را انجام دهید :
public void ConfigureServices(IServiceCollection services)
{
 services.AddFeatureManagement(Configuration.GetSection("MyFeatureManagement"))
}
در این مقاله از همان اسم پیش فرض استفاده شده است.
افزودن یک قابلیت جدید
"FeatureManagement": {
   "MaskAlert":true
}

همان مثال بالا را  در بخش FeatureManagement  اضافه کرده‌ایم  و مقدار true را به معنی فعال بودن، برای آن در نظر گرفته‌ایم. این حالت، ساده‌ترین روش ثبت یک قابلیت با استفاده از این کتابخانه می‌باشد. برای بررسی وضعیت هر کدام از قابلیت‌ها باید اینترفیس  IFeatureManager   را به کلاس مربوطه تزریق نماییم و سپس بر اساس نام قابلیت، وضعیت آن را بررسی نماییم:
 public class HomeController : Controller
    {
        private readonly IFeatureManager _featureManager;

        public HomeController(IFeatureManager featureManager)
        {
            _featureManager = featureManager;
        }
        public async Task<IActionResult> Index()
        {
            if(await _featureManager.IsEnabledAsync("MaskAlert"))
            {
                // show messeage
            }

            return View();
        }
    }
اگر نیاز هست از اسم دیگری برای بخش (section)

فعال سازی بر اساس تاریخ (TimeWindowsFilter)
یکی از قابلیت‌های این کتابخانه، فعال سازی بر اساس بازه زمانی هست. اگر نیاز دارید یک قابلیت در یک بازه‌ی خاص فعال شود، میتوانید از این قابلیت استفاده کنید. برای فعال سازی این امکان، باید فیلتر TimeWindowFilter را که به صورت توکار به همراه کتابخانه وجود دارد، به صورت زیر در متد configureServices ثبت نماییم:
public void ConfigureServices(IServiceCollection services)
{ 
    services.AddFeatureManagement().AddFeatureFilter<TimeWindowFilter>();
}

و سپس یک Feature را در بخش FeatureManagement همانند زیر تعریف میکنیم که توسط آن مشخص کرده‌ایم این قابلیت در بازه‌ی زمانی بین دو تاریخ تعریف شده، فعال باشد :
 "FeatureManagement": {
    "EmergencyBanner": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "Start": "01 Mar 2021 12:00:00 +00:00",
            "End": "01 Apr 2021 12:00:00 +00:00"
          }
        }
      ]
    }
  }
و نحوه‌ی بررسی فعال بودن آن، همانند روش قبل می‌باشد و فقط کافیست اسم Feature را به متد IsEnabledAsync بدهیم:
if(await _featureManager.IsEnabledAsync("EmergencyBanner")){
// show Emergency banner 
}

 پارامتر‌های Start و End میتوانند به صورت تکی هم استفاده شوند؛ به این معنا که میتوانید فقط پارامتر start را مقدار دهی کنید و در این حالت از تاریخ مورد نظر به بعد، Feature مورد نظر فعال می‌باشد و یا اگر فقط پارامتر End مقدار دهی شود، Feature مورد نظر فقط تا تاریخ تعیین شده فعال هست و بعد از آن برای همیشه غیرفعال می‌شود.
در زیر، نمونه‌ای از این حالت تنظیم شده‌است :
"FeatureManagement": {
    "EmergencyBanner": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "End": "01 Apr 2021 12:00:00 +00:00"
          }
        }
      ]
    }
  }

فیلتر‌های سفارشی
از دیگر مزایای این کتابخانه این هست که محدود به فیلترهای توکار خود آن نیستیم و امکان توسعه و نوشتن فیلتر‌های سفارشی را به ما میدهد. برای مثال اگر یک قابلیت را در نرم افزار پیاده سازی کرده‌ایم که میخواهیم فقط بر روی مرورگر‌های خاصی در دسترس باشد، میتوانیم به صورت زیر این کار را انجام دهیم:
ابتدا در appsetting.json قابلیت (Feature) مورد نظر را به صورت زیر تعریف می‌کنیم :
"FeatureManagement": {
    "ChatV2": {
      "EnabledFor": [
        {
          "Name": "BrowserFilter",
          "Parameters": {
            "AllowedBrowsers": [ "Chrome" ]
          }
        }
      ]
    }
  }
سپس فیلتر سفارشی را به صورت زیر پیاده سازی میکنیم :
[FilterAlias("BrowserFilter")]
public class BrowserFilter:IFeatureFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public BrowserFilter(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
        {
            var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
            var settings = context.Parameters.Get<BrowserFilterSettings>();
            return Task.FromResult(settings.AllowedBrowsers.Any(userAgent.Contains));
        }
    }

کلاس BrowserFilter :
  public class BrowserFilterSettings
    {
        public string[] AllowedBrowsers { get; set; }
    }
بعد از پیاده سازی فیلتر فوق نیاز هست فیلتر سفارشی را که در بالا نوشتیم، در متد ConfigureServices ثبت نماییم. با توجه به اینکه برای تشخیص نوع مروگر کاربر نیاز هست  هدر درخواست را بررسی کنیم، پس نیاز هست IHttpContextAccessor را هم ثبت نماییم:
public void ConfigureServices(IServiceCollection services)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddFeatureManagement()
                .AddFeatureFilter<BrowserFilter>();
        }
و برای بررسی فعال بودن قابلیت مورد نظر فقط کافیست مانند قبل، اسم قابلیت مورد نظر را به صورت زیر بررسی کنیم :
if(await _featureManager.IsEnabledAsync("ChatV2")){
// do something 
}

* از دیگر قابلیت‌های این کتابخانه، فعال و غیر فعال کردن کنترلر و اکشن متدها بر اساس وضعیت Feature‌ها می‌باشد که در بخش دوم این مقاله به توضیح این موارد خواهیم پرداخت.
نظرات مطالب
تبدیل HTML فارسی به PDF با استفاده از افزونه‌ی XMLWorker کتابخانه‌ی iTextSharp
بله. یک کلاس لاگر سفارشی درست کنید:
    public class CustomLogger : iTextSharp.text.log.ILogger
    {
        public iTextSharp.text.log.ILogger GetLogger(Type klass)
        {
            return this;
        }

        public iTextSharp.text.log.ILogger GetLogger(string name)
        {
            return this;   
        }

        public bool IsLogging(iTextSharp.text.log.Level level)
        {
            return true;
        }

        public void Warn(string message)
        {
            System.Diagnostics.Trace.TraceWarning(message);
        }

        public void Trace(string message)
        {
            System.Diagnostics.Trace.TraceInformation(message);
        }

        public void Debug(string message)
        {
            System.Diagnostics.Trace.TraceInformation(message);
        }

        public void Info(string message)
        {
            System.Diagnostics.Trace.TraceInformation(message);
        }

        public void Error(string message)
        {
            System.Diagnostics.Trace.TraceError(message);
        }

        public void Error(string message, Exception e)
        {
            System.Diagnostics.Trace.TraceError(message + System.Environment.NewLine + e);
        }
    }
بعد در ابتدای اجرای برنامه آن‌را ثبت کنید:
iTextSharp.text.log.LoggerFactory.GetInstance().SetLogger(new CustomLogger());
خروجی‌ها در پنجره دیباگ VS.NET نمایش داده می‌شوند.
نظرات مطالب
EF Code First #14
- بحث Self tracking entities در حالت database first است و در Code first پشتیبانی نمی‌شود و احتمالا هم نخواهد شد.
در اینجا رسما پایین صفحه قید شده که دیگر از STE با EF 5.0 برای حالت‌های N-Tier استفاده نکنید.
- در EF Code first توصیه می‌شود که برای کار با WCF از WCF Data Services و یا RIA Services استفاده کنید. هر دوی این‌ها برای کار با EF Code first به روز شدن اخیرا. هر دوی این‌ها change tracking سمت کاربر رو هم پشتیبانی می‌کنند.
مطالب
نمایش تقویم ماهیانه شمسی توسط PdfReport
در نگارش 1.6، قالب سلول جدیدی به نام MonthCalendar اضافه شده است که امکان نمایش تقویم ماهیانه شمسی و میلادی را فراهم می‌کند. در ادامه نحوه استفاده از آن‌را بررسی خواهیم کرد. کدهای کامل این مثال را از اینجا نیز می‌توانید دریافت کنید: (^)
فرض کنید اطلاعات حضور و غیاب کارمندان را به نحو زیر در اختیار دارید:
namespace PdfReportSamples.Models
{
    public class UserWorkedHours
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int DayNumber { set; get; }
        public int Month { set; get; }
        public int Year { set; get; }
        public string Description { set; get; }
    }
}
و برای نمونه منبع داده فرضی ما نیز به صورت زیر است (تعدادی روز، به همراه ساعات کارکرد):
        private static List<UserWorkedHours> createUsersWorkedHours()
        {
            var usersWorkedHours = new List<UserWorkedHours>();
            for (int i = 1; i < 11; i++)
            {
                for (int j = 1; j < 28; j++)
                {
                    usersWorkedHours.Add(new UserWorkedHours
                    {
                        Id = i,
                        Name = "کارمند " + i,
                        Year = 1391, // سال و ماه بر اساس نوع تقویم انتخابی مشخص می‌شود
                        Month = i,
                        DayNumber = j,
                        Description = i % 2 == 0 ? "05:00" : "08:00"
                    });
                }
            }
            return usersWorkedHours;
        }
در این منبع داده فرضی، متن Description ذیل شماره روز، در تقویم ماهیانه نمایش داده خواهد شد.
سلولی که قرار است قالب MonthCalendar را نمایش دهد نیاز به شیء‌ایی از نوع PdfRpt.Calendar.CalendarData دارد که به نحو زیر تعریف شده است:
using System.Collections.Generic;

namespace PdfRpt.Calendar
{
    public class CalendarData
    {
        public int Month { set; get; }
        public int Year { set; get; }
        public IList<DayInfo> MonthDaysInfo { set; get; }
    }
}

namespace PdfRpt.Calendar
{
    public class DayInfo
    {
        public int DayNumber { set; get; }
        public int Month { set; get; }
        public int Year { set; get; }

        public string Description { set; get; }
        public bool ShowDescriptionInFooter { set; get; }
    }
}
این ساختار بر اساس اطلاعات یک ماه و روزهای آن است. متن Description در صورت false بودن ShowDescriptionInFooter ذیل شماره روز نمایش داده خواهد شد، در غیراینصورت در پایان ماه به شکل یک سطر جدید نمایش داده می‌شود. در اینجا روزهای ماه و سال بر اساس نوع تقویم معنا خواهند شد.

اکنون نیاز است تا اطلاعات منبع داده خود را به CalendarData نگاشت کنیم تا بتوان از آن در قالب سلول جدید MonthCalendar استفاده کرد. انجام اینکار با استفاده از امکانات LINQ به نحو زیر است:
        public static IList<UserMonthCalendar> CreateDataSource()
        {
            var usersWorkedHours = createUsersWorkedHours();
            // Mapping a list of normal Users WorkedHours to a list of Users + CalendarData
            return usersWorkedHours
                        .GroupBy(x => new
                        {
                            Id = x.Id,
                            Name = x.Name
                        })
                        .Select(
                                 x => new UserMonthCalendar
                                 {
                                     Id = x.Key.Id,
                                     Name = x.Key.Name,
                                     // Calendar's cell data type should be PdfRpt.Calendar.CalendarData
                                     MonthCalendarData = new CalendarData
                                     {
                                         Year = x.First().Year,
                                         Month = x.First().Month,
                                         MonthDaysInfo = x.ToList().Select(y => new DayInfo
                                         {
                                             Description = y.Description,
                                             ShowDescriptionInFooter = false,
                                             DayNumber = y.DayNumber
                                         }).ToList()
                                     }
                                 }).ToList();
        }
UserMonthCalendar، شامل ستون‌هایی است که قرار است در گزارش ما ظاهر شوند:
using PdfRpt.Calendar;

namespace PdfReportSamples.Models
{
    public class UserMonthCalendar
    {
        public int Id { set; get; }
        public string Name { set; get; }
        // Calendar's cell data type should be CalendarData
        public CalendarData MonthCalendarData { set; get; }
    }
}
ستون اول، شماره شخص، ستون دوم شامل نام شخص و ستون سوم، شامل اطلاعات یک ماه شخص است.
برای نمایش این اطلاعات توسط PdfReport، دو ستون اول یاد شده نکته خاصی ندارند، اما نحوه تعریف ستون تقویم ماهیانه آن به صورت زیر خواهد بود:
                columns.AddColumn(column =>
                {
                    // Calendar's cell data type should be PdfRpt.Calendar.CalendarData
                    column.PropertyName<UserMonthCalendar>(x => x.MonthCalendarData);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(3);
                    column.HeaderCell("تقویم ماهیانه");
                    column.ColumnItemsTemplate(itemsTemplate =>
                    {
                        itemsTemplate.MonthCalendar(new CalendarAttributes
                        {
                            CalendarType = CalendarType.PersianCalendar,
                            UseLongDayNamesOfWeek = true,
                            Padding = 3,
                            DescriptionHorizontalAlignment = HorizontalAlignment.Center,
                            SplitRows = true,
                            CellsCustomizer = info =>
                            {
                                if (info.Year == 1391 && info.Month == 1 && info.DayNumber == 1)
                                {
                                    info.NumberCell.BackgroundColor = new BaseColor(System.Drawing.Color.LimeGreen);
                                    var phrase = info.NumberCell.Phrase;
                                    foreach (var chunk in phrase.Chunks)
                                        chunk.Font.Color = new BaseColor(System.Drawing.Color.Yellow);
                                }
                            }
                        });
                    });
                });
توسط CalendarAttributes می‌توان یک سری از خواص تقویم نمایش داده شده را تغییر داد. برای مثال CalendarType مشخص می‌کند که نوع تقویم شمسی است یا میلادی؛ UseLongDayNamesOfWeek برای نمایش نام روزها به صورت کامل «شنبه» یا «ش» (نام کوتاه شده آن) بکار می‌رود. SplitRows مشخص می‌کند که اگر تقویم در یک صفحه جا نشد، به صفحه بعد منتقل شود یا تا جایی که ممکن است در صفحه جاری اطلاعات آن نمایش داده شده و سپس مابقی را در صفحه بعد ترسیم کند (مقدار true آن). به علاوه توسط CellsCustomizer می‌توان فرمت کردن شرطی اطلاعات را انجام داد. برای مثال در اینجا اگر روز مورد نظر، روز اول سال 91 باشد، رنگ زمینه سلول و رنگ متن عدد آن تغییر خواهد کرد.