پلاگین Emerge
docker-compose چیست؟
فرض کنید برنامهی ما، از یک قسمت منطق خود برنامه و قسمت دیگر بانک اطلاعاتی آن تشکیل شدهاست. در این حالت برای توزیع آن توسط کانتینرها، نیاز به دو کانتینر مجزا خواهد بود؛ یکی برای برنامه و دیگری برای بانک اطلاعاتی:
dockerrun --name db` -d ` -p 3306:3306 ` -e MYSQL_ROOT_PASSWORD=my-secret-pw ` -v db:/var/lib/mysql` mysql dockerinspect db # extract ipaddress dockerrun --name web ` -d ` -p 8080:80 ` -e MY_DB_PORT=3306 ` -e MY_DB_HOST=? ` -v /my/php/app:/usr/share/nginx/html ` nginx
- دستور اول مطابق توضیحات قسمت قبل، یک بانک اطلاعاتی MySQL را در پس زمینه، با نام db که در آن، پورت 3306 میزبان به پورت 3306 کانتینر نگاشت شدهاست و همچنین بانک اطلاعاتی آن در یک volume نامدار به نام db با مسیر نگاشت شدهی به /var/lib/mysql/ داخل کانتینر ایجاد میشود، اجرا میکند.
- دستور دوم کار استخراج اطلاعات این کانتینر را انجام میدهد که شامل آدرس IP آن نیز میباشد. از این IP در برنامهی وب استفاده خواهیم کرد.
- دستور سوم مطابق توضیحات قسمت پنجم، یک وب سرور nginx را برای هاست یک برنامهی PHP که در آن پورت 8080 میزبان به پورت 80 کانتینر نگاشت شدهاست و همچنین فایلهای آن از مسیر /my/php/app/ میزبان به مسیر /usr/share/nginx/html/ داخل کانتینر نگاشت و تامین میشوند، اجرا میکند. در اینجا از پارامتر e برای تعریف یک سری متغیر محیطی مانند شمارهی پورت و IP کانتینر اجرا کنندهی mysql، استفاده شدهاست.
در این مثال دو کانتینر به هم وابسته را اجرا کردهایم و برای اجرای کانتینر دوم، نیاز است حداقل IP کانتینر اول را دانست و در قسمت MY_DB_HOST مقدار دهی کرد. روش دیگری نیز برای مدیریت سادهتر اجرای چندین کانتینر به هم وابسته توسط ابزاری به نام docker-compose وجود دارد. اگر از Dockerfile (که آنرا در قسمت پنجم معرفی کردیم) جهت ایجاد Imageهای سفارشی بکار میرود، فایل docker-compose.yml، کار خودکار سازی ایجاد و اجرای چندین کانتینر را انجام میدهد که با قالب YAML تعریف میشود:
version: '2' services: db: image: mysql ports: -3306:3306 environment: -MYSQL_ROOT_PASSWORD=my-secret-pw volumes: -db:/var/lib/mysql web: image: nginx ports: -8080:80 environment: -MY_DB_PORT=3306 -MY_DB_HOST=db volumes: -/my/php/app:/usr/share/nginx/html
در ابتدای این فایل، شماره نگارش قالب YAML مورد استفاده، مشخص شدهاست. در این نگارش، به کانتینرها، services گفته میشود که در اینجا دو سرویس db و web را مشاهده میکنید. در فایلهای yml، فضاهای خالی و indentations مهم هستند و بر این اساس است که کانتینرها و سپس مشخصات این کانتینرها، تمیز داده میشوند.
راه اندازی TeamCity به کمک فایل docker-compose.yml آن
در اینجا محتویات فایل docker-compose.yml مخصوص راه اندازی TeamCity را مشاهده میکنید که از سه کانتینر تشکیل شدهاست و از بانک اطلاعاتی postgres استفاده میکند:
version: '2' services: teamcity: image: sjoerdmulder/teamcity ports: - 8111:8111 teamcity-agent: image: sjoerdmulder/teamcity-agent environment: - TEAMCITY_SERVER=http://teamcity:8111 postgres: image: postgres environment: - POSTGRES_DB=teamcity
در ادامه برای کار با آن، ابتدا این محتویات را به صورت یک فایل متنی docker-compose.yml ذخیره کنید. سپس از طریق خط فرمان به پوشهی آن وارد شده و دستور docker-compose up را صادر کنید. docker-compose یکی دیگر از ابزارهای خط فرمان نصب شدهی به همراه داکر است و پارامتر up آن کار راه اندازی و اجرای کانتینرهای ذکر شدهی در فایل yml موجود را انجام میدهد. نام پوشهای که این فایل در آن قرار دارد، به عنوان نام پروژهی مشترک بین این کانتینرها در گزارشات آن مورد استفاده قرار میگیرد.
پس از صدور این فرمان، ابتدا تمام imageهای ذکر شدهی در فایل yml دریافت میشوند (سه image در اینجا) و هر سه کانتینر راه اندازی میشوند. اکنون میتوان در سیستم میزبان به آدرس http://localhost:8111 مراجعه کرد و از برنامهی teamcity استفاده نمود. البته صفحهی ابتدایی آن کار تنظیمات بانک اطلاعاتی آنرا انجام میدهد و جائیکه در مورد database type سؤال میپرسد میتوان postgres را انتخاب کرد و سپس در ذیل آن مقدار database host را نیز postgres وارد میکنیم. علت آنرا نیز پیشتر توضیح دادیم. postgres در اینجا نام کانتینر نیز هست و ذکر نام آن، با ذکر IP مرتبط با آن کانتینر، یکی است. نام بانک اطلاعاتی را teamcity وارد کنید (مطابق تنظیمات فایل yml فوق) و نام کاربری آن نیز postgres است؛ بدون کلمهی عبور. البته میشد در فایل yml فوق، متغیر محیطی POSTGRES_PASSWORD=xyz را نیز تنظیم کرد و سپس از آن در اینجا استفاده نمود.
docker-compose و ایجاد شبکههای ایزوله
توسط دستور docker network ls میتوان لیست شبکههای مجازی ایجاد شدهی توسط docker را مشاهده کرد (و همچنین سایر network adapters موجود). اگر این دستور را اجرا کنید، کارت شبکهی مجازی متناظر با شبکهی خصوصی teamcity_default را که پیشتر در مورد آن توضیح داده شد، میتوانید مشاهده کنید. این teamcity در اینجا همان نام پروژه و یا در اصل نام پوشهای است که فایل docker-compose را از آنجا اجرا کردیم.
برای دریافت اطلاعات بیشتری در مورد این کارت شبکهی به خصوص، میتوان دستور docker network inspect teamcity_default را صادر کرد. یکی از قسمتهای خروجی این گزارش، لیست کانتینرهایی است که هم اکنون به این شبکه متصل هستند؛ که در اینجا teamcity و بانک اطلاعاتی آن است.
مزیت ایجاد یک شبکهی خصوصی مخصوص کانتینرهای به هم پیوسته، علاوه بر سادگی تشکیل فایل docker-compose آنها با اشارهی به نام کانتینرها، بجای ذکر مستقیم آدرس IP هر کدام، ایزوله ساختن این شبکه، از شبکهی پیشفرض docker و بالا بردن میزان امنیت سایر کانتینرهایی است که هم اکنون از آن شبکه استفاده میکنند.
docker-compose و ایجاد DNS Server توکار
همانطور که عنوان شد، در این شبکهی خصوصی ویژهی کانتینرهای به هم پیوسته که توسط docker-compse اجرا و مدیریت شدهاند، میتوان از نام containerها بجای آدرس IP آنها استفاده کرد و این مورد با وجود یک DNS Server توکار در این شبکه میسر شدهاست. برای آزمایش بیشتر این قابلیت، ابتدا دستور docker ps را صادر میکنیم تا نام کانتینرهای در حال اجرا را بدست بیاوریم. سپس سعی میکنیم پروسهی bash shell داخل کانتینر بانک اطلاعاتی را اجرا کنیم:
docker ps docker exec -it teamcity_postgres_1 bash #exit
docker-compose exec postgres bash
پس از دسترسی به شل، دستور زیر را اجرا کنید:
#ping teamcity #exit
یک نکته: اگر بخواهیم از وضعیت بانکهای اطلاعاتی postgres توسط برنامهی psql آن گزارش بگیریم نیز روش اجرای آن به همین صورت است:
docker-compose exec postgres psql -U postgres postgres=#\l postgres=#\q
اتصال یک کانتینر خارج از شبکهی مجازی ایجاد شدهی توسط docker-compose به آن
فرض کنید میخواهید کانتینر کم حجم لینوکس alpine را اجرا کنید و توسط آن به شبکهی مجازی ایجاد شدهی توسط docker-compose متصل شوید. روش آن به صورت زیر است:
docker run --name apline -it --rm --net teamcity_default alpine sh
این دستور، کانتینر لینوکس alpine را در حالت interactive جهت اجرای shell آن، راه اندازی میکند. سپس به شبکهی مجازی teamcity_default متصل میشود. برای آزمایش این اتصال، در این shell راه اندازی شده، دستور ping teamcity را میتوان صادر کرد. همچنین از داخل کانتینر teamcity نیز میتوان این کانتینر را با نام آن ping کرد.
راه اندازی مجدد کانتینرها توسط docker-compose
اگر دستور docker-compose ps را دقیقا در پوشهای که فایل yml آن قرار دارد اجرا کنیم، میتوان گزارشی را صرفا از وضعیت کانتینرهای مرتبط با این فایل yml بدست آورد. دستور docker ps، لیست وضعیت تمام کانتینرهای در حال اجرای موجود را بر میگرداند. اکنون فرض کنید یکی از کانتینرهای اجرای شدهی توسط docker-compose، دیگر در حال اجرا نیست. برای راه اندازی مجدد آن میتوان از دستور docker-compose start teamcity-agent استفاده کرد. همچنین دستور docker-compose logs teamcity-agent، لیست آخرین لاگهای مرتبط با یک کانتینر را بر میگرداند که میتواند برای رفع اشکال بسیار مفید باشد.
حذف کانتینرهای به هم پیوستهی ایجاد شدهی توسط docker-compose
در ذیل ابتدا یک سری دستور را جهت مشاهدهی وضعیت سیستم مشاهده میکنید. سپس دستور docker-compose stop، کار متوقف کردن کانتینرهای مرتبط با فایل yml آنرا انجام میدهد. دستور docker-compose rm -v، علاوه بر حذف این کانتینرها، volumeهای بانکهای اطلاعاتی مرتبط را نیز حذف میکند. در آخر دستور docker-compose down، شبکهی مجازی مرتبط را نیز حذف خواهد کرد. سپس مجددا از وضعیت سیستم گزارش گیری شدهاست.
docker ps docker-compose ps docker volume ls docker network ls docker-compose stop docker-compose rm -v docker-compose down docker ps -a docker volume ls docker network ls
اجرای پروژهی ASP.NET Core Music Store توسط docker-compose
پروژهی معروف Music Store مایکروسافت را به همراه فایل docker-compose.windows.yml آن، در اینجا میتوانید مشاهده کنید. محتوای این فایل نیز به صورت زیر است:
version: '3' services: db: image: microsoft/mssql-server-windows-developer environment: sa_password: "Password1" ACCEPT_EULA: "Y" ports: - "1433:1433" # REMARK: This is currently required, needs investigation healthcheck: test: [ "CMD", "sqlcmd", "-U", "sa", "-P", "Password1", "-Q", "select 1" ] interval: 1s retries: 30 web: build: context: . dockerfile: Dockerfile.windows environment: - "Data:DefaultConnection:ConnectionString=Server=db,1433;Database=MusicStore;User Id=sa;Password=Password1;MultipleActiveResultSets=True" depends_on: - db ports: - "5000:5000"
- کانتینر db که بر اساس image مخصوص mssql-server-windows-developer راه اندازی میشود. تنظیمات آن نیز بسیار شبیه به مطلب «کار با Docker بر روی ویندوز - قسمت ششم - کار با بانکهای اطلاعاتی درون Containerها» است که پیشتر در مورد آن بحث کردیم.
- کانتینر web آن که از فایل Dockerfile.windows برای build سپس publish و در آخر run خودکار این برنامهی ASP.NET Core، کمک میگیرد. در اینجا context به پوشهی جاری اشاره میکند. در قسمت تنظیمات بانک اطلاعاتی آن، استفادهی از نام کانتینر db را در قسمت رشتهی اتصالی مشاهده میکنید. قسمت depends_on آن ترتیب اجرای این کانتینرها را مشخص میکند. یعنی ابتدا باید کانتینر db اجرا شود و سپس web.
محتوای فایل Dockerfile.windows آن نیز به صورت زیر است که بر اساس دستورات NET Core CLI. تهیه شدهاست:
FROM microsoft/dotnet-nightly:2.0-sdk-nanoserver SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] ENV NUGET_XMLDOC_MODE skip ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1 RUN New-Item -Path \MusicStore\samples\MusicStore -Type Directory WORKDIR app ADD samples/MusicStore/MusicStore.csproj samples/MusicStore/MusicStore.csproj ADD build/dependencies.props build/dependencies.props ADD NuGet.config . RUN dotnet restore --runtime win10-x64 .\samples\MusicStore ADD samples samples RUN dotnet publish --output /out --configuration Release --framework netcoreapp2.0 --runtime win10-x64 .\samples\MusicStore FROM microsoft/dotnet-nightly:2.0-runtime-nanoserver WORKDIR /app COPY --from=0 /out . EXPOSE 5000 ENV ASPNETCORE_URLS http://0.0.0.0:5000 CMD dotnet musicstore.dll
docker-compose -f .\docker-compose.windows.yml build docker-compose -f .\docker-compose.windows.yml up
ASP.NET MVC #8
مثال:
@using WebApplication1.Models @helper GetProductsList(List<Product> products) { <ul> @foreach (var item in products) { <li> @System.Web.Mvc.Html.LinkExtensions.ActionLink( ((System.Web.Mvc.WebViewPage)CurrentPage).Html, item.Name, "ActionName", "ControllerName", new { id = item.ProductNumber }, null) </li> } </ul> }
بدیهی است در اینجا به جای فراخوانی به صورت زیر
Html.ActionLink("foo")
ActionLink(Html, "foo")
- «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid»
Kendo UI Grid دارای امکانات ثبت، ویرایش و حذف توکاری است که در ادامه نحوهی فعال سازی آنها را بررسی خواهیم کرد. مثالی که در ادامه بررسی خواهد شد، در تکمیل مطلب «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid» است.
تنظیمات Data Source سمت کاربر
برای فعال سازی صفحه بندی سمت سرور، با قسمت read منبع داده Kendo UI پیشتر آشنا شده بودیم. جهت فعال سازی قسمتهای ثبت اطلاعات جدید (create)، به روز رسانی رکوردهای موجود (update) و حذف ردیفی مشخص (destroy) نیاز است تعاریف قسمتهای متناظر را که هر کدام به آدرس مشخصی در سمت سرور اشاره میکنند، اضافه کنیم:
var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' }, create: { url: "api/products", contentType: 'application/json; charset=utf-8', type: "POST" }, update: { url: function (product) { return "api/products/" + product.Id; }, contentType: 'application/json; charset=utf-8', type: "PUT" }, destroy: { url: function (product) { return "api/products/" + product.Id; }, contentType: 'application/json; charset=utf-8', type: "DELETE" }, //... }, schema: { //... model: { id: "Id", // define the model of the data source. Required for validation and property types. fields: { "Id": { type: "number", editable: false }, //تعیین نوع فیلد برای جستجوی پویا مهم است "Name": { type: "string", validation: { required: true } }, "IsAvailable": { type: "boolean" }, "Price": { type: "number", validation: { required: true, min: 1 } }, "AddDate": { type: "date", validation: { required: true } } } } }, batch: false, // enable batch editing - changes will be saved when the user clicks the "Save changes" button //... });
- به علاوه در اینجا به ازای هر فیلد، مباحث اعتبارسنجی نیز اضافه شدهاند؛ برای مثال فیلدهای اجباری با required: true مشخص گردیدهاند.
- اگر فیلدی نباید ویرایش شود (مانند فیلد Id)، خاصیت editable آنرا false کنید.
- در data source امکان تعریف خاصیتی به نام batch نیز وجود دارد. حالت پیش فرض آن false است. به این معنا که در حالت ویرایش، تغییرات هر ردیفی، یک درخواست مجزا را به سمت سرور سبب خواهد شد. اگر آنرا true کنید، تغییرات تمام ردیفها در طی یک درخواست به سمت سرور ارسال میشوند. در این حالت باید به خاطر داشت که پارامترهای سمت سرور، از حالت یک شیء مشخص باید به لیستی از آنها تغییر یابند.
مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات
در حالت ثبت، متد Post، توسط آدرس مشخص شده در قسمت create منبع داده گرید، فراخوانی میگردد:
namespace KendoUI06.Controllers { public class ProductsController : ApiController { public HttpResponseMessage Post(Product product) { if (!ModelState.IsValid) return Request.CreateResponse(HttpStatusCode.BadRequest); var id = 1; var lastItem = ProductDataSource.LatestProducts.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } product.Id = id; ProductDataSource.LatestProducts.Add(product); var response = Request.CreateResponse(HttpStatusCode.Created, product); response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.Id })); // گرید آی دی جدید را به این صورت دریافت میکند response.Content = new ObjectContent<DataSourceResult>( new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter()); return response; } } }
var productsDataSource = new kendo.data.DataSource({ //... schema: { data: "Data", total: "Total", } //... });
namespace KendoUI06.Controllers { public class ProductsController : ApiController { public DataSourceResult Get(HttpRequestMessage requestMessage) { var request = JsonConvert.DeserializeObject<DataSourceRequest>( requestMessage.RequestUri.ParseQueryString().GetKey(0) ); var list = ProductDataSource.LatestProducts; return list.AsQueryable() .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter); } } }
response.Content = new ObjectContent<DataSourceResult>( new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
متدهای حذف و به روز رسانی سمت سرور نیز چنین امضایی را خواهند داشت:
namespace KendoUI06.Controllers { public class ProductsController : ApiController { public HttpResponseMessage Delete(int id) { var item = ProductDataSource.LatestProducts.FirstOrDefault(x => x.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); ProductDataSource.LatestProducts.Remove(item); return Request.CreateResponse(HttpStatusCode.OK, item); } [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT' public HttpResponseMessage Update(int id, Product product) { var item = ProductDataSource.LatestProducts .Select( (prod, index) => new { Item = prod, Index = index }) .FirstOrDefault(x => x.Item.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); if (!ModelState.IsValid || id != product.Id) return Request.CreateResponse(HttpStatusCode.BadRequest); ProductDataSource.LatestProducts[item.Index] = product; return Request.CreateResponse(HttpStatusCode.OK); } } }
تنظیمات Kendo UI Grid جهت فعال سازی CRUD
در ادامه کلیه تغییرات مورد نیاز جهت فعال سازی CRUD را در Kendo UI، به همراه مباحث بومی سازی عبارات متناظر با دکمهها و صفحات خودکار مرتبط، مشاهده میکنید:
$("#report-grid").kendoGrid({ //.... editable: { confirmation: "آیا مایل به حذف ردیف انتخابی هستید؟", destroy: true, // whether or not to delete item when button is clicked mode: "popup", // options are "incell", "inline", and "popup" //template: kendo.template($("#popupEditorTemplate").html()), // template to use for pop-up editing update: true, // switch item to edit mode when clicked? window: { title: "مشخصات محصول" // Localization for Edit in the popup window } }, columns: [ //.... { command: [ { name: "edit", text: "ویرایش" }, { name: "destroy", text: "حذف" } ], title: " ", width: "160px" } ], toolbar: [ { name: "create", text: "افزودن ردیف جدید" }, { name: "save", text: "ذخیرهی تمامی تغییرات" }, { name: "cancel", text: "لغو کلیهی تغییرات" }, { template: kendo.template($("#toolbarTemplate").html()) } ], messages: { editable: { cancelDelete: "لغو", confirmation: "آیا مایل به حذف این رکورد هستید؟", confirmDelete: "حذف" }, commands: { create: "افزودن ردیف جدید", cancel: "لغو کلیهی تغییرات", save: "ذخیرهی تمامی تغییرات", destroy: "حذف", edit: "ویرایش", update: "ثبت", canceledit: "لغو" } } });
- برای فعال سازی تولید صفحات خودکار ویرایش و افزودن ردیفها، نیاز است خاصیت editable را به نحوی که ملاحظه میکنید، مقدار دهی کرد. خاصیت mode آن سه حالت incell (پیش فرض)، inline و popup را پشتیبانی میکند.
- اگر حالتهای inline و یا popup را فعال کردید، در انتهای ستونهای تعریف شده، نیاز است ستون ویژهای به نام command را مطابق تعاریف فوق، تعریف کنید. در این حالت دو دکمهی ویرایش و ثبت، فعال میشوند و اطلاعات خود را از تنظیمات data source گرید دریافت میکنند. دکمهی ویرایش در حالت incell کاربردی ندارد (چون در این حالت کاربر با کلیک درون یک سلول میتواند آنرا مانند برنامهی اکسل ویرایش کند). اما دکمهی حذف در هر سه حالت قابل استفاده است.
- به نوار ابزار گرید، سه دکمهی افزودن ردیفهای جدید، ذخیرهی تمامی تغییرات و لغو تغییرات صورت گرفته، اضافه شدهاند. این دکمهها استاندارد بوده و در اینجا نحوهی بومی سازی پیامهای مرتبط را نیز مشاهده میکنید. همانطور که عنوان شد، دکمههای «تمامی تغییرات» در حالت فعال سازی batching در منبع داده و استفاده از incell editing معنا پیدا میکند. در سایر حالات این دو دکمه کاربردی ندارند. اما دکمهی افزودن ردیفهای جدید در هر سه حالت کاربرد دارد و یکسان است.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
KendoUI06.zip
استفاده از LINQ جهت انجام کوئریها توسط NHibernate
نگارش نهایی 1.0 کتابخانهی LINQ to NHibernate اخیرا (حدود سه ماه قبل) منتشر شده است. در این قسمت قصد داریم با کمک این کتابخانه، اعمال متداول انجام کوئریها را بر روی دیتابیس قسمت قبل انجام دهیم.
توسط این نگارش ارائه شده، کلیه اعمال قابل انجام با criteria API این فریم ورک را میتوان از طریق LINQ نیز انجام داد (NHibernate برای کار با دادهها و جستجوهای پیشرفته بر روی آنها، HQL : Hibernate Query Language و Criteria API را سالها قبل توسعه داده است).
جهت دریافت پروایدر LINQ مخصوص NHibernate به آدرس زیر مراجعه نمائید:
پس از دریافت آن، به همان برنامه کنسول قسمت قبل، دو ارجاع را باید افزود:
الف) ارجاعی به اسمبلی NHibernate.Linq.dll
ب) ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم
در ابتدای متد Main برنامه قصد داریم تعدادی مشتری را به دیتابیس اضافه نمائیم. به همین منظور متد AddNewCustomers را به کلاس CDbOperations برنامه کنسول قسمت قبل اضافه نمائید. این متد لیستی از مشتریها را دریافت کرده و آنها را در طی یک تراکنش به دیتابیس اضافه میکند:
public void AddNewCustomers(params Customer[] customers)
{
using (ISession session = _factory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
foreach (var data in customers)
session.Save(data);
session.Flush();
transaction.Commit();
}
}
}
پس از افزودن این ارجاعات، کلاس جدیدی را به نام CLinqTest به برنامه کنسول اضافه نمائید. ساختار کلی این کلاس که قصد استفاده از پروایدر LINQ مخصوص NHibernate را دارد باید به شکل زیر باشد (به کلاس پایه NHibernateContext دقت نمائید):
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample1.Domain;
namespace ConsoleTestApplication
{
class CLinqTest : NHibernateContext
{ }
}
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample1.Domain;
namespace ConsoleTestApplication
{
class CLinqTest : NHibernateContext
{
ISessionFactory _factory;
public CLinqTest(ISessionFactory factory)
{
_factory = factory;
}
public List<Customer> GetAllCustomers()
{
using (ISession session = _factory.OpenSession())
{
var query = from x in session.Linq<Customer>() select x;
return query.ToList();
}
}
}
}
در این کوئری، لیست تمامی مشتریها بازگشت داده میشود.
سپس جهت استفاده و بررسی آن در متد Main برنامه خواهیم داشت:
static void Main(string[] args)
{
using (ISessionFactory session = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
var customer1 = new Customer()
{
FirstName = "Vahid",
LastName = "Nasiri",
AddressLine1 = "Addr1",
AddressLine2 = "Addr2",
PostalCode = "1234",
City = "Tehran",
CountryCode = "IR"
};
var customer2 = new Customer()
{
FirstName = "Ali",
LastName = "Hasani",
AddressLine1 = "Addr..1",
AddressLine2 = "Addr..2",
PostalCode = "4321",
City = "Shiraz",
CountryCode = "IR"
};
var customer3 = new Customer()
{
FirstName = "Mohsen",
LastName = "Shams",
AddressLine1 = "Addr...1",
AddressLine2 = "Addr...2",
PostalCode = "5678",
City = "Ahwaz",
CountryCode = "IR"
};
CDbOperations db = new CDbOperations(session);
db.AddNewCustomers(customer1, customer2, customer3);
CLinqTest lt = new CLinqTest(session);
foreach (Customer customer in lt.GetAllCustomers())
{
Console.WriteLine("Customer: LastName = {0}", customer.LastName);
}
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
مهمترین مزیت استفاده از LINQ در این نوع کوئریها نسبت به روشهای دیگر، استفاده از کدهای strongly typed دات نتی تحت نظر کامپایلر است، نسبت به رشتههای معمولی SQL که کامپایلر کنترلی را بر روی آنها نمیتواند داشته باشد (برای مثال اگر نوع یک ستون تغییر کند یا نام آن، در حالت استفاده از LINQ بلافاصله یک خطا را از کامپایلر جهت تصحیح مشکلات دریافت خواهیم کرد که این مورد در زمان استفاده از یک رشته معمولی صادق نیست). همچنین مزیت فراهم بودن Intellisense را حین نوشتن کوئریهایی از این دست نیز نمیتوان ندید گرفت.
مثالی دیگر:
لیست تمام مشتریهای شیرازی را نمایش دهید:
ابتدا متد GetCustomersByCity را به کلاس CLinqTest فوق اضافه میکنیم:
public List<Customer> GetCustomersByCity(string city)
{
using (ISession session = _factory.OpenSession())
{
var query = from x in session.Linq<Customer>()
where x.City == city
select x;
return query.ToList();
}
}
foreach (Customer customer in lt.GetCustomersByCity("Shiraz"))
{
Console.WriteLine("Customer: LastName = {0}", customer.LastName);
}
لیست کامل دیتابیسهای پشتیبانی شده توسط NHibernate را در این آدرس میتوانید مشاهده نمائید. (البته به نظر لیست آن، آنچنان هم به روز نیست؛ چون در نگارش آخر NHibernate ، پشتیبانی از اس کیوال سرور 2008 هم اضافه شده است)
نکته:
در کوئریهای مثالهای فوق همواره باید session.Linq<T> را ذکر کرد. اگر علاقمند بودید شبیه به روشی که در LINQ to SQL موجود است مثلا db.TableName بجای session.Linq<T> در کوئریها ذکر گردد، میتوان اصلاحاتی را به صورت زیر اعمال کرد:
یک کلاس جدید را به نام SampleContext به برنامه کنسول جاری با محتویات زیر اضافه نمائید:
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample1.Domain;
namespace ConsoleTestApplication
{
class SampleContext : NHibernateContext
{
public SampleContext(ISession session)
: base(session)
{ }
public IOrderedQueryable<Customer> Customers
{
get { return Session.Linq<Customer>(); }
}
public IOrderedQueryable<Employee> Employees
{
get { return Session.Linq<Employee>(); }
}
public IOrderedQueryable<Order> Orders
{
get { return Session.Linq<Order>(); }
}
public IOrderedQueryable<OrderItem> OrderItems
{
get { return Session.Linq<OrderItem>(); }
}
public IOrderedQueryable<Product> Products
{
get { return Session.Linq<Product>(); }
}
}
}
سپس بازنویسی متد GetCustomersByCity بر اساس SampleContext فوق به صورت زیر خواهد بود که به کوئریهای LINQ to SQL بسیار شبیه است:
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHSample1.Domain;
namespace ConsoleTestApplication
{
class CSampleContextTest
{
ISessionFactory _factory;
public CSampleContextTest(ISessionFactory factory)
{
_factory = factory;
}
public List<Customer> GetCustomersByCity(string city)
{
using (ISession session = _factory.OpenSession())
{
using (SampleContext db = new SampleContext(session))
{
var query = from x in db.Customers
where x.City == city
select x;
return query.ToList();
}
}
}
}
}
و در تکمیل این بحث، میتوان به لیستی از 101 مثال LINQ ارائه شده در MSDN اشاره کرد که یکی از بهترین و سریع ترین مراجع یادگیری مبحث LINQ است.
ادامه دارد ...
نکات زیر، جالبترین نکات در خصوص شیوه حمله بوده که قابل توجه است:
- یک کاربر از یک وب سایت حاوی کدهای مخرب و آلوده که یک فایل را میزبانی میکند، مانند evil.html بازدید میکند.
- با توجه به یک بخش از آسیب پذیری، فایل evil.html دانلود میشود و کارت SD آن را بدون هشدار به کاربر، ذخیره میکند!
- با توجه به بخش دیگری از آسیب پذیری، به محض اینکه فایل ذخیره شد، میتوان کدهای جاوا اسکریپت مخرب را روی آن اجرا کرد!
- بدلیل بخش پایانی حمله روی این آسیب پذیری، کدهای جاوااسکریپت تحت شرایطی خاص روی سیستم محلی (local) اجرا میشوند! ابزار آلوده و برنامه نویسی شده، براحتی روی کارت SD ذخیره و مستقر شده و به وب سایت مهاجم برای ارسال اطلاعات قربانی دسترسی کامل دارد.
ممکن است بپرسید، "من فقط یک توسعه دهنده اندرویدی کوچکی هستم که قصد دارم برنامه خودم را در یک مارکت اندرویدی بفروشم و قیمت این برنامه خیلی پایین است. بنابراین آیا واقعا باید زمانی را برای انجام این کار امنیتی از قبل هدر دهم؟
و من با صدای رسا جواب میدهم : "بله! باید این کار را انجام دهید." این کار باعث میشود تا در حین گسترش اپلیکیشن کوچک خود، دیگر نگران مشکلات حملات مستقیم یا غیرمستقیم نباشید.
حملات مستقیم (Direct Attacks)
حملات مستقیم به طور قابلتوجهی متفاوت هستند و میتوانند شکلهای گوناگونی برای حمله داشته باشند. یک حمله مستقیم میتواند به عنوان فردی که مستقیما در برنامه شما هدف قرار داده میشود، طبقهبندی شود. بنابراین، مهاجم به دنبال آسیب پذیری برای نفوذ در طراحی برنامه شما برای جمعآوری اطلاعات حساس در مورد شما و کاربرانتان است. استفاده از کاربران برنامه و یا حمله به کارگزار، از مواردی است که یک مهاجم در اولویت کار خود قرار میدهد! یک مهاجم ممکن است به دنبال برنامههای کاربردی تلفن همراه باشد که متعلق به یک نهاد دولتی است مثل، یک بانک خاص که شما اپلیکیشن آنرا روی تلفن خود نصب کردهاید و آپدیتهای امنیتی راهم انجام ندادهاید! اگر طرح امنیتی روی برنامه ضعیف باشد و دادههای حساس و حیاتی کاربران در محلی امن نگه داری نشود یا حتی ارتباط بین برنامه و ssl و سرور بدرستی برقرار نباشد، مهاجم میتواند حملات خاصی را روی ssl انجام دهد و نقاط ضعف را شناسایی کرده و در کسری از زمان به سرور متصل شود! این حمله یک حمله مستقیم و بدون دخالت فایل یا ابزار خاصی است. این یک حمله مستقیم به یک برنامه خاص است.
Proxim و ذخیره داده
بیایید با یک مثال ساده با نام Proxim شروع کنیم: برای نوشتن یک برنامه که میتواند یک SMS را به افراد خاص و معین ارسال کند، قرار دادی بستهایم؛ با توجه به نزدیکی به مجموعهای از مختصات مکانی آنها روی GPS. برای مثال: کاربر در این برنامه میتواند شماره تماس همسر خود را ذخیره کرده و هر زمان که به فاصله 3 مایلی به خانه یا محل کار برسد، با او تماس میگیرد. بدین صورت همسر فرد مطلع میشود که او نزدیک به محل کار یا خانه است و با یک تماس تلفنی او را آگاه میسازد.
کدهای زیر بخشی از فایل (Save Routine, SaveController. java) هستند:
package net.zenconsult.android.controller; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import net.zenconsult.android.model.Contact; import net.zenconsult.android.model.Location; import android.content.Context; import android.os.Environment; import android.util.Log; public class SaveController { private static final String TAG = "SaveController"; public static void saveContact(Context context, Contact contact) { if (isReadWrite()) { try { File outputFile = new File(context.getExternalFilesDir(null), contact.getFirstName()); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(contact.getBytes()); outputStream.close(); } catch (FileNotFoundException e) { Log.e(TAG, "File not found"); } catch (IOException e) { Log.e(TAG, "IO Exception"); } } else { Log.e(TAG, "Error opening media card in read/write mode!"); } } public static void saveLocation(Context context, Location location) { if (isReadWrite()) { try { File outputFile = new File(context.getExternalFilesDir(null), location.getIdentifier()); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(location.getBytes()); outputStream.close(); } catch (FileNotFoundException e) { Log.e(TAG, "File not found"); } catch (IOException e) { Log.e(TAG, "IO Exception"); } } else { Log.e(TAG, "Error opening media card in read/write mode!"); } } private static boolean isReadOnly() { Log.e(TAG, Environment .getExternalStorageState()); return Environment.MEDIA_MOUNTED_READ_ONLY.equals(Environment .getExternalStorageState()); } private static boolean isReadWrite() { Log.e(TAG, Environment .getExternalStorageState()); return Environment.MEDIA_MOUNTED.equals(Environment .getExternalStorageState()); } }
کدهای فایل ( Location .java)
package net.zenconsult.android.model; publicclass Location { private String identifier; privatedouble latitude; privatedouble longitude; public Location() {} publicdouble getLatitude() { return latitude; } publicvoid setLatitude(double latitude) { this.latitude = latitude; } publicdouble getLongitude() { return longitude; } publicvoid setLongitude(double longitude) { this.longitude = longitude; } publicvoid setIdentifier(String identifier) { this.identifier = identifier; } public String getIdentifier() { return identifier; } public String toString() { StringBuilder ret = new StringBuilder(); ret.append(getIdentifier()); ret.append(String.valueOf(getLatitude())); ret.append(String.valueOf(getLongitude())); return ret.toString(); } publicbyte[] getBytes() { return toString().getBytes(); } }
کدهای فایل (Contact.java)
package net.zenconsult.android.model; publicclass Contact { private String firstName; private String lastName; private String address1; private String address2; private String email; private String phone; public Contact() {} public String getFirstName() { return firstName; } publicvoid setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } publicvoid setLastName(String lastName) { this.lastName = lastName; } public String getAddress1() { return address1; } publicvoid setAddress1(String address1) { this.address1 = address1; } public String getAddress2() { return address2; } publicvoid setAddress2(String address2) { this.address2 = address2; } public String getEmail() { return email; } publicvoid setEmail(String email) { this.email = email; } public String getPhone() { return phone; } publicvoid setPhone(String phone) { this.phone = phone; } public String toString() { StringBuilder ret = new StringBuilder(); ret.append(getFirstName() + "|"); ret.append(getLastName() + "|"); ret.append(getAddress1() + "|"); ret.append(getAddress2() + "|"); ret.append(getEmail() + "|"); ret.append(getPhone() + "|"); return ret.toString(); } publicbyte[] getBytes() { return toString().getBytes(); } }
final Contact contact = new Contact(); contact.setFirstName("User 1"); contact.setLastName("L1"); contact.setAddress1(""); contact.setAddress2(""); contact.setEmail("name@site.net"); contact.setPhone("12120031337");
angular-translate دایرکتیو و فیلتر هایی را به صورت کامپوننت عرضه کرده است که شما میتوانید به وسیلهی آنها پروژه خود را به زبانهای گوناگون localize کنید. آنچه که در تصویر فوق مشاهده میکنید در حقیقت ساختار ماژول angular-translate را نمایش میدهد. به این صورت که در اولین لایه دایرکتیو translate قرار گرفته است.
سطح بعدی شاید کمی جذابتر به نظر برسد. هر دو بخش دایرکتیو و فیلتر از سرویس تزریق شده translate$ استفاده میکنند. این بدین معنی است که در هنگام تغییر یک زبان به زبان دیگر به وسیله دایرکتیوهایی که در view نوشته شده است، در حقیقت شما این دایرکتیوها را به سطح فیلتر انتقال دادهاید و پس از آن، فیلتر آن را به لایه سرویس برده و سرویس کار اصلی را انجام میدهد.
در سطح بعدی لایه Interpolator قرار گرفته است که وظیفه تغییر DOMها را بر عهده دارد. این فرآیند متغیرهایی را که در ریسورس قرار دادهایم، درایه به درایه در view و در مکانهای هم نام قرار میدهد. این فرآیند به وسیله راهکارهای گوناگونی از قبیل Message Format و یا Angular Core Interpolation Service امکان پذیر است.
جعبه خاکستری رنگ بخش missing translation handler را نشان میدهد. این handlerها زمانی فراخوانی میگردند که translator به دنبال یک key میگردد که تعریف نشده باشد. angular-translate خود شامل یک logging service میباشد که به صورت extension باید آن را به پروژه خود اضافه نمایید که در بخشهای بعدی در مورد آن مفصلتر بحث میکنیم.
بخش asynchronous loader شما را قادر میسازد که دادههای زبانهای متفاوت را به صورت غیر هم زمان بارگذاری نمایید. angular-translate از ماژولهای asynchronous loader پشتیبانی میکند. روش معرفی شده در سایت مرجع در مورد بارگذاری غیر همزمان urlLoader و staticFilesLoader هستند که در بخشهای بعدی به آن خواهیم پرداخت.
angular-translate برای ذخیره سازی دادههای بازگذاری شده و نگه داشتن زبان سایت در صورت reload شدن صفحه راهکارهای متفاوتی را ارائه نموده است. این قابلیت در ابتدا چک میکند که ریسورسهای زبان درون یک local storage قرار گرفتهاند یا خیر. این عملیات طی یک فرآیند جستجوی key-value صورت میگیرد و در نهایت فایلهای ریسورس زبان مورد نظر که درون کوکی یا localStorage ذخیره شدهاند بارگذاری میشوند. angular-translate به صورت توکار دارای دو راهکار localStorage و cookieStorage برای ذخیره سازی ریسورسها میباشد.
در بخش بعدی در مورد نحوهی ذخیره سازی دادهها به دو روش cookieStorage و localStorage بیشتر صحبت خواهیم کرد.
ویدیوهای آموزشی QT
http://www.voidrealms.com/tutorials.aspx?filter=qt