Back to "الحفاظ على الدولة بين الطلبات الواردة في نظام المعلومات المسبقة عن البيانات: الأساس: دليل عملي، لا ينغني عن المعلومات (MVC، شاشات الرازز، الحد الأدنى من البوليسات (APIS))"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

AI-Article ASP.NET ASP.NET Core State Web Development

الحفاظ على الدولة بين الطلبات الواردة في نظام المعلومات المسبقة عن البيانات: الأساس: دليل عملي، لا ينغني عن المعلومات (MVC، شاشات الرازز، الحد الأدنى من البوليسات (APIS))

Sunday, 09 November 2025

**)٢٢٥-١١-١٠(**يضاف المزيد من الأمثلة العملية من قاعدة شفرتي الفعلية التي تبين أنماط العالم الحقيقي، والمقايضات، والوصايا، والتطور من نُهُج بسيطة إلى نُهُج متطوِّرة لإدارة الدولة. بما في ذلك الأنماط المفصَّلة ImoryCache، واستخدام ViewBag (الجيد والسيء)، واستراتيجيات الاستجابة لـCache/exputtsCache، والدروس المستفادة من الإنتاج.

أولاً

HTTP هو مشهور عديم الجنسية. التطبيق الخاص بك... ليس. المستخدمون يوقعون في، يضيفون عناصر إلى السلال، يقفزون بين الصفحات، يعودون غدا، ويتوقعون منك أن تتذكر. في ASP.Net Corre (MVC, Razor pages, APIs) هناك الكثير من الطرق للحفاظ على حالة النقل بين الطلبات. بعضها هو لكل طلب فقط. البعض آخر جلسة. البعض يعيش في العميل. البعض منهم موزعون وينجو من عمليات إعادة تشغيل الخوادم. كل خيار له مقايضة بين الأمن، الأداء، الحجم، والمطور.

هذه الوظيفة تصنف الخيارات، تظهر الخرسانة، نسخ الأمثلة القابلة للشفاء في كل الأكوام الثلاثة، وتعطيك توجيه القرار حتى تتمكن من اختيار الأداة الصحيحة للوظيفة.

ملاحظة: هذا جزء من تجاربي مع AI (يساعد في الصياغة) + تحريري الخاص. نفس الصوت، نفس العملية العملية؛ مجرد أصابع أسرع.

المنظر الظاهري عند مَحَلَة

flowchart LR
  subgraph Client
    Q[Query String]
    R[Route Values]
    H[Headers]
    F[Form/Hidden Fields]
    CK[Cookies]
    LS[Local/Session Storage]
  end
  subgraph Server
    I[HttpContext.Items<br/>\nper request only]
    TD[TempData<br/>\none redirect]
    SS[Session]
    MC[IMemoryCache]
    DC[IDistributedCache]
    AU[Auth Cookie / Claims]
    JT[JWT / Bearer]
    DB[Database / Durable Store]
    BUS[Outbox / Queue]
  end
  Q --> Model[Model Binding]
  R --> Model
  F --> Model
  H --> Model
  CK --> App[Your Code]
  Model --> App
  App -->|Set| CK
  App -->|Set| TD
  App -->|Set| SS
  App -->|Set| MC
  App -->|Set| DC
  App -->|Issue| AU
  App -->|Issue| JT
  App -->|Persist| DB
  • يُحمل العميل: استعلام، مسار، ترويسات، استمارات، بسكويت، JWT. مقياس أفقي ولكنه مرئي للعميل (يجب أن يكون مصدقا عليه/موقّعا/مشفّرا حسب الاقتضاء).
  • الخواديم: TMDATAT، الدورة، المخابئ، DB. تتطلب التلاقي أو استراتيجية التوزيع.
  • لكل طلب: HttpConstex.Atems - مفيد لنقل البيانات داخلياً أثناء طلب واحد.

القواعد الذهبية (قبل أن ندخل في سياسات الحد من الانبعاثات)

  1. الأمن أولاً: إن تسرب الدولة يشكل تهديداً حقيقياً. قد تتسرب حالة الخوادم بين المستخدمين من خلال أخطاء الترميز، أو ظروف العرق، أو إدارة دورة الحياة بشكل غير صحيح. إن الأنماط عديمة الجنسية ليست أكثر قابلية للقياس فحسب، بل إنها أكثر أمناً.
  2. يفضل أنماط "الخادوم غير القابل للتثبيت" عندما تحتاج إلى التدرج أفقياً (الحالة إلى العميل الرمزي، أو المخازن المعمرة، أو المخابئ الموزعة).
  3. لا تثق أبداً بالدولة التي يسيطر عليها العميل، المصادقة، التوقيع، و/أو التشفير.
  4. احفظ بيانات كبيرة من الكعك والترويسات؛ إنها تنفخ في كل طلب.
  5. استخدام TMData فقط لـ ما بعد الإستعادة المباشرة (PRG) طلقة واحدة مثل رسائل الوميض.
  6. استخدم الجلسة فقط عندما يجب أن تحافظ على حالة محادثة جانب الخادم و تخطط للتوزيع و تكون مذعوراً بشأن الأمن
  7. المطالبات هي عن الهوية والترخيص المؤلّف من الخزائن، وليس حالة التطبيق العام.
  8. (كاشي) ليس مصدراً للحقيقة، إدعمه بمخزن دائم إن كانت البيانات مهمة.

الدولة الموجه إليها الطلب: HtttpContex.Ims

  • النطاق: الطلب الحالي فقط (الحد عند نهاية خط الأنابيب)
  • الاستخدام: القيم المحسوبة التي تمر بين الأدوات الوسيطة ونقاط النهاية/متحكمي التحكم
  • أولاً - جدول الأعمال: لا أثر
  • الأمن: خامد- فقط
sequenceDiagram
  participant M as Middleware
  participant E as Endpoint/Controller
  M->>M: Compute TenantId
  M->>E: HttpContext.Items["TenantId"] = 42
  E->>E: Read Items["TenantId"]

المثال الأوسطواري (كُلّ الكُشَف):

app.Use(async (context, next) =>
{
    var tenantId = context.Request.Headers["X-TenantId"].FirstOrDefault() ?? "public";
    context.Items["TenantId"] = tenantId;
    await next(context);
});
  • نقطة نهاية الحد الأدنى لمؤشر الحد الأدنى:
app.MapGet("/whoami", (HttpContext ctx) => new { Tenant = ctx.Items["TenantId"] });
  • المراقب المالي عن MVC:
public IActionResult WhoAmI() => Json(new { Tenant = HttpContext.Items["TenantId"] });
  • مُبيّض المُبيّض مُعالج:
public IActionResult OnGet() => new JsonResult(new { Tenant = HttpContext.Items["TenantId"] });

الدولة التي يُقصد بها عملاء: قيم الطريق وسلسلة الأسئلة

  • النطاق: الطلب الحالي؛ والعميل يحمله صراحة.
  • الاستخدام لأغراض: سياق الملاحة، والتصفية، والحرف، وهوية المورد
  • :: الأمن: يجب أن يصادق/يأذن به؛ لا تغرس الأسرار

أمثلة أمثلة

  • الحد الأدنى AP:
app.MapGet("/orders/{id:int}", (int id, int? page) => Results.Ok(new { id, page }));
// GET /orders/5?page=2
  • MMVC:
[HttpGet("/orders/{id:int}")]
public IActionResult Details(int id, int? page)
  => View(new { id, page });
  • صحائف الرازض (الخطوط/التفاصيل.cshtml.cs):
public IActionResult OnGet(int id, int? page)
  => Page();

يُميّز الوصلات التي تُبقي على الحالة:

// Razor Pages
<a asp-page="/Orders/Details" asp-route-id="@Model.Id" asp-route-page="@Model.Page">Next</a>

// MVC
@Html.ActionLink("Next", "Details", "Orders", new { id = Model.Id, page = Model.Page }, null)

ترويسات: هويات الارتباط، مفاتيح عدم القدرة، أعلام الميزات

  • النطاق: الطلب الحالي، يرد صداً اختيارياً في الردود
  • الاستخدام لأغراض: التعقب، إعادة السلامة، أعلام A/B
  • :: الأمن: يُعامل على أنه مدخل غير موثوق به، مصادقة/قائمة بيضاء
app.Use(async (ctx, next) =>
{
    var correlationId = ctx.Request.Headers["X-Correlation-Id"].FirstOrDefault()
                      ?? Guid.NewGuid().ToString("n");
    ctx.Response.Headers["X-Correlation-Id"] = correlationId;
    await next(ctx);
});

عدم الأهلية (الشكل الأول):

flowchart TD
  C[Client POST /pay\nIdempotency-Key:k] --> S{Seen k?}
  S -- No --> E[Execute charge]
  E --> P[Persist result by k]
  P --> R[Return 200 + result]
  S -- Yes --> L[Load result by k]
  L --> R

والصور والمحفوظات والمحفوظات

  • النطاق: الطلب التالي فقط (الوظائف العميلة المسترجعة للقيم)
  • استخدام لـ: خطوات مُيسِر ، غير مُضضدّ مُثبِط صغير من حالة أداء PRG
  • الأمن: التحقق دائماً؛ والجمع مع المكافحة

نمط PRG في صفحات MVC/Razor:

sequenceDiagram
  participant U as User
  participant P as POST Action
  participant R as Redirect
  participant G as GET Action
  U->>P: POST form
  P-->>R: 302 Redirect
  U->>G: GET redirected
  G-->>U: Final page (no resubmits)
  • MMVC:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Save(SettingsModel model)
{
    // validate & persist
    return RedirectToAction(nameof(Summary), new { tab = model.SelectedTab });
}
  • صفحات الميزات:
public IActionResult OnPost(SettingsModel model)
{
    return RedirectToPage("/Settings/Summary", new { tab = model.SelectedTab });
}

صغيرة، موقعة، مشفرة في بعض الأحيان

  • النطاق: كل طلب من المتصفح حتى انتهاء صلاحيته
  • الاستخدام للأفضليات، والأعلام غير الحساسة، والرضا؛ وبسكويت الآوث (الفرع المنفصل)
  • المقايضات: يجب أن تمتثل حدود الحجم (~4KKB لكل كعكة)، وأثر الأداء، لقوانين الموافقة

المثال:

app.MapPost("/prefs/theme/{value}", (HttpContext ctx, string value) =>
{
    ctx.Response.Cookies.Append("theme", value, new CookieOptions
    {
        HttpOnly = false,
        Secure = true,
        SameSite = SameSiteMode.Lax,
        Expires = DateTimeOffset.UtcNow.AddYears(1)
    });
    return Results.Ok();
});

app.MapGet("/prefs/theme", (HttpContext ctx)
  => Results.Text(ctx.Request.Cookies["theme"] ?? "system"));

استخدام صفحات MVC/Razor متطابق عبر HttpContext.

بالنسبة للسلامة/السرية، استخدام نظام حماية البيانات الأساسية ASP.net لحماية الحمولات التي تضعها في الكعك بنفسك.


TMTDDATAT: ناقلة رسائل غير مباشرة واحدة

  • النطاق: قادر على إعادة توجيه واحد
  • مخزن المؤكِّن: الكعكة (القصيرة) أو الدورة
  • لـ: رسائل الوميض، وملخصات المصادقة بعد PRG

إعداد (البرامج:

builder.Services.AddControllersWithViews().AddSessionStateTempDataProvider(); // optional
builder.Services.AddSession();
var app = builder.Build();
app.UseSession();

في متحكم MVC:

TempData["StatusMessage"] = "Saved!";
return RedirectToAction("Index");

في مُعالج مُحَسِّز الصفحة:

TempData["StatusMessage"] = "Saved!";
return RedirectToPage("/Index");

وفي رأي/صفحة:

@if (TempData["StatusMessage"] is string msg) {
  <div class="alert alert-success">@msg</div>
}
flowchart LR
  A[POST /save] -->|TempData set| B[302 Redirect]
  B --> C[GET /index]
  C -->|TempData read consumed| D[Render message]

الدورة: الدولة التي تُعقد فيها مُحادثة

  • النطاق: دورة متصفح (مفتاح المزامي + مخزن الخادوم)
  • للاستخدام: المعالجات المتعددة الخطوات، بيانات العربات الصغيرة، معاوض الاختزال
  • المقايضات: تحتاج إلى دورات لزجة أو إلى مخزن دعم موزع؛ يمكن أن تحد من النطاق
builder.Services.AddDistributedMemoryCache(); // or AddStackExchangeRedisCache
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(20);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});
var app = builder.Build();
app.UseSession();

ا_ستخدَم الجلسة (أي منها):

app.MapPost("/cart/add/{id:int}", (HttpContext ctx, int id) =>
{
    var key = "cart";
    var bytes = ctx.Session.Get(key);
    var list = bytes is null ? new List<int>() : System.Text.Json.JsonSerializer.Deserialize<List<int>>(bytes)!;
    list.Add(id);
    ctx.Session.Set(key, System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(list));
    return Results.Ok(list);
});

مساعدي الجلسات:

public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
      => session.SetString(key, System.Text.Json.JsonSerializer.Serialize(value));

    public static T? Get<T>(this ISession session, string key)
      => session.TryGetValue(key, out var data)
         ? System.Text.Json.JsonSerializer.Deserialize<T>(data)
         : default;
}

حكاية تحذيرية: عندما تصبح حالة الجلسة عنق الزجاجة

ذات مرة عملت على مشروع ضخم لتكنولوجيا المعلومات لحكومة المملكة المتحدة حيث أصبح سوء استخدام الدولة في الجلسة (بين العديد من خطايا معمارية أخرى) " كل شيء " (أ) أفضليات المستعملين، وبيانات الشكل المتعدد الخطوات، ونتائج البحث، والحسابات المؤقتة، وحتى النظريات المختزنة التي كان ينبغي أن تكون في مخبأ مناسب أو قاعدة بيانات مناسبة.

المشكلة: تم تخزين حالة الجلسة أثناء العملية (ASP.Net session State in Web. config, كان هذا قبل - يوم). كان على كل طلب أن يزيل التهجئة عن كائنات الجلسات الضخمة. كلما زاد الحمولة، كانت الجلسة تُنقل إلى عشرات ميغابايت لكل مستخدم. مع آلاف المستخدمين المتزامنين، نفدت الذاكرة من الخوادم.

:الحل المُسْتَحِل: طارنا إلى منشأة HP في شتوتغارت لإجراء اختبارات تحميل على سوبردوم - في ذلك الوقت، اقوى اورو اورو اورو اورو اورو اورو اورو اقوى ماكينة ويندوزكان وحشاً: العشرات من معالجات الإيتانيوم، ومئات الجيجا بايتات من إدارة السجلات والمحفوظات، وكانت الفكرة هي إثبات أنه مع ما يكفي من الأجهزة، يمكن للنظام أن يفي بالمتطلبات.

النتيجة: حتى على السوبرDome، لم نتمكن من ضرب أهداف المستخدم المتزامنة المطلوبة. بنية الحالة الجلسة كانت معطلة بشكل أساسي. التدرج الرأسي لا يستطيع حفظ التصميم السيء. الدورة التسلسلية / التضخيم العام، بالاقتران مع ضغط الذاكرة من كائنات الجلسات الضخمة، عنى النظام ببساطة لا يستطيع التدرج ليس بأي تكلفة معقولة.

الكابوس الأمني: أكثر من مشاكل الأداء، اكتشفنا خطأ في الترميز التسرب بين المستخدمينبيانات جلسات المستخدم A تظهر في وقت ما في جلسة المستخدم B. هذا لم يكن مجرد إحراج لقد كان كارثي. المستخدمون كانوا. موظفو الخدمات الصحية الوطنية قمنا عن طريق الخطأ بإنشاء آلية لخرق حماية البيانات التي يمكن أن تكشف المعلومات الطبية الحساسة عبر مختلف دورات المهنيين في مجال الرعاية الصحية.

ما كان ينبغي أن يحدث:

  1. عديم الجنسية في حالة العجزمعظم تلك الدورة لم يكن ينبغي أبداً أن تكون البيانات موجودة
  2. قاعدة بيانات الحالة الدائمة: التقدم المحرز في الشكل المتعدد الخطوات كان ينبغي أن يكون في قاعدة البيانات مع رقم تعريف لتدفق العمل.
  3. مجلدات البحثالنظرات المشتركة تنتمي إلى IMMorioChech أو إلى مخبأ موزع
  4. جانب العميل للأفضليات: تفضيلات المستخدم يمكن أن تكون في البسكويت أو التخزين المحلي
  5. الدورة الموز توزيع الدورة إذا دعت الحاجة إلى ذلكلو كانت الدورة مطلوبة حقاً، لتقاسمت الدورة المدعومة من جديد العبء.

الدرس: ولاية الجلسة لا تتدرج عمودياً و بالكاد تتدرج أفقياً (حتى مع الجلسات اللاصقة أو المتاجر الموزعة، لا تزال تتسلسلة/تجسيد على كل طلب). أثبتت تجربة سوبردوم أن إلقاء العتاد على المشاكل المعمارية مكلف وغالباً ما يكون عقيماً.

والأهم من ذلك: حالة من الحالات التي تم فيها تحويل حشرات إلى نقاط ضعف أمنيةقضايا السلامة المحيطة، ظروف العرق، التعامل غير الصحيح مع هوية الجلسة - هذه لا تسبب مشاكل في الأداء فحسب، بل يمكنها تسريب بيانات حساسة بين المستخدمين. وفي سياق الرعاية الصحية (أو المصارف، أو أي صناعة خاضعة للتنظيم)، هذا هو كابوس الامتثال والمسؤولية الجنائية المحتملة.

المشورة الحديثة:

  • إذا وجدت نفسك في حاجة أكثر من عدد قليل من KB من بيانات الدورة، لديك على الأرجح مشكلة تصميم
  • إذا كنت تخزن البيانات الحساسة في وقت معين، كنت تخلق هجوم أمني على سطح
  • البنيات المعمارية عديمة الجنسية لا تُدرج بشكل أفضل فقط -- إنها في جوهرها أكثر أمناً لأن ليس هناك حالة خادم بجانبها لتسريب
  • إعادة النظر في استراتيجية إدارة الدولة قبل أن تحتاج إلى الطيران إلى شتوتغارت (أو شرح خرق البيانات لمفوض المعلومات)

Cacching: I cache وIMMMCACHE وHICRDCCAG

  • النطاق: عملية الخادوم (الكتيبة المتعددة الاختصاصات) أو موزعة (الكتيبة المُعَوَّلَة)
  • الاستخدام: البيانات المشتقة/المحسوبة، المعاينة، الحالة القصيرة العمر
  • المقايضات: تحويل إلى مقايضة: اختلاس المخبأ، وتسلسل التوزيع

أنا المُقْصِل:

builder.Services.AddMemoryCache();

app.MapGet("/rates", (IMemoryCache cache) =>
{
    var key = "fx:usd:eur";
    if (!cache.TryGetValue(key, out decimal rate))
    {
        rate = 0.92m; // pretend fetch
        cache.Set(key, rate, TimeSpan.FromMinutes(5));
    }
    return Results.Ok(rate);
});

(على سبيل المثال، Redis):

builder.Services.AddStackExchangeRedisCache(o => o.Configuration = "localhost:6379");

app.MapGet("/feature/{name}", async (IDistributedCache cache, string name) =>
{
    var val = await cache.GetStringAsync($"feat:{name}");
    return Results.Text(val ?? "off");
});

إنذار مضاد من طراز Cach-as-as-as-state: إذا كان يجب أن يكون مستداماً أو موثوقاً، يخزنه في قاعدة بيانات ويخبئه اختيارياً.

الإختيار بيني أنا المُتَزَمَرَرِ بين كَيْكِهِ و الهويّة المُقَرّرُ المُتَخَصّفُ

  • أنا المُقْصِل:
    • وتبقى الأجسام المتجمدة بسرعة، وفي طور التشغيل، كأجسام (بدون تسلسل).
    • المسح بواسطة ضغط الذاكرة، حد الحجم، انتهاء الصلاحية المطلقة/التزلجية، والأولوية.
    • غير مشتركة بين العُقد؛ وتُبرأ على أساس التطبيق/إعادة التدوير/النشر.
    • عظيم لكل نوتة من النوتات الساخنة، النظرات المحسوبة، قصير TTLs.
  • (Redis/SQL/etc.):
    • المشاركة عبر مزرعة؛ البقاء على قيد الحياة من جديد التطبيق؛ يتطلب التسلسل (السلسلات/السمات).
    • (أ) تأخير أعلى إلى حد ما؛ يعتمد الإنتاج على الشبكة والخلفية.
    • يدعم انتهاء الصلاحية المطلقة/الانزلاقية (معتمدة على مقدِّم العرض؛ ويستكمل مورِّد ريديس TLL بشأن إمكانية الوصول إلى الإنزلاق).
    • مُثل مثالي للاتساق بين الشُعب، وقراءات مروحة كبيرة، وأعلام مميزة، وجلسة.

استراتيجيات المخبأات المشتركة

  • المخبأ - الجانب (الأكثر شيوعاً):

    1. جرب المخبأ; 2 إذا خطأ التحميل من المصدر; 3 اكتب إلى المخبأ; أعد.
    • Pros: بسيط؛ مصدر الحقيقة يبقى قاعدة البيانات.
    • الشروط: يكون الطلب الأول بعد الانتهاء بطيئاً؛ ويمكن الحصول على ختم.
  • قراءتها (عن طريق مكتبة/مزود): مقابض المخبأة تُحمّل على الأخطاء.

  • الكتابة من خلال: الكتابة تذهب إلى المخبأ و دعم المخزن متزامناً.

  • الكتابة من بعد: الكتابة إلى المخبأ، والتدفق إلى التخزين بشكل غير متزامن (الخطر: الخسارة/عدم الاتساق).

  • تحديث المفاتيح الساخنة قبل أن تنتهي لتجنّب الأخطاء الباردة.

الانهاء والإخلاء والمساحة

  • تاريخ انتهاء الصلاحية: ينتهي دائماً بعد مدة محددة (السلع بالنسبة لطبيعة البيانات الخارجية).
  • تاريخ الانقضاء في الانزلاق: تمديد فترة الانتقال من TTL إلى TL بشأن الوصول (جيد بالنسبة للدورات/البيانات الخاصة بالمستعملين).
  • الإخلاء القائم على الحجم (ImemoryCache): وضع مدخلاً. الحجم و تشكيل SizeLimitt إلى الذاكرة الملتصقة.
  • الأولوية (الأولوية الاستدلالية): الأولوية: تؤثر المرتفعات/العادية/الحرارة/اللو/الخفيفة/الريموف على عمليات الإخلاء تحت الضغط.
  • Jitter: إضافة عمليات تعويض عشوائية صغيرة إلى TTLs لتجنب تزامن انتهاء صلاحيتها ( الطوابع).

منع المخمودات (القرنة الرمل)

  • استخدم Get get أوركريات/ get أور كريات Assync (ImemoryCache) لضمان عدد سكان أحادي ththread لكل عقدة.
  • الموزعة: استخدام مفتاح قفل قصير العمر (SET NX EX) أو دعم المكتبة؛ إضافة tTL jetter؛ النظر في تحديث الخلفية.
  • Serverty- stale- refervalidate: الاحتفاظ بمفتاح ثانوي له قيمة متقادمة وتمديد قصير بينما تُحسب القيمة الجديدة.

التصميم الرئيسي والأسماء

  • يُفضَّل أن تكون المفاتيح المقوسة - المقوسة - المقوسة - المقوسة - المقوسة منخفضة: التطبيق: الصفة: 123 أو المستأجر: us: المستخدمون: 42.
  • إدراج جزء من النسخة لإبطال فئات كاملة من المفاتيح دون حذف: v2: Products: 123.
  • المستضاف: مفاتيح مع هوية المستأجر أو المنظمة لتجنب الاصطدامات وتسهيل عمليات التطهير.
  • ويحتفظ بمفاتيح صغيرة ولكنها وصفية؛ ويتجنب المدخلات الخام التي يتحكم فيها المستعملون دون تطبيع.

مجموعات المفاتيح (العلامات/المجموعات)

عندما تحتاج إلى إبطال العديد من المدخلات ذات الصلة:

  • البادئات المطبعية: صدم نسخة عالمية في مفتاح صغير ومفاتيح تأليف معه.

    // version key: "v:products"; keys like $"{version}:product:{id}"
    var version = await cache.GetStringAsync("v:products") ?? "1";
    var key = $"{version}:product:{id}";
    

    لإبطال جميع المنتجات: زيادة في المنتجات مقابل المنتجات (الزبائن سيفتقدون بطبيعة الحال المفاتيح البادئة القديمة).

  • مجموعة وسم لكل مجموعة (Redes): حافظ على مجموعة مفاتيح لكل وسمة؛ عند الإبطال وجلب العناصر وحذف.

    // using StackExchange.Redis directly for sets + efficient deletes
    var mux = await ConnectionMultiplexer.ConnectAsync("localhost:6379");
    var db = mux.GetDatabase();
    var tag = "tag:category:42";
    var key = $"prod:{prodId}";
    await db.StringSetAsync(key, serialized, expiry: TimeSpan.FromMinutes(30));
    await db.SetAddAsync(tag, key); // remember membership
    
    // later, invalidate the whole tag
    var members = await db.SetMembersAsync(tag);
    if (members.Length > 0)
    {
        var keys = Array.ConvertAll(members, m => (RedisKey)m);
        await db.KeyDeleteAsync(keys);
    }
    await db.KeyDeleteAsync(tag);
    
  • البوب/الإلغاء الفرعي: نشر رسالة "البطلان: key" ؛ كل عقدة تزيل المفتاح من مكانها المحلي IMMoryCache.

  • (ج) ينبغي تحاشي SCAN/KYES في مسارات ساخنة مبسطة؛

  • أنا المُذكِر Cchacse get أو set مع خيارات:
    T GetOrAdd<T>(IMemoryCache cache, string key, Func<ICacheEntry, T> factory)
      => cache.GetOrCreate(key, e =>
      {
          e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
          e.SlidingExpiration = TimeSpan.FromMinutes(2);
          e.Priority = CacheItemPriority.Normal;
          e.Size = 1;
          return factory(e);
      });
    
  • المُعَرّف مُعْرَفُ مُستخدَم مع JSON والإنتهاء:
    static async Task<T?> GetOrSetJsonAsync<T>(IDistributedCache cache, string key, Func<Task<T>> factory, TimeSpan ttl)
    {
        var json = await cache.GetStringAsync(key);
        if (json is not null)
            return System.Text.Json.JsonSerializer.Deserialize<T>(json);
    
        var value = await factory();
        var opts = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl };
        await cache.SetStringAsync(key,
            System.Text.Json.JsonSerializer.Serialize(value),
            opts);
        return value;
    }
    

الرصد والوضوح والوضوح

  • (ب) قياسات التعرض (القياسات المضادة بروميثيوس) لكل مجموعة رئيسية.
  • إضافة إلى قطع الأشجار حول السكان المخبأين وطرد إعادة استدعاء لIMMorio Cache.
  • وفيما يتعلق بـ Reedis، يُرصد ارتطامات/آثار الفضاء الرئيسية، والتأخر، وتجزئة الذاكرة؛ ويحدد سياسات الحد الأقصى حسب الاقتضاء.

الأنماط المستخلصة من الإنتاج

هذه هي الطريقة التي استخدمها في الواقع I memoryCache في منصة مدونتي، تطورت من خلال التجربة والخطأ. سأريكم ثلاثة أنماط حقيقية من بسيط إلى متطور.

النمط 1: المخبأ البسيط إلى جانب البيانات التي نادراً ما تكون قابلة للتغير (الفئات)

كانت هذه أول عملية تنفيذ. تصنيفات المدونات لا تتغير كثيراً، لذا أخفيها لمدة 30 دقيقة:

// From BaseController.cs
private const string CacheKey = "Categories";

private async Task<List<string>> GetCategories()
{
    baseControllerService.MemoryCache.TryGetValue(CacheKey, out var value);

    if (value is List<string> categories) return categories;

    logger.LogInformation("Fetching categories from BlogService");
    categories = (await BlogViewService.GetCategories(true)).OrderBy(x => x).ToList();
    baseControllerService.MemoryCache.Set(CacheKey, categories, TimeSpan.FromMinutes(30));
    return categories;
}

ولهذا السبب يعمل هذا:

  • تُقرأ الفئات في كل صفحة (تُقرأ في الملاحة)
  • إنها نادراً ما تتغير (فقط عندما أضيف مدونات جديدة مع فئات جديدة)
  • TTL دقيقة 30 دقيقة لا بأس بها؛ وإذا ظهرت فئات جديدة، يراها المستخدمون في غضون 30 دقيقة
  • لأنّنا لا نهتم كم مرّة يتمّ الوصول إليه

:أصبت في البداية استخدمت مفتاح سلسلة "Categories". يعمل بشكل جيد حتى يكون لديك متحكمات متعددة وواحد يعيد استخدام المفتاح نفسه عن طريق الخطأ. الآن أنا استخدم الثوابت أو المفاتيح ذات الشكل القوي (انظر القسم السابق عن تجنب الاصطدامات).

النمط 2: دولة الاستعمال الواحدة التي لها حدود حجم وفترة انتهائها (مهام الترجمة التحريرية)

حالة ترجمة هذه الخزائن لكل مستخدم. إنها أكثر تعقيداً لأنها تحتاج إلى حدود وينبغي أن تبقى على قيد الحياة طالما أن المستخدم نشط:

// From TranslateCacheService.cs - tracks translation tasks per user
public void AddTask(string userId, TranslateTask task)
{
    CachedTasks CachedTasks() => new()
    {
        Tasks = new List<TranslateTask> { task },
        AbsoluteExpiration = DateTime.Now.AddHours(6)
    };

    if (memoryCache.TryGetValue(userId, out CachedTasks? tasks))
    {
        tasks ??= CachedTasks();
        var currentTasks = tasks.Tasks;

        // Keep only the 5 most recent tasks
        currentTasks = currentTasks.OrderByDescending(x => x.StartTime).ToList();
        if (currentTasks.Count >= 5)
        {
            var lastTask = currentTasks.Last();
            currentTasks.Remove(lastTask);
        }

        currentTasks.Add(task);
        currentTasks = currentTasks.OrderByDescending(x => x.StartTime).ToList();
        tasks.Tasks = currentTasks;

        memoryCache.Set(userId, tasks, new MemoryCacheEntryOptions
        {
            AbsoluteExpiration = tasks.AbsoluteExpiration,
            SlidingExpiration = TimeSpan.FromHours(1) // Extends on access
        });
    }
    else
    {
        var absoluteExpiration = DateTime.Now.AddHours(6);
        var cachedTasks = CachedTasks();
        memoryCache.Set(userId, cachedTasks, new MemoryCacheEntryOptions
        {
            AbsoluteExpiration = absoluteExpiration,
            SlidingExpiration = TimeSpan.FromHours(1)
        });
    }
}

وسبب هذا الاختلاف:

  • التأجيلإذا استمر المستخدم في التحقق من حالة الترجمة، أبقِ المخبأ على قيد الحياة لمدة تصل إلى 6 ساعات كحد أقصى
  • سسيسالحفاظ فقط على 5 مهام حديثة العهد لكل مستخدم لمنع نفخة الذاكرة
  • دليل الإدارةأنا أُحدّدُ الحجمَ بشكل صريح لأن MemisCache لا يُنفّذُ حدودَ عدد البنود على مستوى الدخول (حجم المخبأ العام فقط)

الخطأ الذي قمت به: في البداية لم أحصر عدد المهام. مستخدم كهربائي شغّل 50+ ترجمات وكان لدي تسريب ذاكرة. الآن أحتفظ بأقصى 5 لكل مستخدم.

المقايضة: هذا لن يتسع لملايين المستخدمين. إذا أصبحت هذه مشكلة، سأنتقل إلى الـ Digistroped Cache (Redis) أو تخزين في قاعدة البيانات مع فهرس على المستخدم Id + بدء الوقت.

النمط 3: الملصقات التي يمكن ملاحظتها (Metrs caching)

يقوم هذا المخابئ بتحليل القياسات ويتتبع فعالية المخابئ باستخدام تعقب Serilog:

// From UmamiDataSortService.cs - caches Umami analytics metrics
public async Task<List<MetricsResponseModels>?> GetMetrics(DateTime startAt, DateTime endAt, string prefix = "")
{
    using var activity = Log.Logger.StartActivity("GetMetricsWithPrefix");
    try
    {
        var cacheKey = $"Metrics_{startAt:yyyyMMdd}_{endAt:yyyyMMdd}_{prefix}";

        if (cache.TryGetValue(cacheKey, out List<MetricsResponseModels>? metrics))
        {
            activity?.AddProperty("CacheHit", true);
            return metrics;
        }

        activity?.AddProperty("CacheHit", false);
        var metricsRequest = new MetricsRequest
        {
            StartAtDate = startAt,
            EndAtDate = endAt,
            Type = MetricType.url,
            Limit = 500
        };

        var metricRequest = await dataService.GetMetrics(metricsRequest);
        if (metricRequest.Status != HttpStatusCode.OK) return null;

        var filteredMetrics = metricRequest.Data
            .Where(x => x.x.StartsWith(prefix))
            .ToList();

        cache.Set(cacheKey, filteredMetrics, TimeSpan.FromHours(1));

        activity?.AddProperty("MetricsCount", filteredMetrics?.Count() ?? 0);
        activity?.Complete();
        return filteredMetrics;
    }
    catch (Exception e)
    {
        activity?.Complete(LogEventLevel.Error, e);
        return null;
    }
}

ما الذي يجعل هذا الإنتاج جاهزاً:

  • ****:: تسجيل مسارات الأنشطة المتتبعة: قياس المخبأ/معدل المعدل الصافي ومقاييس السجلات
  • مُرّى مُر مُر مُر مُر مُرما يلي: يشمل نطاق التاريخ وفترة ما قبله لتجنب الاصطدامات الرئيسية
  • ثانياً - الإجراءثالثاً - استخدامات: yyyyMMdd تنسيق في صيغة رئيسية مختلفة جداً في أوقات مختلفة جداً في نفس اليوم الذي تشارك فيه المخبأ
  • المناولة: العوائد لاغية إذا فشلت الخدمة الخارجية؛ لا تُخفِف الأعطال
  • ****: تكسر النتيجة المرشّبة، وليس الردّ الخام

تطور: في البداية اختبأت لمدة 10 دقائق. ولكن قياسات أمامي ثقيلة لا يمكن جلبها ولا تتغير كثيراً، لذا لا بأس بساعة واحدة. اكتشفت هذا من خلال النظر في بيانات SerilogTtraves

رصد التنفيذ: في Seq (سجلّ سجلّي مُجَمَّع)، يمكنني الإستفسار:

ActivityName = "GetMetricsWithPrefix" and CacheHit = false

هذا يُخبرني أن معدل فاتني إذا كان مرتفعاً، سأعدّل تي تي إل أو الاستراتيجية الرئيسية.

مقارنة النُهُج الثلاثة الثلاثة

□ النمط □ استخدام القضية □ انتهاء الصلاحية □ مراقبة الحجم □ المراقبة □ المراقبة □ |---------|----------|------------|--------------|---------------| | **** □ على الصعيد العالمي، ونادر التغير، ونادرا ما يتغير، و 30 دقيقة مطلقة مطلقة 30 دقيقة لا حاجة إليها (صغيرة) □ قطع الأشجار الأساسي □ | مهام الترجمة * لكل مستخدم، مقيد برقم 6h المطلق + 1h انزلاق دليل (5 بنود كحد أقصى) □ لا يوجد (ينبغي أن يضاف!) □ | **** ● المكالمات الخارجية الباهظة التكلفة o 1 ساعة مطلقة مطلقة o طبيعية (الزمن - النافذ) □ التعقب الكامل □

(أ) الدروس المستفادة:

  1. (النموذج 1)، إضافة التعقيد عند الحاجة فقط
  2. فكّر دائماً دائماً في استخدام ذاكرة المخبأ - إضافة حدود حجم لـ مستخدم
  3. بالنسبة للعمليات المكلفة، تضاف ملاحظة من اليوم الأول
  4. هذا tTTTL مستند على تردد تغيير البيانات الفعلية، لا تخمينات

عندما لا أستخدم أنا Memory Cache:

  • لـ مستخدم دولة توثيق لـ مستخدم الدولة (المطالبات في auth coke بدلاً)
  • بالنسبة لعربة التسوق (ستستخدم DB + المخبأ الموزع في الإنتاج)
  • لمحتوى تدوينة البريد (بالفعل في DB، تحميل مرة واحدة لكل طلب)
  • الدولة التي يُعنى بها الصليب (ستحتاج إلى بطاقة الهوية التي تُوزَّع عليها الكاكيت/الرّيد)

صكوك التصديق والمطالبات

  • النطاق: بين الطلب والانتهاء من
  • استخدام ما يلي: الهوية، أدوار/انبعاثات خفية، كمية صغيرة من بيانات التوصيف
  • المقايضات: حجم الكعكة؛ لا تبالغ فيها. ينبغي أن تكون المطالبات مستقرة.

إنشاء كِك المعيث:

builder.Services.AddAuthentication("Cookies")
    .AddCookie("Cookies", o =>
    {
        o.LoginPath = "/login";
        o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        o.SlidingExpiration = true;
    });
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

لافتة مع مطالبات (MVC/minimal):

app.MapPost("/login", async (HttpContext ctx) =>
{
    var claims = new[]
    {
        new Claim(ClaimTypes.NameIdentifier, "123"),
        new Claim(ClaimTypes.Name, "Alice"),
        new Claim(ClaimTypes.Role, "Admin")
    };
    var identity = new ClaimsIdentity(claims, "Cookies");
    await ctx.SignInAsync("Cookies", new ClaimsPrincipal(identity));
    return Results.Redirect("/");
});

(أ) المطالبات المقروءة (أي رزمة):

[Authorize]
app.MapGet("/me", (ClaimsPrincipal user)
  => Results.Ok(new { user.Identity!.Name, Roles = user.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value) }));

JWT/ دبر تَكِنَفْكِنَات

  • النطاق: العميل يحمل علامة رمزية؛ خادم عديم الجنسية
  • الاستخدام بالنسبة إلى: المناطق جنوب آسيا/المتنقل/APIS، وعبر النطاق، والخدمات الصغرى
  • المقايضات: الحجم الرمزي؛ والتناوب/الجديد؛ والمطالبات بالحد الأدنى للمخزونات، واستخدام المعاينة الذاتية عند الحاجة
builder.Services.AddAuthentication("Bearer")
   .AddJwtBearer("Bearer", o =>
   {
       o.Authority = "https://demo.identityserver.io"; // example
       o.Audience = "api";
       o.RequireHttpsMetadata = true;
   });

الاستخدام:

[Authorize(AuthenticationSchemes = "Bearer")]
app.MapGet("/secure", () => "ok");

لمحة عامة:

sequenceDiagram
  participant C as Client
  participant STS as Token Service
  participant API as API
  C->>STS: Authenticate (username/password)
  STS-->>C: JWT (signed)
  C->>API: GET /secure (Authorization: Bearer <jwt>)
  API->>API: Validate signature, expiry, audience
  API-->>C: 200

الدولة الحاضرة: قاعدة البيانات والأصدقاء

  • النطاق: إلى الأبد (إلى حين حذفه)
  • لكل ما يلي: أي شيء يجب ألا يُفقد: العربات، والأوامر، والملفات، وتدفقات العمل الطويلة الأمد
  • الأنماط: معيار CCRUD مع EF COR; CQRS; تحديد مصادر الحدث; نمط صندوق خارجي من أجل الموثوقية

EF أساس الرسم التوضيحي:

builder.Services.AddDbContext<AppDb>(o => o.UseSqlServer(cs));

app.MapPost("/cart/items", async (AppDb db, AddItem cmd) =>
{
    var cart = await db.Carts.FindAsync(cmd.CartId) ?? new Cart(cmd.CartId);
    cart.Add(cmd.ProductId, cmd.Qty);
    await db.SaveChangesAsync();
    return Results.Created($"/cart/{cart.Id}", cart);
});

ط ط ط ط ط ط ط ط ط، والطلبات المشروطية

  • لا يقتصر الأمر على "الحمل على الدولة"، بل يقلل من العمل المتكرر بالسماح للعميل/الوكيلات بإعادة استخدام ردود الفعل السابقة. وكثيراً ما يقترن مع حالة الاستعلام/المسار.
builder.Services.AddResponseCaching();
var app = builder.Build();
app.UseResponseCaching();

app.MapGet("/products", (HttpContext ctx) =>
{
    ctx.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue { Public = true, MaxAge = TimeSpan.FromSeconds(30) };
    return Results.Ok(new[] { new { Id = 1, Name = "Widget" } });
}).CacheOutput();

مثال eTTAg:

app.MapGet("/resource", (HttpContext ctx) =>
{
    var version = "W/\"abc123\""; // compute based on data hash
    ctx.Response.Headers.ETag = version;
    if (ctx.Request.Headers.IfNoneMatch == version)
        return Results.StatusCode(StatusCodes.Status304NotModified);
    return Results.Text("payload");
});

(بآلاف دولارات الولايات المتحدة)

أستعمل كلاً من المستجيبين لـ CraffectCache و extractcache معاً على مدونتي لأغراض مختلفة. لهذا السبب قد ترغب في كليهما وكيف يختلفان.

التشويش: اثنين من الصفات؟

SPSP. SP. ENS ثانياً - نُظُم كشك متشابهة:

  1. **الواجهة المستجيبة (HTTTTP قافز)**تُسَطِس مُوَجِزَات HTTP (% 1)Cache-Control, Vary(يخبر المتصفحات و CD CDNs كيف يمكن الاختباء (هـ)
  2. **الناتج المحلـات الدراسـة (جـيـب الخادم)**كهز الناتج المعطى على الخادوم لتخطي تنفيذ الإجراء بالكامل

يكمّلا بعضهما البعض. (أ) مقابض الاستجابة التي يقوم بها العميل/مقابض الاحتياجات المحلية المحلية؛

مدوّنتي المدوّنة (يطبّق كلا المخبأين)

// From BlogController.cs
[Route("{slug}")]
[HttpGet]
[ResponseCache(Duration = 300, VaryByHeader = "hx-request",
    VaryByQueryKeys = new[] { nameof(slug), nameof(language) },
    Location = ResponseCacheLocation.Any)]
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" },
    VaryByQueryKeys = new[] { nameof(slug), nameof(language) })]
public async Task<IActionResult> Show(string slug, string language = "en")
{
    var post = await blogViewService.GetPost(slug, language);
    if (post == null) return NotFound();

    // ... populate user info, comments, etc ...

    if (Request.IsHtmx()) return PartialView("_PostPartial", post);
    return View("Post", post);
}

ما يحدث عندما يطلب شخص ما /blog/my-post:

  1. أولاً: هل لدي رد مختزل على my-post + en لغته؟

    • 1 ف-5، 1 ف-4، 1 ف-3، 1 خ م، 1 م و، 1 م و، 1 م و، 1 م أ م و، 1 م و، 1 م أ م و، 1 م أ م و، 1 م أ مHTML: العودة المخبأة، طريقة العمل لا تعمل أبداً (سريع! ~ 1ms)
    • & ماسسس: تنفيذ الإجراء، تقديم عرض، مخبئ نتيجة لـ 3600 ثانية (ساعة واحدة)
  2. المراس المراس:بعد أن يولّد برنامج الناتج المحلي الإجمالي الاستجابة، يضيف التقرير:

    Cache-Control: public, max-age=300
    Vary: hx-request
    
  3. المُسْف المُسْجيخبئ المتصفح الرد لـ 300 ثانية (5 دقائق). الطلبات اللاحقة من نفس المستخدم لا تضرب الخادم حتى.

  4. DCD (إذا كان باستخدام coodflare/fastly): CDN مخابئ لمدة 5 دقائق. المستخدمون في جميع أنحاء العالم ضربوا CDN، وليس خادمي.

لماذا تختلف الفترات الزمنية؟ (300 vs 3600)

[ResponseCache(Duration = 300)]    // 5 minutes client/CDN cache
[OutputCache(Duration = 3600)]     // 1 hour server cache

السبب:

  • المخبأ أكثر من ذلك (ساعة واحدة): أنا أتحكم بخادمي ، أستطيع أن أطهر المخبأ إذا قمت بتحديث وظيفة
  • مُجلِّز المُقرّر هو أقصر (5 دقائق): لا أستطيع تنقية متصفحات المستخدم أو أجهزة CDN بسهولة ؛ 5 دقائق هو نافذة معقولة
  • ****إذا قمت بتحرير وظيفة، يظهر محتوى جديد:
    • جانب الخادم: فورياً (يمكنني إبطال المخبأ)
    • CDN/المتصفحات: في غضون 5 دقائق (أو أنا أزيل CDN)

تطور: في البداية كان لدي كلاهما عند 5 دقائق. لكن هذا يعني أن خادمي كان يعاد تشغيله كل 5 دقائق على الرغم من أن المحتوى نادراً ما يتغير. الآن:

  • خادم خادم مُخَزَّز HTML لمدة ساعة واحدة
  • العملاء يحصلون على محتوى جديد كل 5 دقائق

VharyBy ترويدر لـ HTMX

VaryByHeader = "hx-request"  // ResponseCache
VaryByHeaderNames = new[] { "hx-request" }  // OutputCache

ولهذا السبب فإن هذا يهم: طلبات HTMX تشمل: hx-request: true أُرجع إجابات مختلفة:

  • الطلب الكامل& & &::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  • الطلب المقدم من شركة HTMXx: عرض جزئي دون تخطيط

بدون بدون بدون بدون بدون بدون بدون VaryByالمخبأ سيرجع الشكل الخاطئ VaryByأخبئ نسختين من كل صفحة

مثال:

User requests /blog/my-post → Cache key: "blog/my-post:en:hx=false" → Full HTML cached
HTMX requests /blog/my-post → Cache key: "blog/my-post:en:hx=true" → Partial HTML cached

VaryByyQQQyryKeyskes for لغوية و ملحقات

[ResponseCache(VaryByQueryKeys = new[] { nameof(slug), nameof(language) })]
[OutputCache(VaryByQueryKeys = new[] { nameof(slug), nameof(language) })]

لا يوجد حل لهذه المشكلة: /blog/my-post?language=fr من شأنه أن يخدم النسخة الإنجليزية المخبأة.

مع VaryByQQureKeyseys: مُنتفِد:

  • /blog/my-post?language=en □ مُدرج في خانة مُجلِز يشمل "en"
  • /blog/my-post?language=fr □ مُدرجات مُكْمِم المُجل في "fr"

الاستخدام الحقيقي من قائمة مدونتي:

[Route("blog")]
[ResponseCache(Duration = 300, VaryByHeader = "hx-request",
    VaryByQueryKeys = new[] { "page", "pageSize", "startDate", "endDate", "language", "orderBy", "orderDir" })]
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" },
    VaryByQueryKeys = new[] { "page", "pageSize", "startDate", "endDate", "language", "orderBy", "orderDir" })]
public async Task<IActionResult> Index(int page = 1, int pageSize = 20, /* ... */)

تحذير: كل مجموعة فريدة من البارامترات = مدخل مستقل للمخبأ:

  • page=1&pageSize=20&language=en مدخلة واحدة
  • page=2&pageSize=20&language=en مدخلة أخرى
  • page=1&pageSize=10&language=en مدخلة أخرى

& & & & & & & & & & & &

  • حجم المخبأ في حدود قصوى معقولة (نبائط الـCuch aut- evtions أقل استعمالاً في الوقت الراهن)
  • مخبئات مشتركة (الصفحة 1، الصفحة الافتراضيةSize)
  • خطأ خطأ خطأ خطأ خطأ خطأ مخبئ (مقبول)

الإعداد مطلوب

المُلْخِل المُرْجِل العمل من الصندوق، ولكن من أجل مخرجات المجلج تحتاج إلى تثبيت:

// Program.cs
builder.Services.AddOutputCache(options =>
{
    options.MaximumBodySize = 64 * 1024 * 1024; // 64 MB max response size
    options.SizeLimit = 100 * 1024 * 1024; // 100 MB total cache size
});

var app = builder.Build();
app.UseOutputCache(); // Must be in middleware pipeline

عندما لا يساعد ملفّ المُخرجات

%s: %s

  • الطلبات الموثقة (يرى مستخدمون مختلفون بيانات مختلفة)
  • PPOP/PUUT/DELETE (فقط الحصول/التعبئة فقط مخبأة)
  • المقدم من Set-Cookie
  • الذي ينص صراحة على ما يلي: Cache-Control: no-store

حيث لا أستخدمه:

// Comment submission - authenticated, POST, and per-user
[HttpPost]
[Authorize]
public async Task<IActionResult> AddComment(CommentModel model)
{
    // No caching attributes - this is user-specific and changes state
}

(د) ما إذا كان

أستعمل بروميثيوس المقاييس (معرضة عن طريق تطبيقي) لتتبع:

// Pseudo-code for metrics
cache_hits_total{cache="output"} 45230
cache_misses_total{cache="output"} 892

معدل نجاحي: حوالي 98 في المائة للمدوّنات (أغلب حركة المرور تضرب نفس المدوّنات الشعبية مراراً وتكراراً).

التأثير:

  • بدون كشط: حوالي 50 متراً من متوسط وقت الاستجابة (السؤال DB + التقدير بالعلامة)
  • مع مخرجات CCch مع مخرجات: نحو 1 - 2 - 2 ms للاستجابات المخبأة
  • سرعة سرعة 25x

عندما أُعطل مؤقتاً

في بعض الأحيان أنا أقوم بالتنقيح و أحتاج إلى إجابات جديدة في كل مرة:

// During development, comment out caching
// [ResponseCache(Duration = 300, ...)]
// [OutputCache(Duration = 3600, ...)]
public async Task<IActionResult> Show(string slug, string language = "en")

أو معرفات استخدامات البيئة المعيّنة:

#if DEBUG
    // No caching in development
#else
    [ResponseCache(Duration = 300, ...)]
    [OutputCache(Duration = 3600, ...)]
#endif

النهج الأفضل: & لا شيء من هذا القبيل:

[ResponseCache(Duration = responseCacheDuration, ...)]

حيث أُدرج responseCacheDuration هو 0 في التطوير، 300 في الإنتاج.

مشاريع هذه الاستراتيجية المزدوجة المحصلات ومعارضها

Pros:

  • الكفاءةالمصدر: وحدة معالجة المنتجات تقلل من حمل المعالج/العامل المعالج بنسبة 98 في المائة.
  • العملاء/المزايا: respat chack يُقلّلُ نطاقَي ويُحسّنُ الانتقاص العالميَ.
  • ****مختلف TTLs لـ خادم عميل
  • الوفورات: عدد أقل من استفسارات أجهزة المعلومات الرقمية، وعدد أقل من النطاق الترددي

كون:

  • :: تحديثات المحتوى تستغرق ما يصل إلى 5 دقائق لتعميمها على العملاء
  • تَعْرِج المُصْدِرتحديث وظيفة تتطلب المصادقة على خادوم ومخبأي CDN على حد سواء:
  • التذكر% 1:% 1:% 1:% 1:% 1:% 1
  • جاري التصحيح:أحياناً النسيان أن المخبأ مفتوح ويتساءل لماذا التغييرات لا تظهر

عندما كنت اقفز

  • بيانات آنية (أسعار الأسهم والألعاب الرياضية الحية)
  • المحتوى الشخصي (التوصيات الخاصة بالمستعملين)
  • عدد الصفحات المنخفضة الحركة المرورية (المصروفات العامة الإجمالية > الاستحقاقات)
  • الصفحات التي تتغير كثيراً جداً

قراري: بالنسبة لمدوّنة ذات محتوى ثابت في الغالب ونسبة قراءة/كتابة عالية، فإن النقش المزدوج هو فوز ضخم. لن أستخدم هذا على لوحات الإدارة أو أجهزة المتابعة مع بيانات سريعة التغير.


عرض باغ، عرض Data، و TempData: المراقب من دولة إلى دولة فيو (و لماذا أتجنب في الغالب اثنين منهم)

هذه الثلاثة غالباً ما تكون مشوشة. وهنا كيف يختلفون وما الذي استخدمه فعلياً في الإنتاج.

تم مقارنة الاصدقاء الثلاثة

// ViewData: string-keyed dictionary
ViewData["Title"] = "Blog";
ViewData["Categories"] = new List<string> { "ASP.NET", "C#" };

// ViewBag: dynamic wrapper around ViewData
ViewBag.Title = "Blog";
ViewBag.Categories = new List<string> { "ASP.NET", "C#" };

// TempData: survives one redirect (backed by session or cookie)
TempData["Message"] = "Post saved!";
return RedirectToAction("Index");

□ المعالم □ عرض عرض بيانات □ عرض □ عرض □ عرض □ عرض □ عرض □ عرض □ □ □ TemData o

--------- ---------- --------- ----------
**** الطلب الحالي الطلب الحالي الطلب الحالي الطلب الأول إعادة توجيه
الوصول إلى المفتاح مفاتيح سلسلة سلسلة مفاتيح TM TRANSLAT TRANSLAT TRANSLAT TRANSLAT TRANSLAT TRANSLAT TRANSLAT
نوع السلامة لا يوجد (احتاجت التوقعات) لا يوجد (ديناميكية) لا يوجد (احتاجت النُهُج)
التحقق من صحة التحقق من الوقت رقم رقم رقم رقم رقم
نُجُو إعادة توجيه □ لا □ لا □ نعم □

ما استخدمه فعلياً: عرض Bag لـ بيانات التخطيط العالمي

في مدونتي، أستخدم فيو باج فقط لنقل البيانات من المتحكمين إلى التصميم المشترك (التحليلات، الفئات، الخ):

// From BaseController.cs - runs before every action
public override async Task OnActionExecutionAsync(ActionExecutingContext filterContext,
    ActionExecutionDelegate next)
{
    logger.LogInformation("OnActionExecutionAsync");

    if (!Request.IsHtmx())
    {
        // Analytics settings for layout
        ViewBag.UmamiPath = AnalyticsSettings.UmamiPath;
        ViewBag.UmamiWebsiteId = AnalyticsSettings.WebsiteId;
        ViewBag.UmamiScript = AnalyticsSettings.UmamiScript;
    }

    logger.LogInformation("Adding categories to viewbag");
    ViewBag.Categories = await GetCategories(); // Cached list

    await base.OnActionExecutionAsync(filterContext, next);
}

ثم في تخطيطي_Layout.cshtml):

@if (ViewBag.Categories is List<string> categories)
{
    <nav>
        @foreach (var cat in categories)
        {
            <a asp-controller="Blog" asp-action="Category" asp-route-category="@cat">@cat</a>
        }
    </nav>
}

@if (!string.IsNullOrEmpty(ViewBag.UmamiPath))
{
    <script async src="@ViewBag.UmamiScript"
            data-website-id="@ViewBag.UmamiWebsiteId"></script>
}

لماذا يعمل هذا النمط:

  • منظمة عالميةكل صفحة تحتاج إلى فئتين من الملاحين والتحليلات
  • المبالغ المحسوبة مرة واحدة:: تشغيل القاعدة قبل كل عمل
  • HTMXالسّسق نص نصّ على طلبات جزئية (HTMX لا يحتاجه مُحقن)
  • مُسُككشاً:الفئات مخبأة (انظر نمطي IMymoryCasche) أعلاه، لذلك لا تضرب DB في كل طلب

الخطأ الذي قمت به مبكراً على: أنا كُنْتُ أَكُونُ ViewBag.Categories في كل طريقة من طرق العمل، وانتهكها وسهل نسيانها، وانقلها إلى OnActionExecutionAsync في قاعدة التحكم حل هذا.

منفذ بكg لـ البيانات الخاصّة بصف صفحة البيانات (ن نمط مقبول)

// From BlogController.cs
[Route("category/{category}")]
public async Task<IActionResult> Category(string category, int page = 1, int pageSize = 10)
{
    ViewBag.Category = category; // Used in view for heading
    ViewBag.Title = category + " - Blog"; // Used in layout <title>

    var posts = await blogViewService.GetPostsByCategory(category, page, pageSize);
    // ... populate posts model ...

    if (Request.IsHtmx()) return PartialView("_BlogSummaryList", posts);
    return View("Index", posts);
}

وفي رأي:

@{
    ViewData["Title"] = ViewBag.Title; // Standard MVC convention for <title>
}

<h1>Category: @ViewBag.Category</h1>

هذا صحيح لأن:

  • مبسط سقلازر القيم (سلس، int)
  • مُستخدم فقط في العرض، لا مرّر
  • وسيضاف بديل ما يلي: Title وقد عقد مؤتمراً بشأن Category إلى كل نموذج عرض

ما أتجنبه: الأجسام المعقّدة في عرض باج

المطهرة:

// DON'T DO THIS
ViewBag.User = new UserViewModel { Name = "Scott", IsAdmin = true };
ViewBag.Posts = new List<Post> { ... };
ViewBag.Metadata = new { Tags = new[] { "a", "b" }, Date = DateTime.Now };

المشاكل:

  • عدم وجود أمان أثناء تجميع البيانات (الطوارئ) ViewBag.Usr الرسوب في وقت التنفيذ)
  • من الصعب تتبع البيانات المتوفرة في الموقع
  • إجراء اختبارات أكثر صعوبة (احتاج إلى فحص قاموس عرض باغ)
  • لا يوجد

أفضل: نماذج رؤية ذات نماذج نموذجية قوية:

// DO THIS instead
public class BlogIndexViewModel : BaseViewModel
{
    public string Category { get; set; }
    public List<PostSummary> Posts { get; set; }
    public PaginationInfo Pagination { get; set; }
}

public IActionResult Category(string category, int page = 1)
{
    var model = new BlogIndexViewModel
    {
        Category = category,
        Posts = await GetPosts(category, page),
        // Inherited from BaseViewModel:
        Authenticated = user.LoggedIn,
        Name = user.Name,
        AvatarUrl = user.AvatarUrl
    };
    return View("Index", model);
}

لا أستخدمه (وإليكم السبب)

حالة استخدام TMPDAT:

[HttpPost]
public IActionResult SavePost(PostModel model)
{
    // Save post...
    TempData["SuccessMessage"] = "Post saved successfully!";
    return RedirectToAction("Index");
}

public IActionResult Index()
{
    // TempData["SuccessMessage"] available here (consumed on read)
    return View();
}

لماذا لا أستخدم TimPData في مدونتي:

  1. استخدم HTMX بدلاً من إعادة توجيه: نماذجي تقدم عن طريق HTMX وأرجع وجهات النظر الجزئية مع رسائل النجاح/التشغيل في الخط. لا إعادة توجيه = لا حاجة إلى TimPData.
[HttpPost]
public async Task<IActionResult> Submit(ContactViewModel model)
{
    if (!ModelState.IsValid)
        return PartialView("_ContactForm", model); // Show errors inline

    await sender.SendEmailAsync(contactModel);

    // Return success view directly (no redirect)
    return PartialView("_Response", new ContactViewModel
    {
        Email = model.Email,
        Name = model.Name,
        Comment = "Message sent!"
    });
}
  1. **بالنسبة للباراغويات التقليدية (ما بعد - إعادة التوجيه - المباشرة - الحصول)**لكنني أفضل تجنب إعادة التوجيه كلما كان ذلك ممكناً من أجل "يو إكس" أفضل

عندما تكون TimDatata منطقية:

  • تطبيقات MVC التقليدية مع إعادة توجيه صفحة كاملة بعد PP
  • مُتعدّد خطوات حيث تُعاد توجيهك بين خطوات

TimDdata حصلت على ما يلي: بإفتراضي مدعوم ببسكويت (منذ ASP.NET or 2.0). إذا وضعت كائنات كبيرة في TmpData، فأنت تنفخ الكعكة المرسلة مع كل طلب. بالنسبة للدولة الكبيرة، استخدم الجلسة مع مخزن دعم أو DB.

أولاً - مُحَكِجَة قرار مُجَرْرِك

flowchart TD
    A[Need to pass data to view?] --> B{What kind?}
    B -->|Global layout data| C[ViewBag in BaseController]
    B -->|Simple page-specific| D[ViewBag in action]
    B -->|Complex model| E[Strongly-typed ViewModel]
    B -->|Survive redirect| F{Using HTMX?}
    F -->|Yes| G[Return partial with message]
    F -->|No| H[TempData for flash message]

قواعدي:

  1. عرض باج لـ لـ لـ لـ لـ هذا فقط (التحليلات، وناف، وفتات الخبز)
  2. منفذ هذا لـ لـ بسيط لـ بسيط عناوين العناوين/ تروي العناوين (الخيار; يمكن أن يمكن استخدام حوار العرض)
  3. عدم (استخدام)
  4. لا (ViewBag عِنْدَهُ systopz ألطف)
  5. فقط إذا كنت حقاً بحاجة حقاً لـ PRG (أنا لا، بفضل HTMX)

الصور: العوارض والتدفقات المتعددة المراحل

أي دولة ستستعمل؟

flowchart TD
  A[Start Wizard] --> B{Short-lived?\nSingle browser?}
  B -- Yes --> S[Session/TempData]
  B -- No/Complex --> D[DB + key in route]
  S --> PRG[Use PRG between steps]
  D --> PRG
  • الدورة الصغيرة والوحيدة: الدورة أو المرحلة الفاصلة بين الخطوات.
  • الجهاز المتقاطع/الممتد: يستمر إلى DB، يحمل مفتاحاً في العنوان.

مثال (DB + مفتاح المسار):

app.MapPost("/wizard/{id}", async (AppDb db, Guid id, StepInput input) =>
{
    var flow = await db.Flows.FindAsync(id) ?? new Flow(id);
    flow.Apply(input);
    await db.SaveChangesAsync();
    return Results.Redirect($"/wizard/{id}/next");
});

لا يوجد: رسائل تذكيرية مع TMData

  • اضبط في POPT; اقرأ مرة بعد إعادة توجيه.
TempData["Flash"] = "Profile saved";
return RedirectToAction("Index");
@if (TempData["Flash"] is string flash) {
  <div class="alert alert-info">@flash</div>
}

الصورة: خرططط

  • عربات صغيرة: الدورة (إذا كان جدولك متواضعاً وكان لديك دورة لزجة/موزعة).
  • عربات كبيرة/متعددة الأجهزة: DB + back-id في الكعكة أو المسار.
flowchart LR
  U[User] -- cart-id cookie --> S[Server]
  S --> DB[(Cart Table)]
  S <--> Cache[Distributed Cache]

الأمن والخصوصية والامتثال

  • التصديق على جميع الطلبات المقدمة من العملاء: استفسار، وترويسة، واستمارات، وبسكويت، ومطالبات شركة JWT.
  • حماية حالة العملاء الحساسة: استخدم حماية البيانات للكعك الذي تصدره؛ لا تخزن أبداً أسراراً في سلاسل الاستعلام.
  • Secure, HttpOnly, SameSite, IsEssential (إذا اقتضت الموافقة/الحاجة الوظيفية ذلك).
  • (د) أن تُعدِّل بسكويتات التوثيق بشأن التغييرات في الامتيازات؛ وأن تحافظ على الحد الأدنى من المطالبات.
  • تشفير في راحة للمستودعات بجانب الخوادم حسب الحاجة؛ ضمان تناوب المفتاح (مفاتيح حماية البيانات، مفاتيح توقيع JWT).
  • (ج) الناتج المحلي الإجمالي/خطة عمل القرن الأفريقي: توفير طرق تصدير/إحلال البيانات الخاصة بالمستعملين؛ والتقليل إلى أدنى حد من الاحتفاظ بها.

مصفوفة مقرر (نشرة قمل)

classDiagram
  class Items {
    +Per-request only
    +Great for middleware->endpoint handoff
  }
  class QueryRoute {
    +Explicit, linkable
    -User-controlled
  }
  class Headers {
    +Tracing, idempotency
    -Noisy, untrusted
  }
  class Cookies {
    +Persist small prefs
    -Size, perf, consent
  }
  class TempData {
    +One-redirect messages
    -Ephemeral
  }
  class Session {
    +Conversational state
    -Scaling complexity
  }
  class MemoryCache {
    +Fast, in-proc
    -Not shared across instances
  }
  class DistributedCache {
    +Shared across servers
    -Serialization, ops
  }
  class AuthCookieClaims {
    +Identity, roles
    -Don’t overstuff
  }
  class JWT {
    +Stateless, cross-domain
    -Revocation/rotation
  }
  class DB {
    +Durable, authoritative
    -Latency, complexity
  }

اختيارات سريعة:

  • -أتحتاجين إلى وميض مباشر واحد؟
  • تحتاج إلى ساحر عبر طلبات متعددة في جلسة واحدة؟ جلسة واحدة؟ (أو DB + مفتاح إذا كان طويل الأمد/متعدد الأجهزة).
  • هل هناك حاجة للقابلية للتعديل وعديمو الجنسية؟ JWT بالنسبة للهوية، DB/DscrismedCashe for State.
  • تحتاج إلى تمرير البيانات داخل خط الأنابيب فقط؟ HttpContex.Ims.
  • هل تحتاج إلى مخبأ من أجل البحث المحتسب؟ أنا أُذكّر كاش محليًّا؛ هويّة محلّى كاش عبر مزرعة.

APIS: نفس الأسس، أشكال مختلفة

جميع الرزمات الثلاثة تقع على نفس البدائيات (HttpConstex, نموذج ملزم, auth, protection).

  • الحد الأدنى من الأرقام القياسية لأسعار الإنتاج: البارامتر الملزم من المسار/الضغط/الجسم/المطالبات؛ العائد؛ Results.*.
  • MVC: خواص، مرشحات، نموذج ملزم في بارامترات/نماذج عملية.
  • صفحات الرازرة: منافذ الصفحات ذات الخواص المقيدة ومساعِدات الوسم لتوليد الوصلات/الأشكال.

وجميعهم يشتركون في نفس آليات الولاية التي نوقشت هنا.


المُصَلِّات والمُضَدَّات

  • تخزين بيانات كبيرة أو حساسة في البسكويت أو TempData.
  • اعتماداً على مخبأ الصواب في الذاكرة الداخلية (إنه مخبأ وليس حقيقة).
  • الإذن بالبناء على أساس الطريق/أعلام الأسلاك التي يرسلها العميل دون التحقق من الخواديم.
  • أو JWTs مع مطالبات متقلبة.
  • دورة بدون استراتيجية توزيع (الأعمال المحلية، والفرق على نطاق واسع).

إدارة الدولة: ما الذي أستخدمه في الواقع (ولا أستعمله)

بعد عرض كل هذه الخيارات، هذا هو تقييمي الصادق لما يعمل في الإنتاج من أجل منصة مدونتي.

رزمة إدارة الدولة (حسب ترتيب التردد)

• الآلية / التواتر / استخدام القضايا / الترضية |-----------|-----------|-----------|--------------| | المُقَرَّخ تصنيفات، ومقاييس، ومهام الترجمة التحريرية العالية جداً | مخرجات المجلج "مدوّنات عالية الجودة" "مدوّنات مقوّمة" "قوائم" "فائز كبير جداً" | المُلْخِل المُرْجِل TTTP عالي الـ HTTP cascing headers o يعمل مع مخرجات Cache o | **** □ متوسط □ إعدادات التحليل، عناوين الصفحات □ □ على ما يرام للاشياء البسيطة □ | ألف- المطالبات □ هوية مستعمل متوسطة، علم إداري □ أداة سليمة للصدى □ | الطريق/الربع □ متوسط □ منخفضات ومرشحات ورصاصات □ عديمة الجنسية وقابلة للوصل □ | قاعدة بيانات • الوظائف، والتعليقات، ومصدر الحقيقة | **** أفضليات مستعملين منخفضة (في المستقبل) لم تكن هناك حاجة لها بعد | ثانياً - الدورة * لم يسبق أبداً أبداً - لا يوجد تجهيزات تخزين موزعة* | لا شيء * أبداً أبداً - يزيل HTMX الحاجة * | HTTTpConCon conconcing. Ims لم يسبق أبداً لم أستفيد من قضية | الاسم المُعَلَف كِصِص * أبداً - خادوم وحيد (في الوقت الحاضر) *

مصنفات/مصنفات من الخبرة في مجال الإنتاج

المُذْكَرَ تَرْكِيخ

ما أستخدمه من أجله:

  • قائمة الفئات (العالم، 30 دقيقة TTL)
  • تتبع مهام الترجمة التحريرية لكل مستعمل من مستعملي الترجمة التحريرية لكل عملية من عمليات التتبع (6 ساعة مطلقة + 1 ساعة انزلاق)
  • الردود على الاستبيانات البينية الخارجية (قياسات أمامي، 1h TTL)

في الممارسة العملية:

  • (يجرى تشغيله)
  • نفقات عامة
  • انخفاض حمولة DB/API بنسبة حوالي 95 في المائة
  • المشاريع التي يمكن تنفيذها وتفهمها
  • سِرِّيلوج- سِرّيّ

:أنا أَضْربُ:

  • تسربات ذاكرة ذاكرة إذا لم تحدّد لمستودعات المستخدم
  • تم مسحه على تطبيق إعادة بدء التشغيل (مقبول لـ استخدامي حالة)
  • لا يتقاسم بين الخواديم (البيان لكل حالة على حدة)
  • خانة الإحرف هو دليل دليل (يلزم لإزالة () بشكل صريح)

حدِّ الترويسة: إذا وصلت إلى خوادم متعددة، فسأحتاج لـ حالة متشاركة لـ الآن خادم مفرد مخبئ ذاكرة مثالي.

مخرجات الملف + المُستَرَج المُسْتَرَجِج

ما أستخدمه من أجله:

  • عملية إنجاز المهام المتصلة بحرف المهمة (خادم ساعة واحدة، و 5 دقائق لعميل/معونة واحدة)
  • القائمة/الصفحات
  • البيانات الإحصائية

في الممارسة العملية:

  • سرعة سرعة 25x (50ms / 2ms)
  • معدلات ارتفاع حركة المرور دون أن تكسر عرقاً
  • يعمل بشكل سلس مع HTMX (VarByheader)
  • التأثير القابل للقياس عن طريق قياسات بروميثيوس

:أنا أَضْربُ:

  • تأجيل التأشّش (النسيان أنّ المخبأ على)
  • فشل في مشرشر مع العديد من مؤمثات
  • نافذة دراجات (خمس دقائق للزبائن)
  • الحاجة إلى إلغاء تحديثات المحتوى يدوياً

أفضل من أجل: تطبيقات مثقلة بالقراءة مع المحتوى الثابت في الغالب. ليست جيدة للبيانات الشخصية أو في الوقت الحقيقي.

ازج/ o

ما أستخدمه من أجله:

  • بيانات التصميم العالمي (التحليلات، الفئات)
  • عنوان العنوان

في الممارسة العملية:

  • البيانات المبسطة والسريعة للبيانات التصميمية
  • ضبط مرة في القاعدة، موجود في كل مكان
  • أشغال الغرامات مع قائمة الفئات المخبئة

:أنا أَضْربُ:

  • لا نوع السلامة (الفشل في وقت التنفيذ)
  • الإغراء بالإفراط في استخدام البيانات المعقدة
  • منرد إلى اختبار

المادة الأولى تنص على ما يلي: عرض باج لـ بسيط قياسي فقط كائنات معقّد بوصة عرض موجّهات.

ألف- المطالبات المتعلقة بالآف

ما أستخدمه من أجله:

  • رقم المستخدم، الاسم، البريد الإلكتروني، anvatar UR UR
  • علم الإدارة (التدقيق) sub مـن المطالبـة

في الممارسة العملية:

  • أمن (البسكويـة الموقـعـة، وحمايـة البيانات)
  • ASP.net
  • يُتاح User.Claims في كل مكان
  • يُدخل المستخدمون في

:أنا أَضْربُ:

  • الحد الأقصى لحجم الكعكة (لا تُبالغ في المطالبات)
  • (أ) المطالبات التي تكون ثابتة إلى حين إعادة
  • إذا أضفت مطالبة (مثل "As "Dititor")، في حاجة إلى إعادة إصدار auth الكعكة

الممارسات الفضلى: لا تضع بيانات متواترة التغير في المطالبات

أشياء لا أستخدمها (ولماذا)

(لم يُستخدم بعد):

  • SSS حاجة توزيع مخزن (red)
  • يُضاف تعقيد لأدنى حد أدنى من المنفعة
  • حالات استخدامي أفضل من خلال:
    • المطالبات ASAth (الهويت)
    • (الولاية القصيرة العمر)
    • قاعدة بيانات (حالة دائمة)

15/م/م أ/م/م/م/م أ/م/م/م أ/م/م أ/م/م أ/م/م/م أ/م/م أ/م/م/م أ/م/ك/م أ/م/م/ك/م/م/ك/م/م/ك/م/م/مم أم/م/ع/م/م/م أم/م/م/ع/م/م/ ع/م/م ع/ ممم/ ع/ م مم/ ع/م/ ع:

  • HTMX مُزالة ما بعد- إعادة توجيه- الحصول على نمط
  • الاستمارات التي ترجع فيها آراء جزئية مع رسائل في الخط
  • لا حاجة إلى البقاء على قيد الحياة

HttpContext. Ims (لم يُستخدم بعد):

  • لم يكن لديك حالة استخدام لحالة طلب الطلب
  • واحتي الوسيطة لا تقوم بضبط قيم المتحكمات
  • إذا كنت بحاجة للكشف عن المستأجرين، وأود أن استخدمه

المحلات المُعَيّل كِشِش (لم يُستخدَم قط):

  • يجري نشر وحدة واحدة
  • المُقَدَّس يُلبّي كُلّ إحتياجات
  • سيستخدم CRCS إذا قمت بالتدرج إلى خادوم متعدد

تطور نهجي

المرحلة الأولى (الرحلة الأولى): كل طلب إصطدم بقاعدة البيانات وأعطى علامة، عمل بشكل جيد لقلة حركة المرور.

المرحلة 2 (الترتيب الأمثل الأول): مضاف IMMMoryCaches لـ فئات. شاهد الانتقاص الفوري من حمولة DB. أبقى الأمر بسيطا: 30 دقيقة TTL، لا منطق فاخر.

المرحلة 3 (التصعيد): إضافة مُضاف مخرجات لـ مدوّنات لـ مدوّنات لـ مُستخدَم أداء خطأ الأوّلي مخبئ لـ 5 دقائق فقط زيادة إلى ساعة بعد رصد المحتوى يُظهِر تغيراً نادراً.

المرحلة 4 (الملاحظات): مُضِف SeriilogTrap إلى المخبأ المترّسِر المخبأ المُخبِّر المُسْتَلَسِر المُخْرِفُ المُخْرَفَات كَانَتْ مُثْرِلًا لـ التاريخ المُنتفِرَفَة في المُمَثِّلَات. yyyyMMdd معدل الاصطدام ارتفع من 60% إلى 95%

المرحلة 5 (التكامل HTMX): VaryByHeader - - - - - - - - - - - - - hx-requestنسيت هذا في البداية وقدمت صفحات كاملة إلى طلبات HTMX.

الحالة الحالية: سعيد مع رزمة I memoryCacke + productCacke + responced Cache التعامل مع 98٪ من احتياجاتي من إدارة الولاية. قاعدة بيانات الحالة الدائمة. Athth يَدّعي للهوية. هذا هو.

تُسَرْ لِتَعْيَزَ لِتَعْلَكَ

بدء التشغيل هنا::

  1. رسم الطريق/بارامترات الماء للملاحة/الملاحة/المراجح (الطريقان الأولان لعديمي الجنسية)
  2. ألف - المطالبات المتعلقة بهويت
  3. قاعدة بيانات لأي شيء يجب أن يستمر
  4. INMMDEOR Cchase for re-------------------------------------------------------------------------------------------------------------------------
  5. عدد الصفحات التي تُدفع فيها تكلفة الصفحات

ما إذا كان هناك حاجة إلى ما يلي: الجلسة 6 (فقط إذا كان يجب أن يكون لديك حالة محادثية في جانب الخادم) كاش (فقط عندما تتدرج إلى خوادم متعددة) 8 - الكعكات (للأفضلات، والرضا، والرضا، جانب العميل)

المُسَجَّل:

  • الأجسام المعقّدة في معرضباق/TempData
  • الدورة التي لا تُوزَّع على مُنْخَزَفًا
  • إعداد البيانات الخاصة بمستخدم محدد أو البيانات التي تتواتر فيها التغييرات
  • (التدبير أولاً)

(أ) الدروس المستفادة:

  • فقط عندما تظهر القياسات أنك تحتاج إليها
  • المراقبة مهمة جداً (اعلموا ان معدل ضربات مخابئكم ! )
  • التفكير في حدود النطاقات في وقت مبكر
  • (الأسس هي مشكلة حقيقية)
  • مستند خيارات TTL الخاص بك (المستقبل سوف تسأل "لماذا 30 دقيقة؟")

الدولة في تطبيقات الويب ليست واحدة تناسب الجميع. اختر الخيار الأضيق الذي يلبي احتياجاتك، تفضل أنماط عديمة الجنسية عندما تستطيع، وكن صريحاً حول الأمن ودورة الحياة.

إذا أردت أن تتعمق أكثر في كيفية تدفق هذه القطع عبر خط الأنابيب، انظر سلسلتي تبدأ مع الجزء 1: لمحة عامة ومؤسسة و خصوصاً المنتصف و الأجزاء الموجّهة

بناء بناء سعيد.


التذييل: أمثلة يمكن الحصول عليها (الواردات)

هذه الأمثلة تُعمّق الأجزاء السابقة مع تفاصيل الإنتاج Quester production production checks يمكنك لصقها في قوالب الحد الأدنى الصافي 9، MVC، أو صفحات الرازور.

حماية القيم مع حماية البيانات

using Microsoft.AspNetCore.DataProtection;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDataProtection();
var app = builder.Build();

app.MapPost("/prefs/secure/{value}", (HttpContext ctx, string value, IDataProtectionProvider dp) =>
{
    var protector = dp.CreateProtector("prefs.theme");
    var protectedValue = protector.Protect(value);
    ctx.Response.Cookies.Append("pref.theme.p", protectedValue, new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Lax,
        Expires = DateTimeOffset.UtcNow.AddYears(1)
    });
    return Results.Ok();
});

app.MapGet("/prefs/secure", (HttpContext ctx, IDataProtectionProvider dp) =>
{
    if (ctx.Request.Cookies.TryGetValue("pref.theme.p", out var v))
    {
        var protector = dp.CreateProtector("prefs.theme");
        return Results.Text(protector.Unprotect(v));
    }
    return Results.NotFound();
});

تلميح: في عمليات النشر المتعددة الأنثوندات، تستمر مفاتيح حماية البيانات (على سبيل المثال، إلى نظام ملفات مشترك، أو Redis، أو Azure Key Voot) بحيث يمكن قراءة الكعك عبر الحالات.

Antiforforgery في MVC، وصحائف الرازز، ومؤشرات الحد الأدنى

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddAntiforgery(o => o.HeaderName = "X-CSRF-TOKEN");
var app = builder.Build();

app.MapGet("/antiforgery/token", (IAntiforgery af, HttpContext ctx) =>
{
    var tokens = af.GetAndStoreTokens(ctx);
    return Results.Json(new { token = tokens.RequestToken });
});

app.MapPost("/submit", (HttpContext ctx) => Results.Ok("posted"))
   .AddEndpointFilter(async (efiContext, next) =>
   {
       var af = efiContext.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
       await af.ValidateRequestAsync(efiContext.HttpContext);
       return await next(efiContext);
   });

app.MapControllers();
app.MapRazorPages();
  • MVCC: إجراءات تزني مع [ValidateAntiForgeryToken] الاستخدام والاستخدام @Html.AntiForgeryToken() أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً أو أشكالاً
  • صفحات الرازض: ممكّنة بالافتراض في شكل الوظائف؛ الاستخدام asp-antiforgery="true" في حالة الضرورة.
  • الحد الأدنى: التحقق عن طريق IAntiforgery كما هو مبين.

مع إعادة التقدير والإنزلال vs statal

builder.Services.AddStackExchangeRedisCache(o => o.Configuration = "localhost:6379");
builder.Services.AddSession(o =>
{
    o.IdleTimeout = TimeSpan.FromMinutes(20); // sliding
    o.IOTimeout = TimeSpan.FromSeconds(2);
    o.Cookie.HttpOnly = true;
    o.Cookie.IsEssential = true;
});
var app = builder.Build();
app.UseSession();

اخزن البيانات الصغيرة والضاغطة فقط واثبت العربات/الطلبات الحقيقية لـ DB

المراسلة مع الدخول خيارات وإخلاء إعادة طلب

builder.Services.AddMemoryCache();

app.MapGet("/fx/{pair}", (IMemoryCache cache, string pair) =>
{
    var key = $"fx:{pair.ToLowerInvariant()}";
    return Results.Ok(cache.GetOrCreate(key, entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        entry.SlidingExpiration = TimeSpan.FromMinutes(2);
        entry.Size = 1; // enable size-based eviction if configured
        entry.RegisterPostEvictionCallback((k, v, reason, state) =>
        {
            Console.WriteLine($"Evicted {k} because {reason}");
        });
        return 0.92m; // fetch from external service in real life
    }));
});

"المُعَدّات المُعَدّة المُعَدّة المُعَدّة المُتَحَجِّز إلى تَجنّب الخواتم"

builder.Services.AddStackExchangeRedisCache(o => o.Configuration = "localhost:6379");

app.MapGet("/feature/{name}", async (IDistributedCache cache, string name) =>
{
    var key = $"feat:{name}";
    var cached = await cache.GetStringAsync(key);
    if (cached is not null) return Results.Text(cached);

    // Lock key to prevent thundering herd (very simple approach)
    var lockKey = key + ":lock";
    var gotLock = await cache.SetStringAsync(lockKey, "1", new DistributedCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)
    });

    try
    {
        cached = await cache.GetStringAsync(key);
        if (cached is null)
        {
            var computed = "on"; // expensive work
            var rnd = Random.Shared.Next(0, 15); // jitter
            await cache.SetStringAsync(key, computed, new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(rnd))
            });
            cached = computed;
        }
    }
    finally
    {
        await cache.RemoveAsync(lockKey);
    }

    return Results.Text(cached);
});

بالنسبة للقفل القوي، يفضل بدائيات Redis (SET NX EX) عن طريق takexchange. Redis.

القضايا والمصادقة محلياً (deem)

using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;

var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("super-secret-key-please-rotate"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            IssuerSigningKey = key,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true
        };
    });
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

app.MapPost("/token", () =>
{
    var claims = new[] { new Claim(ClaimTypes.Name, "alice") };
    var jwt = new JwtSecurityToken(claims: claims, expires: DateTime.UtcNow.AddMinutes(30), signingCredentials: creds);
    var token = new JwtSecurityTokenHandler().WriteToken(jwt);
    return Results.Json(new { access_token = token });
});

app.MapGet("/who", [Microsoft.AspNetCore.Authorization.Authorize] () => "ok");

تحديثات مشروطية مع ETAgs (إذا IF

record Todo(int Id, string Title, string Version);
var store = new Dictionary<int, Todo> { [1] = new(1, "Ship", "v1") };

app.MapGet("/todo/{id:int}", (int id, HttpContext ctx) =>
{
    if (!store.TryGetValue(id, out var t)) return Results.NotFound();
    ctx.Response.Headers.ETag = t.Version;
    return Results.Json(t);
});

app.MapPut("/todo/{id:int}", (int id, HttpContext ctx, Todo input) =>
{
    if (!store.TryGetValue(id, out var current)) return Results.NotFound();
    var ifMatch = ctx.Request.Headers["If-Match"].ToString();
    if (string.IsNullOrEmpty(ifMatch) || ifMatch != current.Version)
        return Results.StatusCode(StatusCodes.Status412PreconditionFailed);

    var next = current with { Title = input.Title, Version = $"v{DateTime.UtcNow.Ticks}" };
    store[id] = next;
    ctx.Response.Headers.ETag = next.Version;
    return Results.Ok(next);
});

THMDATA: أجسام معقدة عن طريق JSON

public static class TempDataJsonExtensions
{
    public static void Put<T>(this ITempDataDictionary tempData, string key, T value)
        => tempData[key] = System.Text.Json.JsonSerializer.Serialize(value);

    public static T? Get<T>(this ITempDataDictionary tempData, string key)
        => tempData.TryGetValue(key, out var o) && o is string s
           ? System.Text.Json.JsonSerializer.Deserialize<T>(s)
           : default;
}

// Usage in MVC action
TempData.Put("WizardState", new { Step = 2, Name = "Alice" });
var state = TempData.Get<dynamic>("WizardState");

EF

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    [Timestamp] public byte[] RowVersion { get; set; } = default!;
}

// On update
try
{
    await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    return Results.StatusCode(StatusCodes.Status412PreconditionFailed);
}

المطالبات المعجِّرة عن طريق إعادة تهويـة إصدار الكعكة

app.MapPost("/promote", async (HttpContext ctx) =>
{
    var u = ctx.User;
    var claims = u.Claims.ToList();
    claims.Add(new Claim(ClaimTypes.Role, "Editor"));
    var id = new ClaimsIdentity(claims, "Cookies");
    await ctx.SignInAsync("Cookies", new ClaimsPrincipal(id));
    return Results.Ok();
});

وهذا من شأنه أن يغطي الثغرات: حالات قصور أمني أقوى، والتأهب المتعدد الأنسانود، وأنماط عالمية حقيقية للمخابئ، والعلامات، والطلبات المشروطة.


D العميق Dve dve: HttpCon tex. Ailems (الأنماط العملية والمعونات)

Http construct. Items عبارة عن حقيبة لكل طلب (IDictionary<object, object?>) تعيش فقط مدى عمر طلب واحد واحد. إنها مثالية لنقل القيم المحسوبة من البرمجيات المتوسطة/المرشحات إلى نقاط النهاية الخاصة بك، وأجهزة التحكم، ومعاملات صفحات المزالق بدون لمس حالة عالمية أو مخازن طويلة العمر.

  • دورة الحياة: تُنشأ بناء على الطلب؛ تُطرح عند اكتمال الاستجابة.
  • النطاق: الطلب الحالي فقط - لا يعبر أبداً عمليات إعادة توجيه أو أعمال أساسية.
  • الأداء: نظرة O(1)؛ حالة مثالية لكل طلب.
  • السلامة: جانب الخادوم فقط؛ غير مرئي للعميل.

لماذا البنود بدلاً من

  • الدورة/TempData: تلك الطلبات المتقاطعة وتطرح الشواغل المتعلقة بالتوزيع.
  • الخدمات DIWSID: استخدم هذه من أجل السلوك والاعتمادات المشتركة. البنود أفضل من أجل القيم المخصصة، المحسوبة (المستأجرة، ومحلات المستخدم، والأعلام المميزة) ومخابئ كل طلب.
  • الخصائص: بالنسبة للسمات الإطارية/المستويات على مستوى النقل (EndgemongFeature, IHttp UppcrealFeature). البنود هي بيانات المستوى التطبيقي.

المواضع الرئيسية التي يُحتمل أن تحدث فيها تصادات رئيسية:

لأن البنود تستخدم مفاتيح كائنات أو تفضل مفاتيح كائنات ثابتة خاصة أو نوع مفتاح مخصص لتجنب الاصطدامات بالاسم.

public static class ItemKeys
{
    public static readonly object TenantId = new();
    public static readonly object UserLocale = new();
    public static readonly object PerRequestCache = new();
}

أو إنشاء a مع:

public static class HttpContextItemsExtensions
{
    public static void Set<T>(this HttpContext ctx, object key, T value)
        => ctx.Items[key] = value!;

    public static T? Get<T>(this HttpContext ctx, object key)
        => ctx.Items.TryGetValue(key, out var v) ? (T?)v : default;

    public static T GetOrCreate<T>(this HttpContext ctx, object key, Func<T> factory)
    {
        if (ctx.Items.TryGetValue(key, out var existing) && existing is T typed)
            return typed;
        var created = factory();
        ctx.Items[key] = created!;
        return created;
    }
}

الأنماط: احتساب في الخوادم الوسطى، الاستهلاك في نقاط النهاية/المتحكمين/الصفحات

// Program.cs
app.Use(async (ctx, next) =>
{
    var tenant = ctx.Request.Headers["X-TenantId"].FirstOrDefault() ?? "public";
    ctx.Set(ItemKeys.TenantId, tenant); // using extension above

    // Per-request cache holder (optional)
    ctx.Set(ItemKeys.PerRequestCache, new Dictionary<string, object?>());

    await next(ctx);
});

// Minimal API
app.MapGet("/whoami", (HttpContext ctx) => new
{
    Tenant = ctx.Get<string>(ItemKeys.TenantId),
});

// MVC Controller
public IActionResult WhoAmI()
    => Json(new { Tenant = HttpContext.Get<string>(ItemKeys.TenantId) });

// Razor Page handler
public IActionResult OnGet()
    => new JsonResult(new { Tenant = HttpContext.Get<string>(ItemKeys.TenantId) });

النمط: المخبأ الذي طُلب فيه لتجنب تكرار العمل

استخدام الأصناف كمخابئ صغير جداً يُقرأ في نفس الطلب أنها لا تعيد تشغيل قواعد البيانات/الخدمات.

public static class PerRequestCacheExtensions
{
    public static async Task<T> GetOrAddAsync<T>(this HttpContext ctx, string key, Func<Task<T>> factory)
    {
        var bag = ctx.Get<Dictionary<string, object?>>(ItemKeys.PerRequestCache)
                  ?? ctx.GetOrCreate(ItemKeys.PerRequestCache, () => new Dictionary<string, object?>());

        if (bag.TryGetValue(key, out var val) && val is T hit)
            return hit;

        var created = await factory();
        bag[key] = created!;
        return created;
    }
}

// Usage in endpoint
app.MapGet("/profile", async (HttpContext ctx, IUserRepo repo) =>
{
    var userId = ctx.User.Identity?.Name ?? "anon";
    var profile = await ctx.GetOrAddAsync($"profile:{userId}", () => repo.LoadAsync(userId));
    return Results.Json(profile);
});
  • حوار: طلب واحد ينفذ عادة على مسار منطقي واحد؛ عناصر ليس آمن الخيط للكتابات المتوازية. إذا بدأت في تشغيل المهام المتوازية التي تشترك في العناصر، اضف مزامنتك الخاصة.
  • الحجم: الحفاظ على قيم صغيرة ورخيصة للحساب/التسلسل.

لم يطرأ تغيير: المرشحات التي تملأ بنوداً (MVC/Razor Pages)

public class TenantFilter : IAsyncResourceFilter
{
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        var tenant = context.HttpContext.Request.Headers["X-TenantId"].FirstOrDefault() ?? "public";
        context.HttpContext.Set(ItemKeys.TenantId, tenant);
        await next();
    }
}

// Register filter globally
services.AddControllersWithViews(o => o.Filters.Add<TenantFilter>());

الأنماط: جذوع الأشجار الغنية بدون مخصصات في كل مكان

الحساب مرة واحدة، ثم قراءة في نطاقات قطع الأشجار أو البرمجيات الوسطى.

app.Use(async (ctx, next) =>
{
    var correlationId = ctx.Request.Headers["X-Correlation-Id"].FirstOrDefault() ?? Guid.NewGuid().ToString("n");
    ctx.Items["CorrelationId"] = correlationId; // string key acceptable for app-local use

    using (logger.BeginScope(new { CorrelationId = correlationId }))
    {
        await next(ctx);
    }
});

إلى استخدام بنود

  • البيانات المطلوبة بعد إعادة توجيهها أو عبر الطلبات (استخدام TemData/Session/DB بدلاً من ذلك).
  • (ب) أو مخابئ الطلبات أو مخابئ الطلبات الشاملة للتصنيف (الاستخدام الأول للمجلّد/المنسّق).
  • (ج) القيم التي تنتمي إلى الهوية/الإذن (مطالبات الاستخدام/التبعات).

القاعدة السريعة: إذا تم حسابها خلال هذا الطلب وقراءتها في إطار هذا الطلب بواسطة رمزك، فإن البنود تكون مثالية.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.