Implementing a RESTful API with .NET Core 2.1 - Jaakko Heikkilä - Theseus
←
→
Page content transcription
If your browser does not render page correctly, please read the page content below
8 Jaakko Heikkilä Implementing a RESTful API with .NET Core 2.1 Metropolia University of Applied Sciences Bachelor of Engineering Software Engineering Bachelor’s Thesis 31 January 2021
Abstract Author Jaakko Heikkilä Title Implementing a RESTful API with .NET Core 2.1 Number of Pages 48 pages Date 31 January 2021 Degree Bachelor of Engineering Degree Programme Information Technology Professional Major Software engineering Instructors Janne Salonen, Principal Lecturer Tuomas Jokimaa, supervisor The objective of this thesis was to design and implement a bespoke backend API using Microsoft’s .NET Core framework. The work included the architectural planning, writing ap- plication logic and unit tests, setting up a version control system in GitHub Enterprise and implementing a continuous integration pipeline using TeamCity and Octopus Deploy. The implemented API is part of a larger group of intranet applications which are used by the end users to manage their sales materials and to partition them for different sales teams. The implemented API works as a facade between the user interface and a SQL Server in which the actual logic was implemented. The thesis explores briefly the technologies which were used, and explains the .NET fram- work’s history and some of its web development tools. The three external tools: GitHub En- terPrise, TeamCity and OctopusDeploy are explained. The software development process and the structure of the code are then looked at in more detail. The thesis was carried out in order for the customer to improve their business critical oper- ations. The application was made as a part of an already existing set of applications which dictated the use of programming technologies. The following technologies and tools were used in the implementation: Visual Studio 2017 Enterprise Edition, GitHub Enterprise, TeamCity, OctopusDeploy Keywords Microsoft .NET Core, ASP .NET Core, REST Api
Abstract Tekijä(t) Jaakko Heikkilä Otsikko REST API:n toteutus .NET Core 2.1 Sovelluskehyksellä Sivumäärä 48 sivua Aika 31.1.2021 Tutkinto Insinööri (AMK) Koulutusohjelma Tieto- ja viestintätekniikka Suuntautumisvaihtoehto Ohjelmistotekniikka Ohjaaja(t) Janne Salonen, Principal Lecturer Tuomas Jokimaa, supervisor Tämän opinnäytetyön aiheena on suunnitella ja ohjelmoida asiakkaan tarpeisiin räätälöity backend API käyttäen Microsoftin .NET Core ohjelmistokehystä. Työhön kuului sovelluk- sen arkkitehtuurin suunnittelu, sovellus- ja testauskoodien kirjoittaminen, versiohallintajär- jestelmän pystyttäminen sekä jatkuvan integraation työkalujen pystyttäminen. Työssä tehty API on osa ohjelmistoa, joka on intraverkossa oleva palvelu, millä loppukäyt- täjät voivat hallita myyntimateriaaleja ja niiden jakamista eri myyntitiimeille. Toteutettu API toimii eräänlaisena fasaadina businessäännöt sisältävän sovelluksen ja käyttöliittymän vä- lillä. Opinnäytetyössä perehdytään käytettyihin teknologioihin ja .NET ohjelmointialustan histori- aan, ohjelmointiprosessiin ja ohjelmakoodin rakenteeseen, sekä automaatiotyökaluihin TeamCityyn ja OctopusDeployhin Opinnäytetyö tehtiin, jotta asiakasyritys voisi hallinnoida ja seurata liiketoiminnalle tärkeän materiaalin myyntiä. Sovellus tehtiin osaksi isompaa kokonaisuutta .NET Core -kehyksellä tehtyjä ohjelmia, joissa on pyritty pilkkomaan isompaa sovelluskokonaisuutta pienempiin hallittaviin osiin. Ohjelmistokehys .NET Core valittiin sen ollessa jatketta kypsälle .NET Frameworkille ja myös sen monialusta-tuen vuoksi. Sovelluksen toteutuksessa käytettiin seuraavia Microsoft .NET työkaluja: Visual Studio 2017, GitHub Enterprise, TeamCity, OctopusDeploy. Avainsanat Microsoft .NET Core, ASP .NET Core, REST Api
Contents List of Abbreviations 1 Introduction 1 1.1 Background for the reasoning of the project 1 1.2 Commissioning the work, challenges faced 2 1.3 Outline for the thesis 3 1.4 Key concepts 3 2 Goals for the project 4 2.1 Implementation limitations 4 2.2 Version control, continuous integration and delivery (CI/CD) 5 2.3 Pursued benefits for the related parties 5 3 Overview of .NET Core framework 7 3.1 A brief history of .NET Core 7 3.2 .NET Standard 8 3.3 .NET Core features 8 3.3.1 ASP.NET Core 8 3.3.2 NuGet Package Manager 9 4 The implementation of the project 12 4.1 Application development tools and environment 12 4.1.1 Limitations set by the development environment 12 4.1.2 Docker 13 4.2 CI/CD tools 13 4.2.1 GitHub Enterprise 14 4.2.2 TeamCity 15 4.2.3 Octopus Deploy 16 4.3 Application architecture 16 4.3.1 REST (REpresentational State Transfer) 17 4.4 Overview of the server application architecture 18 4.4.1 The program class and the WebHost 20 4.4.2 Startup class, middleware 21 4.4.3 Routing 25
4.5 Controllers 26 4.5.1 Controller implementation 27 4.5.2 DTO models 28 4.6 Data-access layer 30 4.6.1 Entity classes 30 4.6.2 Updating the database through entity’s methods 33 4.6.3 DbContext 35 4.6.4 Repositories 36 4.6.5 Services 39 4.7 Unit tests and fake implementations 40 4.7.1 Unit test naming convention 40 4.7.2 Unit test frameworks and libraries 41 4.7.3 Structure of the unit tests 42 4.7.4 Fake services and data initializers 43 4.8 Summary of the implementation 44 5 Conclusions 46 5.1 Successes 46 5.2 Further improvements 47 References 48
List of Abbreviations .NET Microsoft’s leading software framework .NET Core Modern version of the framework ASP.NET A software framework for web based applications On-premises Residing in a local server and not in the cloud REST API HTTP based architectural model introduced in 2000 for web-based appli- cations Docker Virtualized operating systems in small packages. Azure Public cloud service for Microsoft. T-SQL Query language for Microsoft SQL Servers Entity Name for objects that describe the database rows and columns. Used in Entity Framework. Environment In software development there are different environments for the software to exist depending on the stage of the version of the software. Develop- ment, testing, production are just a few examples Pull request For version control systems, the review of code is done via pull requests. A code change is asked to be pulled into another version of the code. AAA Arrange, Act, Assert principle for unit testing.
Appendix 2 1 (49) 1 Introduction The work carried out for this thesis was commissioned by the employer company Elisa Oyj. Elisa Oyj was founded in 1882 and has been developing the infrastructure and tel- ecommunication technologies as a pioneer in Finland ever since. In 2010 Elisa Oyj ac- quired Appelsiini Finland Oy, which later was fused into the parent company. Appelsiini Finland Oy had a history of software development in Microsoft's .NET technologies and had many clients and their projects which were transferred to Elisa Oyj in the fusion. One of these clients had an application critical to their business. This application included number of different business functions that were programmed in a monolithic codebase with .NET Framework 4 using ASP.NET MVC. The applications consisted of a user in- terface that was tightly coupled with the server code logic. The server application handled the requests from a web browser and passed those requests onto an SQL Server in- stance where business logic was handled with parametrized stored procedures. The stored procedures were not in the scope for the project. The application resided on a single, on-premises server that is accessible only through the client's network. It was running on a Windows Server 2012 in an instance of IIS. Starting from 2017, this application was given greenlight to be rewritten in smaller indi- vidual applications reminiscent of microservices and using .NET Core as their technology stack. 1.1 Background for the reasoning of the project As software becomes larger and its codebase grows older, the upkeep of the software becomes more time consuming, difficult and costly. Years of hotfixes and patches can leave software filled with undocumented code that violates many principles of good soft- ware craftmanship. At some point it will be more efficient for long term costs to re-write an application. In 2018 .NET Framework 4 was already on a path to be replaced by .NET Core, an open- source version of Microsoft’s technology stack. For many projects made during that time, or after it, there was no discussion on whether to use the newer stack. But not all kinds
Appendix 2 2 (49) of software could be - or can yet be - ported to the new .NET Core framework. As for simpler web applications that use the ASP.NET framework, the groundwork done in .NET Core was already feasible to be used in production applications in its version 2. This change in the direction for the framework came from Microsoft and subsequently also became a priority for the employer. As the employer is also focusing heavily on Azure cloud competence, the need to make applications with .NET Core rises. 1.2 Commissioning the work, challenges faced New applications for the replacement to the client's application were started to be pro- grammed late 2017, and by late 2018 three out of four of the first applications were ready. The overall software architecture for these was in place, the initial requirements and schedule was in place. However, when the last application was to be started, the original team was unavailable, so a new team was tasked to finish the one remaining application. The team consisted of two frontend developers and two backend developers. Requirements for the backend application was to be able to pass the queries from the new React.js frontend to the database which had not been changed during the migration to the new architecture. During the development, the visibility to the database was limited to excel sheet descriptions of the database which included the names of stored proce- dures and the parameters they used. The database also had views which were queried for the data that was returned for the client application. The project was to be done agile, in sprints, but the client was availably minimally, and was only asked to clarify requirements. During its development, the requirements turned out to be incomplete and needed to be addressed many times by the new team which had not the time to properly get familiarized with the existing software. As for the new team, half of its programmers could not commit to the project due to other projects. De- spite these challenges the work was delivered almost on time.
Appendix 2 3 (49) 1.3 Outline for the thesis In the first section, the goals for that were set for the RESTful API, other implemented features and benefits for all parties are explored. Then in the second section, the history of .NET Core and the relation to .NET Framework is looked at. Also, the main features of ASP.NET Core, the web application framework which were used, are introduced. These sections set up the mindset to understand the chosen technologies and what they offer. The third section looks at the overall architecture of the application, the guiding principles for the design and explanation of some core language features of C# are ex- plained while describing the code through example snippets of the delivered product. The fourth section explains the continuous integration tools, version control and the measures which were implemented to ensure code quality. The last section discusses how the implementation compares to the design principles and what works and what does not. 1.4 Key concepts RESTful API - A RESTful API is a server application that accepts HTTP request verbs, processes those and responds with HTTP responses. Client application - An application that acts as a client to a server. These can be html and JavaScript-based, native phone, console application, and more. They act as an in- termediary for the user ORM - Object Relation Mapping is a process where database tables or views are mapped onto objects, such as C# objects in Entity Framework, that represent the data- base in code. SOLID principles – A set of principles for object-oriented software design to make soft- ware more understandable and maintainable introduced by Robert C. Martin.
Appendix 2 4 (49) 2 Goals for the project Numerous requirements that had to be met were set by the existing architecture and the employer, while other decisions were guided by code libraries that were available for .NET Core. For long term maintainability, and to ease other developers contribute to the application, source control and CI tools had to be used. 2.1 Implementation limitations For the project, the goal was to create a RESTful API that would act as an intermediary between the user interface and the existing database, and to setup a continuous inte- gration and delivery (CI/CD) pipeline for the application. ASP.NET Core 2.1 was the re- quired technology for the server application. For the CI/CD pipeline, the employer had already chosen the services: GitHub Enterprise, TeamCity, Octopus Deploy, So- narQube. Figure 1 shows a simplified overview of the different parts of the whole application. The scope of this project was the REST API running on the application server. Figure 1. Application overview
Appendix 2 5 (49) Most, if not all, of the business logic happens in the stored procedures in the client’s database instance. These stored procedures were not the responsibility of the develop- ment team. As such, the server application’s main responsibilities included those of vali- dation, input sanitation, error logging and handling the HTTP requests and responses. The requirements for the interaction with the client app were simply to accept its re- quests, validate and sanitize the queries, and direct the data onto the database for re- quired stored procedures in case of an insert or an update. In case of errors the server application needed to respond with human readable messages. For the interacting with the database, the application called existing stored procedures for updates and SQL server views for fetching data. Entity Framework Core was chosen for this purpose as it is the de facto ORM in .NET Framework. 2.2 Version control, continuous integration and delivery (CI/CD) In addition to the application itself, to ensure code quality, better teamwork and faster deployments, a version control repository needed to be created and a pipeline for CI/CD was to be created. Using GitHub Enterprise, TeamCity CI server and Octopus Deploy a pipeline was built. The responsibilities of the pipeline were to ensure code reviews, unit test coverage, main code branch code quality and the deployment of the application to the development, QA and production environments. 2.3 Pursued benefits for the related parties The commissioned work was ordered by the client and, as such, brings financial benefits to the employer. The modernization of the existing application should he the employer to further help transform the codebases of its various projects to a more sustainable plat- form and advancing the knowledge in new technologies. The usage of .NET Core tech- nologies is critical in the transformation from on-premises servers to cloud based appli- cations using Microsoft Azure or other cloud platform providers.
Appendix 2 6 (49) For the client, the modernized application helps with their plans in moving their applica- tions from intranet to an Azure based environment and can help with the upkeep costs of their application. For the development team the project was a starter for ASP.NET Core technologies and react.js frontend.
Appendix 2 7 (49) 3 Overview of .NET Core framework .NET is a free software framework originally developed by Microsoft, but subsequently changed to be open source and supported by Microsoft. It supports all major platforms including desktop and mobile operating systems.[1] .NET Core is the continuation of .NET Framework, and it can be used to implement var- ious kinds of desktop, server, mobile, IOT and cloud-based software solutions. For .NET Core the main goals were to create a new codebase that was open source, fast, loosely coupled and updated frequently, as opposed to the older .NET Framework which was not open-source and suffered from having to support older code.[1] .NET Framework has been made available to other platforms than Windows using Mono, but .NET Core has taken this to another level. It has a native UNIX development and run environment and can be run with less resources than its predecessor.[1] 3.1 A brief history of .NET Core In 2016 the version 4.6 of .NET Framework was followed by the first versions of .NET and ASP.NET Core. While the development of .NET Core continued, the main imple- mentation was still .NET Framework which kept getting updates for the following years. The first .NET Core releases were made available to the developers but included only a subset of the .NET platforms features. As such the release was not yet recommended to be used in software meant for production. [2] In 2018, around the time the work done in the thesis discussed here, the release of version 2 was already out and ASP.NET Core could be used to create web applications. Some parts, such as Entity Framework Core, had still limited features. Later versions implemented more of the .NET platform features and in 2020 the version .NET 5.0 was released. In this version the name “Core” was removed because it was going to be the main implementation of .NET platform for the future. [3]
Appendix 2 8 (49) 3.2 .NET Standard For the purposes of supporting different environments and development kits, a set of standards are defined in .NET standard. It is the formal specification of the APIs that .NET platform provides.[4] The first version of .NET standard was introduced together with the release of .NET Core and the final version was .NET Standard 2.1 for the release of .NET Core 3.0.[4] Other implementations of the .NET are Mono, Xamarin, UWP and Unity. These are for UNIX, MacOS, iOS, Android, Windows UWP platform and games.[4] With the release of .NET 5.0, the need for .NET Standard was no more as .NET 5.0 has a different approach for multi-platform support.[5] 3.3 .NET Core features .NET Core source code is available to public in a public GitHub repository, it can be forked and changed and is open to pull requests and feedback. In addition to the main source code, the Roslyn-compiler and the .NET Core SDK and the runtime libraries are all available. [6] The framework supports languages such as C#, F# or Visual basic to create different types of applications with different approaches. Web, mobile, desktop and IOT-device applications are supported. The platform has also support for machine learning libraries, cloud services and game development.[7] 3.3.1 ASP.NET Core Building websites, or services that consume or use other services over the web or HTTP protocol can be built with ASP.NET Core. It has first class support from Microsoft and Visual Studio IDE.[8]
Appendix 2 9 (49) ASP.NET Core supports different frontend frameworks such as Blazor, Razor Pages, Bootstrap, React.js and Angular. For these frameworks there are templates that can be used to scaffold an application rapidly.[9] It also has numerous libraries for essential web development purposes, these include HTTP clients, consuming REST and SOAP services, gRPC and GraphQL support, data validation, serialization, security, CORS, interacting with different cloud providers and much more. 3.3.2 NuGet Package Manager A major part of modern platform frameworks is the capability of downloading and sharing software libraries in packages. Many new languages, for example Rust, come with their own package management systems, and Node.js has managers such as npm and Yarn for this purpose. These packages in .NET platform are compiled code which is wrapped in a NuGet-package. NuGet defines how the packages are created, maintained and used, and also provides the tools to create them. These packages then can be pushed to a public or a private repository from where they are accessible to be used by other code as imports. A pack- age can be created with the dotnet CLI tool that comes with .NET Core, using nuget.exe CLI or using MSBuild. The packages then can be pushed to the repository and consumed by other code.[10] During the installation of Visual Studio IDE, the NuGet package manager can be chosen to be installed. Figure 2 shows the installation option for Visual Studio 2019 Community edition. NuGet package manager is a tool to browse public code packages and managing those for your own .NET code solutions.
Appendix 2 10 (49) Figure 2. Visual Studio 2019 installation, showing optional components. When a specific package is added onto a project, the .csproj-file of said project is up- dated with the reference of the included package. The references include the name and the version of the package as a PackageReference-node. Listing 1 shows how these package references look like in the file. Listing 1. A .NET Core project’s .csproj-file references Advantages of using the packages is that the location of the .dll for an individual devel- oper is not an issue as the packages are only referenced and downloaded when re- quired for the environment. Another advantage is that the packages can be excluded from source control repositories which can lead to a lot smaller file size for the project. Because the packages are usually downloaded over the internet, an internet connection is then required to restore the files for local development environment. NuGet supports private repositories in case public internet access is not available as can be for high security software development. Packages that are available in public NuGet repository and installed locally are shown in the NuGet package manager window as picture in Figure 3. The view also shows which packages have updates available on the repository.
Appendix 2 11 (49) Figure 3. NuGet package manager window in Visual Studio 2019 NuGet Package Manager automatically downloads the packages for each project in a solution during the build process when using either Visual Studio or Visual Studio Code. A manual restoration of the packages could also be triggered using the dotnet restore CLI command. When the packages have been successfully installed for a project, they are available to be used and the Visual Studio’s IntelliSense works for the packages. Many of the libraries used in the implemented REST API were installed using NuGet, while some libraries were included in the .NET Core 2.1 Software Development Kit (SDK).
Appendix 2 12 (49) 4 The implementation of the project The objective for the implementation was a .NET Core based server application that can accept HTTP requests, validate them and create necessary queries and commands to an underlying SQL database. In short, a RESTful API working as a gateway between the user interface and the database. In addition to the application, a CI/CD pipeline had to be configured using the technolo- gies which were already taken into use by the employer. These tools included a TeamCity build server, Octopus Deploy automated software deployment server and GitHub Enterprise for the source code. 4.1 Application development tools and environment The code was written and debugged in Windows 10 using Visual Studio (VS) 2017 En- terprise Edition provided by the employer. VS 2017 is an integrated development envi- ronment from Microsoft offering tools for software development. It has a wide variety of software development tools for many purposes such as: • writing, debugging and refactoring code • code quality analysis and cleanup • creating architectural diagrams • writing and running unit tests .NET Core 2.1 SDK including ASP.NET Core SDK was required for web development for the .NET platform. Docker for Windows was installed to enable running the application in other platforms without requiring the installation of .NET Core runtime. 4.1.1 Limitations set by the development environment During the entirety of the development there was no access to the actual database, and there was not an up-to-date copy of the database available for the development team. This is why a reference excel-sheet was used to model the database structure in the
Appendix 2 13 (49) application. The excel-sheet comprised of the tables and views with their corresponding columns and datatypes. This limitation made it necessary to create a dummy interface that returned data which looked like the actual database. The values for the dummy interfaces were hardcoded in the data-layer of the backend application. For the QA and production environments these were replaced by the real implementations. In the end it proved to make the development slightly more difficult. 4.1.2 Docker The backend software was also necessary to be made available for frontend developers who were not capable of running the server on their machines. A docker containerized version of the application was then made. The only requirement for the developer’s ma- chine was to install Docker for their operating system. Docker is a lightweight OS virtualization layer on top of the native OS. It was originally available only for UNIX based operating systems, but Windows operating system support was added and has been made more robust since. With .NET Core, it is possible to target the application for UNIX or Windows and to make it available for containerization. New Visual Studio releases support this directly from the IDE. Using Visual Studio 2017’s built-in Docker support, a docker-compose file was created for the backend server to provide a container for frontend developers. 4.2 CI/CD tools For any software project that is larger in scope it is recommended to use some form of Continuous Integration, Deployment and Delivery. It means processes that can be auto- mated, which reduce the complexity and overhead that is associated with synchronizing with other developers and stakeholders. Automation also reduces the probability of a human error in repetitive tasks. CI/CD tools provide almost any software functionality needed for software development. These include, but are not limited to
Appendix 2 14 (49) • ensuring code quality with automated tests and • checking for failing tests to ensure compatibility, performance, etc. • creating deployment packages for various deployment scenarios • installing the software and making it available to end users. In addition to creating the application, a baseline for CI/CD was needed as required by the employer. This included code reviews, automated tests, automated deployment and delivery of the application. Three services that were in use during the time at the com- pany were GitHub Enterprise, TeamCity and Octopus Deploy. Some form of automation was added to all of these. 4.2.1 GitHub Enterprise GitHub Enterprise is the on-premises version of the leading source control platform GitHub. It is used for local networks and can be integrated into different organizational authentication systems. Its features are largely the same as the public platform, with focus on private organizations. Some features that require a public repository are only available on GitHub.com might not be available for the enterprise version.[11] A new repository was created for the project and a team was created to limit the people who can change the repository settings. The master branch was set to be protected while other branches could be used for development. All pull requests had to have been re- viewed and a passing unit test report had to have been received from the CI pipeline. The connection to TeamCity required an SSH key that was created using personal cre- dentials on GitHub Enterprise and then used in TeamCity Server. A simple version of trunk-based development was used as a branching strategy for the repository, as shown in Figure 4. Making code changes to the master branch had to go through a short-lived branch which then was merged to the master. These branches were prefixed with feature or bug depending on their nature. This was chosen for its lightweight nature and because the development team was small. The master branch was then used as the source for release packages for different environments.
Appendix 2 15 (49) Figure 4. Simplified trunk-based development branching. The master branch was set as a protected branch so that it could not be deleted by accident. For pull requests that targeted the master branch, at least one other develop- er needed to approve the pull request. The approval requirement was set to enforce code review practices. Code review is good to help find errors in the code and to help other developers stay up to date with the changes in the repository. Code reviews are not fast nor a cheap practice and require the reviewer to understand the language, requirements, and the domain of the application. 4.2.2 TeamCity TeamCity by JetBrains was used as the CI/CD server. Its responsibilities were: to run automated unit tests, building the code, running code quality analysis with SonarQube and creating a package for Octopus Deploy. The server was running on the employer’s private servers. Managing different builds was done via build configurations that could be edited through a web-based GUI. A project was created. Then three different build configurations were made. One configuration was created to run automated tests whenever a pull request was cre- ated on GitHub Enterprise. It was set up only to listen to pull requests the targeted the master branch. Two build configurations were created for deployment: one for the private testing envi- ronment and the other for QA and production environments. Private testing was made to build the application in the debug mode for having access to more detailed error mes- sages on the web browser. The other one was a build configuration using the release build settings.
Appendix 2 16 (49) The two build configurations largely shared the same steps with the exception of using debug or release build and where to push the final deployment package. 4.2.3 Octopus Deploy Octopus Deploy was used as the package delivery tool. It consists of a development server app which has a web portal to manage deployments, releases, variables, access rights for users, and create automated deployment processes. Different processes are grouped together in a project that can in theory have multiple applications, settings and deployment target environments. The server application resided in the employer’s serv- ers. Another part of the tool is the Tentacle-agent. The agent resides in the target environ- ment and runs the steps that are described in the deployment process. Octopus Deploy can receive deployable code multiple ways, but for the thesis the TeamCity server pushed the application package to an endpoint which was specified in the Octopus Deploy environment. First a project was created for the purpose of delivering the REST API to three target environments: internal testing, QA and production. Because the QA and production environments had been configured to be as similar as possible, they were configured with variables that had different values depending on the environment. 4.3 Application architecture When an application’s complexity rises, the task to build that it becomes more difficult. This complexity can be managed from multiple different perspectives whether by taking out features, using existing software or the planning of the developed system. The de- sign should be resistant to changes that happen over time. Changes including new re-
Appendix 2 17 (49) quirements, changes to existing features, hardware or software changes, or even per- sonnel who are maintaining the application. As it is, software development can be quite tricky. To manage complexity and to make an application more resistant to changes can be done through the architectural planning of the software. The planning can be done on many levels, such as the hardware and software the system is used with, or the structure of the code itself. In this thesis the architecture means the different parts of the developed application and their relations to each other and external systems. The application’s different parts can be classes which model the client’s needs, or more general software practices for code craftmanship. N-layered architecture and REST architecture are such general practices. Architectural models are used to try to solve specific software development problems. Three tier architecture, which was used in the thesis, divides responsibilities of the server code into presentation, service and repository layers. This structure has been quite pop- ular for a long time to divide the responsibilities of different parts of a web application. This way the development of different parts can be done more independently, but most bespoke software development will run into changes that penetrate through all layers. For example, a change in UI will most likely be added to the logic layer and the data storage. 4.3.1 REST (REpresentational State Transfer) A pure RESTful service should follow the guidelines of using the correct HTTP-verbs for different use cases. Using the verbs correctly, all of the requests targeting the entities can be placed behind a single URL: Table 1 lists the commonly used http methods and how they are used. For example, making a PUT/PATCH request to the url: website/product/id can be used to update data either with the updated fields in the body of the request or by having them as query parameters on the URL itself. [12]
Appendix 2 18 (49) Table 1. Common http methods and their usage. http verb Usage Payload GET Fetch data URL POST Add new entries URL or request body PUT Update an entry URL or request body PATCH Update part of an entry URL or request Body DELETE Delete an entry URL only A maturity model for REST APIs has been suggested. It is called the Richardson Maturity Model and it states three levels of maturity for REST APIs depending on how thoroughly they implement the HTTP-verbs.[13] A fully functioning API can be implemented using only POST requests, but this way the URLs define the action for the HTTP-request. The url: ‘website/products/delete/id’ would be used for deletion. The endpoints in the API implemented in the thesis are mostly POST-requests but a few endpoints responding to HTTP GET requests exist. 4.4 Overview of the server application architecture The codebase was divided roughly into two projects. One for the API layer facing the client application and another one for the data layer. A third, abstract, separation exists inside the data layer project. Separating the database CRUD (Create Read Update De- lete) implementation details from the business rules and validation. Figure 5 depicts the three layers of the server application architecture.
Appendix 2 19 (49) Figure 5. The server application architecture in three layers Application Core exposes services for the Controllers, repository interfaces that the im- plementations must follow and the entities that match the required data fields. The REST API, or presentation layer uses ASP.NET Core's libraries to handle the common web application needs, while the database facing code uses Entity Framework to access the database and to do parameter sanitation. The API application was created using a template included with the ASP.NET Core SDK, which was then stripped of unnecessary code and customized to fit the needs of the project.
Appendix 2 20 (49) 4.4.1 The program class and the WebHost The API layer is the entry point to the application for client applications. It exposes HTTP endpoints called Controllers that can be called by the client. It is hosted on the ASP.NET Core runtime as a console application that is started by the Main function of a Program- class. The application’s entry point is the program class shown in Listing 2. public class Program { public static void Main(string[] args) { var logger = NLog .LogManager .LoadConfiguration("nlog.config") .GetCurrentClassLogger(); try { logger.Debug("Initializing Thesis API"); CreateWebHostBuild(args).Build().Run(); } catch (Exception ex) { //NLog: Catch setup errors logger.Error(ex, "Stopped program on exception") throw ex; } finally { //Ensure to flush and stop internal // timers/threads before application exits Nlog.LogManager.Shutdown(); } } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Trace); }) .UseNlog(); } Listing 2. The program class of an ASP.NET Core 2.2 WebApi application The class Program has a Main-function, which accepts arguments from the executing environment. The code following sets up an instance of NLog logging utility to log any errors that happened during the application’s startup phase. In the fuction Create- WebHostBuilder, it uses the WebHost-class to build a runtime host for the application’s startup and lifetime management using the configuration created in the Startup-class.
Appendix 2 21 (49) The WebHost.CreateDefaultBuilder-function does many things. It sets up the ASP.NET Core webserver, the content root folder, loads the appsettings.json file for database con- nection strings and the configuration for authentication and logging among others.[14] 4.4.2 Startup class, middleware In the core of the application, the class that sets up required services and which works as a glue between different parts of the architecture is the Startup-class. Each request that the client makes goes through the ASP.NET Core middleware pipeline in which the requests are processed and directed to the designated code block that can handle them. The pipeline can be configured by the developer in the Startup class which is called when the program starts. The code for the Startup-class is depicted in Listing 3. In its constructor, the class re- ceives the following: • IConfiguration object that has the deserialized value of the appsettings.json file • ILogger object for logging events in Startup.ConfigureServices • IHostingEnvironment object that can tell where and in what build configu- ration the application is run. Can be used to configure services based on the envinronment. public class Startup { private readonly IConfiguration _config; private readonly IHostingEnvironment _env; private readonly ILogger _logger; public Startup(IConfiguration configuration, ILogger logger, IHostingEnvironment environment) { _config = configuration; _env = environment; _logger = logger; } // Add services to the .NET Core IoC container. public void ConfigureServices(IServiceCollection service) … // Configure the HTTP request pipeline public void Configure(IApplicationBuilder appBuilder) … } Listing 3. The Startup class and its methods.
Appendix 2 22 (49) In addition to the arguments in the constructor, the class has two methods which are called when the application starts: ConfigureServices and Configure. These methods are looked at next. While the host provides services via the Startup class’s constructor, additional services are configured with the ConfigureServices method using the IServiceCollection object. Listing 4 shows a part of the ConfigureServices method and some of the important ser- vices that were configured within it. // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Ver- sion_2_1); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info { Title = "JaakkoHe API", Version = "v1" }); }); services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.AllowAnyMethod().AllowAnyOrigin().AllowCre- dentials().AllowAnyHeader() ); }); services.AddDbContext(options => options.UseSqlServer(_config.GetConnectionString("ThesisConnection- String"))); Listing 4. Important 3rd party services within ConfigureServices method First the AddMvc is used to add many required functionalities that are used in .NET Core 2.1 REST APIs. Then a Swagger-documentation is added to provide developers or users a way to explore the endpoints the API provides. After that an open CORS-policy was added since the application was running on the intranet. Finally the database-connection is configured with the AddDbContext-method. The DbContext is explored more later in the thesis. Listing 5 shows the two ways in which the implemented services were injected to the application’s runtime using the built-in dependency injection functionalities provided by the ASP.NET Core framework. Firstly, for testing with fake data, and to remove depend- ency from a real database, a “debugging” configuration injects fake implementations of
Appendix 2 23 (49) the services as singletons. Secondly, when using a database connection, the services are injected with AddScoped-method. The lifecycle of a class injected this way lasts for one HTTP request. switch (_env.EnvironmentName) { case "Debugging"://Developing with mock data using fake services // add project services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); break; default://Use a database // add project services services.AddScoped(typeof(IRepository), typeof(BaseRepository)); services.AddScoped(typeof(ISprocRepository), typeof(SprocReposi- tory)); services.AddScoped(typeof(IListContentRepository), typeof(ListCon- tentsRepository)); services.AddScoped(typeof(IUserRepository), typeof(UserRepository)); services.AddScoped(typeof(IDynamicTableRepository), typeof(Dy- namicTableRepository)); services.AddScoped(); services.AddScoped(); break; } Listing 5. Dependency injection in the ConfigureServices-method Services are the components that make up the application functionality such as the built- in routing, JSON formatting, controller-class discovery. In addition to the built-in features and third party libraries, the Inversion-Of-Control container is configured with the appli- cation’s interfaces and the implementations that will be used.[15] The Configure-method lays out the order in which different parts of the application are run. The order in which the code is run is important. For example, to authenticate a user before directing the request to the endpoint. The implemented pipeline can be seen in Listing 6.
Appendix 2 24 (49) public void Configure(IApplicationBuilder appBuilder) { try { appBuilder.UseHttpsRedirection(); appBuilder.UseAuthentication(); if (_env.IsDevelopment() || _env.EnvironmentName == "Debugging") { appBuilder.UseDeveloperExceptionPage(); _env.ConfigureNLog("nlog.Development.config"); appBuilder.UserCors(corsBuilder => corsBuilder .WithOrigins(_config.GetSec- tion("Frontend:Url").Value) .AllowAnyHeader() ); } else { _env.ConfigureNLog("nlog.config"); appBuilder.UseHsts(); } appBuilder.UseSwagger(); appBuilder.UseSwaggerUI(swaggerBuilder => ) } catch (Exception exc) { appBuilder.Run(context => { _logger.LogError(new EventId(), e, e.Message); context.Response.StatusCode = 500; return Task.CompletedTask; }) throw; } } Listing 6. Startup.Configure method There are rules that apply whether used in production or debugging locally. First in the pipeline is to redirect all requests to use HTTPS instead of HTTP and after that the con- figured authentication is run. Enforcing HTTPS is the secure way to create APIs. Then a Swagger-endpoint is called. Last in the pipeline are the handler to catch all exceptions, a CORS-policy enforcing component and the functionalities that are in the ASP.NET Core 2.1 MVC-library. [16] For the release build there is also a specific logging-configuration that is loaded and the app is configure to use HSTS. HSTS is a web security policy against man-in-the-middle- attacks and is generally used for web browsers. Phone or desktop applications do not follow the HSTS instruction.[16]
Appendix 2 25 (49) If the application is run using development-configuration the app shows a developer ex- ception page, which has the stack trace and exception messages printed onto the re- sponse. The logger uses a different configuration for the development environment and the CORS-policy accepts any headers only from the origin of the configured value Frontend:Url. 4.4.3 Routing In web applications, routing is the act of directing requests to their correct endpoints. These requests can be done from the address bar of a web browser, an ajax call in javascript or another application that uses HTTP as its protocol. In ASP.NET Core there are two ways to implement routing: • A middleware-based routing in which the startup-class is used to define the routes and rulesets which dictate how the requests are directed. This way it is possible to separate the routing logic to its own code and keep that code in a single place is necessary. • Attribute-based routing uses the C# language attributes-feature. In control- ler classes, the classes themselves and their methods can be decorated with an at-tributes that specify the route and the HTTP verb that the class or the method will we tied to. These two ways are not mutually exclusive and can be used simultaneously. Using a mix of both strategies it is possible to create general rules for routing and add exceptions via attributes. When using both strategies, ASP.NET Core tries to find the best route for each HTTP request. In this thesis, only the attribute-based routing was used in both classes and their methods. The attributes were used to define the HTTP verbs, and in some cases the specific routes for certain methods. However, only the HttpPost and HttpGet attributes were used. In Listing 7 an HTTP endpoint is shown. This is a method in a controller-class that re- turns an ActionResult-type object. The attribute is shown above the method declaration.
Appendix 2 26 (49) [HttpPost("api/[controller]/[action]")] public ActionResult Add([FromBody]PartitionRuleAddModel model) { if (!ModelState.IsValid) { return BadRequest(); } try { _partitionRulesService.AddPartitionRule( model.ListID, model.RecipientInfoID, model.ProductCode, model.PercentShare, model.CountShare, model.MaxOut, model.Priority, model.Active); return Ok(); } catch (Exception innerException) { throw new ApplicationException ("Failed to add a new PartitionRule.", innerException); } } Listing 7. An MVC Controller’s endpoint. ActionResult is the base class for the result of an action method. In the picture above the two results that are returned are BadRequest and Ok. These return the HTTP responses with their specific HTTP response status codes. 4.5 Controllers In the three-layer architectural model using ASP.NET Core, the implemented API-layer comprises of controller classes and models for validation and data transfer. The control- lers are responsible for catching specific HTTP requests based on their URL and the used HTTP verb, and they are largely based on the conventions set by ASP.NET Core. The controller layer does not have to know what kind of client sends the requests and, in the case of a RESTful API, is not responsible for rendering any views. It takes re- quests, validates the payload and then returns proper responses with data in JSON- format for the client application.
Appendix 2 27 (49) In ASP.NET Core all controllers inherit from a base controller class which is included in the libraries provided by the framework. This class is tied into the ASP.NET Core’s pipe- line which directs requests to their respective classes. The ASP.NET Core conventions recommend naming all created controllers ending with a ‘controller’-suffix in order for them to be discoverable by the built-in pipeline of the framework. The first part of the name then works in tandem with the routing system to define the class as an endpoint for a request. For example, HomeController-class would respond to requests that are made to website/home-url.[17] 4.5.1 Controller implementation A total of ten controllers were created for the API, most of them representing a domain model and one for the abstract base class. As some data was dynamic in its signature, meaning that depending on the state of the database the returned object could have different properties, some controllers were mapped to return a Dynamic Object. First a custom base class, inheriting from the controller-class, was created. Listing 8 show this implemented BaseController class. namespace JaakkoHe.Thesis.Api.Controllers { [ApiController] public class BaseController : ControllerBase where T : BaseControl- ler { protected readonly ILogger _logger; public BaseController(ILogger logger) { _logger = _logger ?? (_logger = logger); } } } Listing 8. BaseController The BaseController-class was created to pass the _logger class onto the ControllerBase. Other controllers then inherit from this class and pass a typed _logger instance to the ControllerBase via the type argument T.
Appendix 2 28 (49) 4.5.2 DTO models Classes that model the payload for data requests and responses were created in the API layer. Request-classes were mapped to the parameters expected in HTTP-requests and had validation rules in them using property attributes. Response-classes were mapped to the data that was required for the functionality of the client app. In general, these classes are called data transfer objects (DTO) as their main responsibility is to carry data, but not have any functionality. An example of a model with validation attributes is shown in listing 9. The validation required in the application was mostly limited to required fields, integer ranges and the length of string values. The limits requirements were dictated by how the database had been set up. public class ExampleADDModel { [Required(ErrorMessage = "ObjectId is required.")] [Range(1, int.MaxValue, ErrorMessage = "ObjectId must be a positive inte- ger.")] public int ObjectId { get; set; } public string Code { get; set; } public string Name { get; set; } public string Description { get; set; } [Required(ErrorMessage = "RequiredDays is Required.")] [Range(0, int.MaxValue, ErrorMessage = "RequiredDays must be a positive integer.")] public int RequiredDays { get; set; } [StringLength(512, ErrorMessage = "Info cannot be more than 512 charac- ters long.")] public string Info { get; set; } public int TotalAvailable { get; set; } } Listing 9. A DTO model for adding a new entry. Part of ASP.NET Core’s MVC functionality is the binding of HTTP request data to match- ing C# objects and vice versa in the case of HTTP responses. It is a part of the MVC Filter pipeline which was configured in the Startup-class methods using AddMVC- and UseMVC-methods. The model binding matches the property names from a class to the specific HTTP data’s parameter names. In listing 10 the method parameter updateModel has been decorated with the attribute FromBody, which tells the application to bind the ExampleCRUDModel from the data in
Appendix 2 29 (49) the HTTP request’s body. For GET requests, the data was passed in the URL of the request. // POST: api/Example/Update [HttpPost("api/[controller]/[action]")] public ActionResult Update([FromBody]ExampleUPDModel updateModel) Listing 10. A total of 15 DTO models were created for the application as shown in figure 6. The classes were named using a convention where the classname told the user the purpose of the class. The classes are used either for the HTTP Request or the HTTP Response, but never both. Figure 6. Implemented DTO models. A few common types of implemented models and their description is explained in the Table 2. Some models, such as rows and cells were used as a part of a collection or a batch.
Appendix 2 30 (49) Table 2. Naming conventions and the descriptions for DTO Models Class naming convention Description Valida- tion AddModel Used for adding a new entry. Yes UpdateModel Used for updating an existing entry. Yes DeleteModel Used for deleting an existing entry. Yes RevertModel Special case where an external process had to be Yes reverted. BatchAddModel Special case where multiple entries had to be Yes added. Consists of Rows which have Cells. ViewModel Used for showing only the required fields for the cli- No ent. 4.6 Data-access layer Regarding the overall architecture, the most problematic and difficult part ended up being the data-access layer. The four main parts are the services, the repositories, the DbCon- text-class, and the entities. The division between services and repositories – or the re- pository pattern - was somewhat agreed upon to be a best practice in Entity Framework but in Entity Framework Core it is seen as an anti-pattern. Most of the data access layer was written using generics. Generics enables to write code that does not know the exact type of the objects that are used. The declaration for a generic class is written using angle brackets (‘’). For the actual implementation, another block of code specifies the type which then is passed onto the generic imple- mentation.[18] 4.6.1 Entity classes Entity classes are a central part of Entity Framework that are used by the framework to build a model which is used when accessing a database. They are C# classes that have properties reflecting the database tables and columns when using a relational database, respectively. For example, a User-class will be mapped to the Users-table and its prop- erties such as Name, would be mapped to their respective columns in the Users-table.
Appendix 2 31 (49) These mappings could be overwritten, but Entity Framework Core’s default conventions were used in most cases. The entities could be configured using a Fluent API in the DbContext-class or using Data annotation attributes in the entities themselves. The latter was used in the thesis. Some exceptions and unconventional solutions were used in the entities.[19] The model that was created in the thesis consisted of 14 classes and an abstract BaseEntity-class was created to be used only to tie together all entities. There were three different kinds of entities: • Simplest entities were only used for fetching data. • Half of the entities had a dependency to a List-table and were subclasses of a ListEntity-class • Entities that were used in stored procedure calls had to implement a ISprocCallingEntity-interface Figure 7 shows the first half of the entities that inherit directly from the BaseEntity. Two of these entities also implement an ISprocCalling-interface. Figure 7. Implemented entities which inherit directly from BaseEntity.
Appendix 2 32 (49) The other half of the entities required a property to a dependency of a table key which was not available to be modeled. These entities inherited from ListEntity as shown in figure 8. Five ListEntity-classes also implemented an ISprocCalling-interface. Figure 8. Implemented entities which inherit from ListEntity-class There were differences in the names between the entities and the actual SQL Database views and their columns, so the Table- and Column-annotations were used to explicitly declare these. The ObjectId-property was annotated as being the primary key for the entity and its value was generated by the database. An example implementation of an entity inheriting from the BaseEntity that was only used to fetch data is seen in listing 11. Both the class and its properties are decorated using Data annotations from the System.ComponentModel.DataAnnotations-namespace.
Appendix 2 33 (49) using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace JaakkoHe.Thesis.Data.Model.Entities { /// /// This class and its properties form the data for the table on the frontpage /// [Table("vInterface_Example")] public class ExampleEntity : BaseEntity { [Key] [Column("ObjectID")] public int ObjectId { get; set; } [Column("AccentColor")] public string AccentColor { get; set; } [Column("RowOrder")] public long RowOrder { get; set; } [Column("Channel")] public string Channel { get; set; } [Column("ObjectCode")] public string ObjectCode { get; set; } } } Listing 11. Example Entity implementation 4.6.2 Updating the database through entity’s methods At the time of the implementation, Entity Framework Core’s features were only partially complete and were missing functionality to call SQL Server’s stored procedures. The classes which were used for inserting, updating or deleting rows from the database were made to include the the related stored procedure’s parameters. These entities implemented the ISprocCallingEntity-interface shown in listing 12. The interface declares a method for fetching the name of the stored procedure and the pa- rameters used for the stored procedure. As an argument the methods accept an enu- meration of DbOperationTypes which defines the type of a procedure that is to be called.
You can also read