فواید استفاده از فریمورک Seneca
- فریمورک Seneca کدنویسی برای ایجاد درخواستها، ارسال پاسخ به درخواستهای رسیده و تبدیل دادهها را که از روتینهای هر سرویسی میتوانند باشند، سادهتر میکند.
- فریمورک Seneca با معرفی ایده Actionها و Pluginها که جلوتر توضیح داده خواهند شد، روند تبدیل کامپوننتهای یک برنامه Monolithic را به نوع سرویسی، تسهیل میکند. روندی که به صورت عادی میتواند کاری طاقت فرسا باشد و نیاز به Refactoring زیادی دارد.
- سرویسهای نوشته شده با زبانهای برنامهنویسی مختلف یا فریمورکهای متفاوت میتوانند با سرویسهای نوشته شده توسط فریمورک Seneca در ارتباط و تبادل باشند.
نصب فریمورک Seneca
{ "name": "seneca-example", "dependencies": { "seneca": "0.6.5", "express": "latest" } }
var seneca = require("seneca")();
Actionها
seneca.add({role: "accountManagement", cmd: "login"}, function(args, respond){ }); seneca.add({role: "accountManagement", cmd: "register"}, function(args, respond){ });
seneca.act({role: "accountManagement", cmd: "register", username: "parham", password: "12345!"}, function(error, response){ }); seneca.act({role: "accountManagement", cmd: "login", username: "parham", password: "12345!"}, function(error, response){ });
Pluginها
function account(options){ this.add({init: "account"}, function(pluginInfo, respond){ console.log(options.message); respond(); }) this.add({role: "accountManagement", cmd: "login"}, function(args, respond){ }); this.add({role: "accountManagement", cmd: "register"}, function(args, respond){ }); } seneca.use(account, {message: "Plugin Added"});
module.exports = function(options){ this.add({init: "account"}, function(pluginInfo, respond){ console.log(options.message); respond(); }) this.add({role: "accountManagement", cmd: "login"}, function(args, respond){ }); this.add({role: "accountManagement", cmd: "register"},function(args, respond){ }); return "account"; } seneca.use("./account.js", {message: "Plugin Added"});
Serviceها
var seneca = require("seneca")(); seneca.use("./account.js", {message: "Plugin Added"}); seneca.listen({port: "9090", pin: {role: "accountManagement"}});
seneca.client({port: "9090", pin: {role: "accountManagement"}});
در اینجا هم موارد port و pin اختیاری هستند. اگر سرویسی که میخواهیم ثبت کنیم در سرور دیگری باشد، بایستی خاصیت host و با مقدار آدرس IP سرور مورد نظر در زمان ثبت، اعمال شود. سوالی که باقی میماند این است که یک سرویس چطور Actionی از یک سرویس دیگر را فراخوانی میکند؟
در بخش Actionها آمد که برای فراخوانی یک Action از متد act، از نمونهی Seneca استفاده میکنیم. رفتار Seneca به این صورت است که ابتدا بر اساس امضای Action درخواست شده، Actionهای محلی را که به سرویس جاری اضافه شدهاند، جستجو میکند. اگر تطبیقی نیافت به سراغ Actionهای ثبت شده خارجی که دارای خاصیت pin هستند، خواهد رفت و در نهایت اگر آنجا هم موردی نیافت، برای تک تک سرویسهایی که آنها را ثبت کرده، اما خاصیت pin را ندارند، درخواستی را ارسال میکند.
برای اطلاعات بیشتر به بخش مستندات فریمورک Seneca رجوع کنید.
در قسمت قبل به معرفی postgresql پرداختیم; در این قسمت قصد ایجاد و راه اندازی یک api با استفاده از دیتابیس postgresql و استفاده از تکنولوژیهای آن را با استفاده از docker داریم.
ابتدا با استفاده از دستور زیر یک پروژهی جدید asp.net core را ایجاد کنید:
dotnet new webapi --minimal -o YourDirectoryPath:\YourFolderName
سپس فایل docker-compose.yaml را به روت پروژه اضافه کنید که شامل کانفیگهای زیر میباشد:
version: '3.1' services: db: image: postgres container_name: db restart: always environment: POSTGRES_PASSWORD: postgres POSTGRES_USERNAME: postgres POSTGRES_DB: BloggingDb ports: - "5432:5432" volumes: - postgres_data:/data/db adminer: image: adminer restart: always ports: - 8080:8080 pgadmin4: image: dpage/pgadmin4 restart: always environment: PGADMIN_DEFAULT_EMAIL: pgadmin4@pgadmin.org PGADMIN_DEFAULT_PASSWORD: admin PGADMIN_CONFIG_SERVER_MODE: 'False' ports: - 5050:80 volumes: - pgadmin:/var/lib/pgadmin depends_on: - db volumes: postgres_data: pgadmin:
سپس با اجرای دستور زیر در روت پروژه، سرویسها را راه اندازی کنید:
docker compose up -d
معرفی سرویسهای استفاده شده در تنظیمات فایل بالا:
سرویس db :
نمونه ایمیج اصلی، volume، تنظیمات connection string در آن استفاده شده است.
سرویس adminer :
https://hub.docker.com/_/adminer /
Adminer - Database management in a single PHP file
یک برنامه تحت وب مدیریت پایگاه داده ساده میباشد که ویژگیها MySql را در کنار سرعت و امنیت ارائه میدهد و در آدرس http://localhost:8080 / اجرا خواهد شد.
سرویس pgadmin4 :
dpage/pgadmin4 - Docker Image | Docker Hub
در حال حاضر این برنامه محبوبترین برنامه مدیریت پایگاه داده میباشد که ویژگیهای پیشرفتهای را نیز پوشش میدهد و در آدرس http://localhost:5050 / اجرا خواهد شد.
اکنون نوبت نوشتن کدها میباشد.
- تنظیم connection string در فایل appsettings.json:
"ConnectionStrings": { "BloggingContext": "Username=postgres;Password=postgres;Server=localhost;Database=BloggingDb” }
- و همینطور پکیجهای زیر را به برنامه خود رفرنس دهید:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design
- مدلهای برنامه را در مسیر /Models ایجاد کنید:
namespace NpgsqlAPI.Models; public class Post { public int PostId { get; set; } public string Title { get; set; } = null!; public string Content { get; set; } = null!; public int BlogId { get; set; } public Blog Blog { get; set; } = null!; } namespace NpgsqlAPI.Models; public class Blog { public int BlogId { get; set; } public string? Url { get; set; } public List<Post>? Posts { get; set; } }
- سپس BloggingContext را در مسیر /Data ایجاد کنید:
using Microsoft.EntityFrameworkCore; using NpgsqlAPI.Models; namespace NpgsqlAPI.Data; public class BloggingContext : DbContext { public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { } public DbSet<Blog> Blogs => Set<Blog>(); public DbSet<Post
- سپس اینترفیس IBlogServices را در مسیر /Servicec/Blogs ایجاد کنید:
using NpgsqlAPI.Models; namespace NpgsqlAPI.Services.Blogs; public interface IBlogServices { Task<IEnumerable<Blog>> GetList(); Task<Blog?> Get(uint id); Task<uint> Add(Blog obj); Task AddRange(Blog[] obj); Task Update(Blog obj); Task UpdateRange(Blog[] obj); Task Remove(uint id); }
- و سپس پیاده سازی آن را در فایل BlogEFServices و در کنار اینترفیس آن قرار دهید:
using Microsoft.EntityFrameworkCore; using NpgsqlAPI.Data; using NpgsqlAPI.Models; namespace NpgsqlAPI.Services.Blogs; public sealed class BlogEFServices : IBlogServices { private readonly BloggingContext _context; public BlogEFServices(BloggingContext context) { _context = context; } public async Task<uint> Add(Blog obj) { await _context.Blogs.AddAsync(obj); return (uint)await SaveChangesAsync(); } public async Task AddRange(Blog[] obj) { await _context.Blogs.AddRangeAsync(obj); await SaveChangesAsync(); } public async Task<Blog?> Get(uint id) { return await _context.Blogs.FirstOrDefaultAsync(x=>x.BlogId == id); } public async Task<IEnumerable<Blog>> GetList() { return await _context.Blogs.ToListAsync(); } public async Task Remove(uint id) { var entity = await Get(id); _context.Blogs.Remove(entity!); await SaveChangesAsync(); } public async Task Update(Blog obj) { _context.Blogs.Update(obj); await SaveChangesAsync(); } public async Task UpdateRange(Blog[] obj) { _context.Blogs.UpdateRange(obj); await SaveChangesAsync(); } private async Task<int> SaveChangesAsync() { return await _context.SaveChangesAsync(); } }
- اکنون endpointهای api را در فایل program.cs ایجاد کنید:
using System.Data; using Microsoft.EntityFrameworkCore; using Npgsql; using NpgsqlAPI.Services.Blogs; using NpgsqlAPI.Data; using NpgsqlAPI.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); string connectionString = builder.Configuration.GetConnectionString("BloggingContext")!; builder.Services.AddDbContext<BloggingContext>(options => options.UseNpgsql(connectionString)); builder.Services.AddTransient<IDbConnection>(_ => new NpgsqlConnection(connectionString)); // builder.Services.AddScoped<IBlogServices, BlogDapperServices>(); // builder.Services.AddScoped<IBlogServices, BlogEFRawQueryServices>(); builder.Services.AddScoped<IBlogServices, BlogEFServices>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapGet("/blogs", async (IBlogServices service) => await service.GetList()) .WithName("GetBlogs") .WithOpenApi(); app.MapGet("/blogs/{id}", async (IBlogServices service, uint id) => await service.Get(id)) .WithName("GetBlog") .WithOpenApi(); app.MapPost("/blogs", async (IBlogServices service, Blog blog) => await service.Add(blog)) .WithName("AddBlog") .WithOpenApi(); app.MapDelete("/blogs/{id}", async (IBlogServices service, uint id) => await service.Remove(id)) .WithName("RemoveBlog") .WithOpenApi(); app.MapPut("/blogs", async (IBlogServices service, Blog blog) => await service.Update(blog)) .WithName("UpdateBlog") .WithOpenApi(); app.MapPut("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) => await service.UpdateRange(blogs)) .WithName("UpdateBulkBlog") .WithOpenApi(); app.MapPost("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) => await service.AddRange(blogs)) .WithName("AddBulkBlog") .WithOpenApi(); app.Run();
تمامی کدهای برنامه تا به اینجا نوشته شدهاند. اکنون migration را پس از اطمینان از اجرا بودن داکر اجرا کنید
dotnet ef migrations add Init dotnet ef database update
و برنامه را اجرا و تست کنید.
ng new apollo-angular-project
ng add apollo-angular
const uri = 'https://localhost:5001/graphql';
npm install --save apollo-angular \ apollo-angular-link-http \ apollo-link \ apollo-client \ apollo-cache-inmemory \ graphql-tag \ graphql
{ "compilerOptions": { // ... "lib": [ "es2017", "dom", "esnext.asynciterable" ] } }
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { HttpClientModule } from "@angular/common/http"; import { ApolloModule, APOLLO_OPTIONS } from "apollo-angular"; import { HttpLinkModule, HttpLink } from "apollo-angular-link-http"; import { InMemoryCache } from "apollo-cache-inmemory"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, ApolloModule, HttpLinkModule ], providers: [ { provide: APOLLO_OPTIONS, useFactory: (httpLink: HttpLink) => { return { cache: new InMemoryCache(), link: httpLink.create({ uri: "https://localhost:5001/graphql" }) } }, deps: [ HttpLink ] }], bootstrap: [ AppComponent ] }) export class AppModule { }
export type OwnerInputType = { name: string; address: string; }
export type AccountType = { 'id': string; 'description': string; 'ownerId' : string; 'type': string; }
import { AccountType } from './accountType'; export type OwnerType = { 'id': string; 'name': string; 'address': string; 'accounts': AccountType[]; }
ng g s graphql
import { Injectable } from '@angular/core'; import { Apollo } from 'apollo-angular'; import gql from 'graphql-tag'; @Injectable({ providedIn: 'root' }) export class GraphqlService { constructor(private apollo: Apollo) { } }
public getOwners = () => { return this.apollo.query({ query: gql`query getOwners{ owners{ id, name, address, accounts{ id, description, type } } }` }); }
export class AppComponent implements OnInit { public owners: OwnerType[]; public loading = true; constructor(private graphQLService: GraphqlService) { } ngOnInit() { this.graphQLService.getOwners().subscribe(result => { this.owners = result.data["owners"] as OwnerType[]; this.loading = result.loading; }); } }
<div> <div *ngIf="!this.loading"> <table> <thead> <tr> <th> # </th> <th> نام و نام خانوادگی </th> <th> آدرس </th> </tr> </thead> <tbody> <ng-container *ngFor="let item of this.owners;let idx=index" [ngTemplateOutlet]="innertable" [ngTemplateOutletContext]="{item:item, index:idx}"></ng-container> </tbody> </table> </div> <div *ngIf="this.loading"> <p> در حال بارگذاری لیست ... </p> </div> </div> <ng-template #innertable let-item="item" let-idx="index"> <tr> <td>{{idx+1}}</td> <td>{{item.name}}</td> <td>{{item.address}}</td> </tr> <tr *ngIf="this.item.accounts && this.item.accounts.length > 0"> <td colspan="4"> <div> <p>Accounts</p> </div> <div> <table> <thead> <tr> <th>#</th> <th>نوع</th> <th>توضیحات</th> </tr> </thead> <tbody> <tr *ngFor="let innerItem of this.item.accounts;let innerIndex=index"> <td> {{innerIndex+1}} </td> <td> {{innerItem.type}} </td> <td> {{innerItem.description}} </td> </tr> </tbody> </table> </div> </td> </tr> </ng-template>
dotnet restore dotnet run
ng serve
public getOwners = () => { return this.apollo.watchQuery<any>({ query: gql`query getOwners{ owners{ id, name, address, accounts{ id, description, type } } }` }) }
export class AppComponent implements OnInit { loading: boolean; public owners: OwnerType[]; private querySubscription: Subscription; constructor(private graphQLService: GraphqlService) { } ngOnInit() { this.querySubscription = this.graphQLService.getOwners() .valueChanges .subscribe(result => { this.loading = result.loading; this.owners = result.data["owners"] as OwnerType[]; }); } ngOnDestroy() { this.querySubscription.unsubscribe(); } }
public getOwner = (id) => { return this.apollo.query({ query: gql`query getOwner($ownerID: ID!){ owner(ownerId: $ownerID){ id, name, address, accounts{ id, description, type } } }`, variables: { ownerID: id } }) }
public createOwner = (ownerToCreate: OwnerInputType) => { return this.apollo.mutate({ mutation: gql`mutation($owner: ownerInput!){ createOwner(owner: $owner){ id, name, address } }`, variables: { owner: ownerToCreate } }) }
public updateOwner = (ownerToUpdate: OwnerInputType, id: string) => { return this.apollo.mutate({ mutation: gql`mutation($owner: ownerInput!, $ownerId: ID!){ updateOwner(owner: $owner, ownerId: $ownerId){ id, name, address } }`, variables: { owner: ownerToUpdate, ownerId: id } }) }
public deleteOwner = (id: string) => { return this.apollo.mutate({ mutation: gql`mutation($ownerId: ID!){ deleteOwner(ownerId: $ownerId) }`, variables: { ownerId: id } }) }
نام پارامتر | شرح کارکرد |
c- یا collection-- | میتواند پشتیبانی گیری را به یک کالکشن خاص محدود کند. |
d- یا db-- | از دیتابیسی مشخص استفاده کند. |
u- یا username- | نام کاربری سرور |
p- یا password-- | کلمه عبور سرور |
dbpath-- | مسیر پوشهای را که دیتاها داخل آن است، دریافت میکند و بجای ایجاد یک Instance مستقیم پشتیبانی گیری را آغاز میکند. توجه : در این حالت پوشه به طور کامل قفل خواهد شد و سرور نباید در حالت اجرا قرار گرفته باشد. |
DirectoryPerDb-- | در صورتیکه هر دیتابیسی دارای محل جداگانهای برای پشتیبان گیری باشد. |
o- یا out-- | محل خروجی و ذخیره پشتیبان را مشخص میکند. |
q- یا query-- | پشتیبان، در قالب کوئریهای جیسون خواهد بود. |
repair-- | اصلاح اسناد در صورت خراب شدن دیتابیس. در این حالت باید مکان ذخیره و نام دیتابیس، با پارامترهای بالا ذکر شود. |
به عنوان مثال دستور زیر از دیتابیس publisher و کالکشن Books، پشتیبان تهیه میکند و در مسیر گفته شده آن را ذخیره میکند:
D:\Program Files\MongoDB\Server\3.4\bin>mongodump --db "publisher" --collection books --out "D:\mydumps" 2017-03-04T21:23:04.615+0330 writing publisher.books to 2017-03-04T21:23:04.637+0330 done dumping publisher.books (7 documents)
D:\Program Files\MongoDB\Server\3.4\bin>mongodump
برای بازگردانی از دستوری به نام Mongorestore استفاده میشود که در شاخه Bin قرار گرفته است و تعدادی از پارامترهای آن به شرح زیر میباشد:
پارامتر | شرح کارکرد |
c- یا collection-- | میتواند پشتیبانی گیری را به یک کالکشن خاص محدود کند. |
d- یا db-- | از دیتابیسی مشخصی استفاده کند. |
u- یا username- | نام کاربری سرور |
p- یا password-- | کلمه عبور سرور |
port-- | شماره پورت سرور |
host-- | هاست مونگو ، ترکیب hostname:port میتواند استفاده از سوییچ port را بی نیاز کند. |
ipv6-- | فعال سازی IPV6 |
dbpath-- | مسیر پوشهای را که دیتاها داخل آن است، دریافت میکند و بجای ایجاد یک Instance مستقیم پشتیبانی گیری را آغاز میکند. توجه : در این حالت پوشه به طور کامل قفل خواهد شد و سرور نباید در حالت اجرا قرار گرفته باشد. |
DirectoryPerDb-- | در صورتی که هر دیتابیسی دارای محل جداگانهای برای پشتیبان گیری باشد. |
keepIndexVersion -- | موقع بازگردانی، ایندکسها را با نسخه جدید به روزرسانی نمیکند. |
مثال زیر کل پشتیبانهای مسیر مربوطه را بازگردانی میکند:
mongorestore "D:\mydumps"
mongorestore "D:\mydumps\publisher\books.bson" --db publisher -c books
mongorestore
CACHE MANIFEST #Cache Section CACHE: /Content/Images/icons-18-white.png /Content/Images/icons-36-white.png /Content/Images/ajax-loader.png /Content/css /Scripts/js
NETWORK webService.Js
CACHE MANIFEST # 2010-06-18:v2 # Explicitly cached 'master entries'. CACHE: /favicon.ico index.html css images/ stylesheet .logo.png scripts/main.js # Resources that require the user to be online. NETWORK: login.php /myapi http://api.twitter.com
AddType text/cache-manifest .appcache
using System.Web; namespace JavaScriptReference { public class Manifest : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/cache-manifest"; context.Response.WriteFile(context.Server.MapPath("Manifest.txt")); } public bool IsReusable { get { return false; } } } }
CACHE MANIFEST # Version Jesus 3! CACHE: index.html js/Custom.js js/Utility.js styles/index.css styles/kendo.common.min.css styles/BlueOpal/loading.gif styles/BlueOpal/slider-h.gif styles/BlueOpal/slider-v.gif NETWORK: * <%@ Page Language="VB" ContentType="text/cache-manifest" ResponseEncoding="utf-8" AutoEventWireup="true" CodeFile="manifest.aspx.vb" Inherits="Configuration_manifest" %>
<!DOCTYPE html> <html manifest="/manifest.aspx">
توسط این کتابخانه میتوانید دیتابیسهای MongoDb را بدون نیاز به نصب آنها، به صورت یکبار مصرف ایجاد کنید. یعنی یک دیتابیس موقت (در پوشه Temp سیستم عامل) برای شما میسازد و در آخر وقتی کار شما با آن تمام شد، آن را حذف میکند. در نتیجه برای Integration Testing بسیار مناسب و کاربردی هست.
طرز کار با آن خیلی سادهاست؛ فقط کافی است بستهی NuGet آن را نصب کنید:
Install-Package Mongo2Go
و به صورت زیر از آن استفاده کنید:
using (var runner = MongoDbRunner.Start()) { var client = new MongoClient(runner.ConnectionString); var database = client.GetDatabase("IntegrationTest"); var collection = database.GetCollection<TestDocument>("TestCollection"); //Just use it! }
از آنجاکه نهایت اندازهی یک سند BSON نمیتواند بیشتر از 16 مگابایت باشد، قابلیتی به نام GridFS ایجاد شدهاست تا بتوان فایلهای باینری را در آن ذخیره کرد. GridFS شامل دو بخش مختلف برای ذخیره اطلاعات یک فایل باینری است:
- fs.chunks که برای ذخیره اطلاعات قطعههای یک فایل باینری به کار میرود.
- fs.files که برای ذخیره اطلاعات و متادیتاها به کار میرود.
قبل از هر چیزی باید بدانید که کتابخانه مربوط به GridFs در یک پکیج جداگانه عرضه شده است و باید آن از طریق nuget نصب کنیم:
install-package MongoDB.Driver.GridFS
برای آپلود یک فایل باینری به داخل سیستم از کد زیر استفاده میکنیم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db);
byte[] source=File.ReadAllBytes(@"D:\Untitled.png"); var options = new GridFSUploadOptions { ChunkSizeBytes = 64512, // 63KB Metadata = new BsonDocument { { "CoverType", "Front" }, { "copyrighted", true } } };
مورد دومی که مقداردهی شدهاست، متادیتاها هستند و این قابلیت را داریم که پرس و جوی خود را بر اساس آنها نیز فیلتر کنیم. این خصوصیت مقدار دریافتی از جنس BsonDocument را دریافت میکند. ولی اگر شما برای فایل خود، کلاس اختصاصی برای متادیتاها در نظر گرفتهاید میتوانید از یک Extension Method به نام ToBsonDocument استفاده کنید و شیء خود را به این نوع تبدیل کنید:
var options = new GridFSUploadOptions { ChunkSizeBytes = 64512, // 63KB Metadata = metaData.ToBsonDocument() };
در نهایت آن را آپلود میکنیم:
var id = bucket.UploadFromBytes("GoneWithTheWind", source, options);
نکته: اگر در یک کلاس، چند فیلد از جنس ObjectId دارید، مونگو در بین تشخیص شناسه اصلی سند و شناسه تصاویر، با توجه به نام خصوصیتها و غیره، تا حد زیادی هوشمند عمل میکند. ولی اگر خواستید صریحا شناسه اصلی را ذکر کنید و آن را متمایز از بقیه نشان دهید، میتوانید از خصوصیت BsonId در بالای نام فیلد ID استفاده کنید:
[BsonId] public ObjectId Id { get; set; }
جهت خواندن فایل آپلود شده، تنها کافی است از طریق شناسهی دریافتی در مرحلهی آپلود، اقدام نماییم:
var bytes = bucket.DownloadAsBytes(id);
نکته: تمام متدهای آپلود و دانلود دیتا، هم به صورت آرایه ای از بایتها و هم به صورت استریم میتوانند مورد استفاده قرار بگیرند و به ازای هر کدام، متدهای همزمان و غیرهمزمان نیز موجود هستند.
اگر قصد دارید بر اساس نام داده شده، فایل را دریافت کنید، ممکن است که چندین فایل، تحت یک نام ذخیره شده باشند که میتوانید در حالتهای مختلفی این تصاویر را واکشی نمایید:
0 : فایل اصلی
1: اولین نسخه فایل
2: دومین نسخه فایل
و الی آخر...
1-: جدیدترین نسخه فایل (مقدار پیش فرض)
2-: نسخه ماقبل جدیدترین نسخه فایل
و الی آخر...
منظور از نسخه، فایلهایی با نامی موجود و از قبل ذخیره شده هستند که نسخه جدیدی از فایل قبلی بوده و فایل اول، فایل اصلی محسوب میشود.
برای درک بهتر مسئله، من تصاویر زیر را به ترتیب از سمت راست به سمت چپ، تحت یک نام، وارد سیستم میکنم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db); var image=bucket.DownloadAsBytesByName("City of Glass-cover"); File.WriteAllBytes(@"D:\a.jpg",image);
برای مقداردهی خواص بالا به شکل زیر عمل میکنیم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db); var options = new GridFSDownloadByNameOptions { Revision = 0 }; var image=bucket.DownloadAsBytesByName("City of Glass-cover",options); File.WriteAllBytes(@"D:\a.jpg",image);
برای بازگردانی تصاویر از طریق مقادیر موجود در متادیتا، باید از کلاس ویژهای به نام GridFSFileInfo استفاده کنیم. در اینجا هم همانند روزهای اول، از کلاس بیلدر جهت ایجاد شرط استفاده میکنیم:
var client = new MongoClient(); var db = client.GetDatabase("publisher"); IGridFSBucket bucket = new GridFSBucket(db); var filter = Builders<GridFSFileInfo>.Filter.Gte(x => x.Length , 600); var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime); var options =new GridFSFindOptions() { Limit = 3, Sort = sort }; var cursor = bucket.Find(filter, options); var list = cursor.ToList();
برای یافتن تصاویر بر اساس متادیتاهای تعریف شده، از کد زیر استفاده میکنیم:
var filter = Builders<GridFSFileInfo>.Filter.Eq("metadata.CoverType","Front");
تغییر نام تصاویر
جهت ویرایش یک نام فایل از طریق متدهای زیر اقدام مینماییم:
bucket.Rename(id, newFilename); //یا در حالت غیرهمزمان await bucket.RenameAsync(id, newFilename);
حذف تصویر
bucket.Delete(id); //یا await bucket.DeleteAsync(id);
برای حذف کل bucket از طریق کد زیر اقدام مینماییم:
bucket.Drop(); //یا await bucket.DropAsync();
dotnet new console -o MyFirstWasiApp cd MyFirstWasiApp dotnet add package Wasi.Sdk --prerelease dotnet build
wasmtime bin/Debug/net7.0/MyFirstWasiApp.wasm
FROM scratch COPY bin/Debug/net7.0/MyFirstWasiApp.wasm MyFirstWasiApp.wasm ENTRYPOINT [ "MyFirstWasiApp.wasm" ]
docker buildx build . --file=Dockerfile --tag=dotnet-webassembly --platform wasi/wasm32
docker run --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 dotnet-webassembly
دقت داشته باشید که این فیچر هنوز در مرحله Beta قرار دارد؛ و ممکن است در حین تهیه ایمیج با edge caseهای روبرو شوید به عنوان مثال من سعی کردم یک وبسرور ASP.NET Core (توسط Wasi.Sdk این امکان وجود دارد) را containerised کنم که در نهایت با خطای زیر مواجه شدم:
[error] instantiation failed: incompatible import type, Code: 0x61 Mismatched function type. Expected: FuncType {params{i32 , i32 , i32} returns{i32}} , Got: FuncType {params{i32 , i32} returns{i32}} When linking module: "wasi_snapshot_preview1" , function name: "sock_accept" At AST node: import description At AST node: import section At AST node: module