مطالب
Minimal API's در دات نت 6 - قسمت سوم - ایجاد endpoints مقدماتی
در دو قسمت قبل، ساختار ابتدایی برنامه‌ی Minimal API's بلاگ دهی را ایجاد کردیم. در این قسمت می‌خواهیم بررسی کنیم، معادل‌های کنترلرهای MVC و اکشن متدهای آن‌ها در سیستم جدید Minimal API، به چه صورتی ایجاد می‌شوند.


ایجاد اولین endpoint از نوع Get مبتنی بر Minimal API

برای افزودن اولین endpoint برنامه، به فایل Program.cs برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
// ...

app.UseHttpsRedirection();

app.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
{
    var authors = await ctx.Authors.ToListAsync();
    return authors;
});

app.Run();
app.MapGet، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpGet هستند، ارائه می‌دهد. در همینجا می‌توان آدرس دقیق این endpoint را به عنوان پارامتر اول، مشخص کرد که پس از فراخوانی آن در مرورگر، یک Delegate که هندلر نام دارد (پارامتر دوم این متد)، اجرا می‌شود تا Response ای را ارائه دهد.
همانطور که مشاهده می‌کنید می‌توان در اینجا، این Delegate را از نوع Lambda expressions تعریف کرد و با ذکر MinimalBlogDbContext به صورت یک پارامتر آن، کار تزریق وابستگی‌های خودکار آن نیز صورت می‌گیرد. شبیه به حالتی که می‌توان یک سرویس را به عنوان پارامتر یک اکشن متد، با ذکر ویژگی [FromServices] در کنترلرهای MVC معرفی کرد؛ البته در اینجا بدون نیاز به ذکر این ویژگی (هرچند هنوز هم قابل ذکر است). مزیت آن این است که هر endpoint، تنها سرویس‌های مورد نیاز خودش را دریافت می‌کند و نه یک لیست قابل توجه از تمام سرویس‌هایی که قرار است در قسمت‌های مختلف یک کنترلر استفاده شوند.
پس از آن می‌توان با Context ای که در اختیار داریم، عملیات مدنظر را پیاده سازی کرده و یک خروجی را ارائه دهیم. در اینجا دیگر نیازی به تعریف IActionResult‌ها و امثال آن نیست و همه چیز ساده شده‌است.


ایجاد اولین endpoint از نوع Post مبتنی بر Minimal API

app.MapPost، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpPost هستند، ارائه می‌دهد:
//...

app.UseHttpsRedirection();

//...

app.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) =>
{
    var author = new Author();
    author.FirstName = authorDto.FirstName;
    author.LastName = authorDto.LastName;
    author.Bio = authorDto.Bio;
    author.DateOfBirth = authorDto.DateOfBirth;

    ctx.Authors.Add(author);
    await ctx.SaveChangesAsync();

    return author;
});

app.Run();

internal record AuthorDto(string FirstName, string LastName, DateTime DateOfBirth, string? Bio);
در ابتدا یک Dto را که حاوی اطلاعات نویسنده‌ی جدیدی است، معادل خواص مدل Author دومین برنامه، تعریف می‌کنیم. سپس می‌توان این Dto را نیز به صورت یک پارامتر جدید به Lambda Expression متد app.MapPost معرفی کرد تا کار نگاشت اطلاعات دریافتی به آن، به صورت خودکار انجام شود (حالت پیش‌فرض آن [FromBody] است که نیازی به ذکر آن نیست).
سعی شده‌است تا این مثال در ساده‌ترین شکل ممکن خودش ارائه شود. در ادامه کار نگاشت خواص Dto را به مدل دومین برنامه، توسط AutoMapper انجام خواهیم داد.
مابقی نکات متد app.MapPost نیز مانند متد app.MapGet است؛ برای مثال در اینجا نیز تعریف مسیر endpoint، توسط اولین پارامتر این متد صورت می‌گیرد و نحوه‌ی تزریق سرویس DbContext برنامه نیز یکی است.


آزمایش برنامه‌ی Minimal API's

برنامه‌ی Minimal API's تهیه شده، به همراه یک Swagger از پیش تنظیم شده نیز هست. به همین جهت برای کار با این API الزاما نیازی به استفاده‌ی از مثلا برنامه‌ی Postman یا راه حل‌های مشابه نیست. بنابراین فقط کافی است تا برنامه‌ی API را اجرا کرده و در رابط کاربری ظاهر شده در آدرس https://localhost:7085/swagger/index.html، بر روی دکمه‌ی Try it out هر کدام از endpointها کلیک کنیم. برای مثال اگر چنین کاری را در قسمت Post انجام دهیم، به تصویر زیر می‌رسیم:



در اینجا پس از ویرایش اطلاعات شیء JSON ای که برای ما تدارک دیده‌است، فقط کافی است بر روی دکمه‌ی execute ذیل آن کلیک کنیم تا اطلاعات این Dto را به app.MapPost متناظر فوق ارسال کند و برای نمونه خروجی بازگشتی از سرور را نیز در همینجا نمایش می‌دهد که در آن، Id رکورد نیز پس از ثبت در بانک اطلاعاتی، مشخص است:



شروع به Refactoring و خلوت کردن فایل Program.cs

اگر بخواهیم به همین نحو تمام endpoints و dtoها را داخل فایل Program.cs اضافه کنیم، پس از مدتی به یک فایل بسیار حجیم و غیرقابل نگهداری خواهیم رسید. بنابراین در مرحله‌ی اول، تنظیمات سرویس‌ها و میان افزارها را به خارج از آن منتقل می‌کنیم. برای این منظور پوشه‌ی جدید Extensions را به همراه دو کلاس زیر ایجاد می‌کنیم:
using Microsoft.EntityFrameworkCore;
using MinimalBlog.Dal;

namespace MinimalBlog.Api.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var connectionString = builder.Configuration.GetConnectionString("Default");
        builder.Services.AddDbContext<MinimalBlogDbContext>(opt => opt.UseSqlServer(connectionString));

        return services;
    }
}
کار این متد الحاقی، خارج کردن تنظیمات سرویس‌های برنامه از کلاس Program است.

همچنین نیاز به متد الحاقی دیگری برای خارج کردن تنظیمات میان‌افزارها داریم:
namespace MinimalBlog.Api.Extensions;

public static class WebApplicationExtensions
{
    public static WebApplication ConfigureApplication(this WebApplication app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        return app;
    }
}
پس از این تغییرات، اکنون ابتدای کلاس Program برنامه‌ی Api به صورت زیر تغییر می‌کند و خلاصه می‌شود:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationServices(builder);

var app = builder.Build();
app.ConfigureApplication();

در قسمت بعد، endpoints را از این کلاس آغازین برنامه خارج می‌کنیم.
مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت اول
در این سلسله مقالات قصد دارم چندین مطلب راجع به افزایش سرعت نرم افزارهای تحت وب مطرح نمایم. این مطالب هرچند بسیار مختصر می‌باشند ولی در کارایی و سرعت برنامه‌های شما در آینده تاثیر خواهند داشت.
1.کش کردن همیشه آخرین حربه می‌باشد
این مهم است که بخش‌های مختلف سایت شما در سطوح مختلف کش شوند (ASP.NET, Kernel, Server, Proxy Server, Browser ,...) ولی این موضوع باید همیشه آخرین حربه و نکته ای باشد که آن را در مورد سایت خود اعمال می‌کنید.
یعنی همیشه مطمئن شوید ابتدا تمامی نکات مربوط به افزایش کارایی در برنامه خود را رعایت کرده اید، سپس اقدام به کش داده‌ها در سطوح مختلف نمایید. توجه کنید کش کردن داده‌ها و صفحات می‌تواند مشکلات را برای شما به عنوان یک برنامه نویس یا تست کننده برنامه پنهان کند و به شما اطمینان دهد که همه چیز خوب کار می‌کند در حالی که این چنین نیست!
البته ذکر این نکته هم بی فایده نیست که کش کردن همه چیز بعضی مواقع دشمن برنامه شما محسوب می‌شود! هیچ وقت یادم نمی‌رود، در پورتال داخلی یک شرکت که در وقت استراحت به کارکنان اجازه مطالعه روزنامه‌های روز را می‌داد (به صورت آفلاین)، این نکته در بالای صفحه آورده شده بود: «لطفا برای به روز رساندن صفحات روزنامه‌ها از کلید Ctrl+F5 استفاده نمایید». این موضوع یعنی بحث کشینگ در برنامه آن پرتال در سطح فاجعه می‌باشد! حالا فرض کنید این مشکل در فرم ورود و یا مرور اطلاعات یک برنامه به وجود آید...
2.حذف View Engine‌های غیر ضروری
به عنوان یک برنامه نویس ASP.NET MVC، یابد اطلاع داشته باشید که CLR به صورت خودکار View Engine‌های Razor و Web Forms را لود می‌کند. این موضوع به این دلیل است که اطلاعی از نحوه برنامه نویسی شما ندارد. اگر شما فقط از یکی از این دو View Engine استفاده می‌کنید،لطفا دیگری را غیر فعال کنید! فعال بودن هر دوی آنها یعنی اتلاف وقت گرانبهای CPU سرور شما برای رندر کردن تمامی صفحات شما توسط دو انجین! ایتدا view‌های شما با Web Forms Engine رندر شده سپس نتیجه به  Razor Engine منتقل شده و مجدد توسط این انجین رندر می‌شود. این موضوع در سایت‌های با تعداد کاربر بالا یعنی فاجعه!
برای حل این مشکل کافی است خطوط زیر را در فایل Global.asax و در رویداد بخش Application_Start وارد نمایید:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
این دو خط یعنی خداحافظ Web Forms Engine...
قبل از استفاده از این کد، اطمینان حاصل کنید کل برنامه شما توسط Razor تهیه شده است وگرنه بنده هیچ مسئولیتی در رابطه با فریادهای کارفرمای شما متقبل نمی‌شوم!
صد البته برای حذف Razor Engine و استفاده از Web Form Engine می‌توان از کد زیر در همان موقعیت فوق استفاده کرد:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new WebFormViewEngine());
البته همانطور که حتما دوستان مطلع هستند امکان گسترش Engine‌های فوق توسط ارث بری از کلاس BuildManagerViewEngine جهت ایجاد Engine‌های دیگر همیشه محیا است. در این صورت می‌توانید تنها انجین سفارشی مورد نظر خود را لود کرده و از لود دیگر انجین‌ها پرهیز کنید. 
3. استفاده از فایل‌های PDB مایکروسافت برای دیباگ و یا پروفایل کردن DLL‌های دیگران  
دیباگ یا پروفایل کردن برنامه ها، DLL ها، اسمبلی‌ها و منابعی از برنامه که شما آن را خود ننوشته اید (سورس آنها در دسترس شما نمی‌باشد) همیشه یکی از سخت‌ترین مراحل کار می‌باشد. جهت کمک به دیباگر‌ها یا پروفایلرها، نیاز است فایل‌های PDB مرتبط با DLL‌ها را در اختیار آنها قرار دهید تا به بهترین نتیجه دسترسی پیدا کنید. این فایل‌ها محتوی نام توابع، شماره خطوط برنامه و metadata‌های دیگر برنامه اصلی قبل از optimize شدن توسط کامپایلر یا JIT می‌باشد.  خوب حالا اگر نیاز شد این کار را در رابطه با DLL‌ها و کلاس‌های پایه Microsoft.NET انجام دهیم چه کار کنیم؟
خیلی ساده! خود Microsoft سروری جهت این موضوع تدارک دیده که فایل‌های PDB را جهت دیباگ کردن در اختیار تیم‌های برنامه نویسی قرار می‌دهد.کافی است از منوی Tools گزینه Options را انتخاب، سپس به بخش Debugging و به بخش Symbols بروید و گزینه Microsoft Symbol Servers as your source for Symbols را انتخاب کنید. برای اطمینان از اینکه هر مرتبه که برنامه را دیباگ می‌کنید مجبور به دانلود این فایل‌ها نشوید، فراموش نکنید پوشه ای را جهت کش این فایل‌ها ایجاد و آدرس آن را در بخش Cache symbols in this directory همین صفحه وارد نمایید.
این امکان در Visual Studio 2010, 2012 در دسترس می‌باشد.
اشتراک‌ها
چگونه یک پیاده سازی واقعی به سبک Micro Frontends داشته باشیم - قسمت اول

امروزه توسعه برنامه‌های کاربردی بر پایه وب دنیای جدیدی رو تجربه می‌کند. علاوه بر ظهور فریم ورک‌ها و کتابخانه‌های متفاوت و قدرتمند در این حوزه، به نظر من نکته ای که حرکت به سمت این دنیای جدید را بیشتر نشان می‌دهد، ظهور و بروز مباحث معماری در حوزه فرانت اند است. مباحثی که هنوز هم شاید از دید بعضی‌ها فقط مربوط به حوزه بک اند هستند.

یکی از مهمترین مباحثی که این روزه در این حوزه مطرح می‌شود موضوع Micro frontends هست. تعریف‌های متفاوتی برای Micro frontends وجود دارد؛ اما شاید یکی از بهترین تعاریف، این تعریف باشد :


"An architectural style where independently deliverable frontend applications are composed into a greater whole"
(https://martinfowler.com/articles/micro-frontends.html) 
چگونه یک پیاده سازی واقعی به سبک Micro Frontends داشته باشیم - قسمت اول
نظرات مطالب
شروع کار با ASP.NET Web API 2
آیا میشه از اندروید و محیط‌های غیر NET. (مثل دلفی ) با سرویس‌های نوشته شده با  Web API  ارتباط برقرار کرد ؟
نظرات مطالب
AngularJS #1
- AngularJS یک فریم ورک سمت کلاینتی به زبان جاوا اسکریپت است و ASP.NET MVC یک فریم ورک سمت سرور و این دو نمی‌توانند جای یکدیگر را پر کنند و ربطی به هم ندارند.
- در اینجا View‌های شما یک HTML ساده است و مفاهیم Strongly Typed View هیچ معنی خاصی ندارد. Model در انگولار یک کلاس جاوا اسکریپتی ساده (POJO) است. view هم یک فایل HTML که قالب کار را مشخص می‌کند و Controller هم باز یک کلاس ساده‌ی جاوا اسکریپت است.

- اتقاقا یک بار همچنین چیزی را از من خواستند (بدون انگولار) و من انجامش دادم. البته این طراحی اشتباه است. آیا شما ده بخش را در چند TAB مختلف باز می‌کنید، آدرس منحصر به فردی هم در نوار آدرس مرورگر برای آن‌ها در نظر گرفته اید؟
این کار در آن زمان که همه‌ی برنامه با یک URL کار می‌کرد خیلی عالی بود، ولی اکنون که شما می‌توانید برای View یک آدرس منحصر به فرد داشته باشید و آن‌ها را Tab خود مرورگر مدریت کنید، احتیاجی به آن کار‌ها نیست.
به هر حال، در انگولار این جمله بی معنی است که؛ بین فایل‌های جاوا اسکریپت تداخل ایجاد شود!
در انگولار هر View دارای Controller متناظر خود است. اعمال تجاری مشترک هم در سرویس‌ها تعریف می‌شوند و در کنترلرها تزریق می‌شوند. پس کد جاوا اسکریپتی روی هوا نوشته نمی‌شود(حتی یک خط)

صبر کنید تا کمی پیش برویم. این فقط مقدمه بود...
نظرات مطالب
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS
با سلام؛
مشکلی که من دارم نمیدانم مربوط میشود به مدیریت حافظه یا موضوعی دیگر
من یک وب سایت کوچک دارم که با تکنولوژی‌های زیر ایجاد شده:
ASP.Net MVC 4,  Entity Framework 4 , SQL CE
آن را بر روی یک ویندوز سرور 2012 نسخه دیتاسنتر نصب کردم
سرور : 2GB Ram و CPU Dual Core 1.8 
غیر از این سایت هیچ سایت دیگری بر روی این سرور میزبانی نشده است.
صفحات با سرعت نسبتاً خوبی باز میشوند، اما به هر شکلی iis را تنظیم میکنم، اگر پس از 2 یا 3 دقیقه درخواستی به سمت سرور ارسال نگردد، برنامه از حافظه خارج میشود. اگر درخواستی برای مشاهده صفحه به سرور ارسال شود 15 تا 20 ثانیه طول میکشد تا دوباره کامپایل انجام شود و صفحه درخواستی نمایش یابد.
تصویر تنظیمات Application Pool

پ.ن: لطفاً اگر امکان دارد بهترین تنظیمات را برای سروری که فقط به یک سایت میخواهد سرویس دهد عنوان کنید.
مطالب
تنظیمات امنیتی SMTP Server متعلق به IIS 6.0 جهت قرارگیری بر روی اینترنت

فرض کنید یک سرور را بر روی اینترنت قرار داده‌اید و از SMTP Server متعلق به IIS قصد دارید جهت ارسال ایمیل توسط برنامه‌های خود استفاده نمائید. در این حالت مواردی را باید رعایت نمود تا این سرور تبدیل به سرور رایگان ارسال spam توسط "دشمنان" نشود.



1- پورت پیش فرض را عوض کنید
پورت پیش فرض اتصال به SMTP Server مساوی 25 است. از آنجائیکه به سادگی در برنامه‌های خود می‌توان این پورت را نیز تنظیم نمود، بهتر است به عنوان اولین قدم، این پورت را تغییر داد. یک شماره پورت دلخواه خالی را یافته و بجای 25 قرار دهید. برای این منظور مسیر زیر را طی کنید:
بر روی Default SMTP Virtual Server در کنسول IIS کلیک راست کرده و گزینه خواص را انتخاب کنید. در برگه General روی دکمه Advanced کلیک کرده و در صفحه باز شده سطر مربوط به پورت 25 را یافته، بر روی دکمه Edit کلیک نموده و آن‌را به عددی دیگر تغییر دهید.



2- دسترسی عموم را به سرور قطع کنید!
متاسفانه تنظیمات پیش فرض SMTP Server متعلق به IIS در جهت قطع دسترسی "دشمنان" کاملا نادرست بوده و بر مبنای ایده حداقل دسترسی صورت نگرفته‌ است. اگر سرور را به این حال رها کنید فقط "دشمنان" را خوشحال کرده‌اید.
برای قطع دسترسی دشمنان سه مرحله باید صورت گیرد:
الف) در برگه Access مربوط به تنظیمات SMTP server ، روی دکمه relay کلیک کرده، ابتدا تیک مربوط به Allow all computers which successfully authenticated to relay‌ را بردارید (این مورد در یک شبکه داخلی حائز اهمیت می‌شود و سایر کامپیوترها را منع می‌کند). سپس در قسمت بالای صفحه گزینه only the list below را انتخاب کرده و IP آن‌را مساوی 127.0.0.1 وارد کنید (یعنی فقط این کامپیوتر مجاز است که از این سرویس جهت ارسال ایمیل استفاده کند؛ نه دشمنان خارجی).



ب) مورد الف را درباره‌ی قسمت مرتبط با دکمه connections نیز تکرار کنید. (پیش فرض آن تمام عالم است!)



ج) در همین برگه‌ی Access بر روی دکمه Authentication کلیک کرده و فقط تیک مربوط به integrated windows authentication را قرار دهید. (همیشه تحت ویندوز این روش authentication یکی از امن‌ترین‌ها است. همچنین در حالت قرارگیری سرور بر روی اینترنت سخت گیرانه‌ترین حالت ممکن را در اینجا انتخاب کرده‌ایم.)



خوب، با این تنظیم قسمت (ج) دیگر برنامه‌ها با روش متداول قابل به ارسال ایمیل نخواهند بود. یک یوزر معمولی local را به کامپیوتر افزوده (با حداقل دسترسی) و پسورد آن‌را در حالت never expires قرار دهید. از این یوزر ویندوزی جهت برقراری امکان اتصال به میل سرور محلی در برنامه‌های خود استفاده خواهیم کرد (فرض بر این است که برنامه‌ای هم که قرار است ایمیل ارسال کند بر روی همان کامپیوتر سرور قرار دارد).

پس از اعمال این تنظیمات بر روی دکمه apply کلیک کنید، تا تنظیمات اعمال شوند. یکبار نیز میل سرور را استاپ و استارت کنید.

3- تنظیمات ویژه برنامه‌ها برای ارسال ایمیل:
در این حالت برنامه‌های دات نت شما نیاز به چهار تنظیم اضافه‌تر پیش از فراخوانی تابع Send دارند:

MailMessage message = new MailMessage("from@site.com", strTo, subject, body)
{
IsBodyHtml = true,
BodyEncoding = Encoding.UTF8
};

SmtpClient client = new SmtpClient("127.0.0.1",portNumber);//portNumber is new
client.UseDefaultCredentials = false; //new
client.DeliveryMethod = SmtpDeliveryMethod.Network; //new
client.Credentials = new NetworkCredential("mail_user", "pass"); //new
client.Send(message);

همانطور که ملاحظه می‌کنید باید شماره پورت جدید را معرفی نمود، همچنین روش authentication و معرفی مشخصات یوزر ویندوزی که اضافه کردیم را نیز نباید فراموش کرد.

4- تمامی رخ‌دادهای میل سرور را ثبت کنید.
برای این منظور در برگه general ، تیک مربوط به enable logging را فعال کنید. سپس بر روی دکمه خواص کنار آن کلیک کرده و در صفحه باز شده به برگه extended properties مراجعه نموده و تمامی موارد را تیک بزنید. به ازای هر یک روز فعالیت سرور، یک فایل متنی در مسیر C:\WINDOWS\System32\LogFiles تشکیل خواهد شد.


سؤال چگونه تشخیص دهم که میل سرور من هک شده است یا خیر؟!
اگر موارد فوق را رعایت نکنید، در قسمت current sessions کنسول IIS می‌توانید "دشمنان" را مشاهده کنید! همچنین مصرف CPU پروسه inetinfo.exe عملکرد سرور را مختل کرده، بعلاوه در مسیر C:\Inetpub\mailroot\Queue احتمالا چند هزار ایمیل درصف قرار گرفته شده برای ارسال را می‌توانید مشاهده کنید! (همینطور در مسیر C:\Inetpub\mailroot\Badmail نیز این تعرض قابل مشاهده است)
اگر این موارد را مشاهده کردید، ابتدا سرور را استاپ کنید، سپس محتویات پوشه‌های یاد شده را تخلیه کرده و از مرحله یک فوق شروع به اعمال تنظیمات نمائید.


نظرات مطالب
ASP.NET MVC #2
در متن در تعریف MVC گفته بودید :
 هدف اصلی آن جدا سازی مسئولیت‌های اجزای تشکیل دهنده «لایه نمایشی» برنامه است.  
یعنی ساختار سه لایه ای که مثلا در پروژه‌های ویندوز فرمی داشتیم مثل domainModel برای تعریف اشیاء و یا Business layer برای کنترل بر روی عملیات منطقی بر روی اشیاء  و یا Data Layer که بمنظور کار با بانک اطلاعاتی بود مثل قبل وجود دارند و MVC  تنها لایه Presentation رو باز مهندسی میکنه؟
و اینکه Model  موجود در MVC مجزا از تعریف لایه DomainModel مرسوم در معماری سه لایه هست؟
نظرات مطالب
WF:Windows Workflow #۴

بله مشکل از همینجا بود ممنون

و یک سوال : فرض بر اینکه ما یک برنامه حسابداری داریم که به چند شرکت مختلف فروختیم هر شرکت روش کاره خودش رو برای کار داره مثلا یه شرکت یک سند حسابداری مستقیم به دست حسابدار میرسونه ولی یه شرکت دیگه اول به دست رییس مربوط میرسونه و در صورت تایید به دست حسابدار امکان نوشتن workflow وجود داره که خود کاربر نهایی بتونه یه همچین چیزیو تنظیم کنه بنویسه و به راحتی ازش استفاده کنه
و اینکه از workflow میشه تو معماری چند لایه استفاده کرد؟
و این ترجمه کتاب beginning wf از apressنیست ؟
ولی در کل از این مطالبتون بسیار ممنون مرجع فارسی از workflow میشه گفت اصلا وجود نداره