نظرات مطالب
Ajax.BeginForm و ارسال فایل به سرور در ASP.NET MVC
تفاوتی نمی‌کند. افزونه‌ی مورد استفاده وابستگی خاصی به ASP.NET MVC ندارد. خلاصه عملیات فوق به این نحو است که فراخوانی این افزونه در زمان submit فرم انجام می‌شود. همین رویداد را در فرم‌های مودال یا هر فرم دیگری نیز می‌توان تحت کنترل قرار داد و بازنویسی کرد.
مطالب
با رویه‌های ذخیره شده خود، وب سرویس ایجاد کنید

قابلیت جالبی از SQL Server 2005 به بعد به این محصول اضافه شده است که امکان ایجاد یک وب سرویس بومی را بر اساس رویه‌های ذخیره شده و یا توابع تعریف شده در دیتابیس‌های موجود، فراهم می‌سازد. این قابلیت نیازی به IIS یا هر هاست دیگری برای اجرا ندارد و توسط خود اس کیوال سرور راه اندازی و مدیریت می‌شود.
توضیحات مفصل آن‌‌را در MSDN می‌توانید ملاحظه کنید و در اینجا یک مثال عملی از آن را با هم مرور خواهیم کرد:

الف) ایجاد یک جدول آزمایشی به همراه تعدادی رکورد دلخواه در آن

CREATE TABLE [tblWSTest](
[id] [int] IDENTITY(1,1) NOT NULL,
[f1] [nvarchar](50) NULL,
[f2] [nvarchar](500) NULL,
CONSTRAINT [PK_tblWSTest] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

SET IDENTITY_INSERT [tblWSTest] ON
INSERT [tblWSTest] ([id], [f1], [f2]) VALUES (1, N'a1', N'a2')
INSERT [tblWSTest] ([id], [f1], [f2]) VALUES (2, N'b1', N'b2')
INSERT [tblWSTest] ([id], [f1], [f2]) VALUES (3, N'c1', N'c2')
INSERT [tblWSTest] ([id], [f1], [f2]) VALUES (4, N'd1', N'd2')
INSERT [tblWSTest] ([id], [f1], [f2]) VALUES (5, N'e1', N'e2')
SET IDENTITY_INSERT [dbo].[tblWSTest] OFF
ب) ایجاد یک رویه ذخیره شده در دیتابیس جاری

CREATE PROCEDURE GetAllData
AS
SELECT f1,
f2
FROM tblWSTest
ج) ایجاد یک HTTP Endpoint

CREATE ENDPOINT GetDataService
STATE = STARTED
AS HTTP(
PATH = '/GetData',
AUTHENTICATION = (INTEGRATED),
PORTS = (CLEAR),
CLEAR_PORT = 8080,
SITE = '*'
)
FOR SOAP(
WEBMETHOD 'GetAllData'
(NAME = 'testdb2009.dbo.GetAllData'),
WSDL = DEFAULT,
DATABASE = 'testdb2009',
NAMESPACE = DEFAULT
)

توضیحات:
Ports در حالت clear و یا ssl می‌تواند باشد. همچنین برای اینکه با IIS موجود بر روی سیستم هم تداخل نکند CLEAR_PORT به 8080 تنظیم شده است. سایر پارامترهای آن بسیار واضح هستند. برای مثال تعیین دیتابیسی که این رویه ذخیره شده در آن قرار دارد و همچنین مسیر کامل دسترسی به آن دقیقا مشخص می‌گردند.


این وب سرویس هم اکنون آغاز به کار کرده است. برای مشاهده wsdl آن، آدرس زیر را در مرورگر وب خود وارد نمائید (PATH و CLEAR_PORT معرفی شده در endPoint اینجا بکار می‌رود):

http://localhost:8080/GetData?wsdl

د) استفاده از این وب سرویس در یک برنامه ویندوزی
یک برنامه ساده winForms را شروع کنید. سپس یک DataGridView را بر روی فرم قرار دهید (بدیهی است این مورد می‌تواند یک برنامه ASP.Net هم باشد و موارد مشابه دیگر). سپس از منوی پروژه، یک service reference را در VS2008 بر اساس آدرس wdsl فوق اضافه کنید (شکل زیر):


برای اینکه این مثال در VS2008 درست کار کند باید فایل app.config ایجاد شده را کمی ویرایش کرد. قسمت security آن را یافته و تغییرات زیر را با توجه به AUTHENTICATION مورد نیاز تغییر دهید:

<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
سپس کد برنامه ما به صورت زیر خواهد بود:

using System;
using System.Data;
using System.Windows.Forms;

namespace WebServiceTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
ServiceReference1.GetDataServiceSoapClient data =
new ServiceReference1.GetDataServiceSoapClient();
dataGridView1.DataSource = (data.GetAllData()[0] as DataSet).Tables[0];
}
}
}




مطالب
React 16x - قسمت 25 - ارتباط با سرور - بخش 4 - یک تمرین
همان مثال backend قسمت 22 را با افزودن وب سرویس‌هایی برای قسمت‌های نمایش لیست فیلم‌ها، ژانرها و سایر صفحات اضافه شده‌ی به برنامه‌ی تکمیل شده‌ی تا قسمت 21، توسعه می‌دهیم. کدهای کامل آن، به علت شباهت و یکی بودن نکات آن با مطلب 22، در اینجا تکرار نخواهند شد و می‌توانید کل پروژه‌ی آن‌را از پیوست انتهای بحث دریافت کنید. سپس فایل dotnet_run.bat آن‌را اجرا کنید تا در آدرس https://localhost:5001 قابل دسترسی شود.


افزودن سرویس httpService.js به برنامه

تا این قسمت، تمام اطلاعات نمایش داده شده‌ی در لیست فیلم‌ها، از سرویس درون حافظه‌ای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین می‌شوند. اکنون در ادامه می‌خواهیم این سرویس‌ها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامه‌ی نمایش لیست فیلم‌ها نیز اضافه می‌کنیم (فایل آن‌را از پروژه‌ی قبلی کپی کرده و در اینجا paste می‌کنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگی‌های خارجی react-toastify و axios است. به همین جهت برای افزودن آن‌ها مراحل زیر را طی می‌کنیم:
- نصب کتابخانه‌های react-toastify و axios از طریق خط فرمان (با فشردن دکمه‌های ctrl+back-tick در VSCode):
> npm i axios --save
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />


دریافت اطلاعات لیست نمایش ژانرها از سرویس backend

با فراخوانی آدرس https://localhost:5001/api/Genres، می‌توان لیست ژانرهای سینمایی تعریف شده‌ی در سرویس‌های backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
چون نمی‌خواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت می‌کند، جایگزین می‌کنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد می‌کنیم. سپس آن‌را طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json";
import http from "./httpService";

export function getGenres() {
  return http.get(apiUrl + "/genres");
}
همچنین در اینجا import وابستگی config.json را نیز مشاهده می‌کنید که در قسمت قبل در مورد آن توضیح دادیم. به همین جهت برای تمیزتر شدن قسمت‌های مختلف برنامه، فایل config.json را در مسیر src\config.json ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
apiUrl به ریشه‌ی URLهای ارائه شده‌ی توسط backend service ما، اشاره می‌کند.

پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
را با سطر جدید زیر، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، خطای زیر را مشاهده خواهید کرد:
Uncaught TypeError: Object is not a function or its return value is not iterable
علت اینجا است که سرویس قبلی fakeGenreService، دارای متد export شده‌ای به نام getGenres بود که یک آرایه‌ی معمولی را بازگشت می‌داد. اکنون این سرویس جدید نیز همان ساختار را دارد، اما اینبار یک Promise را بازگشت می‌دهد. به همین جهت متد componentDidMount را به صورت زیر اصلاح می‌کنیم:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ _id: "", name: "All Genres" }, ...data];
    this.setState({ movies: getMovies(), genres });
  }
متد getGenres باید await شود تا نتیجه‌ی آن توسط خاصیت data شیء بازگشتی از آن، قابل دسترسی شود. با این تغییر، نیاز است این متد را نیز به صورت async معرفی کرد.


دریافت اطلاعات لیست فیلم‌ها از سرویس backend

پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونه‌ی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/movies";

function movieUrl(id) {
  return `${apiEndpoint}/${id}`;
}

export function getMovies() {
  return http.get(apiEndpoint);
}

export function getMovie(movieId) {
  return http.get(movieUrl(movieId));
}

export function saveMovie(movie) {
  if (movie.id) {
    return http.put(movieUrl(movie.id), movie);
  }

  return http.post(apiEndpoint, movie);
}

export function deleteMovie(movieId) {
  return http.delete(movieUrl(movieId));
}
سپس شروع به اصلاح کامپوننت movies می‌کنیم.
ابتدا دو متد دریافت لیست فیلم‌ها و حذف یک فیلم را که در این کامپوننت استفاده شده‌اند، import می‌کنیم:
import { getMovies, deleteMovie } from "../services/movieService";
بعد متد getMovies پیشین، که یک آرایه را بازگشت می‌داد، توسط متد جدیدی که یک Promise را بازگشت می‌دهد، جایگزین می‌شود:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ id: "", name: "All Genres" }, ...data];

    const { data: movies } = await getMovies();
    this.setState({ movies, genres });
  }
همچنین مدیریت حذف رکوردها را نیز به صورت زیر با پیاده سازی «به‌روز رسانی خوشبینانه UI» که در قسمت قبل در مورد آن بیشتر بحث شد، تغییر می‌دهیم. در این حالت فرض بر این است که به احتمال زیاد،  await deleteMovie با موفقیت به پایان می‌رسد. بنابراین بهتر است UI را ابتدا به روز رسانی کنیم تا کاربر حس کار کردن با یک برنامه‌ی سریع را داشته باشد:
  handleDelete = async movie => {
    const originalMovies = this.state.movies;

    const movies = originalMovies.filter(m => m.id !== movie.id);
    this.setState({ movies });

    try {
      await deleteMovie(movie.id);
    } catch (ex) {
      if (ex.response && ex.response.status === 404) {
        console.log(ex);
        toast.error("This movie has already been deleted.");
      }

      this.setState({ movies: originalMovies }); //undo changes
    }
  };
ابتدا ارجاعی را از state قبلی ذخیره می‌کنیم تا در صوت بروز خطایی در سطر await deleteMovie، بتوانیم مجددا state را به حالت اول آن بازگردانیم. به همین منظور پیاده سازی «به‌روز رسانی خوشبینانه UI»، حتما نیاز به درج صریح try/catch را دارد. برای نمایش خطاهای ویژه‌ی 404 نیز از یک toast استفاده شده که نیاز به import زیر را دارد:
import { toast } from "react-toastify";
سایر خطاهای رخ داده، توسط interceptor درج شده‌ی در سرویس http، به صورت خودکار پردازش می‌شوند.


اتصال فرم ثبت و ویرایش یک فیلم به backend server

تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شده‌ی در صفحه، به صفحه‌ی not-found هدایت می‌شویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
قبلی را با نمونه‌ها‌ی جدیدی که با سرور کار می‌کنند، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
import { getMovie, saveMovie } from "../services/movieService";
سپس ارجاعات به این سه متد import شده را با await، همراه کرده و متد اصلی را به صورت async معرفی می‌کنیم:
  async componentDidMount() {
    const { data: genres } = await getGenres();
    this.setState({ genres });

    const movieId = this.props.match.params.id;
    if (movieId === "new") return;

    const { data: movie } = await getMovie(movieId);
    if (!movie) return this.props.history.replace("/not-found");

    this.setState({ data: this.mapToViewModel(movie) });
  }
البته می‌توان جهت بهبود کیفیت کدها، از متد componentDidMount، دو متد با مسئولیت‌های مجزای دریافت ژانرها (populateGenres) و سپس نمایش فرم اطلاعات فیلم (populateMovie) را استخراج کرد:
  async populateGenres() {
    const { data: genres } = await getGenres();
    this.setState({ genres });
  }

  async populateMovie() {
    try {
      const movieId = this.props.match.params.id;
      if (movieId === "new") return;

      const { data: movie } = await getMovie(movieId);
      this.setState({ data: this.mapToViewModel(movie) });
    } catch (ex) {
      if (ex.response && ex.response.status === 404)
        this.props.history.replace("/not-found");
    }
  }

  async componentDidMount() {
    await this.populateGenres();
    await this.populateMovie();
  }
در متد populateMovie، اگر movieId اشتباهی وارد شود و یا کلا عملیات دریافت اطلاعات، با شکست مواجه شود، کاربر را به صفحه‌ی not-found هدایت می‌کنیم. یعنی وجود try/catch در اینجا ضروری است. چون اگر movieId اشتباهی وارد شود، اینبار دیگر خطوط بعدی اجرا نمی‌شوند و در همان سطر await getMovie، یک استثناء صادر شده و کار خاتمه پیدا می‌کند. بنابراین نیاز داریم بتوانیم این استثنای احتمالی را مدیریت کرده و کاربر را به صفحه‌ی not-found هدایت کنیم.
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت می‌داد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت می‌گرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.

مرحله‌ی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام می‌شود:
  doSubmit = async () => {
    await saveMovie(this.state.data);

    this.props.history.push("/movies");
  };


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip
مطالب
مدیریت هماهنگ شماره نگارش اسمبلی در چندین پروژه‌ی ویژوال استودیو
عموما برای نگهداری ساده‌تر قسمت‌های مختلف یک پروژه، اجزای آن به اسمبلی‌های مختلفی تقسیم می‌شوند که هر کدام در یک پروژه‌ی مجزای ویژوال استودیو قرار خواهند گرفت. یکی از نیازهای مهم این نوع پروژه‌ها، داشتن شماره نگارش یکسانی بین اسمبلی‌های آن است. به این ترتیب توزیع نهایی ساده‌تر شده و همچنین پشتیبانی از آن‌ها در دراز مدت، بر اساس این شماره نگارش بهتر صورت خواهد گرفت. برای مثال در لاگ‌های خطای برنامه با بررسی شماره نگارش اسمبلی مرتبط، حداقل می‌توان متوجه شد که آیا کاربر از آخرین نسخه‌ی برنامه استفاده می‌کند یا خیر.
روش معمول انجام این‌کار، به روز رسانی دستی تمام فایل‌های AssemblyInfo.cs یک Solution است و همچنین اطمینان حاصل کردن از همگام بودن آن‌ها. در ادامه قصد داریم با استفاده از فایل‌های T4، یک فایل SharedAssemblyInfo.tt را جهت تولید اطلاعات مشترک Build بین اسمبلی‌های مختلف یک پروژه، تولید کنیم.


ایجاد پروژه‌ی SharedMetaData

برای نگهداری فایل مشترک SharedAssemblyInfo.cs نهایی و همچنین اطمینان از تولید مجدد آن به ازای هر Build، یک پروژه‌ی class library جدید را به نام SharedMetaData به Solution جاری اضافه کنید.



سپس نیاز است یک فایل text template جدید را به نام SharedAssemblyInfo.tt، به این پروژه اضافه کنید.


به خواص فایل SharedAssemblyInfo.tt مراجعه کرده و Transform on build آن‌را true کنید. به این ترتیب مطمئن خواهیم شد این فایل به ازای هر build جدید، مجددا تولید می‌گردد.



اکنون محتوای این فایل را به نحو ذیل تغییر دهید:
 <#@ template debug="false" hostspecific="false" language="C#" #>
//
// This code was generated by a tool. Any changes made manually will be lost
// the next time this code is regenerated.
//
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyCompany("some name")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguageAttribute("en")]

[assembly: AssemblyProduct("product name")]
[assembly: AssemblyCopyright("Copyright VahidN 2014")]
[assembly: AssemblyTrademark("some name")]

#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif

// Assembly Versions are incremented manually when branching the code for a release.
[assembly: AssemblyVersion("<#= this.MajorVersion #>.<#= this.MinorVersion #>.<#= this.BuildNumber #>.<#= this.RevisionNumber #>")]
// Assembly File Version should be incremented automatically as part of the build process.
[assembly: AssemblyFileVersion("<#= this.MajorVersion #>.<#= this.MinorVersion #>.<#= this.BuildNumber #>.<#= this.RevisionNumber #>")]

<#+
// Manually incremented for major releases, such as adding many new features to the solution or introducing breaking changes.
int MajorVersion = 1;
// Manually incremented for minor releases, such as introducing small changes to existing features or adding new features.
int MinorVersion = 0;
// Typically incremented automatically as part of every build performed on the Build Server.
int BuildNumber = (int)(DateTime.UtcNow - new DateTime(2013,1,1)).TotalDays;
// Incremented for QFEs (a.k.a. “hotfixes” or patches) to builds released into the Production environment.
// This is set to zero for the initial release of any major/minor version of the solution.
int RevisionNumber = 0;
#>
در این فایل اجزای شماره نگارش برنامه به صورت متغیر تعریف شده‌اند. هر بار که نیاز است یک نگارش جدید ارائه شود، می‌توان این اعداد را تغییر داد.
MajorVersion با افزودن تعداد زیادی قابلیت به برنامه، به صورت دستی تغییر می‌کند. همچنین اگر یک breaking change در برنامه یا کتابخانه وجود داشته باشد نیز این شماره باید تغییر نماید.
MinorVersion با افزودن ویژگی‌های کوچکی به نگارش فعلی برنامه تغییر می‌کند.
BuildNumber به صورت خودکار بر اساس هر Build انجام شده باید تغییر یابد. در اینجا این عدد به صورت خودکار به ازای هر روز، یک واحد افزایش پیدا می‌کند. ابتدای مبداء آن در این مثال، 2013 قرار گرفته‌است.
RevisionNumber با ارائه یک وصله جدید برای نگارش فعلی برنامه، به صورت دستی باید تغییر کند. اگر اعداد شماره نگارش major یا minor تغییر کنند، این عدد باید به صفر تنظیم شود.

اکنون اگر این محتوای جدید را ذخیره کنید، فایل SharedAssemblyInfo.cs به صورت خودکار تولید خواهد شد.


افزودن فایل SharedAssemblyInfo.cs به صورت لینک به تمام پروژه‌ها

نحوه‌ی افزودن فایل جدید SharedAssemblyInfo.cs به پروژه‌های موجود، اندکی متفاوت است با روش معمول افزودن فایل‌های cs هر پروژه. ابتدا از منوی پروژه گزینه‌ی add existing item را انتخاب کنید. سپس فایل  SharedAssemblyInfo.cs را یافته و به صورت add as link، به تمام پروژه‌های موجود اضافه کنید.


اینکار باید در مورد تمام پروژه‌ها صورت گیرد. به این ترتیب چون فایل SharedAssemblyInfo.cs به این پروژه‌ها صرفا لینک شده‌است، اگر محتوای آن در پروژه‌ی metadata تغییر کند، به صورت خودکار و یک دست، در تمام پروژه‌های دیگر نیز منعکس خواهد شد.

در ادامه اگر بخواهید Solution را Build کنید، پیام تکراری بودن یک سری از ویژگی‌ها را یافت خواهید کرد. این مورد از این جهت رخ می‌دهد که هنوز فایل‌های AssemblyInfo.cs اصلی، در پروژه‌های برنامه موجود هستند.
این فایل‌ها را یافته و صرفا چند سطر همیشه ثابت ذیل را در آن‌ها باقی بگذارید:
 using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("title")]
[assembly: AssemblyDescription("")]
[assembly: ComVisible(false)]
[assembly: Guid("9cde6054-dd73-42d5-a859-7d4b6dc9b596")]


اضافه کردن build dependency  به پروژه  MetaData

در پایان کار نیاز است اطمینان حاصل کنیم، فایل SharedAssemblyInfo.cs به صورت خودکار پیش از Build هر پروژه، تولید می‌شود. برای این منظور، از منوی Project، گزینه‌ی Project dependencies را انتخاب کنید. سپس در برگه‌ی dependencies آن، به ازای تمام پروژه‌های موجود، گزینه‌ی SharedMetadata را انتخاب نمائید.


این مساله سبب اجرای خودکار فایل SharedAssemblyInfo.tt پیش از هر Build می‌شود و به این ترتیب می‌توان از تازه بودن اطلاعات SharedAssemblyInfo.cs اطمینان حاصل کرد.
اکنون اگر پروژه را Build کنید، تمام اجزای آن شماره نگارش یکسانی را خواهند داشت:

و در دفعات آتی، تنها نیاز است تک فایل SharedAssemblyInfo.tt را برای تغییر شماره نگارش‌های اصلی، ویرایش کرد.
مطالب
چگونگی استفاده از افزونه Isotope در AngularJS

حتما تا به حال در وب سایت‌های زیادی قسمت هایی را دیده اید که چیدمان عناصر آن به شکل زیر است:

این گونه چیدمان را حتما در منوی Start ویندوز 8 بار‌ها دیده‌اید! عناصر تشکیل دهنده‌ی این شکل از چیدمان، می‌توانند یک سری عکس باشند که تشکیل یک گالری عکس را داده‌اند و یا یک سری div که محتوای پست‌های یک وبلاگ را در خود جای داده‌اند. چیزی که این شکل از چیدمان عناصر را نسبت به چیدمان‌های معمول متمایز می‌کند این است که طول و عرض هر یک از این عناصر با یکدیگر متفاوت است و هدف از این گونه چیدمان آن است که این عناصر در فضایی که به آن‌ها اختصاص داده شده است، به صورت بهینه قرار گیرند تا کمترین فضا هدر رود.

برای اعمال این شکل از چیدمان در دنیای وب افزونه‌های زیادی بر فراز کتاب خانه‌ی jQuery تدارک دیده شده است که از جمله مطرح‌ترین آن‌ها می‌توان به افزونه های Isotope ، Masonry و Gridster  اشاره کرد.

افزونه‌ی Isotope مزایایی را برای من در پی داشت و این افزونه را برای انجام کارهای خود، مناسب دیدم. نکته‌ی مهم اینجا است که هدف من بررسی Isotope نیست، چرا که اگر به وب سایت آن مراجعه کنید، با کوهی از مستندات مواجه می‌شوید که چگونه از آن در وب سایت‌های معمولی استفاده کنید.

در این مقاله قصد من این است که نشان دهم چگونه از افزونه‌ی Isotope در AngularJS استفاده کنیم؛ چگونه چیدمان آن را راست به چپ کنیم و چگونه آن را با محیط‌های واکنش گرا (Responsive) سازگار کنیم.

فرض کنید در یک وب سایت قصد داریم اطلاعات یک سری مطلب خبری را از سرور، به فرمت JSON دریافت کرده و نمایش دهیم. در AngularJS شیوه‌ی کار بدین صورت است که اطلاعاتی که به فرمت JSON هستند را با استفاده از directive ایی به نام ng-repeat پیمایش کرده و آن‌ها را نمایش دهیم.  حال اگر بخواهیم چیدمان مطالب را با استفاده از Isotope تغییر دهیم، می‌بینیم که هیچ چیزی نمایش داده نمی‌شود. دلیل آن بر می‌گردد به مراحل کامپایل کردن AngularJS و نامشخص بودن زمان اعمال چیدمان Isotope به عناصر است.

در AngularJS هنگامیکه با دستکاری DOM سر و کار پیدا می‌کنیم، معمولا باید به سراغ Directive‌ها رفت و یک Directive سفارشی برای کار با Isotope تعریف کرد تا با مکانیزم‌های Angular سازگار باشد. خوشبختانه Directive Isotope برای Angular موجود می‌باشد. نکته‌ی مهم این است که این Directive برای نگارش 1 افزونه‌ی Isotope نوشته شده است. البته با نگارش 2 هم کار می‌کند که من برای انجام کار خود نسخه‌ی 1 را ترجیح دادم استفاده کنم.

نکته‌ی بعدی که باید رعایت شود این است که چیدمان عناصر باید از راست به چپ شوند. خوشبختانه این کار در نسخه‌ی 1 Isotope با تغییر کوچکی در سورس Isotope و تغییر یک تابع انجام میشود. گویا نسخه‌ی دوم امکان پیش فرضی را برای این کار دارد، اما نتوانستم آن را به خوبی پیاده سازی کنم و به همین دلیل ترجیح دادم از همان نسخه‌ی اول استفاده کنم.

برای اینکه در هنگام جابه جا شدن عناصر، انیمیشن‌ها نیز از راست به چپ انجام شوند، باید css‌های زیر را نیز اعمال نمود:

.isotope .isotope-item {
  -webkit-transition-property: right, top, -webkit-transform, opacity;
     -moz-transition-property: right, top, -moz-transform, opacity;
      -ms-transition-property: right, top, -ms-transform, opacity;
       -o-transition-property: right, top, -o-transform, opacity;
          transition-property: right, top, transform, opacity;
}

Responsive بودن این عناصر مسئله‌ی دیگری است که باید حل گردد. امروزه اکثر فریم ورک‌های مطرح css، واکنشگرا نیز هستند و برای پشتیبانی از سایز‌های متفاوت صفحه نمایش، تدابیری در نظر گرفته‌اند. اساس کار واکنش گرا بودن این فریم ورک‌ها در تعیین ابعاد عناصر، بیان ابعاد به صورت درصدی است. مثلا فلان عرض div برابر 50% باشد بدین معناست که همیشه عرض این div نصف عرض عنصر والد آن باشد.

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

  برای حل این مشکل می‌توان از امکانات CSS به مانند دستورات زیر استفاده کرد: 
@media (min-width: 768px) and (max-width: 980px) {
    .card {
        width: 320px;
    }
}

@media (min-width: 980px) and (max-width: 1200px) {
    .card {
        width: 260px;
    }
}

@media (min-width: 1200px) {
    .card {
        width: 340px;
    }
}
بدین صورت می‌توان در ابعاد مختلف نمایشگر تعیین کرد که عرض عناصر ما چقدر باشد.
اکنون یک گالری عکس را در نظر بگیرید که در زیر هر عکس توضیحی نیز نوشته شده است و ساختار HTML آن به این صورت است که داخل هر div عکسی نیز موجود است. اگر به شیوه‌ی ذکر شده عمل کنید با یک اشکال مواجه می‌شوید و عناصر روی هم قرار گرفته و اصطلاحا overlapping اتفاق می‌افتد. دلیل این امر این است که لود شدن عکس‌ها عملی زمان گیر است و Isotope قبل از این که عکس لود شود، سایز آن عنصر را محاسبه کرده که در حقیقت این سایز بدون احتساب سایز عکس است و ابعاد واقعی عنصر ما نیست؛ در نتیجه وقتی عکس لود می‌شود آن div فضای بیشتری احتیاج دارد و به همین دلیل به زیر div‌های دیگر می‌رود.
برای حل این مشکل باید به این صورت عمل کرد که وقتی عکس‌ها کامل لود شدند، Isotope وارد عمل شده و سایز عناصر را به دست آورده و آن‌ها را بچیند. برای این کار معمولا از افزونه‌ی  imagesLoaded استفاده می‌کنند که با کمک این افزونه می‌توان مشخص کرد که وقتی تمام عکس‌های موجود در فلان div کامل لود شدند، Isotope وارد عمل شده و عناصر را چیدمان کند.
البته بدون استفاده از افزونه‌ی imagesLoaded و به کمک امکانات AngularJS و تعریف یک Directive سفارشی می‌توان زمان لود شدن عکس‌ها را کنترل کرد.
app.directive('imageOnload', function () {
            return {
                restrict: 'A',
                link: function (scope, element, attrs) {
                    element.bind('load', function () {
                        scope.$emit('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items
                    });
                }
            };
        });
کار این directive این است که به ازای بارگذاری هر عکس، متد reLayout را از Isotope، فراخوانی می‌کند. از این جهت فراخوانی reLayout به ازای لود شدن هر عکس بهتر است که لود شدن تمامی عکس‌ها ممکن است مدت زمان زیادی طول بکشد و کاربر برای مدتی با یک ساختار بهم ریخته مواجه شود.
    
اگر در نمونه کدی که قرار داده‌ام، به انتهای کدهای کنترلر ListController دقت کنید، برای رویداد resize شی window، تابعی تعریف شده است تا به هنگام تغییر سایز صفحه فراخوانی شود. در این رویداد هر بار که سایز پنجره تغییر کرد، پس از یک ثانیه تابع reLayout  افزونه‌ی Isotope را فراخوانی می‌کنیم تا مجددا المنت‌های صفحه چیده شوند. البته ضرورتی وجود نداشته ولی در بعضی مواقع عناصر خوب چیده نمی‌شدند که با فراخوانی reLayout از چیدمان صحیح عناصر مطابق با سایز جدید صفحه اطمینان حاصل پیدا می‌کنیم. دلیل یک ثانیه تاخیر این است که اگر به ساز و کار تعاریف متد‌ها در directive Isotope دقت کنید، از سرویس timeout$  به وفور استفاده شده است. ظاهرا اگر برای فراخوانی reLayout زودتر عمل کنیم با فراخوانی هایی این متد در ساختار خودش تداخل پیدا می‌کند.
$(window).resize(function () {
                $timeout(function myfunction() {
                    $scope.$broadcast('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items
                },1000);
                
            });
   
در نهایت تمامی نکات گفته شده را به صورت یک نمونه کد آماده کردم:
   

مطالب
معرفی افزونه CAT.NET

اخیرا مایکروسافت افزونه‌ رایگانی را برای آنالیز امنیتی کدهای برنامه‌های نوشته شده با VS.Net ارائه داده است به نام CAT.Net.
دریافت افزونه 32 بیتی، 64 بیتی

این افزونه قابلیت بررسی کدهای شما را جهت یافتن خطرات جدی SQL Injection ، XSS و XPath Injection دارد.
نحوه استفاده:
VS.Net خود را ببندید. در ادامه، پس از نصب، به منوی Tools و گزینه‌ی جدید CAT.NET Code Analysis مراجعه نمائید.
صفحه‌ای ظاهر خواهد شد که پس از کلیک بر روی دکمه آغاز آنالیز آن، کار بررسی امنیتی پروژه را آغاز می‌کند (شکل زیر)




این افزونه همانند FxCop ، اسمبلی برنامه را آنالیز می‌کند. پس از پایان آنالیز،‌ با کلیک بر روی هر سطری که گزارش داده، آن سطر را می‌توان در پروژه یافت و تغییرات لازم را اعمال نمود.



همچنین پس از پایان کار بررسی، یک فایل xml هم در مسیر فایل‌های پروژه ایجاد می‌کند که در آینده در صورت نیاز، توسط همین افزونه قابل گشودن است.

بعلاوه پس از نصب، یک فایل chm هم در دایرکتوری آن جهت آشنایی بیشتر با اجزای مختلف این افزونه قرار خواهد گرفت.

البته، برنامه هنوز در مراحل آزمایشی به سر می‌برد. برای مثال پس از نصب در مسیر دیگری غیر از مسیر پیش فرض نصب، پیغام می‌داد که فایل‌ها را نمی‌تواند پیدا کند. اگر این مورد برای شما هم رخ داد، مسیری را که گزارش می‌دهد به صورت دستی درست کنید و فایل‌های کانفیگ آن‌را از جایی که نصب کرده‌اید به آنجایی که برنامه به شما گزارش می‌دهد کپی کنید تا کار کند!

یا می‌توان این افزونه را از طریق خط فرمان هم اجرا کرد (مسیر پروژه، اسمبلی آن و مسیر نصب cat.net‌ را لازم دارد). به صورت زیر:

CATNETCmd /file:"I:\prog\bin\prog.dll" /search:"I:\prog" /report:"I:\prog\report.xsl" /rule:"J:\microsoft\cat.net\Rules"

که نتیجه حاصل را در فایل xsl نهایی ثبت خواهد نمود.

اگر علاقمند به مطالعه تاریخچه‌ی این برنامه هستید به وبلاگ زیر مراجعه نمائید:
مشاهده وبلاگ

نظرات مطالب
استفاده از modal dialogs مجموعه Twitter Bootstrap برای گرفتن تائید از کاربر
باتشکر.کلیه مراحل فوق انجام شد.جهت معرفی فایل JS ساخته شده از روش آموزشی در بخش اول استفاده شد :
    @BundleConfig.AddStyles("~/Content/css",
                            "~/Content/bootstrap.min.css",
                            "~/Content/Site.css")
    @BundleConfig.AddScripts("~/Scripts/js",
    "~/Scripts/jquery-1.10.2.min.js",
                            "~/Scripts/bootstrap.min.js",
                            "~/Scripts/modernizr-2.6.2.js",
                            "~/Scripts/jquery.bootstrap-modal-confirm.js")
    @RenderSection("JavaScript", required: false)
و همچنین کد‌های View طبق مطلب جاری استفاده شده.ولی با کلیک بر روی دکمه حذف صفحه به شکل زیر نمایش داده میشود و فرم مدال نمایش داده نمی‌شود.لطفا راهنمائی کنید.

مطالب
پیاده سازی PWA در Angular
امروزه طراحی اپلیکیشن‌های موبایل بخش زیادی از جامعه را در برگرفته است و روز به روز در حال توسعه میباشند. موازی با رشد روز افزون و نیاز بیشتر به این اپلیکیشن‌ها فریمورک‌های زیادی نیز  ابداع شده اند. از جمله این فریم ورک‌ها می‌توان به موارد زیر اشاره کرد:

Ionic  , react native , flutter , xamarin ….

دیگر لازم نیست برای طراحی اپلیکیشن خود حتما از زبان‌های native  استفاده کنید. بیشتر فریم ورک‌های معرفی شده جاوا اسکریپتی میباشند.

 
مزایای نوشتن یک اپلیکیشن با  فریم ورک‌ها:

1-  نوشتن کد به مراتب آسانتر است.
۲- چون اکثر فریم ورک‌ها، فریم ورک‌های جاوا اسکریپتی هستند، سرعت بالایی هنگام run کردن برنامه دارند ولی در build آخر و خروجی نهایی بر روی پلتفرم، این سرعت کندتر می‌شود.
۳- cossplatform بودن. با طراحی یک اپلیکیشن برای یک پلتفرم می‌توان همزمان برای پلتفرم‌های دیگر خروجی گرفت.


معایب:

۱- برنامه نویس را محدود  میکند.
۲- سرعت اجرای پایینی دارد.
۳- حجم برنامه به مراتب بالاتر میباشد.
۴- از منابع سخت افزاری بیشتری استفاده می‌کند.

اگر شما هم میخواهید از فریم ورک‌ها برای طراحی اپلیکیشن استفاده کنید در اینجا  می‌توانید اطلاعات بیشتری را درباره هر کدام از فریم ورک‌ها ببینید. هر کدام از این فریم ورک‌ها دارای مزایا معایب و همچنین رزومه کاری خوب می‌باشند. بیشتر فریم ورک‌های بالا در رندر آخر، همان کد native را تولید می‌کنند؛ مثلا اگر برنامه‌ای را با react native بنویسید، میتوانید همان برنامه را بر روی اندروید استودیو و کد native بالا بیارید. توضیحات  بالا مقایسه فریم ورک‌های Cross Platform با زبان‌های native بود. اما در این مقاله قصد آشنایی با تکنولوژی جدیدی را داریم که شما را قادر می‌سازد یک وب اپلیکیشن را با آن طراحی کنید.


 pwa چیست؟

pwa مخفف Progressive Web Apps است و یک تکنولوژی برای طراحی وب اپلیکیشن‌های تحت مرورگر میباشد. شما با pwa میتوانید اپلیکیشن خود را بر روی پلتفرم‌های مختلفی اجرا کنید، طوری که کاربران متوجه نشوند با یک صفحه‌ی وبی دارند کار میکنند. شاید شما هم فکر کنید این کار را هم میتوان با html ,css و responsive کردن صفحه انجام داد! ولی اگر بخواهید کاربر متوجه استفاده‌ی از یک صفحه‌ی وبی نشود، باید حتما از pwa استفاده کنید! برای مثال یک صفحه‌ی وبی معمولی حتما باید در بستر اینترنت اجرا شود، ولی با pwa با یکبار وصل شدن به اینترنت و کش کردن داده‌ها، برای بار دوم دیگر نیازی به اینترنت ندارد و میتواند به صورت offline  کار کند. شما حتی با pwa می‌توانید اپلیکیشن را در background اجرا کنید و notification ارسال کنید.


مزیت pwa

۱- یکی از مزیت‌های مهم pwa، حالت offline آن میباشد که حتی با قطع اینترنت، شما میتوانید همچنان با اپلیکیشن کار کنید.
۲- با توجه به اینکه شما در حقیقت با یک صفحه‌ی وبی کار میکنید، دیگر نیازی به دانلود و نصب ندارید.
۳- امکان به‌روز رسانی کردن، بدون اعلام کردن نسخه جدید.
۴- از سرعت بسیار زیادی برخوردار است.
۵- چون pwa از پروتکل https استفاده می‌کند، دارای امنیت بالایی میباشد.


معایب

۱- محدود به مرورگر می‌باشد.
۲- هرچند امروزه اکثر مرورگر‌ها pwa را پشتیبانی میکنند، ولی در بعضی از مرورگر‌ها و مرورگر‌های با ورژن پایین، pwa پشتیبانی نمیشود.
3- نمیتوان یک برنامه‌ی مبتنی بر os را نوشت و محدود به مرورگر میباشد


چند نکته درباره pwa

1- برای pwa  لزومی ندارد حتما از فریم ورک‌های spa استفاده کنید. شما از هر فریم ورک Client Side ای می‌توانید استفاده کنید.
۲- چون صفحات شما در پلتفرم‌های مختلف و با صفحه نمایش‌های مختلفی اجرا می‌شود، باید صفحات به صورت کاملا responsive شده طراحی شوند.
۳- باید از پروتکل https استفاده کنید.

ما در این مقاله از فریم ورک angular  استفاده  خواهیم کرد.

قبل از شروع، با شیوه کار pwa آشنا خواهیم شد. یکی از قسمت‌های مهم  Service Worker ،pwa میباشد که از جمله کش کردن، notification فرستادن و اجرای پردازش‌ها در پس زمینه را بر عهده دارد.


با توجه به شکل بالا، Service Worker مانند یک لایه‌ی نرم افزاری مابین کلاینت و سرور قرار دارد که درخواست‌های داده شده از کلاینت را در صورت اتصال به اینترنت، ارسال کرده و مجددا response را برگشت میدهد‌. اگر به هر دلیلی اینترنت قطع باشد، درخواست به صورت آفلاین به کش مرورگر که توسط proxy ساخته شده است، فرستاده میشود و response را برگشت میدهد.


چند نکته  در رابطه با Service Worker

- نباید برای  نگهداری داده global از Service Worker استفاد کرد. برای استفاده از داده‌های Global میتوان از Local Storage یا IndexedDB استفاده کرد.
- service worker  به dom دسترسی ندارند.


 سناریو

 قبل از شروع، سناریوی پروژه را تشریح خواهیم کرد. رکن اصلی یک برنامه‌ی وب، UI آن میباشد و قصد داریم کاربران را متوجه کار با یک صفحه‌ی وبی نکنیم. قالبی که ما در این مثال در نظر گرفتیم خیلی شبیه به یک اپلیکیشن پلتفرم اندرویدی میباشد. یک کاربر با کشیدن منوی کشویی میتواند گزینه‌های خود را انتخاب نمایند. اولین گزینه‌ای که قصد پیاده سازی آن را داریم، ثبت کاربران میباشد. بعد از ثبت کاربران در یک Component جدا، کاربران را در یک جدول نمایش خواهیم داد.


 قبل از شروع لازم است به چند نکته زیر توجه کرد

1-  سرویس crud را به صورت کامل در پروژه قرار خواهیم داد، ولی چون از حوصله‌ی مقاله خارج است، فقط ثبت کاربران و نمایش کاربران را پیاده سازی خواهیم کرد.
2 - هر اپلیکیشنی قطعا نیاز به یک وب سرویس دارد که واسط بین دیتابیس و اپلیکیشن باشد. ولی ما چون در این مثال به عنوان front end کار قصد طراحی  اپلیکیشن را داریم، برای حذف back end از firebase استفاده خواهیم کرد و مستقیما از انگولار به دیتابیس firebase کوئری خواهیم زد.


 شروع به کار

پیش نیاز‌های یک پروژه‌ی انگیولاری را بر روی سیستم خود فراهم کنید. ما در این مثال از یک template آماده انگیولاری استفاده خواهیم کرد. پس برای اینکه با جزئیات و طراحی ui درگیر نشویم، از لینک github پروژه را دریافت کنید.
سپس وارد root پروژه شوید و با دستور زیر پکیج‌های پروژه را نصب کنید:
 npm install
پس از نصب پکیج‌ها، با دستور ng serve، پروژه را اجرا کنید.

پس از اجرا باید خروجی بالا را داشته باشید. می‌خواهیم یکی از قابلیت‌های pwa را بررسی کنیم. بر روی صفحه کلیک راست کرده و وارد inspect شوید. وارد تب Application  شوید و Service Worker را انتخاب کنید.
 


قبل از اینکه کاری را انجام دهید، چند بار صفحه را refresh کنید! صفحه بدون هیچ تغییری refresh میشود. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید.
این بار صفحه No internet به معنی قطع بودن اینترنت نمایش داده میشود و شما دیگر نمی‌توانید بر روی اپلیکیشن خود فعالیتی داشته باشید! 


نصب pwa  بر روی پروژه

برای اضافه کردن pwa به پروژه وارد ریشه‌ی پروژه شوید و دستور زیر را وارد کنید:
 ng add @angular/PWA
پس از  اجرای دستور بالا، تغییراتی در پروژه لحاظ میشود از جمله در فایل‌هایindex.html,app-module,angular.json، همچنین یک پوشه‌ی جدید به نام icons در assets و دو فایل جیسونی زیر به روت پروژه اضافه شده‌اند:
 Manifest.json : اگر محتویات فایل را مشاهده کرده باشید، شامل تنظیمات فنی وب اپلیکیشن می‌باشد؛ از جمله Home Screen Icon و نام وب اپلیکیشن و سایر تنظیمات دیگر.
 Nsgw-config.json : این فایل نسبت به فایل manifest فنی‌تر میباشد و بیشتر به کانفیگ مد آفلاین و کش کردن مرتبط میشود. در ادامه با این فایل بیشتر کار داریم.

بعد از نصب pwa  بر روی پروژه، همه تنظیمات به صورت خودکار انجام می‌شود. البته می‌توانید تنظیمات مربوط به کش کردن داده‌ها را به صورت پیشرفته‌تر کانفیگ کنید.


  اجرا کردن وب اپلیکیشن

 برای اجرا کردن و نمایش خروجی از وب اپلیکیشن، ابتدا باید از پروژه build گرفت. با استفاده از دستور زیر از پروژه خود build بگیرید:
 ng build -- prod
سپس  با دستور زیر وارد پوشه‌ای که فایل‌های Build  شده در آن قرار دارند، شوید:
 cd dist/Web Application  Pwa
بعد با دستور زیر برنامه را اجرا کنید:
 Http-server -o
اگر چنانچه با دستور بالا به خطا برخوردید، با دستور زیر پکیج npm آن را نصب کنید:
 npm i http-server
اگر تا به اینجای کار درست پیش رفته باشید، پروژه را بدون هیچ تغییری مشاهده خواهیم کرد. inspect گرفته و وارد تب Application شوید. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید! اینبار برنامه بدون هیچ مشکلی کار میکند. حتی شما میتوانید در کنسول، برنامه را به صورت کامل stop کنید.


برسی فایل Nsgw-config.json 

وارد فایل Nsgw-config.json شوید: 

"$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }

    }
  ],
  "dataGroups": [
    {
      "name": "api-performance",
      "urls": [
        "https://api/**"
      ],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 100,
        "maxAge": "3d"
      }
    }
  ]
}
 این فایل مربوط به کش کردن داده‌ها میباشند و میتواند شامل چند object زیر باشد. مهمترین آنها را بررسی خواهیم کرد:
  ۱- assetGroups : کش کردن اطلاعات مربوط به اپلیکیشن
  ۲- Index : کش کردن  فایل مربوط به index.html
  ۳-  assetGroups : کش کردن فایل‌های مربوط به asset، شامل فایل‌های js، css  و  غیره
  ۴- dataGroups : این object مربوط به وقتی است که برنامه در حال اجرا است. میتوان داده‌های در حال اجرای اپلیکیشن را کش کرد. داده‌ی در حال اجرا میتواند شامل فراخوانی apiها باشد. برای مثال فرض کنید شما در حالت کار کردن online با اپلیکیشن، لیستی از اسامی کاربران را از api گرفته و نمایش میدهید. وقتی دفعه‌ی بعد در حالت offline  اپلیکیشن را باز کنیم، اگر api را کش کرده باشیم، اپلیکیشن داده‌ها را از کش فراخوانی میکند. این عمل درباره post کردن داده‌ها هم صدق میکند.

خود dataGroups شامل چند object زیر میباشد:
  ۱- name :  یک نام انتخابی برای Groups  میباشد.
 ۲- urls  : شامل آرایه‌ای از آدرس‌ها میباشد. میتوان آدرس یک دومین را همراه با کل apiها به صورت زیر کش کرد:
"https://api/**"
۳- cacheConfig  : که شامل
  ۱- maxSize  : حداکثر تعداد کش‌های مربوط به Groups .
  ۲- maxAge  : حداکثر  lifetime مربوط به کش.
  ۳- strategy  : که میتواند یکی از مقادیر freshness به معنی Network-First یا performance  به معنی Cache-First باشد.


 پیاده سازی پیغام نمایش به‌روزرسانی جدید وب اپلیکیشن

در اپلیکیشن‌های native  وقتی به‌روزرسانی جدیدی برای app اعلام میشود، در فروشگاهای اینترنتی پیغامی مبنی بر به‌روزرسانی جدید app برای کاربران ارسال میشود که کاربران میتوانند app خود را به‌روزرسانی کنند. ولی در pwa تنها با یک رفرش صفحه میتوان اپلیکیشن را به جدیدترین امکانات به‌روزرسانی کرد! برای اینکه بتوانیم با هر تغییر، پیغامی را جهت به‌روزرسانی نسخه یا هر  پیغامی دیگری را نمایش دهیم، از کد زیر استفاده میکنم:
if(this.swUpdate.isEnabled)
    {
      this.swUpdate.available.subscribe(()=> {

        if(confirm("New Version available.Load New Version?")){
          window.location.reload();
        }
      })
    }


وصل شدن به دیتابیس

برای اینکه بتوانیم یک وب اپلیکیشن کامل را طراحی کنیم، قطعا نیاز به دیتابیس داریم. برای دیتابیس از firebase استفاده میکنیم. قبل از شروع، وارد سایت firebase  شوید. پس از لاگین، بر روی Add Project کلیک کنید. بعد از انتخاب نام مناسب، create Project را انتخاب کنید. ساختار دیتابیس‌های firebase  شبیه nosqlها می‌باشد و بر پایه‌ی json کار میکنند. پس از ساخت پروژه و دریافت کد جاواسکریپتی زیر شروع به طراحی فیلد‌های دیتابیس خواهیم کرد.


در منوی سمت چپ، بر روی database کلیک کنید و یک دیتابیس را در حالت test mode  ایجاد نماید. سپس یک collection را به نام user ایجاد کرده و فیلد‌های زیر را به آن اضافه کنید:
Age :number
Fullname :string
Mobile : string
مرحله‌ی بعد، config پروژه‌ی انگیولاری میباشد. وارد  vscode شوید و با دستور زیر پکیج firebase را اضافه کنید:
  npm install --save firebase @angular/fire
سپس وارد appmodule شوید و آن‌را به صورت زیر کانفیگ کنید:

@NgModule({
  declarations: [AppComponent, RegisterComponent,AboutComponent, UserListComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    AngularFireModule.initializeApp(environmentFirebase.firebase),
    AngularFireDatabaseModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
  ],
  providers: [FirebaseService],
  bootstrap: [AppComponent]
})
export class AppModule {}
خط ۱۰ مربوط به pwa است که هنگام نصب pwa اضافه شده‌است و خط ۸ و مربوط به apiKey فایربیس میباشد. می‌شود گفت environmentFirebase.firebase شبیه  connection string می‌باشد که به صورت زیر کانفیگ شده است:
export const environment ={
   production: true,
   firebase: {
     apiKey: "AIz×××××××××××××××××××××××××××××××××8",
     authDomain: "pwaangular-6c041.firebaseapp.com",
     databaseURL: "https://pwaangular-6c041.firebaseio.com",
     projectId: "pwaangular-6c041",
     storageBucket: "pwaangular-6c041.appspot.com",
     messagingSenderId: "545522081966"
   }

}

FirebaseService در قسمت providers مربوط به سرویس crud میباشد. اگر وارد فایل مربوطه شوید، چند عمل اصلی به صورت زیر در آن پیاده سازی شده است:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import * as firebase from 'firebase';
import { AngularFireDatabase } from '@angular/fire/database';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { ThrowStmt } from '@angular/compiler';
@Injectable({
  providedIn: 'root'
})
export class FirebaseService {

  ref = firebase.firestore().collection('users');
  constructor(public db: AngularFireDatabase,public _http:HttpClient) { 
 
  }

  getUsers(): Observable<any> {
    return new Observable((observer) => {
      this.ref.onSnapshot((querySnapshot) => {
        let User = [];
        querySnapshot.forEach((doc) => {
          let data = doc.data();
          User.push({
            key: doc.id,
            fullname: data.fullname,
            age: data.age,
            mobile: data.mobile
          });
        });
        observer.next(User);
      });
    });
  }

  getUser(id: string): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).get().then((doc) => {
        let data = doc.data();
        observer.next({
          key: doc.id,
          title: data.title,
          description: data.description,
          author: data.author
        });
      });
    });
  }

  postUser(user): Observable<any> {
    return new Observable((observer) => {
      
      this.ref.add(user).then((doc) => {
        observer.next({
          key: doc.id,
        });
      });
    });
  }

  updateUser(id: string, data): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).set(data).then(() => {
        observer.next();
      });
    });
  }

  deleteUser(id: string): Observable<{}> {
    return new Observable((observer) => {
      this.ref.doc(id).delete().then(() => {
        observer.next();
      });
    });
  }

getDataOnApi(){
  return this._http.get('https://site.com/api/General/Getprovince')
  .pipe(
      map((res: Response) => {
return res;

      })
  );
}

getOnApi(){
  return  this._http.get("https://site.com/api/General/Getprovince",).pipe(
    map((response:any) => {
       
return  response
    } )
    );
}
}
اگر دقت کرده باشید، خیلی شبیه به کد نویسی سمت سرور میباشد.

با دستورات زیر میتوانید مجددا پروژه را اجرا کنید:
ng build --prod
cd dist/Pwa-WepApp
Http-server -o


تست وب اپلیکیشن بر روی پلتفرم‌های مختلف

برای اینکه بتوانیم خروجی وب اپلیکیشن را بر روی پلتفرم‌های مختلفی تست کنیم، میتوانیم آن را آپلود کرده و مثل یک سایت اینترنتی، با وارد کردن دومین، وارده پروژه شد. ولی کم هزینه‌ترین راه، استفاده از ابزار ngrok میباشد. میتوانید توسط این مقاله پروژه خودتان در لوکال بر روی https سوار کنید.

نکته! توجه کنید apiهای مربوط به firebase را نمیتوان کش کرد.


کد‌های مربوط به این قسمت را میتوانید از این repository دریافت کنید .
مطالب
نمایش یک فایل PDF پویا در یک iframe در ASP.NET
عموما در برنامه‌های وب برای نمایش فایل‌های پویای باینری تولید شده، یا ابتدا آن‌ها را بر روی سخت دیسک ذخیره کرده و مسیر نهایی را به نحوی به کاربر نمایش می‌دهند و یا فایل را بدون ذخیره سازی، در مرورگر کاربر اصطلاحا Flush می‌کنند. حالت Flush سبب نمایش صفحه دیالوگ ذخیره سازی فایل گردیده و در همینجا Response خاتمه خواهد یافت.
برای نمونه در اینجا توسط متد inMemoryFile، یک فایل PDF در حافظه تشکیل شده و سپس به صورت یک Byte Array بازگشت داده می‌شود. در ادامه کار، این اطلاعات در مرورگر کاربر Flush خواهد شد:
using System.IO;
using System.Net.Mime;
using System.Web;

namespace WebApplication
{
    public class PdfHandler : IHttpHandler
    {
        private static byte[] inMemoryFile()
        {
            //تولید پویای فایل در حافظه و یا حتی خواندن از یک نمونه موجود
            return File.ReadAllBytes(@"D:\path\DynamicCrosstabSampleRpt.pdf");
        }

        public void ProcessRequest(HttpContext context)
        {
            var pdf = inMemoryFile();

            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.ContentType = MediaTypeNames.Application.Pdf;
            context.Response.AddHeader("Content-Length", pdf.Length.ToString());
            context.Response.AddHeader("content-disposition", "attachment;filename=test.pdf");
            context.Response.Buffer = true;
            context.Response.Clear();
            context.Response.OutputStream.Write(pdf, 0, pdf.Length);
            context.Response.OutputStream.Flush();
            context.Response.OutputStream.Close();
            context.Response.End();
        }

        public bool IsReusable
        {
            get  {  return false;  }
        }
    }
}
و برای نمایش آن در یک iframe در صفحه:
 <iframe width="100%" src="PdfHandler.ashx" height="200px"></iframe>
نتیجه کار، نمایش صفحه دیالوگ ذخیره سازی فایل به کاربر است:


سؤال: فرض کنید Adobe reader بر روی سیستم نصب است و مرورگر با استفاده از Active-X آن می‌تواند این نوع فایل‌ها را نمایش دهد. آیا راهی وجود دارد تا بجای نمایش save popup dialog، این فایل توسط مرورگر نمایش داده شود؟
پاسخ: بلی. در کدهای فوق تنها کافی است یک سطر آن تغییر کند:
 Response.AddHeader("content-disposition", "inline;filename=test.pdf");
در اینجا تنها نحوه مقدار دهی content-disposition تفاوت کرده است. حالت attachment سبب نمایش save popup dialog می‌شود و مقدار inline، فایل را در مرورگر نمایش خواهد داد.
اینبار اگر برنامه را اجرا کنیم، iframe ایی که به PdfHandler.ashx اشاره می‌کند، فایل PDF را در صفحه نمایش می‌دهد.
مطالب
افزونه تاریخ فارسی برای outlook 2007

عموما در محیط کاری اگر شبکه ویندوزی و mail server مورد استفاده هم ms exchange باشد، به طور قطع از outlook برای انجام امور روزمره ارسال و دریافت ایمیل استفاده می‌شود.
طبق معمول هم مشکل ما تاریخ فارسی است! یکی از شرکت‌های ایرانی که در این‌باره محصولی را ارائه داده با hook کردن تاریخ ویندوز، هر جایی که تاریخی قرار است نمایش داده شود، آنرا فارسی می‌کند. این محصول دو ایراد دارد: الف) رایگان نیست! ب) این hook بر روی عملکرد سایر برنامه‌ها تاثیرگذار است. برای مثال برنامه‌های دات نت تاریخ قمری را نمایش خواهند داد، بر روی عملکرد و کارآیی کلی سیستم تاثیر منفی دارد و مشکلاتی از این دست.

افزونه زیر بدون دستکاری تاریخ ویندوز، دو کار را در MS outlook 2007 انجام خواهد داد:

الف) اضافه کردن ستون "تاریخ دریافت" شمسی



ب) در متن دریافتی، تمام تاریخ‌های sent موجود را یافته و شمسی می‌کند



دریافت افزونه:
لطفا اینجا کلیک کنید.

تذکر مهم:
نصب دو بسته به روز رسانی سیستم و دات نت فریم ورک را پیش از نصب این افزونه فراموش نکنید.
/Post/14/افزونه-فارسی-به-پارسی-برای-word-2007


در کل برای من کار راه انداز بوده :)

در طی روزهای آتی، سورس کامل و نحوه برنامه نویسی آن‌را بررسی خواهیم کرد.