# إضافة مراجع C# لAPI أمامي

<!--category-- ASP.NET, Umami -->
<datetime class="hidden">2024-08-14T01:27</datetime>

## التقديم

في هذا المقال, سأريكم كيفية إنشاء نموذج C# لAPI إبلاغumamiM SK2 هذا مثال بسيط يوضح كيفية التحقق من الهوية مع الAPI وإستخراج البيانات منه

يمكنك أن تجد كل شفرة المصدر لهذا [على موقع GitHub](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid/Umami).

[TOC]

## المتطلبات الأساسية

Install Umami. يمكنكم العثور على التعليمات установки [هنا](https://www.mostlylucid.net/blog/usingumamiforlocalanalytics) هذا يوضح كيف أقوم بتركيب و استخدام "أمامي" لإيصال التحليلات لهذا الموقع.

مرة أخرى, هذا تطبيق بسيط لبعض من نقاط نهاية API لبيانات موقع "أمامي" [هنا](https://umami.is/docs/api/website-stats).

في هذا اخترت أن أقوم بتطبيق هذه النقاط النهائية:

- `GET /api/websites/:websiteId/pageviews` - كما يقترح اسمه, هذه النقطة النهائية تعيد المشاهد على الصفحة و 'sessionsM SK3 لموقع معين على مدى فترة زمنية

```json
{
  "pageviews": [
    { "x": "2020-04-20 01:00:00", "y": 3 },
    { "x": "2020-04-20 02:00:00", "y": 7 }
  ],
  "sessions": [
    { "x": "2020-04-20 01:00:00", "y": 2 },
    { "x": "2020-04-20 02:00:00", "y": 4 }
  ]
}
```

- `GET /api/websites/:websiteId/stats` - هذا يعيد إحصائيات أساسية لموقع معينM SK1

```json
{
  "pageviews": { "value": 5, "change": 5 },
  "visitors": { "value": 1, "change": 1 },
  "visits": { "value": 3, "change": 2 },
  "bounces": { "value": 0, "change": 0 },
  "totaltime": { "value": 4, "change": 4 }
}
```

- `GET /api/websites/:websiteId/metrics` - هذا يعيد القياسات لموقع معين URL أو ما شابه

```json
[
  { "x": "/", "y": 46 },
  { "x": "/docs", "y": 17 },
  { "x": "/download", "y": 14 }
]
```

كما ترون من الأبحاث , كل هذه تقبل عدد من العوامل ( وقد قمت بتمثيلها كعوامل بحث في الشفرة التالية

## الاختبار في المركبة httpClient

أبدأ دائما باختبار API في Rider's built-in HTTP clientM SK2 هذا يسمح لي باختبار الAPI بسرعة ورؤية الاستجابة

```http
### Login Request and Store Token
POST https://{{umamiurl}}/api/auth/login
Content-Type: application/json

{
  "username": "{{username}}",

  "password": "{{password}}"
}
> {% client.global.set("auth_token", response.body.token);
    client.global.set("endAt", Math.round(new Date().getTime()).toString() );
    client.global.set("startAt", Math.round(new Date().getTime() - 7 * 24 * 60 * 60 * 1000).toString());
%}


### Use Token in Subsequent Request
GET https://{{umamiurl}}/api/websites/{{websiteid}}/stats?endAt={{endAt}}&startAt={{startAt}}
Authorization: Bearer {{auth_token}}

### Use Token in Subsequent Request
GET https://{{umamiurl}}/api/websites/{{websiteid}}/pageviews?endAt={{endAt}}&startAt={{startAt}}&unit=day
Authorization: Bearer {{auth_token}}


###
GET https://{{umamiurl}}}}/api/websites/{{websiteid}}/metrics?endAt={{endAt}}&startAt={{startAt}}&type=url
Authorization: Bearer {{auth_token}}
```

إنها ممارسة جيدة لحفظ أسماء المتغيرات هنا في `{{}}` ឯកសារ env.json التي يمكن أن تشير إليها كما في الأسفل.

```json
{
  "local": {
    "umamiurl":"umamilocal.mostlylucid.net",
    "username": "admin",
    "password": "<password{>",
    "websiteid" : "32c2aa31-b1ac-44c0-b8f3-ff1f50403bee"
  }
}
```

## الإعداد

أولاً نحتاج إلى إعداد HttpClient والخدمات التي سنستخدمها لإجراء الطلبات.

```csharp

public static class UmamiSetup
{
    public static void SetupUmamiServices(this IServiceCollection services, IConfiguration config)
    {
        var umamiSettings = services.ConfigurePOCO<UmamiSettings>(config.GetSection(UmamiSettings.Section));
        services.AddHttpClient<AuthService>(options =>
        {
            options.BaseAddress = new Uri(umamiSettings.BaseUrl);
            
        }) .SetHandlerLifetime(TimeSpan.FromMinutes(5))  //Set lifetime to five minutes
        .AddPolicyHandler(GetRetryPolicy());;
        services.AddScoped<UmamiService>();
        services.AddScoped<AuthService>();

    }
    static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
    {
        return HttpPolicyExtensions
            .HandleTransientHttpError()
            .OrResult(msg =>  msg.StatusCode == HttpStatusCode.ServiceUnavailable)
            .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
    }

}
```

هنا نقوم بتنظيم فئة الإعدادات `UmamiSettings` وإضافة `AuthService` و `UmamiService` إلى جمع الخدمات. نحن أيضاً نضيف سياسة إعادة المحاولة إلى HttpClient للتعامل مع الأخطاء المؤقتة.

لاحقاً نحتاج لخلق `UmamiService` و `AuthService` الكليات.

الـ `AuthService` هو ببساطة مسؤول عن الحصول على توكين JWT من API.

```csharp
public class AuthService(HttpClient httpClient, UmamiSettings umamiSettings, ILogger<AuthService> logger)
{
    private string _token;
    public HttpClient HttpClient => httpClient;

    public async Task<bool> LoginAsync()
    {
        var loginData = new
        {
            username = umamiSettings.Username,
            password = umamiSettings.Password
        };

        var content = new StringContent(JsonSerializer.Serialize(loginData), Encoding.UTF8, "application/json");
        var response = await httpClient.PostAsync("/api/auth/login", content);

        if (response.IsSuccessStatusCode)
        {
            var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();


            _token = authResponse.Token;
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
            logger.LogInformation("Login successful");
            return true;
        }

        logger.LogError("Login failed");
        return false;
    }
}
```

لدينا هنا طريقة بسيطة `LoginAsync` التي ترسل طلباً POST إلى `/api/auth/login` نقطة النهاية مع username و password. إذا كان الطلب ناجحاً, سنخزن رمز JWT في `_token` وضبط الحقل `Authorization` عنوان على HttpClient.

الـ `UmamiService` هو المسؤول عن تقديم الطلبات إلى API.
في كل من الطرق الرئيسية، قمت بتحديد أجسام الطلب التي تقبل جميع العوامل لكل نقطة النهاية.

إنهم جميعاً يتبعون نمط سيميير , لذا سأريكم واحد منهم هنا

```csharp
public async Task<UmamiResult<StatsResponseModels>> GetStatsAsync(StatsRequest statsRequest)
{
    // Start building the query string
    var queryParams = new List<string>
    {
        $"start={statsRequest.StartAt}",
        $"end={statsRequest.EndAt}"
    };

    // Add optional parameters if they are not null
    if (!string.IsNullOrEmpty(statsRequest.Url)) queryParams.Add($"url={statsRequest.Url}");
    if (!string.IsNullOrEmpty(statsRequest.Referrer)) queryParams.Add($"referrer={statsRequest.Referrer}");
    if (!string.IsNullOrEmpty(statsRequest.Title)) queryParams.Add($"title={statsRequest.Title}");
    if (!string.IsNullOrEmpty(statsRequest.Query)) queryParams.Add($"query={statsRequest.Query}");
    if (!string.IsNullOrEmpty(statsRequest.Event)) queryParams.Add($"event={statsRequest.Event}");
    if (!string.IsNullOrEmpty(statsRequest.Host)) queryParams.Add($"host={statsRequest.Host}");
    if (!string.IsNullOrEmpty(statsRequest.Os)) queryParams.Add($"os={statsRequest.Os}");
    if (!string.IsNullOrEmpty(statsRequest.Browser)) queryParams.Add($"browser={statsRequest.Browser}");
    if (!string.IsNullOrEmpty(statsRequest.Device)) queryParams.Add($"device={statsRequest.Device}");
    if (!string.IsNullOrEmpty(statsRequest.Country)) queryParams.Add($"country={statsRequest.Country}");
    if (!string.IsNullOrEmpty(statsRequest.Region)) queryParams.Add($"region={statsRequest.Region}");
    if (!string.IsNullOrEmpty(statsRequest.City)) queryParams.Add($"city={statsRequest.City}");

    // Combine the query parameters into a query string
    var queryString = string.Join("&", queryParams);

    // Make the HTTP request
    var response = await authService.HttpClient.GetAsync($"/api/websites/{WebsiteId}/stats?{queryString}");

    if (response.IsSuccessStatusCode)
    {
        var content = await response.Content.ReadFromJsonAsync<StatsResponseModels>();
        return new UmamiResult<StatsResponseModels>(response.StatusCode, response.ReasonPhrase ?? "Success", content ?? new StatsResponseModels());
    }

    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        await authService.LoginAsync();
        return await GetStatsAsync(statsRequest);
    }

    logger.LogError("Failed to get stats");
    return new UmamiResult<StatsResponseModels>(response.StatusCode, response.ReasonPhrase ?? "Failed to get stats", null);
}

```

هنا يمكنكم أن تروا أنني أخذت غرض الطلب

```csharp
public class BaseRequest
{
    public long StartAt => StartAtDate.ToMilliseconds(); // Timestamp (in ms) of starting date
    public long EndAt => EndAtDate.ToMilliseconds(); // Timestamp (in ms) of end date
    public DateTime StartAtDate { get; set; }
    public DateTime EndAtDate { get; set; }
}
public class StatsRequest : BaseRequest
{
    // Optional properties
    public string? Url { get; set; } // Name of URL
    public string? Referrer { get; set; } // Name of referrer
    public string? Title { get; set; } // Name of page title
    public string? Query { get; set; } // Name of query
    public string? Event { get; set; } // Name of event
    public string? Host { get; set; } // Name of hostname
    public string? Os { get; set; } // Name of operating system
    public string? Browser { get; set; } // Name of browser
    public string? Device { get; set; } // Name of device (e.g., Mobile)
    public string? Country { get; set; } // Name of country
    public string? Region { get; set; } // Name of region/state/province
    public string? City { get; set; } // Name of city
}
```

وقم ببناء سطر الطلب من العوامل . إذا كان الطلب ناجحاً , سنعيد المحتوى كـ `UmamiResult` object. إذا فشلت الطلب مع 401 شفرة حالةM SK2 نتصل بالـ `LoginAsync` طريقة وإعادة تجربة الطلب. هذا يضمن لنا 'élégantlyM SK2 التعامل مع نهاية الرموز

## الاستنتاجات

هذا مثال بسيط لكيفية إنشاء عملاء C# لAPI أمامي. يمكنك استخدام هذا كبداية لبناء عملاء أكثر تعقيدا أو دمج الAPI في تطبيقات الخاصة بك