Pull to refresh

Development of “YaRyadom” (“I’mNear”) application under the control of Vk Mini Apps. Part 1 .Net Core

Reading time 8 min
Views 939
Original author: Анатолий Кабанов
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 “VKontakte”. 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 few lines about Vk Mini Apps.


Vk Mini App is a website which is loaded in iframe 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 VkBridge Library. 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 here you can find a Vk Mini Apps community with necessary links for the documents and other useful stuff.

Backend.


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 .Net Core, EF Core as ORM, and Swagger 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.

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 here 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.

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();
}



The parameter vkUrl itself can be extracted from the header “referer”. Also separately we can use the parameter vk_user_id 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.

SecretKey — is a secured key of your VK mini app, you can find it in the settings (on the page of settings of your app — “secured key”), 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.

Vk mini app

On the server side it is also important and absolutely necessary to include spam-protection. There is a ready to use library AspNetCoreRateLimit, 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.

 
// ConfigureServices
services.AddMemoryCache();
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>(); 
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
 
 
// Configure 
app.UseIpRateLimiting();
app.UseMvc();



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’t work. The rules themselves are described in appsettings separately for debagging and release.

"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
		}
	]
},



Additionally we can mention the validation process. Up until this moment I haven’t 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.
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.

The interaction with the database is done via EF Core, and apart from the main library for this database Npgsql.EntityFrameworkCore.PostgreSQL, there is also an additional one Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite, 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.
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.

 
// 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);



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.

 
HasColumnType("geography (point)")



Here I should mention an important peculiarity concerning using PostGIS, and to be more exact — 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 here. 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 DbContext class for the using types cash update and choose this method when starting the app.

 
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();
	}
}



If we talk about the usage of some VKontakte functions, such as sending notifications or receiving users’ information, I created a small separate library VkApi. Here you will need Service token from your application settings on your VK page. In the code the field is called _accessToken, because the setting has the same name — access_token, 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 notifications.sendMessage, the name of VK methods itself is kept in enum — VkApiMethod for now.

 
public async Task<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<NotificationResponse>(json);
	return notificationResponse;
}



The description of VK API methods can be found on the official website, before the description you will see the phrase “This method can be called using the access service key”. 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.

 
public class BaseResponse<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; }
}



Before each and every notification sending, it is necessary to make sure whether the user’s notifications are turned on or not, using another method apps.isNotificationsAllowed.

 
public async Task<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<NotificationAllowanceResponse>(json);
	return notificationAllowanceResponse;
}



The logic of sending should be as follows:
  • look for all the users to which we have to send the message;
  • do the checking apps.isNotificationsAllowed;
  • form the groups of users according to the message we want to send in order to make as minimum requests as possible;
  • send the message up to 100 users at once.

Debugging.


All the requests from VK applications should be secured, i.e. should start with https. That is why in order to debug we need some kind of proxy service, for example, ngrok, 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 «ngrok http 3033» parameters, where 3033 — is your application port. More details on ngrok and its set up you can find here.

P.S.


If anybody is interested in helping out with the code — 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.
Tags:
Hubs:
0
Comments 0
Comments Leave a comment

Articles