Serwis dla generycznych powiadomień

0

Buduję aplikację konsolową która ma wysyłać żądania do Web API. Aplikacja ta będzie posiadać kilka serwisów odpowiedzialnych za:

  • pobranie danych przez Console.ReadLine() ,
  • wygenerowanie danych,
  • serializację danych,
  • migrację bazy i tabeli,
  • wywołanie żądań API.

Po każdej wykonanej czynności chcę żeby w konsoli się pojawił komunikat:

  • rozpoczęto coś tam,
  • zakończono coś tam,
  • rezultat jakiś tam,
  • stan jakiś tam, itp.

Generalnie to tych Console.WriteLine się trochę zbierze. Zastanawiam się czy znacie jakieś ciekawe generyczne rozwiązanie dla wywołań odpowiedniego stringu przy każdym komunikacie? Na razie przychodzi mi do głowy pomysł wykorzystujące słownik i wywołanie go z kluczem nazwy wywołującej metody wyciągniętej przez [CallerMemberName], jednak nie wiem jeszcze jak przeskoczyć fakt, że każda metoda może chcieć wyświetlić kilka komunikatów w zależności od etapu egzekucji i stanu.

2

Co jest złego w klasycznym Console.WriteLine() (bądź innych, bardziej logging-oriented rozwiązaniach)?

Takie komunikaty jest bajecznie prosto potem w kodzie odnaleźć, bajecznie prosto debugować i modyfikować... same zalety, w porównaniu do refleksyjnej magii.

2

jednak nie wiem jeszcze jak przeskoczyć fakt, że każda metoda może chcieć wyświetlić kilka komunikatów w zależności od etapu egzekucji i stanu.

Dictionary<string, Func>();

mozesz wtedy zrobic takie cos

a = new Dictionary<string, Func>();
a["bla"] = () => 
{
  Console.WriteLine("1");
  Console.WriteLine("2");
  Console.WriteLine("3");
  Console.WriteLine("4");
}

mogłem ze składnią się walnąc jako ze pisane z głowy

1

Kilka rozwiązań można tutaj zastosować:

  1. Wprost wrzucasz w kod Console.WriteLine() (lub jakiś inny logger NLog, log4net) - osobiście także nie lubię jak komunikaty przesłaniają to co metoda robi. Ale czasem idę na taki kompromis.
  2. Dictionary - tak jak zaproponowałeś.
  3. Idziesz w coś w rodzaju Domain Logging https://martinfowler.com/articles/domain-oriented-observability.html.
  4. Wyciągasz armatę i wybierasz Fody https://github.com/Fody/Fody.
0

Albo publikujesz eventy na kolejkę :D

0

Ostatecznie zdecydowałem się wykorzystać dwa słowniki, jeden dla wiadomości, drugi dla błędu, wchodząc do nich nazwą metody z [CallerMemberName]. Największym minusem rozwiązania jest fakt, że przy zmianie nazwy metody muszę zmienić też string klucza w słownikach. Plusem jest zebranie komunikatów w jednym miejscu i mniejszy bałagan w metodzie.

        Dictionary<string, string> _messagesInfo;
        Dictionary<string, string> _messagesError;

I wokorzystując je tak:

public string SerializeData(List<PersonModel> dataCollection)
        {
            string callerName = _messageService.GetCallerName();
            string json = "";

            Console.Write(_messagesInfo[callerName]);

            try
            {
                if (dataCollection.Count != 0)
                {
                    json = JsonConvert.SerializeObject(dataCollection);
                }

                Console.WriteLine("OK");

                return json;
            }
            catch (Exception e)
            {
                Console.WriteLine(_messagesError[callerName]);

                return "";
            }
        }

Ale chcę dorzucić jeszcze NLog żeby zapisywał też komunikaty w pliku.

1
bakunet napisał(a):

Największym minusem rozwiązania jest fakt, że przy zmianie nazwy metody muszę zmienić też string klucza w słownikach.

Nie możesz użyć operatora nameof?

0
somekind napisał(a):

Jak użyjesz callerName jako klucz słownika?

List<string> _methodList;
        public MessageService() //ctor
        {
            _methodList = new List<string>()
            {
                "EnterTheQty",
                "GenerateRandomData",
                "SerializeData"
            };
        }
        public string GetCallerName([CallerMemberName] string caller = null)
        {
            return caller;
        }

        public Dictionary<string, string> GetMessagesError()
        {
            Dictionary<string, string> messages = new Dictionary<string, string>();
            messages.Add(_methodList[0], "\nError when getting the number.");
            messages.Add(_methodList[1], "\nError when generating random data.");
            messages.Add(_methodList[2], "\nError when serializing data.");

            return messages;
        }

        public Dictionary<string, string> GetMessagesInfo()
        {
            Dictionary<string, string> messages = new Dictionary<string, string>();
            messages.Add(_methodList[0], "\nHow many records you do want to generate? ");
            messages.Add(_methodList[1], "\nGenerating {0} models... ");
            messages.Add(_methodList[2], "\nSerializing collection to the JSON format... ");

            return messages;
        }

A pobieranie z serwisu poprzedni post

1

Plusem jest zebranie komunikatów w jednym miejscu i mniejszy bałagan w metodzie.

Jeśli twoja aplikacja ma szansę się rozrosnąć to możesz w przyszłości zmienić zdanie.
Patrząc na pierwszy post gdzie definiujesz 4 miejsca w których chciałbyś wyświetlić komunikat to można wykorzystać np bazowy abstrakcyjny handler i wzorzec template method

np. coś takiego

abstract class OperationHandlerBase<TInput, TResult>
    {
        protected abstract TResult Handle(TInput input);

        public TResult Execute(TInput input)
        {
           LogActionExecutionStart();
           var result = Handle(input);
           LogActionExecutionEnd();
           LogActionExecutionResult(result);
           LogActionExecutionState(result);
           return result;

        }

        protected virtual void LogActionExecutionStart(/*params*/)
        {
        }

        protected virtual void LogActionExecutionEnd(/*params*/)
        {
        }

        protected virtual void LogActionExecutionResult(TResult result)
        {

        }

        protected virtual void LogActionExecutionState(TResult result)
        {
        }
    }

    class SampleHandler : OperationHandlerBase<int, string>
    {
        protected override string Handle(int input)
        {
            return input.ToString();
        }
    }

    class SampleService
    {
        private readonly SampleHandler _handler;

        public SampleService(SampleHandler handler)
        {
            _handler = handler;
        }

        public string GetString(int input)
        {
            return _handler.Execute(input);
        }
    }

Tutaj w przykładowej klasie 'Sample handler' możesz przeciążyć metody logujące stan i wykonać tam co tylko chcesz.

1
bakunet napisał(a):

A pobieranie z serwisu poprzedni post

Przecież w Twoim kodzie kluczem słownika jest stały string z _methodList a nie jakiś callerName.
Nadal proponuję użyć nameof:

var messages = new Dictionary<string, string>
{
    [nameof(Klasa.EnterTheQty)] = "\nError when getting the number.",
    [nameof(Klasa.GenerateRandomData)] = "\nError when generating random data.",
    [nameof(Klasa.SerializeData)] = "\nError when serializing data.",
};

Przy czym zamiast takich kombinacji, ja bym chyba po prostu użył loggera i go ręcznie wywoływał, przynajmniej wiadomo wtedy na jakim etapie jest wołany.

1 użytkowników online, w tym zalogowanych: 0, gości: 1