{"id":406060,"date":"2024-06-29T18:41:43","date_gmt":"2024-06-29T18:41:43","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=406060"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=406060","title":{"rendered":"<span>Development of \u201cYaRyadom\u201d (\u201cI\u2019mNear\u201d) application under the control of Vk Mini Apps. Part 1 .Net Core<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">Application is developed in order to help people find their peers who share similar interests and to be able to spend some time doing what you like. The project is currently on the stage of beta-testing in the social network \u201cVKontakte\u201d. Right now I am in the process of fixing bugs and adding everything that is missing. I felt like I could use a bit of destruction and decided to write a little about the development. While I was writing, I decided to divide the text into different parts. Here we are going to pay more attention to backend nuances which I faced, and to everything that a user does not see. <a name=\"habracut\"><\/a><\/p>\n<h3>A few lines about Vk Mini Apps. <\/h3>\n<p>  Vk Mini App is a website which is loaded in <b>iframe <\/b>format with an opportunity of interaction with basic functionalities of VK through API. That means that one part of the functions is available right away without the implementation of interaction with their API, but in order to do so, one needs to have <b>VkBridge Library<\/b>. It is great to have such a library, however, the same as in the case with API documentation, here we have a peculiarity concerned about the language you read in. I have English language chosen by default, but there is a little trick about documentation in different languages in VKontakte. One can get much more information in Russian, whereas some pages in English either do not exist at all or have just a partial translation. So my advice to you is to switch your language into Russian and only then continue to look through the documents. And <a href=\"https:\/\/vk.com\/vkappsdev\" rel=\"nofollow\">here <\/a>you can find a Vk Mini Apps community with necessary links for the documents and other useful stuff. <\/p>\n<h3>Backend.<\/h3>\n<p>  I had to create a web API for the interaction of a mini app with the server. From the point of view of technology, we use <b>.Net Core, EF Core<\/b> as ORM, and <b>Swagger <\/b>in order for the other person to understand what kinds of requests exist, which input data and masthead the person needs to work with during the development. Also I used several additional libraries. <\/p>\n<p>  First of all, in order to get requests only from VKontakte side, the person needs to complete the process of authorization. Here, it is important to provide the verification of parameters of each request, the examples from VK are <a href=\"https:\/\/vk.com\/dev\/vk_apps_launch_params\" rel=\"nofollow\">here <\/a>and the example for .Net Core is provided below. OnActionExecuting is a method which is inherited from ActionFilterAttribute class, i.e. you create your own attribute which can be implemented into the verification. <\/p>\n<pre><code class=\"cs\">public static string GetToken(string message, string secret) { secret ??= \"\"; var encoding = new UTF8Encoding(); var keyByte = encoding.GetBytes(secret); var messageBytes = encoding.GetBytes(message); using var hmacsha256 = new HMACSHA256(keyByte); byte[] hashMessage = hmacsha256.ComputeHash(messageBytes); return Convert .ToBase64String(hashMessage) .Replace('+', '-') .Replace('\/', '_') .Replace(\"=\", string.Empty); }  public override void OnActionExecuting(ActionExecutingContext actionExecutingContext) { string vkUrl = actionExecutingContext.HttpContext.Request.Headers[Header.VkReferers]; \/\/ \"Referer\" if (!string.IsNullOrWhiteSpace(vkUrl)) { var uri = new Uri(vkUrl); var queryParameters = HttpUtility.ParseQueryString(uri.Query); var orderedKeys = queryParameters.AllKeys.Where(p => p.StartsWith(\"vk_\")).OrderBy(p => p); var orderedQuery = HttpUtility.ParseQueryString(string.Empty); foreach (var key in orderedKeys) { orderedQuery[key] = queryParameters[key]; } var token = HmacHash.GetToken(orderedQuery.ToString(), _appSettings.SecretKey); var valid = token.Equals(queryParameters[\"sign\"]); if (valid) return; } actionExecutingContext.Result = new BadRequestResult(); }  <\/code><\/pre>\n<p>  The parameter <b>vkUrl <\/b>itself can be extracted from the header \u201c<b>referer<\/b>\u201d. Also separately we can use the parameter <b>vk_user_id <\/b>from this header, in order to use it as an identificator or to make a comparison with it in case you are going to use it in property of your object. <\/p>\n<p>  <b>SecretKey <\/b> \u2014 is a secured key of your VK mini app, you can find it in the settings (on the page of settings of your app \u2014 \u201csecured key\u201d), there you can also find a service token which can be useful when there is a request of data about the users from the server side. <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/9k\/3_\/fe\/9k3_feelq7lgdfdpvezf2r-vqyo.png\" alt=\"Vk mini app\" data-src=\"https:\/\/habrastorage.org\/webt\/9k\/3_\/fe\/9k3_feelq7lgdfdpvezf2r-vqyo.png\"\/><\/p>\n<p>  On the server side it is also important and absolutely necessary to include spam-protection. There is a ready to use library <b>AspNetCoreRateLimit<\/b>, with the help of which you can set some frequency of requests constraints based on IP or ID of the client. There you can simply initialize a ratelimiter in Startup method Configure and ConfigureServices. <\/p>\n<pre><code class=\"cs\">  \/\/ ConfigureServices services.AddMemoryCache(); services.Configure&lt;IpRateLimitOptions>(Configuration.GetSection(\"IpRateLimiting\")); services.AddSingleton&lt;IIpPolicyStore, MemoryCacheIpPolicyStore>();  services.AddSingleton&lt;IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();     \/\/ Configure  app.UseIpRateLimiting(); app.UseMvc();  <\/code><\/pre>\n<p>  I additionally added UseMvc() method after calling for UseIpRateLimiting() (as it is mentioned in documentation), and also I placed calls before the initialization of swagger, because otherwise UseIpRateLimiting doesn\u2019t work. The rules themselves are described in appsettings separately for debagging and release. <\/p>\n<pre><code class=\"json\">\"IpRateLimiting\": { \/\/ Limit splitted to different types of http (get, post e.t.c) \"EnableEndpointRateLimiting\": true,  \"StackBlockedRequests\": false, \"RealIpHeader\": \"X-Real-IP\", \"ClientIdHeader\": \"X-ClientId\", \"HttpStatusCode\": 429, \"GeneralRules\": [ { \"Endpoint\": \"*\", \"Period\": \"1s\", \"Limit\": 3 }, { \"Endpoint\": \"*\", \"Period\": \"1m\", \"Limit\": 20 }, { \"Endpoint\": \"*\", \"Period\": \"1h\", \"Limit\": 900 } ] },  <\/code><\/pre>\n<p>  Additionally we can mention the validation process. Up until this moment I haven\u2019t thought about the validation of ID for the positive value, however here I decided to validate the models and minimal value of ID. And then of course I started adding the validation of all the values which could be validated. For example, expected values in arrays, and the fact that all the elements of the array are unique, also I started validating string dates and time. <br \/>  I used PostgreSQL as the database, and considering that the project is connected with the geolocation, it is important to save the geographical coordinates somewhere, that is why it is advisable to add PostGIS extension to the database, the component is located in the additional installer Stack Builder. This extension will allow you to do basic calculations, such as the distance between the dots right in the request. <\/p>\n<p>  The interaction with the database is done via EF Core, and apart from the main library for this database <b>Npgsql.EntityFrameworkCore.PostgreSQL<\/b>, there is also an additional one <b>Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite<\/b>, which is perfect for PostGIS extension. There you can find basic data types which can be found in Entity requests. In my case the geographical coordinates are used; the extension allows you to keep the coordinates of not only two, but three dots, i.e. the dot in the volume perspective, and that is why it is important to set which type of dots are going to be used. <br \/>  During the process of initialization of the application, there is an option of setting up a geolocations producing factory and adding it as a singleton in IoC.<\/p>\n<pre><code class=\"cs\">  \/\/ 4326 refers to WGS 84, a standard used in GPS and other geographic systems. var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); \/\/ To use single factory when we need to create some point services.AddSingleton(geometryFactory);  <\/code><\/pre>\n<p>  Also do not forget to mention the type (out of all the possible types of data in the database) in the annotation to the property like Point.<\/p>\n<pre><code class=\"cs\">  HasColumnType(\"geography (point)\")  <\/code><\/pre>\n<p>  Here I should mention an important peculiarity concerning using <b>PostGIS<\/b>, and to be more exact \u2014 concerning the installation of additional extensions to the database with the usage of migrations. There is a problem with the types of data which are not updated in the process of migration, the problem itself you can see <a href=\"https:\/\/github.com\/npgsql\/efcore.pg\/issues\/292\" rel=\"nofollow\">here<\/a>. In order to prevent the occurrence of such mistakes which show up while making requests in the database, you can add the following code in <b>DbContext <\/b>class for the using types cash update and choose this method when starting the app.<\/p>\n<pre><code class=\"cs\">  public void MigrateDatabase() { if (Database.GetPendingMigrations().Any()) { Database.Migrate(); \/\/ Need to reload postgis types, cause of some weird behaviour Database.OpenConnection(); ((NpgsqlConnection)Database.GetDbConnection()).ReloadTypes(); Database.CloseConnection(); } }  <\/code><\/pre>\n<p>  If we talk about the usage of some VKontakte functions, such as sending notifications or receiving users\u2019 information, I created a small separate library <i>VkApi<\/i>. Here you will need Service token from your application settings on your VK page. In the code the field is called <i>_accessToken<\/i>, because the setting has the same name \u2014 <i>access_token<\/i>, it is precisely the place where you should send Service token. Not all API methods are available for calling using the service key, please mind it. In the main class of VkApi I added SendNotificationAsync method in order to call VK <b>notifications.sendMessage<\/b>, the name of VK methods itself is kept in enum \u2014 VkApiMethod for now. <\/p>\n<pre><code class=\"cs\">  public async Task&lt;NotificationResponse> SendNotificationAsync(long[] usersIds, string message) { if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); if (message.Length > 254) throw new ArgumentOutOfRangeException(nameof(message));   var queryString = HttpUtility.ParseQueryString(string.Empty); var users = string.Join(\",\", usersIds); queryString[\"user_ids\"] = users; queryString[\"message\"] = message; queryString[\"v\"] = ApiVersion; queryString[\"access_token\"] = _accessToken; var postValues = new FormUrlEncodedContent(queryString.AllKeys.ToDictionary(k => k, k => queryString[k])); var response = await _httpClient .PostAsync($\"{_apiUrl}{VkApiMethod.NotificationsSendMessage.GetDescription()}?{queryString}\", postValues) .ConfigureAwait(false); var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var notificationResponse = JsonConvert.DeserializeObject&lt;NotificationResponse>(json); return notificationResponse; }  <\/code><\/pre>\n<p>  The description of VK API methods can be found on <a href=\"https:\/\/vk.com\/dev.php?method=methods\" rel=\"nofollow\">the official website<\/a>, before the description you will see the phrase \u201c<b>This method can be called using the access service key<\/b>\u201d. These methods can be used using the service key. Also you should pay attention to restrictions of sending notifications and other functions of calling. After each request you get a VK response in json format, in the previous method it is NotificationResponse class. Generally speaking, most of the answers have a couple of similar features, that is why I have a basic class BaseResponse.<\/p>\n<pre><code class=\"cs\">  public class BaseResponse&lt;TResponse> { [JsonProperty(\"response\")] public TResponse Response { get; set; } [JsonProperty(\"error\")] public Error Error { get; set; } } public class NotificationResponseModel { [JsonProperty(\"status\")] public bool Status { get; set; } [JsonProperty(\"user_id\")] public long UserId { get; set; } }  <\/code><\/pre>\n<p>  Before each and every notification sending, it is necessary to make sure whether the user\u2019s notifications are turned on or not, using another method <b>apps.isNotificationsAllowed<\/b>.<\/p>\n<pre><code class=\"cs\">  public async Task&lt;NotificationAllowanceResponse> IsNotificationsAllowedAsync(long usersId) { var queryString = HttpUtility.ParseQueryString(string.Empty); queryString[\"user_id\"] = usersId.ToString(); queryString[\"v\"] = ApiVersion; queryString[\"access_token\"] = _accessToken; var response = await _httpClient .GetAsync($\"{_apiUrl}{VkApiMethod.IsNotificationsAllowed.GetDescription()}?{queryString}\") .ConfigureAwait(false); var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var notificationAllowanceResponse = JsonConvert.DeserializeObject&lt;NotificationAllowanceResponse>(json); return notificationAllowanceResponse; }  <\/code><\/pre>\n<p>  The logic of sending should be as follows:  <\/p>\n<ul>\n<li>look for all the users to which we have to send the message;<\/li>\n<li>do the checking apps.isNotificationsAllowed;<\/li>\n<li>form the groups of users according to the message we want to send in order to make as minimum requests as possible;<\/li>\n<li>send the message up to 100 users at once.<\/li>\n<\/ul>\n<p>  <\/p>\n<h3>Debugging. <\/h3>\n<p>  All the requests from VK applications should be secured, i.e. should start with <b>https<\/b>. That is why in order to debug we need some kind of proxy service, for example, <b>ngrok<\/b>, which creates a temporary global address with a secured connection to our local API. All you need to do is start your Web API and then start ngrok with \u00abngrok http 3033\u00bb parameters, where 3033 \u2014 is your application port. More details on ngrok and its set up you can find <a href=\"https:\/\/dashboard.ngrok.com\/login\" rel=\"nofollow\">here<\/a>. <\/p>\n<h3>P.S.<\/h3>\n<p>  If anybody is interested in helping out with the code \u2014 feel free to contact me, but at this point the help can be just a voluntary one. I will write the next part a bit later and then I will drop the link here.<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/531904\/\"> https:\/\/habr.com\/ru\/articles\/531904\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">Application is developed in order to help people find their peers who share similar interests and to be able to spend some time doing what you like. The project is currently on the stage of beta-testing in the social network \u201cVKontakte\u201d. Right now I am in the process of fixing bugs and adding everything that is missing. I felt like I could use a bit of destruction and decided to write a little about the development. While I was writing, I decided to divide the text into different parts. Here we are going to pay more attention to backend nuances which I faced, and to everything that a user does not see. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-406060","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/406060","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=406060"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/406060\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=406060"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=406060"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=406060"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}