FaqService
Documentație pentru serviciul FaqService
1. Descriere Generală
FaqService
este un serviciu pentru gestionarea întrebărilor frecvente (FAQ) asociate cu diferite servicii guvernamentale. Serviciul comunică cu API-ul Info Portal pentru a obține întrebări și răspunsuri relevante pentru un anumit serviciu.
Caracteristici principale: - Obținere FAQ-uri pe bază de ID serviciu - Paginare configurabilă - Filtrare după status activ/inactiv - Integrare cu Info Portal API - Logging pentru debugging - Configurare centralizată prin options pattern
2. Configurare și Înregistrare
Înregistrare în Program.cs sau Startup.cs
// Configurare opțiuni
builder.Services.Configure<FodConfiguration>(
builder.Configuration.GetSection("FodConfiguration"));
// Înregistrare serviciu cu HttpClient
builder.Services.AddHttpClient<IFaqService, FaqService>(client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
// Sau înregistrare simplă
builder.Services.AddScoped<IFaqService, FaqService>();
Configurare în appsettings.json
{
"FodConfiguration": {
"Services": {
"InfoPortal": {
"BaseUrl": "https://api.infoportal.gov.md",
"ApiKey": "your-api-key",
"Timeout": 30
}
}
}
}
3. Interfața IFaqService
public interface IFaqService
{
/// <summary>
/// Obține elementele FAQ pentru un serviciu specific
/// </summary>
/// <param name="serviceId">ID-ul serviciului</param>
/// <param name="pageSize">Numărul de elemente per pagină</param>
/// <param name="active">Doar elemente active</param>
/// <param name="page">Numărul paginii</param>
/// <returns>Model cu întrebări și răspunsuri</returns>
Task<FaqModel> GetFaqItemsAsync(
string serviceId,
int pageSize = 9999,
bool active = true,
int page = 1);
}
4. Modele de Date
FaqModel
public class FaqModel
{
public List<FaqItem> Items { get; set; }
public int TotalCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
}
public class FaqItem
{
public int Id { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
public string ServiceId { get; set; }
public int OrderIndex { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public string Category { get; set; }
public List<string> Tags { get; set; }
}
5. Utilizare de Bază
Obținere FAQ pentru un serviciu
@page "/service/{ServiceId}/faq"
@inject IFaqService FaqService
<h3>Întrebări frecvente</h3>
@if (isLoading)
{
<FodLoadingLinear />
}
else if (faqItems?.Items?.Any() == true)
{
<FodAccordion>
@foreach (var item in faqItems.Items)
{
<FodAccordionItem Title="@item.Question">
<FodText>@((MarkupString)item.Answer)</FodText>
</FodAccordionItem>
}
</FodAccordion>
}
else
{
<FodAlert Severity="FodSeverity.Info">
Nu există întrebări frecvente pentru acest serviciu.
</FodAlert>
}
@code {
[Parameter] public string ServiceId { get; set; }
private FaqModel faqItems;
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
try
{
faqItems = await FaqService.GetFaqItemsAsync(ServiceId);
}
catch (Exception ex)
{
// Handle error
Logger.LogError(ex, "Error loading FAQ items");
}
finally
{
isLoading = false;
}
}
}
6. Exemple Avansate
FAQ cu paginare și căutare
<div class="faq-container">
<FodTextField @bind-Value="searchTerm"
Label="Caută în întrebări"
OnKeyUp="@(e => { if (e.Key == "Enter") SearchFaq(); })"
AdornmentIcon="@FodIcons.Material.Filled.Search" />
<div class="faq-list mt-4">
@foreach (var item in filteredItems)
{
<FodCard Class="mb-3">
<FodCardContent>
<FodText Typo="Typo.h6">
<FodIcon Icon="@FodIcons.Material.Filled.HelpOutline"
Color="FodColor.Primary" />
@item.Question
</FodText>
<FodDivider Class="my-2" />
<FodText>@((MarkupString)item.Answer)</FodText>
@if (item.Tags?.Any() == true)
{
<div class="mt-2">
@foreach (var tag in item.Tags)
{
<FodChip Size="FodSize.Small" Class="me-1">@tag</FodChip>
}
</div>
}
</FodCardContent>
</FodCard>
}
</div>
@if (faqModel.HasNextPage || faqModel.HasPreviousPage)
{
<FodPagination Count="@totalPages"
@bind-Selected="currentPage"
Color="FodColor.Primary"
ShowFirstButton="true"
ShowLastButton="true" />
}
</div>
@code {
private FaqModel faqModel;
private List<FaqItem> filteredItems = new();
private string searchTerm;
private int currentPage = 1;
private int pageSize = 10;
private int totalPages;
private async Task LoadFaq()
{
faqModel = await FaqService.GetFaqItemsAsync(
ServiceId, pageSize, true, currentPage);
totalPages = (int)Math.Ceiling(
(double)faqModel.TotalCount / pageSize);
FilterItems();
}
private void FilterItems()
{
filteredItems = string.IsNullOrWhiteSpace(searchTerm)
? faqModel.Items
: faqModel.Items.Where(i =>
i.Question.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ||
i.Answer.Contains(searchTerm, StringComparison.OrdinalIgnoreCase))
.ToList();
}
private void SearchFaq()
{
currentPage = 1;
FilterItems();
}
}
Service extins cu cache
public class CachedFaqService : IFaqService
{
private readonly IFaqService _innerService;
private readonly IMemoryCache _cache;
private readonly ILogger<CachedFaqService> _logger;
public CachedFaqService(
IFaqService innerService,
IMemoryCache cache,
ILogger<CachedFaqService> logger)
{
_innerService = innerService;
_cache = cache;
_logger = logger;
}
public async Task<FaqModel> GetFaqItemsAsync(
string serviceId,
int pageSize = 9999,
bool active = true,
int page = 1)
{
var cacheKey = $"faq_{serviceId}_{pageSize}_{active}_{page}";
if (_cache.TryGetValue<FaqModel>(cacheKey, out var cachedResult))
{
_logger.LogDebug("FAQ items retrieved from cache for service {ServiceId}", serviceId);
return cachedResult;
}
var result = await _innerService.GetFaqItemsAsync(
serviceId, pageSize, active, page);
// Cache pentru 1 oră
_cache.Set(cacheKey, result, TimeSpan.FromHours(1));
return result;
}
}
7. Integrare cu FodFaqViewer
<!-- Utilizare simplă cu componenta existentă -->
<FodFaqViewer ServiceId="@ServiceId" />
<!-- Sau personalizat -->
<FodFaqViewer ServiceId="@ServiceId"
PageSize="5"
ShowSearch="true"
CollapsedByDefault="false"
CustomClass="custom-faq-style" />
8. Gestionare Erori
public class ResilientFaqService : IFaqService
{
private readonly IFaqService _innerService;
private readonly ILogger<ResilientFaqService> _logger;
public async Task<FaqModel> GetFaqItemsAsync(
string serviceId, int pageSize = 9999,
bool active = true, int page = 1)
{
try
{
return await _innerService.GetFaqItemsAsync(
serviceId, pageSize, active, page);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex,
"Network error fetching FAQ for service {ServiceId}",
serviceId);
// Returnează rezultat gol în loc să arunce excepția
return new FaqModel
{
Items = new List<FaqItem>(),
TotalCount = 0,
PageNumber = page,
PageSize = pageSize
};
}
catch (TaskCanceledException ex)
{
_logger.LogError(ex,
"Timeout fetching FAQ for service {ServiceId}",
serviceId);
throw new TimeoutException(
"Timpul de așteptare pentru FAQ a expirat", ex);
}
}
}
9. Best Practices
- Caching: Implementați cache pentru a reduce încărcarea API-ului
- Error Handling: Tratați erorile de rețea graceful
- Logging: Logați toate erorile pentru debugging
- Timeout: Setați timeout-uri rezonabile
- Retry Logic: Implementați retry pentru erori tranzitorii
- Validare: Validați parametrii de intrare
10. Monitorizare și Metrici
public class MonitoredFaqService : IFaqService
{
private readonly IFaqService _innerService;
private readonly IMetrics _metrics;
public async Task<FaqModel> GetFaqItemsAsync(
string serviceId, int pageSize = 9999,
bool active = true, int page = 1)
{
using var timer = _metrics.Measure.Timer.Time("faq.fetch.duration");
try
{
var result = await _innerService.GetFaqItemsAsync(
serviceId, pageSize, active, page);
_metrics.Measure.Counter.Increment("faq.fetch.success");
_metrics.Measure.Gauge.SetValue("faq.items.count", result.TotalCount);
return result;
}
catch (Exception)
{
_metrics.Measure.Counter.Increment("faq.fetch.error");
throw;
}
}
}
11. Teste Unitare
[TestClass]
public class FaqServiceTests
{
private Mock<HttpMessageHandler> _mockHandler;
private HttpClient _httpClient;
private FaqService _service;
[TestInitialize]
public void Setup()
{
_mockHandler = new Mock<HttpMessageHandler>();
_httpClient = new HttpClient(_mockHandler.Object);
var options = Options.Create(new FodConfiguration
{
Services = new ServicesConfiguration
{
InfoPortal = new InfoPortalOptions
{
BaseUrl = "https://test.api"
}
}
});
_service = new FaqService(_httpClient, options,
Mock.Of<ILogger<FaqService>>());
}
[TestMethod]
public async Task GetFaqItemsAsync_ReturnsItems_WhenApiSucceeds()
{
// Arrange
var expectedJson = JsonSerializer.Serialize(new FaqModel
{
Items = new List<FaqItem>
{
new() { Id = 1, Question = "Test?", Answer = "Da" }
},
TotalCount = 1
});
_mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(expectedJson)
});
// Act
var result = await _service.GetFaqItemsAsync("SERVICE001");
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Items.Count);
Assert.AreEqual("Test?", result.Items[0].Question);
}
}
12. Concluzie
FaqService
oferă o interfață simplă și eficientă pentru gestionarea întrebărilor frecvente în aplicații guvernamentale. Cu suport pentru paginare, filtrare și integrare ușoară cu componente UI, serviciul facilitează implementarea secțiunilor FAQ informative și ușor de navigat.