Sytuacja kobiet w IT w 2024 roku
16.09.20213 min
Szymon Rożek

Szymon Rożek.NET Developer

Co to jest ETag i jak go zaimplementować w .NET

Poznaj ETag, czyli header protokołu HTTP i dowiedz się, jak zaimplementować go w .NET.

Co to jest ETag i jak go zaimplementować w .NET

W dzisiejszych czasach prawie każda aplikacja czy też strona internetowa jest wypełniona mnóstwem danych. Większość z nich nieustannie się zmienia oraz jest aktualizowana po stronie użytkownika. Każde odświeżenie spowalnia aplikację, dlatego dane powinny być przeładowywane tylko wtedy, gdy różnią się od swojej poprzedniej wersji. 

Z pomocą przychodzi header protokołu HTTP, a mianowicie – ETag. Odpowiada on za identyfikowanie wersji zasobów zwracanych przez serwer. Wartość ETag może być tworzona na wiele sposobów. Używane do tego są przeważnie algorytmy hashujące, aczkolwiek nie jest to wymagane.


Najważniejsze jest to, aby mieć pewność, iż wygenerowana wartość będzie unikalna. Jeśli użyjemy zbyt słabego generatora, możemy uzyskać dla różnych wersji zasobów ten sam identyfikator, co doprowadzi do niepoprawnego działania. 

Zawsze gdy dane dostępne pod konkretnym endpointem ulegną zmianie, powinna zostać wygenerowana nowa wartość Etag. Aby dodać go do odpowiedzi, wystarczy nowy header „ETag” wraz z wygenerowaną przez nas wartością identyfikującą konkretne zasoby: ETag: "etag value".

Po wykonaniu zapytania oraz uzyskaniu w odpowiedzi wartości ETag powinniśmy ją zapisać po stronie użytkownika. Podczas przygotowywania kolejnego requestu uzyskaną wartość musimy dodać jako header „If-None-Match”. 

W przypadku gdy wysłana wartość jest taka sama jak wartość ETag zwracana w danym momencie przez serwer, zamiast kodu odpowiedzi 200 zwrócony zostanie 304, czyli „Not modified”.

Poniżej przykład zastosowania takiego rozwiązania w .NET.:

Stworzenie nowego atrybutu:

internal sealed class ETagFilter : Attribute, IActionFilter
{
    private readonly int[] _statusCodes;

    public ETagFilter(params int[] statusCodes)
    {
        _statusCodes = statusCodes;
        if (statusCodes.Length == 0) _statusCodes = new[] { 200 };
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.HttpContext.Request.Method != WebRequestMethods.Http.Get) return;
        if (!((IList) _statusCodes).Contains(context.HttpContext.Response.StatusCode)) return;
        var content = JsonConvert.SerializeObject(context.Result);

        var etag = ETagGenerator.GetETag(Encoding.UTF8.GetBytes(content));
                
        if (context.HttpContext.Request.Headers.Keys.Contains(HeaderNames.IfNoneMatch) && context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch].ToString() == $"\"{etag}\"")
        {
            context.Result = new StatusCodeResult((int) HttpStatusCode.NotModified);
        }
        context.HttpContext.Response.Headers.Add(HeaderNames.ETag, new[] { $"\"{etag}\"" });
    }        
}

internal static class ETagGenerator
{
    public static string GetETag(byte[] contentBytes)
    {
        return GenerateETag(contentBytes);
    }

    private static string GenerateETag(byte[] data)
    {
        using var md5 = MD5.Create();
        var hash = md5.ComputeHash(data);
        var hex = BitConverter.ToString(hash);
        return hex.Replace("-", "");
    }
}


Powyżej został załączony kod atrybutu, który możemy stworzyć w celu implementacji obsługi ETag. 

ETagGenerator jest klasą pomocniczą, która tworzy unikalny string odpowiadający tablicy bajtów, wygenerowanej na podstawie danych, które są zwracane przy zapytaniu. W powyższym rozwiązaniu został użyty algorytm MD5.

W metodzie OnAcionExecuted, która jest wykonywana już po przeprowadzeniu operacji wygenerowania danych po stronie serwera, sprawdzane jest, czy do zapytania został dodany header „If-None-Match”. Jego wartość jest porównywana do wartości generowanej na podstawie danych zwróconych w danym momencie.

Użycie atrybutu:

Aby zastosować filtrację, wystarczy dodać stworzony przez nas atrybut nad konkretnym endpointem.

Przykład działania:

Poniżej do zobrazowania requestów używałem wtyczki do przeglądarki google Talend API Tester – Free Edition.

Pierwsze zapytanie do serwera:


Odpowiedź:


Jak widać w odpowiedzi. zwracana jest lista sklepów wraz z ETagiem.

W drugim zapytaniu otrzymaną wartość ETag musimy dodać jako header „If-None-Match”.

Drugie zapytanie: 


Odpowiedź:


Jak widać nasza logika zadziałała, pobierane dane się nie zmieniły, dlatego wartość przez nas wysłana była równa tej, którą dostaliśmy podczas wykonywania pierwszego podsumowania.

Użycie powyższego rozwiązania pozwala na lepszą wydajność aplikacji klienckich, ponieważ zawsze możemy zweryfikować to, iż pobierane dane niczym nie różnią się od danych pobranych wcześniej. 

<p>Loading...</p>