StiImage stiImg = new StiImage(); stiImg.Image = img;
اگر در یک پروژه EF Code first چندین Context وجود داشته باشد و دستور enable-migrations را بدون پارامتری فراخوانی کنیم، پیغام خطای More than one context type was found in the assmbly xyz را دریافت خواهیم کرد.
الف) اما در EF 6 میتوان با بکار بردن سوئیچ جدید ContextTypeName، به ازای هر Context، مهاجرت مرتبط با آنرا تنظیم نمود:
enable-migrations -ContextTypeName dbContextName1 -MigrationDirectory DataContexts\Name1Migrations
ب) در مرحله بعد، نیاز به فراخوانی دستور add-migration است:
add-migration -ConfigurationTypeName FullNameSpaceCtx1.Configuration "InitialCreate"
ذکر کامل فضای نام، از این جهت مهم است که کلاس Configuration به ازای Contextهای مختلف ایجاد شده، یک نام را خواهد داشت؛ اما در فضاهای نام متفاوتی قرار میگیرد.
با اجرای دستور add-migration، کدهای سی شارپ مورد نیاز جهت اعمال تغییرات بر روی ساختار بانک اطلاعاتی تولید میشوند. در مرحله بعد، این کدها تبدیل به دستورات SQL متناظری شده و بر روی بانک اطلاعاتی اجرا خواهند شد.
بدیهی است اگر دو Context در برنامه تعریف کرده باشید، دوبار باید دستور enable-migrations و دوبار دستور add-migration را با پارامترهای اشاره کننده به Conetxtهای مدنظر اجرا کرد.
ج) سپس برای اعمال این تغییرات، باید دستور update-database را اجرا کرد.
update-database -ConfigurationTypeName FullNameSpaceCtx1.Configuration
نهایتا اگر به بانک اطلاعاتی مراجعه کنید، تمام جداول و تعاریف را یکجا در همان بانک اطلاعاتی میتوانید مشاهده نمائید.
داشتن چندین Context در برنامه و مدیریت تراکنشها
در EF، هر DbContext معرف یک واحد کار است. یعنی تراکنشها و چندین عمل متوالی مرتبط انجام شده، درون یک DbContext معنا پیدا میکنند. متد SaveChanges نیز بر همین اساس است که کلیه اعمال ردیابی شده در طی یک واحد کار را در طی یک تراکنش به بانک اطلاعاتی اعمال میکند. همچنین مباحثی مانند lazy loading نیز در طی یک Context مفهوم دارند. به علاوه دیگر امکان join نویسی بین دو Context وجود نخواهد داشت. باید اطلاعات را از یکی واکشی و سپس این اطلاعات درون حافظهای را به دیگری ارسال کنید.
یک نکته
میتوان یک DbSet را در چندین Context تعریف کرد. یعنی اگر بحث join نویسی مطرح است، با تکرار تعریف DbSetها اینکار قابل انجام است اما این مساله اساس جداسازی Contextها را نیز زیر سؤال میبرد.
داشتن چندین Context در برنامه و مدیریت رشتههای اتصالی
در EF Code first روشهای مختلفی برای تعریف رشته اتصالی به بانک اطلاعاتی وجود دارند. اگر تغییر خاصی در کلاس مشتق شده از DbContext ایجاد نکنیم، نام کلید رشته اتصالی تعریف شده در فایل کانفیگ باید به نام کامل کلاس Context برنامه اشاره کند. اما با داشتن چندین Context به ازای یک دیتابیس میتوان از روش ذیل استفاده کرد:
public class Ctx1 : DbContext { public Ctx1() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } } public class Ctx2 : DbContext { public Ctx2() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } }
<connectionStrings> <add name="DefaultConnection" connectionString="…." providerName="System.Data.SqlClient" /> </connectionStrings>
چه زمانی بهتر است از چندین Context در برنامه استفاده کرد؟
عموما در طراحیهای سازمانی SQL Server، تمام جداول از schema مدیریتی به نام dbo استفاده نمیکنند. جداول فروش از schema خاص خود و جداول کاربران از schema دیگری استفاده خواهند کرد. با استفاده از چندین Context میتوان به ازای هر کدام از schemaهای متفاوت موجود، «یک ناحیه ایزوله» را ایجاد و مدیریت کرد.
public class Ctx2 : DbContext { public Ctx2() : base("DefaultConnection") { //Database.Log = sql => Debug.Write(sql); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("sales"); base.OnModelCreating(modelBuilder); } }
React، برای مدیریت پروژهی خود، از Webpack استفاده میکند و در این حالت، کار با فایلهای ایستا مانند تصاویر و قلمهای وب، شبیه به کار با فایلهای CSS خواهد بود؛ یعنی این نوع فایلها را باید در فایلهای جاوا اسکریپتی برنامه، import کرد. به این ترتیب Webpack کار یکی سازی این فایلها را با bundle نهایی تولید شده، انجام میدهد.
یک مثال: فرض کنید فایل button.css به صورت زیر تعریف شدهاست:
.Button { padding: 20px; }
برای استفادهی از این فایل css در یک کامپوننت، ابتدا آنرا import کرده و سپس از classNameهای تعریف شدهی در آن استفاده میکنیم:
import React, { Component } from 'react'; import './Button.css'; class Button extends Component { render() { return <div className="Button" />; } }
البته برخلاف حالت کار با CSS imports، با import یک فایل ایستا، یک رشته در اختیار ما قرار میگیرد که از آن میتوان در کدهای خود استفاده کرد. برای کاهش تعداد رفت و برگشتهای به سرور، اگر فایلهای تصویری با فرمتهای bmp, gif, jpg, jpeg و png، کمتر از 10,000 بایت باشند، از data URI آنها بجای مسیر نهایی استفاده خواهد شد. این مورد شامل فایلهای svg نمیشود.
یک مثال:
import React from 'react'; import logo from './logo.svg'; console.log(logo); function Header() { return <img src={logo} alt="Logo" />; } export default Header;
این مورد برای تصاویر ذکر شدهی در فایلهای CSS نیز صادق است:
.Logo { background-image: url(./logo.png); }
یک مثال: فرض کنید در برنامهی ASP.NET Core خود که با React یکی شدهاست، فایل project_folder/ClientApp/src/images/progress_bar.gif را قرار دادهاید. روش import آن با توجه به مسیرهای نسبی برنامه به صورت زیر است:
import progressBar from '../images/progress_bar.gif';
<img alt="loading..." src={progressBar} />
روش ذکر فایلهای ایستا در کامپوننتهای تایپ اسکریپتی برنامههای React
اگر از تایپاسکریپت استفاده میکنید، چنین importهایی سبب بروز خطای «'Cannot find module './logo.png» میشوند. برای رفع این مشکل، فایلی را به نام assets.d.ts به پروژهی خود اضافه کرده و آنرا به صورت زیر تکمیل کنید:
declare module "*.gif"; declare module "*.jpg"; declare module "*.jpeg"; declare module "*.png"; declare module "*.svg";
نحوهی پردازش پوشهی ویژهی public در برنامههای React
اگر فایلی در پوشهی ویژهی public برنامههای react قرار گیرد، توسط webpack پردازش نخواهد شد. در این حالت این نوع فایلها بدون هیچ نوع تغییری به پوشهی build نهایی کپی میشوند. بنابراین برای کار با فایلهای ایستای قرار گرفتهی در پوشهی public باید از متغیر خاصی به نام PUBLIC_URL استفاده کرد. برای مثال درون فایل index.html، چنین تعریفی را میتوان مشاهده کرد:
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
برای دسترسی به این مسیر در کامپوننتهای برنامه نیز میتوان از متغیر محیطی process.env.PUBLIC_URL استفاده کرد:
render() { return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; }
بنابراین اکنون این سؤال مطرح میشود که چه زمانی بهتر است از پوشهی public استفاده شود؟
- اگر میخواهید نام فایل نهایی ایستای مدنظر مانند manifest.json، بدون تغییر باقی بماند.
- هزاران فایل ایستا را دارید و میخواهید این مسیرها را به صورت پویا در برنامه فراخوانی کنید (و قرار نیست جزئی از bundle نهایی شوند).
- میخواهید فایلهای js خاصی را خارج از سیستم bundle اصلی قرار دهید؛ چون به هر دلیلی این نوع فایلها با سیستم webpack سازگاری ندارند و نباید توسط آن پردازش شوند. در این حالت باید این نوع فایلها را با تگ script به فایل index.html به صورت دستی معرفی کنید.
در ابتدای کار نیاز است تا repository خود را ایجاد کنیم. بدین منظور از طریق محیط command prompt به آدرس پوشه مورد نظر رفته و دستور git init را اجرا میکنیم. این کار سبب میشود تا پوشه git. در داخل فولدر جاری ایجاد شود. این پوشه در واقع همان repository و پوشه جاری، همان working tree ما خواهند بود. حال با استفاده از یک ادیتور نظیر notepad یک فایل متنی جدید را با نام readme1.txt در پوشه ایجاد کنید (توجه کنید در working tree، نه در پوشه git.؛ محتویات این پوشه جز در مورد برخی فایلها نباید توسط کاربر تغییر کند)
اکنون دستور زیر را اجرا کنید:
git status
برای آنکه این فایل را در repository ذخیره کنیم همانطور که قبلا گفته شد باید ابتدا آنرا به index اضافه کنیم این کار با استفاده از دستور زیر انجام میشود:
git add readme1.txt
git commit
git commit -m “commit descriptions”
git add .
git add -u
git log
git log --until [date] git log --since [date] git log -[number]
چگونگی حذف فایلها:
تا اینجا با نحوه چگونگی ایجاد فایلهای جدید و یا ویرایش فایلهای قدیمی آشنا شدید. برای حذف یک فایل میتوان به دو صورت عمل کرد:
1) ابتدا فایل را را مستقیما حذف نموده، سپس با استفاده از دستور زیر ابتدا فایل حذف شده را به stage آورده و سپس آن را commit میکنیم:
git rm [filename]
چگونگی تغییر نام و یا جابجایی یک فایل:
برای تغییر نام و جابجایی یک فایل نیز مانند حذف، دو روش وجود دارد:
۱) ابتدا فایل مورد نظر را تغییر نام داده و یا جابجا میکنیم. در این حالت اگر status بگیریم خواهیم دید که git به ما میگوید فایلی با نام قبلی حذف شده و فایلی با نام جدید اضافه شده است. یعنی git تشخیص نمیدهد که این دو فایل یکی هستند و تنها تغییر نام داده شده است. اما به محض آنکه فایل اول را با دستور rm حذف و فایل دوم را با دستور add اضافه کنیم، git متوجه میشود که این دو فایل در واقع یک فایل تغییر نام یافته هستند. البته در صورتیکه حداقل ۵۰ درصد فایل دوم با فایل اول شباهت داشته باشد، بعد از انجام عملیات فوق از دستور commit استفاده میکنیم.
۲) در این روش از دستور زیر استفاده کرده و سپس commit را انجام میدهیم:
git mv [firstname][secondname]
روش اول :
یک Flex Container متداول به این صورت کار میکند:
این کلاسها که موارد داخل پرانتز آنها اختیاری است، المان را تبدیل به یک المان Flexbox میکنند. حالت نمایشی پیشفرض آنها block است؛ اما اگر نیاز بود میتوان آنها را تبدیل به in-line نیز کرد. بنابراین سادهترین Flex Container را میتوان با افزودن کلاس d-flex ایجاد کرد.
بر روی یک Flex Container میتوان کلاسهای تعیین جهت را نیز تعریف کرد:
به این ترتیب میتوان آیتمها را به صورت ردیفها و یا ستونهایی، نمایش داد. مقدار row در اینجا به صورت پیشفرض اعمال میشود. بنابراین ذکر کلاس خالی flex به معنای قرار دادن المانها در طی چندین ردیف در صفحه است. در اینجا استفادهی از reverse، نمایش المانها را از راست به چپ میسر میکند.
در یک Flex Container امکان تعیین ترتیب عناصر نیز وجود دارد:
این مورد را در مطلب «طرحبندی صفحات وب با بوت استرپ 4 - قسمت دوم » بررسی کردیم. کلاس order را علاوه بر ستونها، بر روی هر دربرگیرندهای که دارای کلاس d-flex است نیز میتوان اعمال کرد.
همچنین امکان تنظیم فواصل بین آیتمها نیز در یک Flex Container پیش بینی شدهاست:
برای مثال استفادهی از مقدار تراز center، روش بسیار مناسبی برای قرار دادن عناصر، در میانهی افقی صفحه است. این مورد را نیز در قسمت قبل بررسی کردیم.
میتوان نحوهی Wrap المانها را بر اساس فضای خالی در یک Flex Container به صورت زیر تنظیم کرد:
که در اینجا دو مقدار wrap و nowrap قابل تنظیم است. در حالت wrap، اگر آیتمها با اندازهی خودشان در ردیف جاری جا نشدند، به سطر یا سطرهای بعدی منتقل خواهند شد. حالت پیشفرض nowrap است.
برای تغییر تراز عمودی المانها در یک Flex Container از کلاس align-content استفاده میشود:
این مورد را نیز در قسمت قبل بررسی کردیم و همانند کار با ستونها میباشد.
یک مثال: بررسی ویژگیهای یک Flex Container
<head> <style> .item { background: #f0ad4e; text-align: center; width: 150px; height: 30px; border: 1px solid white; } </style> </head> <body> <div class="container bg-danger"> <div class="bg-info" style="height:100vh"> <div class="item">Exotic</div> <div class="item">Grooming</div> <div class="item">Health</div> <div class="item">Nutrition</div> <div class="item">Pests</div> <div class="item">Vaccinations</div> </div> </div> </body>
در ابتدا کلاس d-flex را به div داخل container اضافه میکنیم:
<div class="bg-info d-flex" style="height:100vh">
و اگر جهت این Flex Container را به صورت صریح مشخص کنیم:
<div class="bg-info d-flex flex-column" style="height:100vh">
و یا اگر بخواهیم آیتمها را از راست به چپ به صورت یک ردیف نمایش دهیم میتوان از flex-row-reverse استفاده کرد:
<div class="bg-info d-flex flex-row-reverse" style="height:100vh">
و اگر بجای row در این حالت column را مقدار دهی کنیم:
<div class="bg-info d-flex flex-sm-column-reverse" style="height:100vh">
آیتمها از پایین صفحه شروع خواهند شد. البته در این مثال break-point از نوع sm نیز ذکر شدهاست تا پس از گذر از این اندازهی صفحه، چنین اتفاقی رخ دهد.
و یا اگر بخواهیم آیتمها از راست به چپ در طی یک ردیف، پس از اندازهی صفحهی sm و همچنین در میانهی صفحه ظاهر شوند، میتوان از کلاس justify-content استفاده کرد:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center" style="height:100vh">
و اگر wrap را فعال کنیم:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-wrap" style="height:100vh">
اگر آیتمها با اندازهی اصلی خودشان، در ردیف جاری جا نشدند، به سطرهای بعدی منتقل خواهند شد.
اگر nowrap را فعال کنیم:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-nowrap" style="height:100vh">
و با فعالسازی align-content-start، تمام آیتمها را به سمت بالای صفحه هدایت میکند و align-content-end، آنها را از پایین صفحه شروع خواهد کرد:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-wrap align-content-start" style="height:100vh">
کنترل آیتمهای قرار گرفتهی درون یک Flex Container در بوت استرپ 4
علاوه بر امکان کنترل ویژگیهای یک Flex Container، اجزای قرار گرفتهی درون آنها را نیز میتوان کنترل کرد و اینکار توسط کلاس align-self میسر است:
این مورد نیز همانند توضیحات کلاس align-self اعمالی به ستونها است که در قسمت قبل بررسی کردیم.
به علاوه در اینجا امکان تعریف floating elements نیز مسیر است که شبیه به دسترسی به امکانات CSS در بوت استرپ است با امکان تنظیم break-points:
فرض کنید به تمام آیتمهای داخل Flex Container کلاس float-left را اضافه کردهایم. در این حالت Container قابلیت ردیابی اندازهی این آیتمها را از دست میدهد. به همین جهت با اعمال کلاس clearfix بوت استرپ به container، مجددا امکان ردیابی این آیتمها را پیدا میکند.
کلاسهای تعریف margin و padding در بوت استرپ 4
در بوت استرپ 4 کلاسهای ویژهای برای ایجاد margin و padding بین عناصر در نظر گرفته شدهاند که خلاصهی آنها فرمول زیر است:
ابتدا با تعریف یک خاصیت شروع میشود؛ مانند m یا p، برای کنترل margin و padding. سپس لبهای که باید به آن اعمال شود بدون فاصله و یا - ذکر میشود؛ مانند mt به معنای margin-top. در این فرمول x به معنای اعمال همزمان به چپ و راست است و y به معنای اعمال همزمان به بالا و پایین و اگر میخواهید آیتمهای کناری آیتم جاری را به دو طرف لبهها هدایت کنید از mx-auto استفاده کنید.
در اینجا امکان اعمال یک break-point اختیاری نیز وجود دارد. در آخر اندازه ذکر میشود که بین 0 تا 5 متغیر است.
یک مثال: اعمال کلاسهای padding و margin بوت استرپ 4
<head> <style> .item { background: #f0ad4e; text-align: center; border: 1px solid white; } </style> </head> <body> <div class="container bg-danger"> <div class="bg-info d-flex"> <div class="item">Exotic</div> <div class="item">Grooming</div> <div class="item bg-danger ml-3 my-sm-3 pb-3 pt-5">Health</div> <div class="item">Nutrition</div> <div class="item">Pests</div> <div class="item">Vaccinations</div> </div> </div> </body>
نمایش و مخفی سازی عناصر در بوت استرپ 4
کلاس invisible سبب میشود تا المانی در صفحه نمایش داده نشود، اما این المان همچنان فضای اختصاصی خود را خواهد داشت. کلاس visible به معنای نمایان بودن المانی تنها برای screen readers است (دستگاههای کمکی معلولها).
اما روش اصلی نمایش و یا مخفی سازی عناصر در بوت استرپ 4، استفاده از خاصیت display است:
برای مثال با انتساب کلاس d-sm-none به المانی، میتوان سبب مخفی شدن آن پس از گذر از sm شد.
امکان تعیین اندازهی عناصر در بوت استرپ 4
بوت استرپ 4 تعدادی کلاس ویژه را برای تعیین اندازهی عناصر نیز افزودهاست:
در اینجا w=width، h=height، mw=max-height و mh=max-height است با مقادیر 25، 50، 75 و 100 و مقدار پیشفرض آن 100 است (یعنی پوشاندن کل container).
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_06.zip
پیاده سازی چند Layout در React
صفحات کاربری و مدیریتی عموما از آن دسته صفحاتی هستند که عموما قالب متفاوتی با یکدیگر دارند. همچنین صفحات لاگین و یا عضویت نیز میتواند چنین حالتی داشته باشند. جهت تعیین قالب در این پروژه و با استفاده از کتابخانه React-Route میتواند لی اوت متفاوتی را برای هر صفحه تدارک دید.
با محصور کردن کامپوننت Route در کامپوننتی مانند AppRoute و افزودن تعداد خواص میتوان کامپپوننت جدیدی را ایجاد کرد و کارکردهای جدیدتری را به آن اضافه کرد...
در قسمت قبل « کار با اسکنر در برنامههای تحت وب (قسمت اول) » دیدی از کاری که قرار است انجام دهیم، رسیدیم. حالا سراغ یک پروژهی عملی و پیاده سازی مطالب مطرح شده میرویم.
ابتدا پروژهی WCF را شروع میکنیم. ویژوال استودیو را باز کرده و از قسمت New Project > Visual C# > WCF یک پروژهی WCF Service Application جدید را مثلا با نام "WcfServiceScanner" ایجاد نمایید. پس از ایجاد، دو فایل IService1.cs و Service1.scv موجود را به IScannerService و ScannerService تغییر نام دهید. سپس ابتدا محتویات کلاس اینترفیس IScannerService را به صورت زیر تعریف نمایید :
[ServiceContract] public interface IScannerService { [OperationContract] [WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "GetScan")] string GetScan(); }
public class ScannerService : IScannerService { public string GetScan() { // TODO Add code here } }
بر روی پروژهی خود راست کلیک کرده و Add Reference را انتخاب نموده و سپس در قسمت COM، گزینهی Microsoft Windows Image Acquisition Library v2.0 را به پروژهی خود اضافه نمایید.
با اضافه شدن این ارجاع به پروژه، دسترسی به فضای نام WIA برای ما امکان پذیر میشود که ارجاعی از آن را در کلاس ScannerService قرار میدهیم.
using WIA;
public string GetScan() { var imgResult = String.Empty; var dialog = new CommonDialogClass(); try { // نمایش فرم پیشفرض اسکنر var image = dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType); // ذخیره تصویر در یک فایل موقت var filename = Path.GetTempFileName(); image.SaveFile(filename); var img = Image.FromFile(filename); // img جهت ارسال سمت کاربر و نمایش در تگ Base64 تبدیل تصویر به imgResult = ImageHelper.ImageToBase64(img, ImageFormat.Jpeg); } catch { // از آنجاییه که امکان نمایش خطا وجود ندارد در صورت بروز خطا رشته خالی // بازگردانده میشود که به معنای نبود تصویر میباشد } return imgResult; }
CommonDialogClass کلاس اصلی در اینجا جهت نمایش فرم کار با اسکنر میباشد و متدهای مختلفی را جهت ارتباط با اسکنر در اختیار ما قرار میدهد که بسته به نیاز خود میتوانید از آنها استفاده کنید. برای نمونه در مثال ما نیز متد اصلی که مورد استفاده قرار گرفته، ShowAcquireImage میباشد که این متد، فرم پیش فرض دریافت اسکنر را به کاربر نمایش میدهد و کاربر از طریق آن میتواند قبل از شروع اسکن، یکسری تنظیمات را انجام دهد.
این متد ابتدا به صورت خودکار فرم تعیین دستگاه اسکنر ورودی را نمایش داده :
و سپس فرم پیش فرض اسکنرهای TWAIN را جهت تعیین تنظیمات اسکن نمایش میدهد که این امکان نیز در این فرم فراهم است تا دستگاههای Feeder یا Flated انتخاب گردند.
خروجی این متد همان عکس اسکن شده است که از نوع WIA.ImageFile میباشد و ما پس از دریافتش، ابتدا آن را در یک فایل موقت ذخیره نموده و سپس با استفاده از یک متد کمکی آن را به فرمت Base64 برای درخواست کننده اسکن ارسال مینماییم.
کدهای کلاس کمکی ImageHelper:
public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format) { if (image != null) { using (MemoryStream ms = new MemoryStream()) { // Convert Image to byte[] image.Save(ms, format); byte[] imageBytes = ms.ToArray(); // Convert byte[] to Base64 String string base64String = Convert.ToBase64String(imageBytes); return base64String; } } return String.Empty; }
این مثال به سادهترین شکل نوشته شد. کلاس دیگری هم در اینجا وجود دارد و در صورتیکه از اسکنر نوع Feeder استفاده میکنید، میتوانید از کدهای آن استفاده کنید.
جهت رفع این خطا، در قسمت Referenceهای پروژه خود، WIA را انتخاب نموده و از Propertiesهای آن خصوصیت Embed Interop Types را به False تغییر دهید؛ مشکل حل میشود.
به سراغ پروژهی ویندوز فرم جهت هاست کردن این WCF سرویس میرویم. میتوانید این سرویس را بر روی یک Console App یا Windows Service هم هاست کنید که در اینجا برای سادگی مثال، از WinForm استفاده میکنیم.
یک پروژهی WinForm جدید را ایجاد کنید و سپس از قسمت Add Reference > Solution به مسیر پروژهی قبلی رفته و dllهای آن را به پروژه خود اضافه نمایید.
Form1.cs را باز کرده و ابتدا دو متغیر زیر را در آن به صورت عمومی تعریف نمایید:
private readonly Uri _baseAddress = new Uri("http://localhost:6019"); private ServiceHost _host;
حال در رویداد Form_Load برنامه، کدهای زیر را جهت هاست کردن سرویس اضافه مینماییم:
private void Form1_Load(object sender, EventArgs e) { _host = new ServiceHost(typeof(WcfServiceScanner.ScannerService), _baseAddress); _host.Open(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { _host.Close(); }
فایل App.Config پروژهی WinForm را باز کرده و کدهای آنرا مطابق زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="BehaviourMetaData"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="WcfServiceScanner.ScannerService" behaviorConfiguration="BehaviourMetaData"> <endpoint address="" binding="basicHttpBinding" contract="WcfServiceScanner.IScannerService" /> </service> </services> </system.serviceModel> </configuration>
اگر موفق به اجرا نشدید و احیانا با خطای زیر مواجه شدید، اطمینان حاصل کنید که ویژوال استودیو Run as Administrator باشد. مشکل حل خواهد شد.
به سراغ پروژهی بعدی، یعنی وب سایت خود میرویم. یک پروژهی MVC جدید ایجاد نمایید و در View مورد نظر خود، کدهای زیر را جهت صدا زدن متد GetScan اضافه میکنیم.
( از آنجا که کدها به صورت جاوا اسکریپت میباشد، پس مهم نیست که حتما پروژه MVC باشد؛ یک صفحهی HTML ساده هم کافی است).
<a href="#" id="get-scan">Get Scan</a> <img src="" id="img-scanned" /> <script> $("#get-scan").click(function () { var url = 'http://localhost:6019/'; $.get(url, function (data) { $("#img-scanned").attr("src","data:image/Jpeg;base64, "+ data.GetScanResult); }); }); </script>
راه حلهای زیادی برای این مشکل ارائه شده است، و متاسفانه بسیاری از آنها در شرایط پروژهی ما جوابگو نمیباشد (به دلیل هاست روی یک پروژه ویندوزی). تنها راه حل مطمئن (تست شده) استفاده از یک کلاس سفارشی در پروژهی WCF Service میباشد که مثال آن در اینجا آورده شده است.
برای رفع مشکل به پروژه WcfServiceScanner بازگشته و کلاس جدیدی را به نام CORSSupport ایجاد کرده و کدهای زیر را به آن اضافه کنید:
public class CORSSupport : IDispatchMessageInspector { Dictionary<string, string> requiredHeaders; public CORSSupport(Dictionary<string, string> headers) { requiredHeaders = headers ?? new Dictionary<string, string>(); } public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) { var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty; if (httpRequest.Method.ToLower() == "options") instanceContext.Abort(); return httpRequest; } public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty; var httpRequest = correlationState as HttpRequestMessageProperty; foreach (var item in requiredHeaders) { httpResponse.Headers.Add(item.Key, item.Value); } var origin = httpRequest.Headers["origin"]; if (origin != null) httpResponse.Headers.Add("Access-Control-Allow-Origin", origin); var method = httpRequest.Method; if (method.ToLower() == "options") httpResponse.StatusCode = System.Net.HttpStatusCode.NoContent; } } // Simply apply this attribute to a DataService-derived class to get // CORS support in that service [AttributeUsage(AttributeTargets.Class)] public class CORSSupportBehaviorAttribute : Attribute, IServiceBehavior { #region IServiceBehavior Members void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { var requiredHeaders = new Dictionary<string, string>(); //Chrome doesn't accept wildcards when authorization flag is true //requiredHeaders.Add("Access-Control-Allow-Origin", "*"); requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS"); requiredHeaders.Add("Access-Control-Allow-Headers", "Accept, Origin, Authorization, X-Requested-With,Content-Type"); requiredHeaders.Add("Access-Control-Allow-Credentials", "true"); foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher ed in cd.Endpoints) { ed.DispatchRuntime.MessageInspectors.Add(new CORSSupport(requiredHeaders)); } } } void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } #endregion }
using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher;
[CORSSupportBehavior] public class ScannerService : IScannerService {
کار تمام است، یکبار دیگر ابتدا پروژهی WcfServiecScanner و سپس پروژه هاست را Build کرده و برنامهی هاست را اجرا کنید. اکنون مشاهده میکنید که با زدن دکمهی اسکن، اسکنر فرم تنظیمات اسکن را نمایش میدهد که پس از زدن دکمهی Scan، پروسه آغاز شده و پس از اتمام، تصویر اسکن شده در صفحهی وب سایت نمایش داده میشود.
Install-Package Breeze.WebApi2.EF6
var instance = breeze.config.initializeAdapterInstance("ajax", "angular"); instance.setHttp($http);
Install-Package Breeze.Angular
var manager = new breeze.EntityManager({ dataService: dataService, metadataStore: metadataStore, saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: true, tag: [{}] }) });
var dataService = new breeze.DataService({ serviceName: "/breeze/"+ "Automobile", hasServerMetadata: false, namingConvention: breeze.NamingConvention.camelCase }); var metadataStore = new breeze.MetadataStore({});
var myMetadataStore = new breeze.MetadataStore(); myMetadataStore.addEntityType({...});
var customer = function () { this.City = ""; }; myMetadataStore.registerEntityTypeCtor("Customer", customer);
[BreezeController] public class AutomobileController : ApiController { readonly EFContextProvider<ApplicationDbContext> _contextProvider = new EFContextProvider<ApplicationDbContext>(); [HttpGet] public string Metadata() { return _contextProvider.Metadata(); } [HttpGet] public IQueryable<Customer> Customers() { return _contextProvider.Context.Customers; } [System.Web.Http.HttpPost] public SaveResult SaveChanges(JObject saveBundle) { _contextProvider.BeforeSaveEntitiesDelegate = BeforeSaveEntities; _contextProvider.AfterSaveEntitiesDelegate = afterSaveEntities; return _contextProvider.SaveChanges(saveBundle); } protected Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) { } private void afterSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap, List<KeyMapping> keyMappings) { } }
manger.saveChanges().then(function success() { }, function failer(e) { });
manger.rejectChanges()
کوئری:بعد از تعریف Entity Manger میتوانیم کوئری خود را اجرا نماییم. کوئری ما شامل گرفتن اطلاعات از جدول Customer، با مرتب سازی بر روی فیلد آیدی میباشد و با اجرا کردن کوئری میتوانیم موفقیت یا عدم موفقیت آنرا بررسی نماییم.
var query = breeze.EntityQuery .from("Customer") .orderBy("Id"); var result= manager.executeQuery(query); result.then(querySucceeded) .fail(queryFailed); query = query.where("Id", "==", 1)
var predicate = new breeze.Predicate("Id", "==", false); query = query.where(predicate) var p1 = new breeze.Predicate("IsArchived", "==", false); var p2 = breeze.Predicate("IsDone", "==", false); var predicate = p1.and(p2); query = query.where(predicate).orderBy("Id")
?$filter=IsArchived eq false&IsDone eq false &$orderby=Id
اعتبارسنجی :اعتبارسنجی در breeze، هم در سمت کلاینت و هم در سمت سرور امکان پذیر میباشد که در مثالی، در قسمت بعدی، validator سفارشی خودمان را خواهیم ساخت و به entity مورد نظر اعمال خواهیم کرد.
breeze.Validator.required({ allowEmptyStrings: true });
ردیابی تغییرات: هر آیتم Entity دارای EntityAspect است که وضعیت آنرا مشخص میکند و میتواند یکی از وضعیتهای Added،Modified،Deleted،Detached،Unchanged باشد. با مشخص کردن حالت هر آیتم، با فراخوانی SaveChanges تغییرات بر روی دیتابیس اعمال میگردد.
manager.createEntity('Customer', jsonValue);
manager.createEntity("Customer", jsonValue, breeze.EntityState.Modified, breeze.MergeStrategy.OverwriteChanges)
manager.createEntity("Customer", item, breeze.EntityState.Deleted)
برای اشنایی بیشتر با امکانات Breeze، قصد داریم یک سایت ایجاد آگهی را راه اندازی کنیم. پیش نیازهای ضروری این بخش typescript ،angularjs ،requirejs هستند. قصد داریم سایتی را برای آگهیهای خرید و فروش خودرو، مشابه با سایت باما ایجاد نماییم:
امکانات این سایت:
Install-Package angularjs Install-Package angularjs.TypeScript.DefinitelyTyped Install-Package bootstrap Install-Package bootstrap.TypeScript.DefinitelyTyped Install-Package jQuery Install-Package jquery.TypeScript.DefinitelyTyped Install-Package RequireJS Install-Package requirejs.TypeScript.DefinitelyTyped bower install angularAMD
مدلهای برنامه:
public class BaseEntity { public int Id { get; set; } public bool Status { get; set; } public DateTime CreatedDateTime { get; set; } }
public class Ad : BaseEntity { public string Title { get; set; } public float Price { get; set; } public double Rating { get; set; } public int? RatingNumber { get; set; } public string UserId { get; set; } public DateTime ModifieDateTime { get; set; } public string Description { get; set; } public virtual ICollection<Comment> Comments { get; set; } public virtual IdentityUser User { get; set; } public virtual ICollection<AdLabel> Labels { get; set; } public virtual ICollection<AdMedia> Medias { get; set; } }
public class Label { public int Id { get; set; } public string Title { get; set; } public int? ParentId { get; set; } public virtual Label Parent { get; set; } public virtual ICollection<Label> Items { get; set; } }
public class Media { public int Id { get; set; } public string Name { get; set; } public string MimeType { get; set; } }
public class AdLabel { public int Id { get; set; } public virtual Ad Ad { get; set; } public virtual Label Label { get; set; } [Index("IX_AdLabel", 1, IsUnique = true)] public int AdId { get; set; } [Index("IX_AdLabel", 2, IsUnique = true)] public int LabelId { get; set; } public string Value { get; set; } }
public class AdMedia { public int Id { get; set; } public virtual Ad Ad { get; set; } public virtual Media Media { get; set; } [Index("IX_AdMedia", 1, IsUnique = true)] public int AdId { get; set; } [Index("IX_AdMedia", 2, IsUnique = true)] public int MediaId { get; set; } }
public class Comment : BaseEntity { public string Body { get; set; } public double Rating { get; set; } public int? RatingNumber { get; set; } public string EntityName { get; set; } public string UserId { get; set; } public int? ParentId { get; set; } public int? AdId { get; set; } public virtual Comment Parent { get; set; } public virtual Ad Ad { get; set; } public virtual ICollection<Comment> Items { get; set; } public virtual IdentityUser User { get; set; } }
public class Customer:BaseEntity { public string UserId { get; set; } public virtual string DisplayName { get; set; } public virtual string BirthDay { get; set; } public string City { get; set; } public string Address { get; set; } public int? MediaId { get; set; } public bool? NewsLetterSubscription { get; set; } public string PhoneNumber { get; set; } public virtual IdentityUser User { get; set; } public virtual Media Media { get; set; } }
public class Rating { public int Id { get; set; } public string UserId { get; set; } public Double Rate { get; set; } public string EntityName { get; set; } public int DestinationId { get; set; } }
اضافه کردن مدلهای برنامه به ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { } public DbSet<Ad> Ads { get; set; } public DbSet<AdLabel> AdLabels { get; set; } public DbSet<AdMedia> AdMedias { get; set; } public DbSet<Comment> Comments { get; set; } public DbSet<Label> Labels { get; set; } public DbSet<Media> Medias { get; set; } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } }
لود کردن فایل main.js در فایل layout.cshtml ترجیحا در انتهای body
<script src="~/Scripts/require.js" data-main="/app/main"></script>
ایجاد فایل main.ts
requirejs.config({ paths: { "app": "app", "angularAmd":"/Scripts/angularAmd", "angular": "/Scripts/angular", "bootstrap": "/Scripts/bootstrap", "angularRoute": "/Scripts/angular-route", "jquery": "/Scripts/jquery-2.2.2", }, waitSeconds: 0, shim: { "angular": { exports: "angular" }, "angularRoute": { deps: ["angular"] }, "bootstrap": { deps: ["jquery"] }, "app": { deps: ["bootstrap","angularRoute"] } } }); require(["app"]);
ایجاد فایل app.ts: کارهایی که در فایل app انجام دادهایم:
<body ng-controller="SecurityCtrl"> ... </body>
"use strict"; module AdApps { class SecurityCtrl { private $scope: Interfaces.IAdvertismentScope; constructor($scope: Interfaces.IAdvertismentScope) { // security check this.$scope = $scope; } } define(["angularAmd", "angular"], (angularAmd, ng) => { angularAmd = angularAmd.__proto__; var app = ng.module("AngularTypeScript", ['ngRoute']); var viewPath = "app/views/"; var controllerPath = "app/controller/"; app.config(['$routeProvider', $routeProvider => { $routeProvider .when("/", angularAmd.route({ templateUrl: viewPath + "home.html", controllerUrl: controllerPath + "home .js" })) .otherwise({ redirectTo: '/' }); } ]); app.controller('SecurityCtrl', ['$scope', SecurityCtrl]); return angularAmd.bootstrap(app); })}
یک محیط مجتمع برای کار کردن با پاور شل. از نسخه های ۲ تا ۴ رو هم پشتیبانی میکنه. اگر از ویندوز ۸.۱ یا Windows Server 2012 R2
استفاده میکنید به صورت پیش فرض روی سیستم شما وجود داره (البته چون یه محیط گرافیکی تو Server Core قابل دسترسی نیست). برای دسترسی به اون فقط کافیه تو پاورشل بنویسید ise بعد هم یه اینتر. ولی برای نسخههای ویندوز پایینتر نیاز به یک بروز رسانی دارید.
اطلاعات بیشتر: +