Of the patterns, I was satisfied with mvc, registry. For requests, I wrote a small abstraction layer, for routing - my own request parsing function.
The structure of the web application will be like this
application folder
The input file index.php includes bootstrap.php. That, in turn, connects the kernel, a config file, some libraries and starts the router.
Use Core\Route; require_once "lib/registry.php"; require_once "config.php"; require_once "lib/datebase.php"; require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; $router = new Route(); $router->start(); //start the router
The registry is simple:
Namespace Lib; class Lib_Registry ( static private $data = array(); static public function set($key, $value) ( self::$data[$key] = $value; ) static public function get($key) ( return isset( self::$data[$key]) ? self::$data[$key] : null; ) static public function remove($key) ( if (isset(self::$data[$key])) ( unset (self::$data[$key]); ) ) )
Here are getters and setters for storing global values.
Use Lib\Lib_Registry; define("PATH_SITE", $_SERVER["DOCUMENT_ROOT"]); define("HOST", "localhost"); define("USER", "root"); define("PASSWORD", "mypass"); define("NAME_BD", "articles"); define("DS", DIRECTORY_SEPARATOR); $mysqli = new mysqli(HOST, USER, PASSWORD,NAME_BD)or die("Unable to establish a connection to the database."$mysqli->connect_errno()); Lib_Registry::set("mysqli",$mysqli); $mysqli->query("SET names "utf8""); //base set the data encoding in the database
A request like http://domen.ru/articles/index is converted into a Controller - Action. The name of the controller and action is set in the Zend framework style, camel case - Controller_Name, function action_name (). The controller, model, view file must match the controller name in lower case without the Controller_ or Model_ prefix
The model class is specified in the same way - Model_Name, we have already found out the view file - by the name of the action or explicitly in the generate(name) method
Since we are planning to create an admin panel in the future, we will create the client and admin folders. By the way, our router will take into account subfolders, i.e. it will be possible to create subfolders in controllers (e.g. /about/contacts/contacts.php) and access it along its path /about/contacts/
So we started the router
The dispatcher calls the getDirections() method, i.e. get request directives. By default, the default controller is articles, the action is index.
/** * @param $file * @param $controller * @param $action * @param $args */ private function getDirections(&$file, &$controller, &$action, &$args) ( $route = ( empty($_SERVER["REQUEST_URI"])) ? "" : $_SERVER["REQUEST_URI"]; unset($_SERVER["REQUEST_URI"]); $route = trim($route, "/\\"); $ controller_path = $this->path; if (empty($route)) ( /* ******************* Default directions ******** */ $controller = "articles"; $action = "action_index"; $controller_path = $this->controller_path_folder = "application/controllers/$this->namespace/"; $file = $controller_path.$controller.".php"; ) else ( $parts = explode("/", $route); /* ************** namespace ********** */ if($parts = = "admin") ( $this->namespace = "admin"; array_shift($parts); ) /* ***************** folders & subfolders ***** ** */ $fullpath = $this->controller_path_folder = $controller_path . $this->namespace; foreach ($parts as $part) ( $fullpath .= DS . $part; if (is_dir($fullpath)) ( array_shift ($parts); continue; ) if (is_file($fullpath . ".php")) ( array_shift($parts); $file = "$fullpath.php"; break; ) ) /* ************* ** Controller, Action, Params ******** */ if(!isset($part)) $part = "articles"; $controller = $part; if(!$file) $file = $fullpath."/$part.php"; $action = array_shift($parts); if(!$action) $action = "action_index"; else $action = "action_$action"; $args = $parts; ) )
In the next lesson we will look at creating a basic controller, models and views, and write queries.
Lesson 1 files here
https://github.com/vaajnur/create_php_application
The master branch will be the 1st lesson, then for each lesson there will be a branch of the same name - lesson1, lesson2, 3..
You may have even heard about design patterns and even leafed through these wonderful books:
- E. Gamma, R. Helm, R. Johnson, J. Vlissides “Object-oriented design techniques. Design Patterns";
- M. Fowler "Architecture of Enterprise Software Applications."
This article will be useful primarily for beginners. In any case, I hope that in a couple of hours you will be able to get an idea of the implementation of the MVC pattern, which underlies all modern web frameworks, and also get “food” for further reflection on “how to do it.” At the end of the article there is a selection of useful links that will also help you understand what web frameworks consist of (besides MVC) and how they work.
Seasoned PHP programmers are unlikely to find anything new for themselves in this article, but their comments and comments on the main text would be very helpful! Because Without theory, practice is impossible, and without practice, theory is useless, then first there will be a little theory, and then we will move on to practice. If you are already familiar with the MVC concept, you can skip the theory section and go straight to the practice.
1. Theory The MVC pattern describes a simple way to structure an application, the purpose of which is to separate business logic from the user interface. As a result, the application is easier to scale, test, maintain and, of course, implement.Let's look at the conceptual diagram of the MVC pattern (in my opinion, this is the most successful diagram I have seen):
In MVC architecture, the model provides the data and business logic rules, the view is responsible for the user interface, and the controller provides interaction between the model and the view.
A typical flow of an MVC application can be described as follows:
This displays the view, say home page site.
which, for example, contains model calls that read information from the database.
The model should not directly interact with the user. All variables related to the user request must be processed in the controller.
The model should not generate HTML or other display code that can change depending on the user's needs. Such code should be processed in views.
The same model, for example: the user authentication model can be used in both the user and administrative parts of the application. In this case, you can move the general code into a separate class and inherit from it, defining sub-application-specific methods in its descendants.
View - used to specify the external display of data received from the controller and model.
Views contain HTML markup and small inserts of PHP code to traverse, format, and display data.
Should not directly access the database. This is what models should do.
Should not work with data obtained from a user request. This task must be performed by the controller.
Can directly access properties and methods of a controller or models to obtain output-ready data.
Views are usually divided into a common template, containing markup common to all pages (for example, a header and footer) and parts of the template that are used to display data output from the model or display data entry forms.
The controller is the glue that connects models, views and other components into working application. The controller is responsible for processing user requests. The controller should not contain SQL queries. It is better to keep them in models. The controller should not contain HTML or other markup. It’s worth bringing it into view.
In a well-designed MVC application, controllers are usually very thin and contain only a few dozen lines of code. The same cannot be said about Stupid Fat Controllers (SFC) in CMS Joomla. The controller logic is quite typical and most of it is transferred to base classes.
Models, on the contrary, are very thick and contain most of the code related to data processing, because the data structure and business logic contained within it are usually quite specific to a particular application.
I hope you have already noticed that different sites can have completely different formats for constructing the address bar. Each format can display the architecture of a web application. Although this is not always the case, in most cases it is a clear fact.
Let's consider two options for the address bar, which display some text and a user profile.
Approximate processing code in this case:
switch($_GET["action"]) ( case "about" : require_once("about.php"); // "About Us" page break; case "contacts" : require_once("contacts.php"); // "Contacts" page break; case "feedback" : require_once("feedback.php"); // "Feedback" page break; default: require_once("page404.php"); // page "404" break; )
I think almost everyone has done this before.
Using a URL routing engine, you can configure your application to accept requests like this to display the same information:
http://www.example.com/contacts/feedback
Here contacts represents the controller and feedback is the contacts controller method that displays the form feedback etc. We will return to this issue in the practical part.
It's also worth knowing that many web frameworks' routers allow you to create custom URL routes (specify what each part of the URL means) and rules for processing them.
Now we have sufficient theoretical knowledge to move on to practice.
Looking ahead, I will say that the core classes Model, View and Controller will be stored in the core folder.
Their children will be stored in the controllers, models and views directories. The index.php file is the entry point into the application. The bootstrap.php file initiates the loading of the application, including everything required modules etc.
We will go sequentially; Let's open the index.php file and fill it with the following code:
ini_set("display_errors", 1); require_once "application/bootstrap.php";
There shouldn't be any questions here.
Next, let's immediately go to the bootstrap.php file:
require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; Route::start(); //start the router
The first three lines will include currently non-existent kernel files. Last lines connect the file with the router class and launch it for execution by calling the static start method.
RewriteEngine On RewriteCond %(REQUEST_FILENAME) !-f RewriteCond %(REQUEST_FILENAME) !-d RewriteRule .* index.php [L]
This code will redirect all page processing to index.php, which is what we need. Remember in the first part we talked about Front Controller?!
We will place the routing in a separate file route.php in the core directory. In this file we will describe the Route class, which will run controller methods, which in turn will generate the page view.
Contents of the route.php file
class Route ( static function start() ( // controller and default action $controller_name = "Main"; $action_name = "index"; $routes = explode("/", $_SERVER["REQUEST_URI"]); // get the controller name if (!empty($routes)) ( $controller_name = $routes; ) // get the action name if (!empty($routes)) ( $action_name = $routes; ) // add prefixes $model_name = " Model_".$controller_name; $controller_name = "Controller_".$controller_name; $action_name = "action_".$action_name; // hook up the file with the model class (there may not be a model file) $model_file = strtolower($model_name). ".php"; $model_path = "application/models/".$model_file; if(file_exists($model_path)) ( include "application/models/".$model_file; ) // hook up the file with the controller class $controller_file = strtolower ($controller_name)..php"; $controller_path = "application/controllers/".$controller_file; if(file_exists($controller_path)) ( include "application/controllers/".$controller_file; ) else ( /* it would be correct to throw an exception here, but to simplify things, we’ll immediately redirect to the 404 page */ Route::ErrorPage404(); ) // create a controller $controller = new $controller_name; $action = $action_name; if(method_exists($controller, $action)) ( // call the controller action $controller->$action(); ) else ( // here it would also be wiser to throw an exception Route::ErrorPage404(); ) ) function ErrorPage404( ) ( $host = "http://".$_SERVER["HTTP_HOST"]."/"; header("HTTP/1.1 404 Not Found"); header("Status: 404 Not Found"); header(" Location:".$host."404"); ) )
I note that the class implements very simplified logic (despite the voluminous code) and may even have security problems. This was done intentionally, because... writing a full-fledged routing class deserves at least a separate article. Let's look at the main points...
The global array element $_SERVER["REQUEST_URI"] contains the full address to which the user contacted.
For example: example.ru/contacts/feedback
Using the function explode The address is divided into components. As a result, we get the name of the controller, for the example given, this is controller contacts and the name of the action, in our case - feedback.
Next, the model file (the model may be missing) and the controller file, if any, are connected and finally, an instance of the controller is created and the action is called, again, if it was described in the controller class.
Thus, when going to, for example, the address:
example.com/portfolio
or
example.com/portfolio/index
The router will perform the following actions:
example.com/ufo
then he will be redirected to the “404” page:
example.com/404
The same thing will happen if the user accesses an action that is not described in the controller.2.2. Let's return to the MVC implementation Let's go to the core folder and add three more files to the route.php file: model.php, view.php and controller.php
Let me remind you that they will contain base classes, which we will now begin writing.
Contents of the model.php file
class Model ( public function get_data() ( ) )
The model class contains a single empty data fetch method, which will be overridden in descendant classes. When we create descendant classes everything will become clearer.
Contents of the view.php file
class View ( //public $template_view; // here you can specify the default general view. function generate($content_view, $template_view, $data = null) ( /* if(is_array($data)) ( // convert array elements into variables extract($data); ) */ include "application/views/".$template_view; ) )
It is not difficult to guess that the method generate intended to form a view. The following parameters are passed to it:
to display the content of a specific page.
In our case, the general template will contain header, menu, sidebar and footer, and the page content will be contained in a separate form. Again, this is done for simplicity.
Contents of the controller.php file
class Controller ( public $model; public $view; function __construct() ( $this->view = new View(); ) function action_index() ( ) )
Method action_index- this is an action called by default; we will override it when implementing descendant classes.
In the previous figure, the file template_view.php is highlighted separately - this is a template containing markup common to all pages. In the simplest case it could look like this:
home
To give the site a presentable look, we design CSS template and integrate it into our website by changing the structure of the HTML markup and connecting CSS and JavaScript files:
At the end of the article, in the “Result” section, there is a link to a GitHub repository with a project in which steps have been taken to integrate a simple template.
class Controller_Main extends Controller ( function action_index() ( $this->view->generate("main_view.php", "template_view.php"); ) )
In method generate an instance of the View class, the names of the files of the general template and the view with the page content are passed.
In addition to the index action, the controller can of course contain other actions.
We reviewed the general view file earlier. Consider the content file main_view.php:
Welcome!
OLOLOSHA TEAM is a team of first-class specialists in the field of website development with many years of experience in collecting Mexican masks, bronze and stone statues from India and Ceylon, bas-reliefs and sculptures created by masters of Equatorial Africa five or six centuries ago...
This contains simple markup without any PHP calls.
To display the main page, you can use one of the following addresses:
We will consider an example using a view displaying data obtained from the model below. 2.3.2. Create a “Portfolio” page In our case, the “Portfolio” page is the only page that uses the model.
The model usually includes data sampling methods, for example:
Place the model file model_portfolio.php in the models folder. Here are its contents:
class Model_Portfolio extends Model ( public function get_data() ( return array(array("Year" => "2012", "Site" => "http://DunkelBeer.ru", "Description" => "Promotional site of the dark Dunkel beer from the German manufacturer Löwenbraü produced in Russia by the brewing company "SUN InBev."), array("Year" => "2012", "Site" => "http://ZopoMobile.ru", "Description" => "Russian-language catalog of Chinese phones from Zopo on Android based OS and accessories for them."), // todo); ) )
The model controller class is contained in the controller_portfolio.php file, here is its code:
class Controller_Portfolio extends Controller ( function __construct() ( $this->model = new Model_Portfolio(); $this->view = new View(); ) function action_index() ( $data = $this->model->get_data( ); $this->view->generate("portfolio_view.php", "template_view.php", $data); ) )
To a variable data the array returned by the method is written get_data which we looked at earlier.
This variable is then passed as a method parameter generate, which also contains: the name of the file with the general template and the name of the file containing the view with the page content.
The view containing the page content is in the portfolio_view.php file.
Portfolio
Year | Project | Description |
Everything is simple here, the view displays the data obtained from the model. 2.3.3. Creating the remaining pages The remaining pages are created in the same way. Their code is available in the GitHub repository, a link to which is provided at the end of the article, in the “Result” section.3. Result Here's what happened in the end:
Screenshot of the resulting business card website
GitHub link: https://github.com/vitalyswipe/tinymvc/zipball/v0.1
But in this version I sketched out the following classes (and their corresponding types):
- Controller_Login in which a view is generated with a form for entering login and password, after filling which the authentication procedure is performed and, if successful, the user is redirected to the admin panel.
- Contorller_Admin with an index action that checks whether the user was previously authorized on the site as an administrator (if so, the admin panel view is displayed) and a logout action for logging out.
But, using web frameworks such as Yii or Kohana, consisting of several hundred files, when developing simple web applications (for example, business card sites) is not always advisable. Now we can create a beautiful MVC model so as not to mix Php, Html, CSS and JavaScript code in one file.
This article is more of a starting point for learning CMF than an example of something truly correct that you can use as the basis for your web application. Perhaps it even inspired you and you are already thinking about writing your own microframework or CMS based on MVC. But, before reinventing the next wheel with “blackjack and whores,” think again: maybe it would be more reasonable to direct your efforts to the development and helping the community of an already existing project?!
P.S.: The article was rewritten taking into account some comments left in the comments. The criticism turned out to be very useful. Judging by the response: comments, PMs and the number of users who added the post to favorites, the idea of writing this post turned out to be not so bad. Unfortunately, it is not possible to take into account all the wishes and write more and in more detail due to lack of time... but perhaps those mysterious individuals who downvoted the original version will do this. Good luck with your projects!
5. A selection of useful links on the subject The article very often touches on the topic of web frameworks - this is a very broad topic, because even microframeworks consist of many components cleverly interconnected and it would take more than one article to talk about these components. However, I decided to present here a small selection of links (which I followed while writing this article) that in one way or another relate to the topic of frameworks.Tags: Add tags
The first article on developing your own framework will be devoted to an overview of the main intended capabilities and architectural features of the new framework. Here I will describe modules and components, the development of which will be described in detail in subsequent articles. So, let’s decide how we organize the code of the framework and applications that will use it, and then we’ll create a roadmap for the development of the main components.
To begin with, I chose a simple and concise project directory structure:
- /
- app – directory of the webroot server, put index.php here
- public – here we will store static application files
- src – directory with source code the application itself
- Blog – each application is located in its own directory
- lib – directory for external libraries. The framework code will also be located here.
- var – directory for application files (cache, file storage, sessions, etc.)
We've decided on the directories. Now we can conveniently separate individual application components into different directories, use PSR0 to autoload classes, etc.
Next, you need to select the name of the framework in order to simply create a directory for it inside lib. Without thinking twice, I chose the name Bun - short, memorable and seemingly not in conflict with anything.
Inside lib we create a bun directory (namespace of the vendor), in it another bun directory (namespace of the library), and inside it src - here we will place the code of our framework. This sophisticated directory structure will allow us to connect the framework via http://packagist.org in the future without unnecessary problems.
Next, we select a namespace for the base classes of the framework - I chose Core. To organize the connection of components outside the Core directory in the future, you will need to organize a modular structure of the framework. Therefore, the first component of the system I have is a module (Bun\Core\Module).
Bun\Core\ModuleThe module should provide basic information to an independent framework component. To begin with, the configurations and dependencies used by the module, version, description of the module, etc. Each directory inside lib/bun/bun/src will be a separate module and must contain a class of the same name (for example, Bun\Core\Core.php) - which represents the implementation of the module.
Bun\Core\ApplicationThe application is an implementation of the Front Controller pattern - a single entry point into the application for all requests. The Bun architecture will provide that all requests, including php cli or requests for static files, must be processed within the Application. An application instance will be created in index.php (start file); when initializing the application, its execution environment (production, development, testing, etc) will be indicated. The application will then do the work of loading the configuration, initializing the service container, routing requests through auxiliary components, calling controllers, issuing a response, and terminating the application.
The first thing we do when an application starts, as a rule, is to load configuration files, so the next basic component will be Config
Bun\Core\ConfigTo configure the application and framework components, a separate set of classes is allocated - Bun\Core\Config\ApplicationConfig - the service itself for managing configs and the Bun\Core\Config\AbstractConfig class - an abstract class that is the base class for configuration elements. The PHP class is selected as the configuration format. This is convenient from a caching point of view, i.e. it is more profitable to store configs directly in the application code than to use separate files xml formats, json, ini, etc.
Using classes as configs is also convenient for separating the configurations of individual components and parts of the application. It is also convenient for overriding the default framework settings within an application, or application settings within a specific execution environment. By design, each module or application contains within itself the Config namespace - in which configuration classes are stored. Each class has its own namespace, and stores configuration parameters as an array in a protected property.
Access to configs is assumed via dot notation $config->get("name1.name2.param1") .
After we have initialized the application configuration, we can begin processing the request. A separate set of Http components is allocated for working with requests and responses from web applications
Bun\Core\Http
The set of Http components will be responsible for abstracting from working with superglobal variables $_SERVER, $_GET, $_POST, etc. The Bun\Core\Http\Request service will be responsible for this. In addition, Http will include the Response class - which will be the standard for the result of the application, i.e. launching the controller must end with receiving the Bun\Core\Http\Response object. This class abstracts us from working with http headers, etc. In addition, it is convenient to use derived classes such as AjaxResponse, DownloadResponse, ConsoleResponse, etc.
When we are ready to receive information about the request, we can move on to routing: the next component is Router
Bun\Core\RouterRouter is a standard component of modern PHP web applications. There is nothing extra ordinary in the Bun Framework router, a simple config in the form of an array of URL request templates, which is mapped to controller classes and their actions. I plan to implement the ability to parse parameters from a url like /page/view/:page_id - which will be passed to the controller action as arguments. I also plan to separate requests by method (convenient when some methods can only be called via POST - no need to do unnecessary checks in the business logic code)
From one standard component php applications Let's move on to something else - request routing is closely related to the implementation of the MVC (Model View Controller) pattern. This involves separating application logic, data and display.
Bun Framework MVCIt is difficult to distinguish yourself in the implementation of MVC - so everything here is also quite prosaic. Inside Core, I allocate the Controller namespace - here I create a base controller class that has access to all components of the system, stores the object of the application that launched it, and a config service. During initialization, the controller receives launch parameters: the name of the method (action) and its arguments.
I allocate the display code to a separate View directory inside Core. The Bun Framework does not provide any specific components of the View type - it is assumed that you simply pull the desired template inside the controller, passing data there. By default, I intend to add support for the Twig template engine and support for native *.phtml templates into the framework
The last component is the Model. I also allocate a separate namespace for it inside Core: Model. This is where another basic component of the framework will come into play - ObjectMapper. The models themselves are just classes, i.e. are not ActiveRecord, but they implement a certain Bun\Core\Model\ModelInterface. It is these classes that ObjectMapper can work with and save them to some kind of storage.
Now we have to talk about both the object mapper and the storage, let's start with the first.
Bun\Core\ObjectMapperThe object mapper is perhaps the most complex part of the framework's Core module. It will be a service that can turn a model object into records in some database, and also do the opposite - take a record from the data store and map it into a model object. By default, the Core module will include a service that implements file storage objects.
Bun\Core\StorageThe Storage component set represents the abstraction and interfaces that any storage implementation in an application must follow. The first such storage will be Bun\Core\Storage\FileStorage . To operate, the file storage will use a set of auxiliary classes for working with files, as well as for constructing queries to search for records in the file storage.
The object mapper described above will be able to work with any implementation of Storage to store its objects there. Here it is worth highlighting another important component of the Core module – Repository.
Bun\Core\RepositoryRepositories are a layer of access to an object. In order not to overload the object mapper with search functionality and various selections of objects from the storage - this work submitted to the repository. The repository can work directly with the repository, select data from there, and then turn it into objects through ObjectMapper and transfer it to the application.
Let me immediately mention something related to the components described above – cache
Bun\Core\CacheCache – will contain a set of abstractions for implementing interaction with various cache storages, such as Redis, Memacached, etc. In addition, the Core module will include the FileCacheDriver component, which implements data caching in files.
Here we move on to an important and, in my opinion, architecture-defining component of the framework. Flexibility and replaceability of components is achieved when your code is not strictly tied to leading services, but can quickly switch between them. In addition, the next component does a great job of efficiently organizing the framework and application code.
Bun\Core\ContainerContainer – implements one of my favorite programming patterns – Dependency Injection. Dependency injection is good in everything - parts of the application are weakly dependent on specific components, individual classes are easy to test by replacing their dependencies. Convenient to implement alternative options implementation in several services, and then easily switch between them even without changes to the application code. Plus, your classes that use dependency injection clearly show their dependencies - you can even build a dependency graph of your application components.
Container initializes and stores configured services during application runtime when these services are accessed by the application. Within one class, you essentially have control over all parts of the application.
This completes the basic components of the framework in the Core module for now. During implementation, the Core module may include implementations of event management, FormBuilder. Among the auxiliary components, it is worth noting the typing of exceptions. The base exception class Bun\Core\Exception\Exception is provided so that all other typed exceptions within the application and framework inherit from it. This provides centralized exception interception at the application level and prevents uncaught exceptions from occurring and causing the application to crash.
In the following parts of the story about the development of the Bun Framework, I will begin to talk about the Application component, and also describe the essence of autoloading files according to the PSR0 standard. After which, I’ll move on to a description of the application configuration service and a set of Http components
Last update: 11/29/2017
Angular has its own routing system that allows you to map routes to components. But ASP.NET Core also has its own routing system, which is used to process requests. What problems might we face? For example, let's take the project from the previous topic, where routing was already used. If we follow links inside the Angular application, then everything will be fine.
However, if we are directly in address bar browser, we enter the address we need and send a request, then such a request will not be properly processed:
Despite the fact that the request is being made to the same address, in the second case we will receive a 404 error.
When we click on a link or programmatically inside an Angular application, the corresponding HTML5 API (Pushstate API) is used, which changes the URL in the browser without sending an HTTP request. But when we manually enter the resource address in the browser's address bar, the browser sends a new request to the ASP.NET Core application.
In the example above, the request "product/1" was sent. But we don't have a controller action that maps to such a request. So naturally we will get an error. And we need to add additional server-side code so that such requests will also be processed by the Angular application.
What can we do in this case? It all depends on how the web page is formed. Or it is directly a static web page that is sent directly to the user. Or this is a view (a file with a cshtml extension) on which the loading code for the Angulr application is defined.
Queries for static filesIn the previous topic, the Angular application was loaded into a static web page, index.html, which was located in the project in the wwwroot folder:
Angular in ASP.NET Core
And in this case, we need that, if the request was not intended for a static file, and was not intended for any of the controller actions, then the index.html file would be sent in response to such a request. And in this case, on the server side we can use several approaches.
Run methodThe easiest way is to add middleware to the end of the request processing pipeline, which would send the index.html file. To do this, change the Configure method in the Startup class:
Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplacement = true )); ) app.UseDefaultFiles(); app.UseStaticFiles( ); app.UseMvc(); // process routes that are not previously mapped to resources app.Run(async (context) => ( context.Response.ContentType = "text/html"; await context.Response.SendFileAsync(Path .Combine(env.WebRootPath, "index.html")); )); )
Thus, for all requests that are not mapped to resources in the application, the file wwwroot/index.html will be sent.
The disadvantage of this approach is that it will be difficult for us to enable 404 error handling on the server side. Since if we do not have controllers and actions corresponding to the request, then the request will still be processed, albeit on the client side in Angular.
Sending a file using the controller methodA similar approach is provided by sending a file using the controller method. For example, let's say you have the following HomeController in your project in the Controllers folder:
Using Microsoft.AspNetCore.Mvc; using System.IO; using Microsoft.AspNetCore.Hosting; namespace HelloAngularApp.Controllers ( public class HomeController: Controller ( IHostingEnvironment env; public HomeController(IHostingEnvironment env) ( this.env = env; ) public IActionResult Index() ( return new PhysicalFileResult(Path.Combine(env.WebRootPath, "index.html "), "text/html"); ) ) )
In this case, the Index method sends the index.html file.
In this case, change the Configure method of the Startup class as follows:
Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplacement = true )); ) ` app.UseDefaultFiles(); app.UseStaticFiles (); app.UseMvc(routes =>
In this case, two routes are defined for the MVC infrastructure. The first route maps to regular controllers and their actions. The second route is defined using the MapSpaFallbackRoute() method and maps to the Index action of the Home controller, which in this case sends an index.html file back to the user.
Requests to controller actionsWe can optionally define the loading of an Angular application in a static html page. It could also be a performance. For example, let’s define the Views/Home directory in the project and place it in new file Index.cshtml:
Angular in ASP.NET Core
In this case, the view contains only html code, although if necessary, you can also define Razor instructions in it, create a master page for it, etc.
And the HomeController's Index method will use this view:
Using Microsoft.AspNetCore.Mvc; namespace HelloAngularApp.Controllers ( public class HomeController: Controller ( public IActionResult Index() ( return View(); ) ) )
In the Configure method of the Startup class, we define the following code:
Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplacement = true )); ) //app.UseDefaultFiles(); - this the method is no longer needed app.UseStaticFiles(); app.UseMvc(routes => ( routes.MapRoute(name: "default", template: "(controller=Home)/(action=Index)/(id?)"); routes.MapSpaFallbackRoute("angular-fallback", new ( controller = "Home", action = "Index" )); )); )
Here again, using the routes.MapSpaFallbackRoute method, all other requests that are not mapped to other resources will be handled by the Index method of the HomeController.