Hello! With this material we are opening a cycle of several articles devoted to a long story about how we came to CD from one side and to high availability based on redundancy from the other.
Let's start in order. We have a mobile app API that is in a product environment written in .NET.
And the first step is to transfer it to .NET Core and share with you the subtleties that we encountered along the way.
Some facts about our web API:
The task is very simple and clear - to build a CD pipeline, as our application develops quickly and dynamically, and we need to be guided by the principle “done means released”.
As it is now deployed on the current product environment:
What are we going to:
How we will do it:
Everything looks very simple and logical. But we are sure that we will certainly encounter a number of problems, issues, difficulties, etc. in this way. and try to tell you about them consistently. We gain patience and get down to business.
We start with the transition from ASP.NET Web Api 2 to ASP.NET Core 2 for the core part of the mobile service (so far without pushing and banners. We will deal with them a little later if there are pitfalls - we'll tell in a separate article).
There are many tasks. Some of them are solved in fairly standard ways, following the official manual , but there is something that does not lie on the surface. And, probably, will cause you questions when solving a similar problem. They want to share with you.
Our mobile service refactoring plan is as follows:
1. Create a new solution in Visual Studio 2017
2. Connect external dependencies
2.1 Connecting WCF services
First of all, we check the table to see if .Net Core 2.0 has support for the necessary WCF client features.
What was previously called Service References is now referred to as Connected Services. Add to the WCF Web Service Reference project via the Add Connected Service menu.
Since ASP.NET Core no longer contains Web.config files, all WCF client settings are stored in the generated Reference.cs code.
The client class has a method for entering additional client settings:
static partial void ConfigureEndpoint(ServiceEndpoint serviceEndpoint, ClientCredentials clientCredentials)
We write the partial implementation. In our case, this method contains Credentials for authorization in the service.
In addition to changes in client settings, the second major change is that there are no more synchronous methods in the client. We used the async option immediately.
2.2. We connect nuget-packages TimeZoneConverter, Swashbuckle, Mime, XmlSerializer.Generator
With the transfer of packages TimeZoneConverter, Swashbuckle, Mime no problems.
Let's talk about the findings. When using a standard Xml serializer, there is an important point: the code generator starts in runtime when it is first used. Accordingly, it increases the cold start time. This behavior can be detected if Just My Code is disabled in your studio. A quick search on the Internet brought us to the nuget-package XmlSerializer.Generator , which came to replace sgen and supports minimal Net Core 2 and Net Standard 2. Its purpose is to generate xml code of the serializer in compile-time.
We are pleased to add it to the project. The package is automatically included in the project build sequence.
Of the restrictions:
a) The generator cannot rezolvit class names taking into account the namespace, so you have to get rid of duplicating the names of DTO-classes, if any.
b) Lazy people use xmltocsharp.azurewebsites.net to generate DTO classes from an XML description. The online service is sinning with the ubiquitous XmlRoot attribute placement. The generator is offended by this behavior. Fire and sword mow out of unnecessary places XmlRoot.
3. We broadcast Global.asax in Startup.cs.
4. Configure configurations
4.1. We set up the environment through IHostingEnvironment.Environment
At the output of the build, we plan to get a single docker image and pass it unchanged through all testing environments. Environment configuration should be fully controlled through the ASPNETCORE_ENVIRONMENT environment variable. Therefore, to the maximum we get rid of the conditional compilation in the code and look at the value of IHostingEnvironment.Environment.
4.2. We transfer the AppSettings block from to the Web. {Configuration} .config to AppSettings. {Environment} .json.
4.3. We prescribe configuration for WCF services for environments Dev, Test, Stage, Release.
5. Remove HttpContext dependency
HttpContext singleton played in the box, IHttpContextAccessor replaced it.
Here are 3 things that have been affected by this change:
Instead of HttpContext.Current.AddErrors, we will use IHttpContextAccessor.HttpContext.Items
With the increase in the number of requests, they often began to face overlapping requests for Timestamp, i.e. the same tag was used for different requests. The IHttpContextAccessor.HttpContext.TraceIdentifier property is available in NET Core - a truly unique identifier.
6. Transfer the controller code
6.1. ASP NET WebApi has been absorbed into ASP NET MVC.
Routing, binding, negotiation are affected, many classes disappeared - starting with ApiController and beyond.
In order to get by with minimal blood flow when transferring the controller code, there is a workaround in the form of a nuta-package WebApiCompatShim , which emulates Web Api concepts based on MVC.
We decided to immediately abandon the layer and use pure MVC with our crutches to feel the pain and clearly understand how much work remains to be done in order to bring everything to an appropriate view of the latest ASP NET Core in the near bright future. As it turned out, everything is not at all sad.
6.2. We write the HttpResponseException
In the project in the old manner, the result object is returned in action instead of IActionResult. In .NET Core, about the grief, they removed the HttpResponseException, explaining that the platform developers are concerned about the correct use of their offspring and tell us not to use exceptions for the query logic — bad request, unauthorized, etc ...
Having agreed with conscience, we postpone the routine for later and saw our HttpResponseException and ActionFilter for it, because as part of the quick transition, it is too long to rewrite everything to IActionResult. And besides, in each method it is necessary to specify the ProducesResponseType attribute, by which the swagger will understand the result class for the action and generate the documentation.
public class HttpResponseException : Exception { public int StatusCode { get; private set; } public string ContentType { get; private set; } = "text/plain"; public HttpResponseException(int statusCode) { StatusCode = statusCode; } public HttpResponseException(int statusCode, string message) : base(message) { StatusCode = statusCode; } } public class HttpResponseExceptionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { if (context.Exception is HttpResponseException) { var ex = (HttpResponseException)context.Exception; context.Result = new ContentResult() { StatusCode = ex.StatusCode, Content = ex.Message, ContentType = ex.ContentType }; context.ExceptionHandled = true; } } }
For the methods of uploading and sending files, an exception was made: here we honestly copied from HttpRequestMessage and HttpContent to FileContentResult and IFormFile, otherwise it is impossible.
7. Documentation
PS Not to say that refactoring was a long time, but still demanded a lot of effort. This experience is now with us (and with you) and next time we can walk this path faster with you.
In the next series we will share the findings on how to configure the assembly in the Docker container. As they say, "do not switch."
Source: https://habr.com/ru/post/348590/