44 KiB
📄 Dynamic Content Pages System - Implementation Plan
نگاه کلی
یک سیستم مدیریت محتوای پویا (Dynamic CMS) برای مدیریت صفحات استاتیک مانند:
- درباره ما (About Us)
- تماس با ما (Contact Us)
- قوانین و مقررات (Terms & Conditions)
- حریم خصوصی (Privacy Policy)
- سوالات متداول (FAQ)
- و هر صفحه دلخواه دیگر
ویژگیهای کلیدی
- ✅ Flexible Structure: هر صفحه میتواند چندین بخش (Section) داشته باشد
- ✅ Multi-Section Types: Hero, TextBlock, ImageGallery, ContactForm, TeamMembers, FAQ, etc.
- ✅ JSON-Based Content: محتوای هر Section به صورت JSON ذخیره میشود
- ✅ Reorderable: امکان تغییر ترتیب Sections با Drag & Drop
- ✅ SEO-Friendly: Meta Tags, Structured Data, Sitemap
- ✅ Multi-Language Ready: آماده برای چند زبانه (فارسی/انگلیسی)
- ✅ BackOffice Management: مدیریت کامل از پنل ادمین
معماری سیستم
┌─────────────────────────────────────────────────────────────┐
│ ContentPage Table │
├─────────────────────────────────────────────────────────────┤
│ Id, Slug (about-us), Title, MetaDescription, │
│ IsActive, IsPublic, SortOrder, Created, CreatedBy │
└────────────────────┬────────────────────────────────────────┘
│ 1-to-Many
▼
┌─────────────────────────────────────────────────────────────┐
│ ContentSection Table │
├─────────────────────────────────────────────────────────────┤
│ Id, PageId (FK), SectionType (Enum), Title, │
│ Content (JSON), SortOrder, IsActive, Created │
└─────────────────────────────────────────────────────────────┘
SectionType Enum:
- Hero
- TextBlock
- ImageGallery
- ContactForm
- TeamMembers
- FAQ
- Map
- SocialMedia
مراحل پیادهسازی (7 مرحله)
✅ Phase 1: Domain Layer - Entities
مسیر: CMS/src/CMSMicroservice.Domain/Entities/Content/
1.1 Create ContentPage.cs
namespace CMSMicroservice.Domain.Entities.Content;
public class ContentPage : BaseAuditableEntity
{
public long Id { get; set; }
// Core Properties
public string Slug { get; set; } = string.Empty; // "about-us", "contact-us"
public string Title { get; set; } = string.Empty; // "درباره ما"
public string? Subtitle { get; set; } // اختیاری
// SEO Properties
public string? MetaDescription { get; set; }
public string? MetaKeywords { get; set; }
public string? OgImage { get; set; } // Open Graph Image
// Display Properties
public bool IsActive { get; set; } = true;
public bool IsPublic { get; set; } = true; // نمایش در منو/سایت
public int SortOrder { get; set; } = 0; // ترتیب نمایش در منو
// Navigation Property
public virtual ICollection<ContentSection> Sections { get; set; } = new List<ContentSection>();
}
1.2 Create ContentSection.cs
namespace CMSMicroservice.Domain.Entities.Content;
public class ContentSection : BaseAuditableEntity
{
public long Id { get; set; }
// Foreign Key
public long PageId { get; set; }
public virtual ContentPage Page { get; set; } = null!;
// Section Properties
public SectionType SectionType { get; set; }
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = "{}"; // JSON Content
// Display Properties
public int SortOrder { get; set; } = 0;
public bool IsActive { get; set; } = true;
}
1.3 Create SectionType.cs (Enum)
namespace CMSMicroservice.Domain.Entities.Content;
public enum SectionType
{
Hero = 0, // بنر بزرگ با عکس و متن
TextBlock = 1, // بلوک متنی ساده (HTML)
ImageGallery = 2, // گالری تصاویر
ContactForm = 3, // فرم تماس
TeamMembers = 4, // اعضای تیم
FAQ = 5, // سوالات متداول
Map = 6, // نقشه (Google Maps)
SocialMedia = 7, // لینکهای شبکههای اجتماعی
VideoEmbed = 8, // ویدیو Embed
Testimonials = 9, // نظرات مشتریان
Features = 10, // ویژگیها (با آیکون)
Statistics = 11, // آمار (عدد + توضیح)
Timeline = 12, // تایملاین (تاریخچه)
CallToAction = 13 // دکمه Call-to-Action
}
فایلهای ایجاد شده:
- ✅
ContentPage.cs - ✅
ContentSection.cs - ✅
SectionType.cs
✅ Phase 2: Infrastructure Layer - EF Core Configurations
مسیر: CMS/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/
2.1 Create ContentPageConfiguration.cs
using CMSMicroservice.Domain.Entities.Content;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content;
public class ContentPageConfiguration : IEntityTypeConfiguration<ContentPage>
{
public void Configure(EntityTypeBuilder<ContentPage> builder)
{
builder.ToTable("ContentPages", "CMS");
builder.HasKey(x => x.Id);
builder.Property(x => x.Slug)
.IsRequired()
.HasMaxLength(100);
builder.Property(x => x.Title)
.IsRequired()
.HasMaxLength(200);
builder.Property(x => x.Subtitle)
.HasMaxLength(500);
builder.Property(x => x.MetaDescription)
.HasMaxLength(500);
builder.Property(x => x.MetaKeywords)
.HasMaxLength(300);
builder.Property(x => x.OgImage)
.HasMaxLength(500);
// Unique constraint on Slug
builder.HasIndex(x => x.Slug)
.IsUnique();
// Index for active pages
builder.HasIndex(x => new { x.IsActive, x.IsPublic, x.SortOrder });
// Relationship
builder.HasMany(x => x.Sections)
.WithOne(x => x.Page)
.HasForeignKey(x => x.PageId)
.OnDelete(DeleteBehavior.Cascade);
}
}
2.2 Create ContentSectionConfiguration.cs
using CMSMicroservice.Domain.Entities.Content;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content;
public class ContentSectionConfiguration : IEntityTypeConfiguration<ContentSection>
{
public void Configure(EntityTypeBuilder<ContentSection> builder)
{
builder.ToTable("ContentSections", "CMS");
builder.HasKey(x => x.Id);
builder.Property(x => x.PageId)
.IsRequired();
builder.Property(x => x.SectionType)
.IsRequired();
builder.Property(x => x.Title)
.IsRequired()
.HasMaxLength(200);
builder.Property(x => x.Content)
.IsRequired()
.HasColumnType("nvarchar(max)"); // JSON Storage
builder.Property(x => x.SortOrder)
.IsRequired();
// Index for querying sections by page
builder.HasIndex(x => new { x.PageId, x.SortOrder });
// Index for section type filtering
builder.HasIndex(x => x.SectionType);
}
}
فایلهای ایجاد شده:
- ✅
ContentPageConfiguration.cs - ✅
ContentSectionConfiguration.cs
✅ Phase 3: Update ApplicationDbContext
مسیر: CMS/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
3.1 Add DbSets
// در بخش DbSets اضافه شود (بعد از خط 85):
// ============= Content Management System DbSets =============
public DbSet<ContentPage> ContentPages => Set<ContentPage>();
public DbSet<ContentSection> ContentSections => Set<ContentSection>();
3.2 Configuration Auto-Discovery
Configuration ها به صورت خودکار از Assembly کشف میشوند (قبلاً پیادهسازی شده):
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
// ...
}
تغییرات:
- ✅ 2 DbSet جدید اضافه شد
✅ Phase 4: Database Migration
مسیر: CMS/src/CMSMicroservice.Infrastructure/
4.1 Create Migration
cd /home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Infrastructure
dotnet ef migrations add AddDynamicContentSystem --startup-project ../CMSMicroservice.WebApi
4.2 Review Migration
بررسی فایل Migration ایجاد شده:
Migrations/YYYYMMDD_AddDynamicContentSystem.cs
4.3 Apply Migration
dotnet ef database update --startup-project ../CMSMicroservice.WebApi
نتیجه:
- ✅ جدول
[CMS].[ContentPages]ایجاد شد - ✅ جدول
[CMS].[ContentSections]ایجاد شد - ✅ Indexes و Constraints اعمال شدند
✅ Phase 5: Application Layer - CQRS
مسیر: CMS/src/CMSMicroservice.Application/ContentCQ/
5.1 Queries
5.1.1 GetContentPageBySlug
ContentCQ/
├── Queries/
│ └── GetContentPageBySlug/
│ ├── GetContentPageBySlugQuery.cs
│ ├── GetContentPageBySlugQueryHandler.cs
│ ├── GetContentPageBySlugQueryValidator.cs
│ └── GetContentPageBySlugResponseDto.cs
Query:
public record GetContentPageBySlugQuery(string Slug) : IRequest<GetContentPageBySlugResponseDto>;
Handler:
public class GetContentPageBySlugQueryHandler : IRequestHandler<GetContentPageBySlugQuery, GetContentPageBySlugResponseDto>
{
private readonly IApplicationDbContext _context;
public async Task<GetContentPageBySlugResponseDto> Handle(...)
{
var page = await _context.ContentPages
.Include(x => x.Sections.Where(s => s.IsActive))
.FirstOrDefaultAsync(x => x.Slug == request.Slug && x.IsActive);
// Map to DTO
}
}
5.1.2 GetAllContentPages (for BackOffice)
ContentCQ/
├── Queries/
│ └── GetAllContentPages/
│ ├── GetAllContentPagesQuery.cs
│ ├── GetAllContentPagesQueryHandler.cs
│ └── GetAllContentPagesResponseDto.cs
5.2 Commands
5.2.1 CreateContentPage
ContentCQ/
├── Commands/
│ └── CreateContentPage/
│ ├── CreateContentPageCommand.cs
│ ├── CreateContentPageCommandHandler.cs
│ ├── CreateContentPageCommandValidator.cs
│ └── CreateContentPageResponseDto.cs
5.2.2 UpdateContentPage
ContentCQ/
├── Commands/
│ └── UpdateContentPage/
│ ├── UpdateContentPageCommand.cs
│ ├── UpdateContentPageCommandHandler.cs
│ ├── UpdateContentPageCommandValidator.cs
│ └── UpdateContentPageResponseDto.cs
5.2.3 DeleteContentPage
ContentCQ/
├── Commands/
│ └── DeleteContentPage/
│ ├── DeleteContentPageCommand.cs
│ └── DeleteContentPageCommandHandler.cs
5.2.4 AddContentSection
ContentCQ/
├── Commands/
│ └── AddContentSection/
│ ├── AddContentSectionCommand.cs
│ ├── AddContentSectionCommandHandler.cs
│ ├── AddContentSectionCommandValidator.cs
│ └── AddContentSectionResponseDto.cs
5.2.5 UpdateContentSection
ContentCQ/
├── Commands/
│ └── UpdateContentSection/
│ ├── UpdateContentSectionCommand.cs
│ ├── UpdateContentSectionCommandHandler.cs
│ ├── UpdateContentSectionCommandValidator.cs
│ └── UpdateContentSectionResponseDto.cs
5.2.6 DeleteContentSection
ContentCQ/
├── Commands/
│ └── DeleteContentSection/
│ ├── DeleteContentSectionCommand.cs
│ └── DeleteContentSectionCommandHandler.cs
5.2.7 ReorderContentSections
ContentCQ/
├── Commands/
│ └── ReorderContentSections/
│ ├── ReorderContentSectionsCommand.cs
│ └── ReorderContentSectionsCommandHandler.cs
کل فایلهای Application Layer: ~21 فایل
✅ Phase 6: API Layer - Protobuf & gRPC
مسیر: CMS/src/CMSMicroservice.Protobuf/Protos/
6.1 Create content.proto
syntax = "proto3";
option csharp_namespace = "CMSMicroservice.Protobuf.Protos.Content";
package content;
import "google/protobuf/timestamp.proto";
// ============= Services =============
service ContentService {
rpc GetContentPageBySlug(GetContentPageBySlugRequest) returns (GetContentPageBySlugResponse);
rpc GetAllContentPages(GetAllContentPagesRequest) returns (GetAllContentPagesResponse);
rpc CreateContentPage(CreateContentPageRequest) returns (CreateContentPageResponse);
rpc UpdateContentPage(UpdateContentPageRequest) returns (UpdateContentPageResponse);
rpc DeleteContentPage(DeleteContentPageRequest) returns (DeleteContentPageResponse);
rpc AddContentSection(AddContentSectionRequest) returns (AddContentSectionResponse);
rpc UpdateContentSection(UpdateContentSectionRequest) returns (UpdateContentSectionResponse);
rpc DeleteContentSection(DeleteContentSectionRequest) returns (DeleteContentSectionResponse);
rpc ReorderContentSections(ReorderContentSectionsRequest) returns (ReorderContentSectionsResponse);
}
// ============= Messages =============
// GetContentPageBySlug
message GetContentPageBySlugRequest {
string slug = 1;
}
message GetContentPageBySlugResponse {
int64 id = 1;
string slug = 2;
string title = 3;
string subtitle = 4;
string meta_description = 5;
string meta_keywords = 6;
string og_image = 7;
repeated ContentSectionModel sections = 8;
}
message ContentSectionModel {
int64 id = 1;
string section_type = 2; // "Hero", "TextBlock", etc.
string title = 3;
string content = 4; // JSON string
int32 sort_order = 5;
bool is_active = 6;
}
// GetAllContentPages
message GetAllContentPagesRequest {
bool include_inactive = 1;
}
message GetAllContentPagesResponse {
repeated ContentPageSummary pages = 1;
}
message ContentPageSummary {
int64 id = 1;
string slug = 2;
string title = 3;
bool is_active = 4;
bool is_public = 5;
int32 sections_count = 6;
google.protobuf.Timestamp created = 7;
}
// CreateContentPage
message CreateContentPageRequest {
string slug = 1;
string title = 2;
string subtitle = 3;
string meta_description = 4;
string meta_keywords = 5;
string og_image = 6;
bool is_active = 7;
bool is_public = 8;
int32 sort_order = 9;
}
message CreateContentPageResponse {
bool success = 1;
string message = 2;
int64 page_id = 3;
}
// UpdateContentPage
message UpdateContentPageRequest {
int64 page_id = 1;
string slug = 2;
string title = 3;
string subtitle = 4;
string meta_description = 5;
string meta_keywords = 6;
string og_image = 7;
bool is_active = 8;
bool is_public = 9;
int32 sort_order = 10;
}
message UpdateContentPageResponse {
bool success = 1;
string message = 2;
}
// DeleteContentPage
message DeleteContentPageRequest {
int64 page_id = 1;
}
message DeleteContentPageResponse {
bool success = 1;
string message = 2;
}
// AddContentSection
message AddContentSectionRequest {
int64 page_id = 1;
string section_type = 2;
string title = 3;
string content = 4; // JSON string
int32 sort_order = 5;
bool is_active = 6;
}
message AddContentSectionResponse {
bool success = 1;
string message = 2;
int64 section_id = 3;
}
// UpdateContentSection
message UpdateContentSectionRequest {
int64 section_id = 1;
string section_type = 2;
string title = 3;
string content = 4;
int32 sort_order = 5;
bool is_active = 6;
}
message UpdateContentSectionResponse {
bool success = 1;
string message = 2;
}
// DeleteContentSection
message DeleteContentSectionRequest {
int64 section_id = 1;
}
message DeleteContentSectionResponse {
bool success = 1;
string message = 2;
}
// ReorderContentSections
message ReorderContentSectionsRequest {
int64 page_id = 1;
repeated SectionOrder orders = 2;
}
message SectionOrder {
int64 section_id = 1;
int32 new_sort_order = 2;
}
message ReorderContentSectionsResponse {
bool success = 1;
string message = 2;
}
6.2 Update .csproj
<!-- در CMSMicroservice.Protobuf.csproj -->
<ItemGroup>
<Protobuf Include="Protos\content.proto" GrpcServices="Server" />
</ItemGroup>
6.3 Generate Protobuf Code
cd /home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Protobuf
dotnet build
فایلهای ایجاد شده:
- ✅
content.proto - ✅ Auto-generated C# classes
✅ Phase 7: WebApi Layer - gRPC Service
مسیر: CMS/src/CMSMicroservice.WebApi/Services/
7.1 Create ContentGrpcService.cs
using CMSMicroservice.Application.ContentCQ.Commands.*;
using CMSMicroservice.Application.ContentCQ.Queries.*;
using CMSMicroservice.Protobuf.Protos.Content;
using Grpc.Core;
using Mapster;
using MediatR;
namespace CMSMicroservice.WebApi.Services;
public class ContentGrpcService : ContentService.ContentServiceBase
{
private readonly IMediator _mediator;
private readonly ILogger<ContentGrpcService> _logger;
public ContentGrpcService(IMediator mediator, ILogger<ContentGrpcService> logger)
{
_mediator = mediator;
_logger = logger;
}
public override async Task<GetContentPageBySlugResponse> GetContentPageBySlug(
GetContentPageBySlugRequest request,
ServerCallContext context)
{
var query = new GetContentPageBySlugQuery(request.Slug);
var result = await _mediator.Send(query);
return result.Adapt<GetContentPageBySlugResponse>();
}
public override async Task<GetAllContentPagesResponse> GetAllContentPages(
GetAllContentPagesRequest request,
ServerCallContext context)
{
var query = new GetAllContentPagesQuery(request.IncludeInactive);
var result = await _mediator.Send(query);
return result.Adapt<GetAllContentPagesResponse>();
}
// ... سایر متدها
}
7.2 Register Service در Program.cs
// در WebApi/Program.cs اضافه شود:
app.MapGrpcService<ContentGrpcService>();
فایلهای ایجاد شده:
- ✅
ContentGrpcService.cs - ✅ تغییرات در
Program.cs
JSON Schema Examples
1. Hero Section
{
"backgroundImage": "/images/hero-bg.jpg",
"title": "به شرکت فورست خوش آمدید",
"subtitle": "راهکارهای نوین دیجیتال مارکتینگ",
"buttonText": "شروع کنید",
"buttonLink": "/contact",
"alignment": "center",
"overlayOpacity": 0.5
}
2. TextBlock Section
{
"title": "درباره شرکت",
"body": "<p>شرکت فورست از سال 1395...</p>",
"backgroundColor": "#f5f5f5",
"textAlign": "justify",
"padding": "40px 20px",
"hasReadMore": false
}
3. ContactForm Section
{
"title": "تماس با ما",
"description": "لطفاً فرم زیر را پر کنید",
"fields": [
{
"name": "fullName",
"label": "نام و نام خانوادگی",
"type": "text",
"required": true,
"placeholder": "نام خود را وارد کنید"
},
{
"name": "email",
"label": "ایمیل",
"type": "email",
"required": true,
"placeholder": "example@email.com"
},
{
"name": "phone",
"label": "شماره تماس",
"type": "tel",
"required": false,
"placeholder": "09123456789"
},
{
"name": "message",
"label": "پیام",
"type": "textarea",
"required": true,
"placeholder": "پیام خود را بنویسید...",
"rows": 5
}
],
"submitButton": "ارسال پیام",
"successMessage": "پیام شما با موفقیت ارسال شد",
"errorMessage": "خطا در ارسال پیام",
"emailTo": "info@foursat.com"
}
4. TeamMembers Section
{
"title": "تیم ما",
"members": [
{
"name": "علی احمدی",
"position": "مدیر عامل",
"image": "/images/team/ali.jpg",
"bio": "بیش از 15 سال تجربه در...",
"linkedin": "https://linkedin.com/in/ali",
"email": "ali@foursat.com"
},
{
"name": "سارا محمدی",
"position": "مدیر فنی",
"image": "/images/team/sara.jpg",
"bio": "متخصص در زمینه...",
"linkedin": "https://linkedin.com/in/sara",
"email": "sara@foursat.com"
}
],
"layout": "grid",
"columns": 3
}
5. FAQ Section
{
"title": "سوالات متداول",
"questions": [
{
"question": "چگونه میتوانم ثبت نام کنم؟",
"answer": "برای ثبت نام کافیست به صفحه...",
"isExpanded": false
},
{
"question": "روشهای پرداخت چیست؟",
"answer": "شما میتوانید از طریق...",
"isExpanded": false
}
],
"layout": "accordion"
}
6. Map Section
{
"title": "موقعیت ما",
"latitude": 35.6892,
"longitude": 51.3890,
"zoom": 15,
"markerTitle": "شرکت فورست",
"address": "تهران، خیابان ولیعصر، پلاک 123",
"height": "400px",
"showControls": true
}
7. SocialMedia Section
{
"title": "ما را دنبال کنید",
"platforms": [
{
"name": "Instagram",
"icon": "instagram",
"url": "https://instagram.com/foursat",
"color": "#E4405F"
},
{
"name": "Telegram",
"icon": "telegram",
"url": "https://t.me/foursat",
"color": "#0088cc"
},
{
"name": "LinkedIn",
"icon": "linkedin",
"url": "https://linkedin.com/company/foursat",
"color": "#0077b5"
}
],
"layout": "horizontal",
"iconSize": "large"
}
8. Features Section
{
"title": "ویژگیهای ما",
"features": [
{
"icon": "shield-check",
"title": "امنیت بالا",
"description": "تمام اطلاعات شما با استانداردهای..."
},
{
"icon": "clock",
"title": "پشتیبانی 24/7",
"description": "تیم پشتیبانی ما همیشه آماده..."
},
{
"icon": "globe",
"title": "دسترسی جهانی",
"description": "از هر جای دنیا میتوانید..."
}
],
"layout": "grid",
"columns": 3
}
9. Statistics Section
{
"title": "اعداد و ارقام",
"stats": [
{
"number": 10000,
"suffix": "+",
"label": "کاربر فعال",
"icon": "users",
"animateOnView": true
},
{
"number": 500,
"suffix": "+",
"label": "پروژه موفق",
"icon": "briefcase",
"animateOnView": true
},
{
"number": 98,
"suffix": "%",
"label": "رضایت مشتریان",
"icon": "heart",
"animateOnView": true
}
],
"layout": "horizontal",
"backgroundColor": "#f8f9fa"
}
10. Timeline Section
{
"title": "تاریخچه شرکت",
"events": [
{
"year": "1395",
"title": "تأسیس شرکت",
"description": "شرکت فورست با هدف...",
"image": "/images/timeline/2016.jpg"
},
{
"year": "1397",
"title": "توسعه محصول اول",
"description": "راهاندازی اولین محصول...",
"image": "/images/timeline/2018.jpg"
},
{
"year": "1400",
"title": "جایزه بهترین استارتاپ",
"description": "دریافت جایزه ملی...",
"image": "/images/timeline/2021.jpg"
}
],
"layout": "vertical",
"alternateLayout": true
}
BackOffice UI Design
صفحه لیست (ContentPagesMainPage.razor)
┌─────────────────────────────────────────────────────────────┐
│ 📄 مدیریت صفحات محتوا [+ صفحه جدید] │
├─────────────────────────────────────────────────────────────┤
│ 🔍 جستجو: [_____________] 🔧 فیلتر: [همه ▼] │
├─────┬──────────────┬────────┬────────┬──────────┬──────────┤
│ ID │ عنوان صفحه │ Slug │ وضعیت │ Sections │ عملیات │
├─────┼──────────────┼────────┼────────┼──────────┼──────────┤
│ 1 │ درباره ما │about-us│ ✅ فعال│ 5 │[✏️][🗑️][👁️]│
│ 2 │ تماس با ما │contact │ ✅ فعال│ 3 │[✏️][🗑️][👁️]│
│ 3 │ قوانین │terms │ ❌ غیرف│ 2 │[✏️][🗑️][👁️]│
└─────┴──────────────┴────────┴────────┴──────────┴──────────┘
صفحه ویرایش (ContentPageEditor.razor)
┌─────────────────────────────────────────────────────────────┐
│ ✏️ ویرایش صفحه: درباره ما [💾 ذخیره] [❌] │
├─────────────────────────────────────────────────────────────┤
│ Tab: [📝 اطلاعات پایه] [🧩 بخشها] [🔍 SEO] │
├─────────────────────────────────────────────────────────────┤
│ │
│ عنوان: [______________________________________] │
│ Slug: [about-us________________] (منحصر به فرد) │
│ زیرعنوان: [________________________________] │
│ │
│ ☑ فعال ☑ نمایش عمومی ترتیب: [0___] │
│ │
└─────────────────────────────────────────────────────────────┘
Tab بخشها (Sections)
┌─────────────────────────────────────────────────────────────┐
│ 🧩 بخشهای صفحه [+ افزودن بخش] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────┐ │
│ │ ☰ 1. Hero Section [↑][↓][✏️][🗑️]│ │
│ │ نوع: Hero | فعال: ✅ │ │
│ │ محتوا: بنر خوش آمد گویی... │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ ☰ 2. TextBlock [↑][↓][✏️][🗑️]│ │
│ │ نوع: TextBlock | فعال: ✅ │ │
│ │ محتوا: درباره شرکت... │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ ☰ 3. TeamMembers [↑][↓][✏️][🗑️]│ │
│ │ نوع: TeamMembers | فعال: ✅ │ │
│ │ محتوا: 4 عضو تیم │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
ویرایشگر Section (SectionEditor.razor)
┌─────────────────────────────────────────────────────────────┐
│ ✏️ ویرایش بخش: Hero Section [💾 ذخیره] [❌] │
├─────────────────────────────────────────────────────────────┤
│ │
│ نوع بخش: [Hero ▼] │
│ عنوان: [بنر خوش آمد گویی_________________] │
│ فعال: ☑ │
│ │
│ ─── تنظیمات Hero Section ─── │
│ │
│ تصویر پسزمینه: [انتخاب فایل] /images/hero-bg.jpg │
│ عنوان اصلی: [به شرکت فورست خوش آمدید____] │
│ زیرعنوان: [راهکارهای نوین دیجیتال مارکتینگ__] │
│ متن دکمه: [شروع کنید_________] │
│ لینک دکمه: [/contact__________] │
│ موقعیت متن: [○ چپ ● وسط ○ راست] │
│ شفافیت لایه: [▓▓▓▓▓▓░░░░] 50% │
│ │
│ 👁️ [پیشنمایش] │
│ │
└─────────────────────────────────────────────────────────────┘
Validation Rules
ContentPage Validation
-
✅
Slug:- الزامی
- فقط حروف کوچک، اعداد، و خط تیره (-)
- طول: 3-100 کاراکتر
- منحصر به فرد
- مثال:
about-us,contact-us,terms-conditions
-
✅
Title:- الزامی
- طول: 3-200 کاراکتر
-
✅
MetaDescription:- اختیاری
- طول: حداکثر 500 کاراکتر
ContentSection Validation
-
✅
SectionType:- الزامی
- باید یکی از مقادیر Enum باشد
-
✅
Title:- الزامی
- طول: 3-200 کاراکتر
-
✅
Content:- الزامی
- باید JSON معتبر باشد
- Schema validation بر اساس
SectionType
SEO Optimization
1. Meta Tags
<!-- در صفحه About Us -->
<title>درباره ما - شرکت فورست</title>
<meta name="description" content="شرکت فورست از سال 1395 فعالیت خود را...">
<meta name="keywords" content="فورست، درباره ما، تاریخچه شرکت">
<!-- Open Graph -->
<meta property="og:title" content="درباره ما - شرکت فورست">
<meta property="og:description" content="شرکت فورست از سال 1395...">
<meta property="og:image" content="https://foursat.com/og-about.jpg">
<meta property="og:type" content="website">
2. Structured Data (JSON-LD)
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "شرکت فورست",
"url": "https://foursat.com",
"logo": "https://foursat.com/logo.png",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+98-21-12345678",
"contactType": "Customer Service",
"areaServed": "IR",
"availableLanguage": ["fa", "en"]
},
"address": {
"@type": "PostalAddress",
"streetAddress": "خیابان ولیعصر، پلاک 123",
"addressLocality": "تهران",
"postalCode": "1234567890",
"addressCountry": "IR"
}
}
3. Sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://foursat.com/about-us</loc>
<lastmod>2025-12-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://foursat.com/contact-us</loc>
<lastmod>2025-12-06</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
</urlset>
Multi-Language Support (آینده)
Approach 1: Separate Tables
ContentPage (fa) ──┐
ContentPage (en) ──┼─→ ContentPageTranslation
ContentPage (ar) ──┘
Approach 2: JSON Field
public class ContentPage
{
public string Title { get; set; } // فارسی (پیشفرض)
public string TitleTranslations { get; set; } // JSON: {"en": "About Us", "ar": "معلومات عنا"}
}
Approach 3: Separate Content Column
public class ContentSection
{
public string Content { get; set; } // فارسی
public string ContentEn { get; set; } // انگلیسی
public string ContentAr { get; set; } // عربی
}
توصیه: Approach 2 (JSON Field) - flexible و مقیاسپذیر
Performance Optimization
1. Caching Strategy
// Cache صفحات برای 30 دقیقه
services.AddMemoryCache();
public class GetContentPageBySlugQueryHandler
{
private readonly IMemoryCache _cache;
public async Task<...> Handle(...)
{
var cacheKey = $"content-page-{request.Slug}";
if (!_cache.TryGetValue(cacheKey, out GetContentPageBySlugResponseDto result))
{
result = await GetFromDatabase(...);
_cache.Set(cacheKey, result, TimeSpan.FromMinutes(30));
}
return result;
}
}
2. Database Indexes
-- Index برای Slug (Unique)
CREATE UNIQUE INDEX IX_ContentPages_Slug ON ContentPages(Slug);
-- Index برای فیلتر صفحات فعال
CREATE INDEX IX_ContentPages_Active ON ContentPages(IsActive, IsPublic, SortOrder);
-- Index برای Sections
CREATE INDEX IX_ContentSections_PageId ON ContentSections(PageId, SortOrder);
3. Lazy Loading
// فقط Sections فعال را بارگذاری کن
var page = await _context.ContentPages
.Include(x => x.Sections.Where(s => s.IsActive).OrderBy(s => s.SortOrder))
.FirstOrDefaultAsync(x => x.Slug == slug);
Security Considerations
1. Authorization
// فقط Admin میتواند صفحات را مدیریت کند
[Authorize(Roles = "Admin")]
public class ContentGrpcService : ContentService.ContentServiceBase
{
// ...
}
2. Input Validation
public class CreateContentPageCommandValidator : AbstractValidator<CreateContentPageCommand>
{
public CreateContentPageCommandValidator()
{
RuleFor(x => x.Slug)
.NotEmpty()
.Matches("^[a-z0-9-]+$") // فقط حروف کوچک، اعداد، و خط تیره
.Length(3, 100);
RuleFor(x => x.Title)
.NotEmpty()
.Length(3, 200);
}
}
3. XSS Prevention
// Sanitize HTML content در TextBlock
using HtmlAgilityPack;
public class ContentSanitizer
{
public string SanitizeHtml(string html)
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
// حذف تگهای خطرناک
doc.DocumentNode.Descendants()
.Where(n => n.Name == "script" || n.Name == "iframe")
.ToList()
.ForEach(n => n.Remove());
return doc.DocumentNode.OuterHtml;
}
}
Testing Strategy
1. Unit Tests
// Test: GetContentPageBySlug با Slug نامعتبر
[Fact]
public async Task GetContentPageBySlug_WithInvalidSlug_ReturnsNull()
{
// Arrange
var handler = new GetContentPageBySlugQueryHandler(_context);
var query = new GetContentPageBySlugQuery("invalid-slug");
// Act
var result = await handler.Handle(query, CancellationToken.None);
// Assert
Assert.Null(result);
}
2. Integration Tests
// Test: ایجاد صفحه با Slug تکراری
[Fact]
public async Task CreateContentPage_WithDuplicateSlug_ThrowsException()
{
// Arrange
await CreatePage("about-us");
// Act & Assert
await Assert.ThrowsAsync<DbUpdateException>(
() => CreatePage("about-us")
);
}
3. E2E Tests
// Test: BackOffice - ایجاد و ویرایش صفحه
[Fact]
public async Task BackOffice_CreateAndEditPage_Success()
{
// 1. Navigate to ContentPages
await Page.GotoAsync("/content-pages");
// 2. Click "New Page"
await Page.ClickAsync("button:has-text('صفحه جدید')");
// 3. Fill form
await Page.FillAsync("input[name='title']", "تست صفحه");
await Page.FillAsync("input[name='slug']", "test-page");
// 4. Submit
await Page.ClickAsync("button:has-text('ذخیره')");
// 5. Verify
await Expect(Page.Locator("text=تست صفحه")).ToBeVisibleAsync();
}
Deployment Checklist
Pre-Deployment
- همه Unit Tests پاس شوند
- Integration Tests پاس شوند
- Migration در Staging اجرا شود
- Performance Testing انجام شود
- Security Audit انجام شود
- Documentation کامل باشد
Deployment Steps
- Backup دیتابیس Production
- اجرای Migration در Production
- Deploy کد جدید
- Smoke Testing
- Cache را Clear کنید
- Monitor logs برای 24 ساعت
Post-Deployment
- Verify صفحات About Us و Contact Us
- بررسی Performance Metrics
- بررسی Error Logs
- User Acceptance Testing (UAT)
Monitoring & Analytics
1. Application Insights
// Log Page Views
_telemetry.TrackPageView(new PageViewTelemetry
{
Name = $"ContentPage-{slug}",
Url = new Uri($"https://foursat.com/{slug}")
});
2. Custom Metrics
// Track Section Rendering Time
using (_telemetry.StartOperation<RequestTelemetry>("RenderSection"))
{
await RenderSection(section);
}
3. Alerts
- ⚠️ اگر Page Load Time > 3 ثانیه
- ⚠️ اگر Error Rate > 1%
- ⚠️ اگر Cache Miss Rate > 20%
Future Enhancements
Phase 8: Advanced Features (آینده)
- Versioning: نگهداری تاریخچه تغییرات
- Scheduling: زمانبندی انتشار محتوا
- A/B Testing: تست نسخههای مختلف
- Analytics Dashboard: آمار بازدید صفحات
- SEO Score: امتیاز SEO برای هر صفحه
- Content Templates: قالبهای آماده برای صفحات
- Workflow: تایید چند مرحلهای (Draft → Review → Published)
- Media Library: مدیریت تصاویر و فایلها
- Comments System: سیستم نظرات برای صفحات
- Related Pages: پیشنهاد صفحات مرتبط
خلاصه آمار پیادهسازی
| Phase | فایلها | خطوط کد تقریبی | زمان تخمینی |
|---|---|---|---|
| 1. Domain Entities | 3 | ~150 | 1 ساعت |
| 2. EF Configurations | 2 | ~100 | 1 ساعت |
| 3. DbContext Update | 1 | ~10 | 15 دقیقه |
| 4. Migration | 1 | Auto | 30 دقیقه |
| 5. Application (CQRS) | ~21 | ~800 | 4 ساعت |
| 6. Protobuf | 1 | ~200 | 1 ساعت |
| 7. WebApi (gRPC) | 1 | ~150 | 1 ساعت |
| Total | ~30 | ~1,410 | ~9 ساعت |
BackOffice UI (اختیاری)
| Phase | فایلها | خطوط کد تقریبی | زمان تخمینی |
|---|---|---|---|
| 8. Razor Pages | ~5 | ~800 | 4 ساعت |
| 9. Services | ~2 | ~200 | 1 ساعت |
| 10. Components | ~10 | ~500 | 3 ساعت |
| Total | ~17 | ~1,500 | ~8 ساعت |
جمع کل: ~47 فایل، ~2,910 خط کد، ~17 ساعت توسعه
نتیجهگیری
این سیستم یک راهکار کامل و انعطافپذیر برای مدیریت صفحات محتوایی ارائه میدهد که:
✅ Scalable: امکان افزودن هر تعداد صفحه و بخش
✅ Flexible: ساختار JSON برای محتوای پویا
✅ Maintainable: معماری Clean Architecture و CQRS
✅ User-Friendly: رابط کاربری BackOffice ساده و کاربردی
✅ SEO-Optimized: Meta Tags و Structured Data
✅ Production-Ready: Caching، Validation، Security
زمان شروع پیادهسازی: هر زمان که آماده باشید! 🚀
نسخه: 1.0
تاریخ: December 6, 2025
وضعیت: ✅ آماده برای پیادهسازی
اولویت: متوسط (بعد از فیچرهای اصلی)