Во втором посте из моей серии об отправке push-уведомлений из WordPress в приложения Xamarin через Azure мы рассмотрим функцию Azure, которая обрабатывает наш недавно созданный веб-перехватчик.

Во-первых, давайте еще раз взглянем на состав этой серии:

  • Подготовка вашего WordPress (блога/сайта)
  • Подготовка функции Azure и подключение веб-перехватчика (этот пост)
  • Подготовка концентратора уведомлений
  • Отправить уведомление на Android
  • Отправить уведомление на iOS
  • Добавление в Xamarin.Forms

Создание новой функции Azure в Visual Studio

Самый простой способ создать новую функцию Azure (если у вас уже есть учетная запись Azure) — добавить новый проект в решение Xamarin:

После загрузки проекта дважды щелкните файл .csproj в обозревателе решений, чтобы открыть файл для редактирования. Убедитесь, что у вас есть следующие две записи PropertyGroup:

<PropertyGroup>
    <TargetFramework>net461</TargetFramework>
    <AzureFunctionsVersion>v1</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <!--DO NOT UPDATE THE AZURE PACKAGES, IT WILL BREAK EVERYTHING!!!!-->
    <PackageReference Include="Microsoft.Azure.NotificationHubs" Version="1.0.9" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.NotificationHubs" Version="1.3.0" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.31" />
    <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
  </ItemGroup>

Вы, наверное, заметили, что во второй записи PropertyGroup я написал комментарий прописными буквами. Поскольку я использую функцию v1, это последние пакеты, которые я могу использовать. Они выполняют свою работу и позволяют нам использовать простой способ привязки Function к Azure NotificationHub , который мы собираемся реализовать в следующем посте. Я отложил обновление всей настройки, чтобы намеренно использовать функцию v2 на этом этапе.

Обработка полезной нагрузки Webhook

Чтобы иметь возможность обрабатывать полезную нагрузку (помните, мы получаем строку JSON) из нашего WordPress Webhook, нам нужно ее десериализовать. Давайте создадим класс, который содержит всю информацию о нем:

using Newtonsoft.Json;

namespace NewPostHandler
{
    public class PublishedPostNotification
    {
        [JsonProperty("id")]
        [JsonConverter(typeof(StringToLongConverter))]
        public long Id { get; set; }

        [JsonProperty("title")]
        public string Title { get; set; }

        [JsonProperty("status")]
        public string Status { get; set; }

        [JsonProperty("featured_media")]
        public string FeaturedMedia { get; set; }
    }
}

Класс делает это довольно прямолинейно, мы будем использовать эту реализацию как есть для полезной нагрузки, которую мы отправляем в Android позже. Использование StringToLongConverter необязательно. Для полноты вот реализация:

using Newtonsoft.Json;
using System;

namespace NewPostHandler
{
    public class StringToLongConverter : JsonConverter
    {
        public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);

        public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return default(long);
            var value = serializer.Deserialize<string>(reader);
            if (long.TryParse(value, out var l))
            {
                return l;
            }

            return default(long);
        }

        public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
        {
            if (untypedValue == null)
            {
                serializer.Serialize(writer, null);
                return;
            }
            var value = (long)untypedValue;
            serializer.Serialize(writer, value.ToString());
            return;
        }

        public static readonly StringToLongConverter Instance = new StringToLongConverter();
    }
}

Теперь, когда мы подготовили наш объект для передачи данных, пришло время, наконец, взглянуть на код процессора.

[FunctionName("HandleNewPostHook")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
    _log = log;
    _log.Info("arrived at 'HandleNewPostHook' function trigger.");

    //ignoring any query parameters, only using POST body

    PublishedPostNotification result = null;

    try
    {
        _jsonSerializerSettings = new JsonSerializerSettings()
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal },
                StringToLongConverter.Instance
            }
        };

        _jsonSerializer = JsonSerializer.Create(_jsonSerializerSettings);

        using (var stream = await req.Content.ReadAsStreamAsync())
        {
            using (var reader = new StreamReader(stream))
            {
                using (var jsonReader = new JsonTextReader(reader))
                {
                    result = _jsonSerializer.Deserialize<PublishedPostNotification>(jsonReader);
                }
            }
        }

        if (result == null)
        {
            _log.Error("There was an error processing the request (serialization result is NULL)");
            return req.CreateResponse(HttpStatusCode.BadRequest, "There was an error processing the post body");
        }

       //subject of the next post
      //await TriggerPushNotificationAsync(result);
    }
    catch (Exception ex)
    {
        _log.Error("There was an error processing the request", ex);
        return req.CreateResponse(HttpStatusCode.BadRequest, "There was an error processing the post body");
    }

    if (result.Id != default)
    {
        _log.Info($"initiated processing of published post with id {result.Id}");
        return req.CreateResponse(HttpStatusCode.OK, "Processing new published post...");
    }
    else
    {
        _log.Error("There was an error processing the request (cannot process result Id with default value)");
        return req.CreateResponse(HttpStatusCode.BadRequest, "There was an error processing (postId not valid)");
    }
}

Давайте пройдемся по коду. Первое, что я хочу знать, это если мы когда-нибудь войдем в Function, поэтому я регистрирую вход. Второй шаг — настройка JsonSerializer для десериализации полезной нагрузки в класс DTO, который я создал ранее.

Есть несколько сценариев, которые я обрабатываю и возвращаю разные ответы. В идеале мы должны пройти и получить вызов TriggerPushNotificationAsync, за которым следует переход к ответу 'OK', если идентификатор сообщения, полученный от нашего Webhook, действителен. Однако во время тестирования я столкнулся и с другими ситуациями, когда я возвращал ответ 'Bad Request' с намеком на то, что что-то пошло не так.

Реализация метода TriggerPushNotificationAsync не показана в этом посте, так как он будет предметом следующего поста в этой серии.

Локальное тестирование кода

Одна из причин, по которой я решил запустить Function в Visual Studio, — это возможность отлаживать его локально. Если у вас не установлены необходимые инструменты, Visual Studio предложит вам это сделать. После их установки вы сможете продолжить.

Как только служба будет запущена, мы сможем протестировать нашу функцию. Если вы еще не слышали об этом, Почтальон будет самым простым инструментом для этого. Скопируйте URL-адрес функции и вставьте его в поле URL-адреса в Postman. Затем добавьте образец полезной нагрузки JSON в тело (настройки: raw, JSON) и нажмите кнопку Отправить:

Если все пойдет хорошо, Postman даст вам ответ об успешном завершении:

Azure CLI также запишет некоторые выходные данные:

Как видите, все наши записи журнала были записаны в CLI, а также некоторая дополнительная информация из самой Azure. На данный момент не беспокойтесь о состоянии анонимной авторизации, это просто потому, что мы работаем локально. Теоретически мы уже сейчас можем опубликовать функцию в Azure. Поскольку мы знаем, что мы расширим Function в следующем посте, мы не будем делать это прямо сейчас.

Вывод

Как видите, написать Azure Function не так сложно, как кажется. Visual Studio предоставляет все инструменты, необходимые для быстрого начала работы. Возможность локального тестирования кода Function — еще одно большое преимущество Visual Studio.

В следующем посте мы настроим NotificationHub в Azure и расширим наш Function, чтобы вызывать его и запускать уведомления.

До следующего поста, всем удачного кодирования!

Первоначально опубликовано на https://msicc.net 15 марта 2020 г.