Gravatar użytkownika

Cześć,

dzisiejszy wpis należy potraktować z lekkim przymrużeniem oka, ponieważ „funkcjonalność” którą zaimplementujemy  za wiele nie wniesie do projektu. Jest to bardziej coś co po prostu chciałem zawsze mieć w aplikacji tj. avatary użytkowników w stylu retro, które są generowane np. w Worpressie. Wszystkich z Was, którzy liczyli na coś bardziej konkretnego przepraszam, ale i obiecuję że to pierwszy i ostatni taki wpis.

Do osiągnięcia naszego dzisiejszego celu skorzystamy z API dostarczonego przez gravatar.com. W dokumentacji znajduje się informacja w jaki sposób należy zbudować link, aby zwrócony został nam obrazek. Dla naszego przypadku będzie on miał następującą postać:

http://www.gravatar.com/avatar/{hasz}?size=56&d=retro

gdzie:

  • hasz – skrót otrzymany po zahaszowaniu nazwy użytkownika funkcją MD5.
  • size – wysokość obrazka w pikselach.
  • d – parametr determinujący styl naszego obrazka. Nas interesuje retro czyli pikselowy.

 

Wobec tego pierwszą rzeczą jaką zaimplementujemy będzie klasa, która dostarczy metodę budującą ów link. Jej implementacja wygląda następująco:

 


public static class GravatarHelper
    {
        public static string CreateGravatarUrl(string userName)
        {
            var md5 = MD5.Create();
 
            var userNameTextBytes = Encoding.UTF8.GetBytes(userName);
            var hashBytes = md5.ComputeHash(userNameTextBytes);
 
            var hashString = string.Concat(hashBytes.Select(h => h.ToString("x2"))).ToLower();
 
            return $"http://www.gravatar.com/avatar/{hashString}?size=56&d=retro";
        }
    }

 

Jest to klasa pomocnicza, która posiada jedną metodę statyczną CreateGravatarUrl. Korzystamy tu z dostarczonej przez System.Security.Cryptography klasy MD5. Po utworzeniu skrótu musimy przekształcić go z postaci tablicy bajtów do tekstu. W tym celu korzystamy z LINQ oraz przeciążenia metody ToString. Na końcu zwracamy link i to chyba wszystko 😛

W zasadzie mógłbym tutaj skończyć pisząc Wam „ok, link zapisujemy w bazie danych a potem wyświetlamy go w tagu <img>”. Byłaby to najprostsza i zarazem najszybsza metoda. Problem tkwi w tym, że takie podejście nie zabezpiecza nas przed brakiem dostępu do serwisu. Co jeżeli nastąpi jakaś przerwa techniczna lub coś po prostu nawali po stronie gravatara? Requesty będą stały, aż w końcu dostaniemy timeout. Żeby się przed tym częściowo zabezpieczyć podczas procesu rejestracji użytkownika pobierzemy obrazek, który następnie zapiszemy w bazie danych. Dzięki temu nie będziemy zależni od dostępności zewnętrznego serwisu. Tu jeszcze mała uwaga. Domyślam się, że wielu z Was nie podoba się trzymanie obrazków na bazie i całkowicie to rozumiem. Można by wynieść je do jakiegoś CDN-a, lub zapisywać jako pliki na dysku. Dlaczego zatem tego nie robię? CDN odpada bo mam za z tym małe doświadczenie (marzy mi się Azure CDN). Pliki na dysku zapiszę, a potem zapomnę usunąć i zrobi mi się śmietnik. Poza tym to jedyne obrazki jakie pojawią się w projekcie, dlatego też nie patrzę na to pod kątem ultra optymalizacji. No dobra wracamy do głównego wątku. Skoro mamy pobrać obrazek, to warto pomyśleć o jakimś serwisie, który będzie tę czynność realizował. Wygląda on tak:

 


public class HttpService : IHttpService
    {
        public async Task<byte[]> GetByteArrayAsync(string url)
        {
            using (var httpClient = new HttpClient())
            {
                return await httpClient.GetByteArrayAsync(url);
            }
        }
    }

 

Póki co posiada on jedynie metodę GetByteArrayAsync. W klauzurze using tworzymy klienta http, dzięki temu nie będziemy musieli pamiętać o jego pozbyciu się. Następnie wywołujemy odpowiednią metodę, przekazując URL do naszego gravatara. To wszystko. Pamiętamy oczywiście o zarejestrowaniu naszego serwisu w Autofac-u! Następnym krokiem będzie modyfikacja klas UserRegisterDto oraz UserEntity. Obu musimy dodać pole, które będzie zawierać gravatar:

 


public byte[] Gravatar { get; set; }

 

Znów mała uwaga/ tłumaczenie się 😀 W przypadku UserEntity nie bawiłem się w wyciąganie obrazka do oddzielnej tabeli, bo nie ma to sensu. Tak jak mówiłem, to jedyny taki przypadek i nie ma co komplikować sobie nadto życia. Dobrze, dostosujmy akcję Register w AccountController, tak aby pobierać obrazek. Aktualnie wygląda ona tak:

 


[HttpPost("Register"), AllowAnonymous]
        public async Task<IResult> RegisterUserAsync([FromBody] UserRegisterDto userRegisterDto)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values.SelectMany(v => v.Errors).Select(v => v.ErrorMessage).ToArray();
                return CreateResult(ResultStateEnum.Failed, errors);
            }
 
            var gravatarUrl = GravatarHelper.CreateGravatarUrl(userRegisterDto.UserName);
            userRegisterDto.Gravatar = await _httpService.GetByteArrayAsync(gravatarUrl);
 
            var registerResult = await _userAuthDomainServiceProxy.CreateUserAsync(userRegisterDto);
 
            if (!registerResult.Succeeded)
            {
                var errors = registerResult.Errors.Select(e => e.Description).ToArray();
                return CreateResult(ResultStateEnum.Failed, errors);
            }
 
            return CreateResult();
        }

 

Jak widać, używamy wcześniej napisanych klas, aby do DTO przypisać obrazek w postaci tablicy bajtów. Na tym etapie posiadamy już gravatar zapisany w bazie danych jako varbinary(max). Jak zatem go wyświetlimy? W tym celu musimy zaimplementować  akcję w kontrolerze użytkowników, która na podstawie nazwy użytkownika zwróci jego gravatar. Poniżej listingi z komentarzem, który informuje w jakiej warstwie znajduje się dny kod:

 


//Domain

public async Task<byte[]> GetUserGravatarAsync(string userName)
        {
            return await Repository.Query.Where(u => u.UserName == userName).Select(u => u.Gravatar).SingleOrDefaultAsync();
        }


//DomainProxy

public async Task<byte[]> GetUserGravatarAsync(string userName)
        {
            using (var unitOfWork = _unitOfWorkFactory.Get())
            {
                var userDomainService = _userDomainServiceFactory.Get(unitOfWork);
                return await userDomainService.GetUserGravatarAsync(userName);
            }
        }

//Web
[HttpGet("{userName}/Gravatar"), AllowAnonymous]
        public async Task<FileResult> GetUserGravatarAsync(string userName)
        {
            var gravatar = await _userDomainServiceProxy.GetUserGravatarAsync(userName);
            return new FileContentResult(gravatar, "image/jpeg");
        }

 

Tak na prawdę cały kod sprowadza się do pobrania z bazy danych gravatara w postaci tablicy bajtów i zwrócenia go do klienta w postaci FileContentResult. Nothing special. Ostatnim krokiem będzie wyświetlenie obrazka w headerze obok nazwy zalogowanego użytkownika.  W tym celu dodamy następujący tag:

 

<li if.bind="authService.user"><img src="${host}/Users/${authService.user.userName}/Gravatar"/></li>


 

Tag zostanie wyrenderowany tylko gdy użytkownik się zalogował (o całym procesie pisałem we wcześniejszym wpisie o tu). Jak widać, aby zbudować ścieżkę, wykorzystujemy tajemniczy zmienną host, która określa hosta aplikacji oraz nazwę aktualnie zalogowanego użytkownika. No dobra czas zobaczyć, co udało nam się osiągnąć:

error

AWESOME !!! Marzenie spełnione 😀 Dobra na dziś to wszystko, jutro kolejny wpis konkursowy 🙂 Przypominam Wam, że cały kod macie do wglądu na githubie. Zachęcam również do śledzenia mnie na twitterze, żeby nic Was w przyszłości nie ominęło !

Stay tuned !

You may also like...