Symfony 7 vs. .NET Core 8 – Templating

Disclaimer

This is a tutorial or a training course. Please don’t expect a walk-through tutorial showing how to use ASP.NET Core. It only compares similarities and differences between Symfony and ASP.NET Core. Symfony is taken as a reference …


This content originally appeared on DEV Community and was authored by Aleksander Wons

Disclaimer

This is a tutorial or a training course. Please don't expect a walk-through tutorial showing how to use ASP.NET Core. It only compares similarities and differences between Symfony and ASP.NET Core. Symfony is taken as a reference point, so if features are only available in .NET Core, they may never get to this post (unless relevant to the comparison).

Most of the concepts mentioned in this post will be discussed in more detail later. This should be treated as a comparison of the "Quick Start Guides."

Templating in Symfony and .NET Core

Creating templates

Symfony

When we build web applications (not APIs), we must render the content of our pages. Symfony uses a third-party templating engine Twig for that purpose.

Twig is a real templating engine. This means it's a "language" in itself, not a mix of PHP and HTML. It is very simple yet easy to learn and, despite its simplicity, very powerful. A template will be rendered into PHP and cached for reuse, so no real performance penalty exists. Here is an example of how a template might look like:

<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1>{{ page_title }}</h1>

        {% if user.isLoggedIn %}
            Hello {{ user.name }}!
        {% endif %}

        {# ... #}
    </body>
</html>

It has three primary constructs that build the engine:

  • {{ ... }}, used to display the content of a variable or the result of evaluating an expression;
  • {% ... %}, used to run some logic, such as a conditional or a loop;
  • {# ... #}, used to add comments to the template (unlike HTML comments, these comments are not included in the rendered page).

We cannot write and call PHP directly from a template. What we can do, though, is to use built-in functions and filters or crate custom functions that we can call from within a template (so-called Extensions).

It might not be obvious at first, but even though we can't call PHP directly, we can still call methods on objects (variables) passed into a template like this:

{% if user.isLoggedIn %}
    Hello {{ user.name }}!
{% endif %}

Here, Twig will determine if we want to access a property, call a method on an object, or access a key in an array. It does some reverse engineering to figure out what this user.name actually means in PHP.

Everything we output in a template is escaped by default, so we should not have any security concerns. However, it is possible to turn auto-escaping off for parts of a template or all templates altogether. On top of that, we can render raw data using the raw filter:

<h1>{{ product.title|raw }}</h1>

Linking to pages

We can link to routes using the path extension:

<a href="{{ path('blog_index') }}">Homepage</a>

{# ... #}

{% for post in blog_posts %}
    <h1>
        <a href="{{ path('blog_post', {slug: post.slug}) }}">{{ post.title }}</a>
    </h1>

    <p>{{ post.excerpt }}</p>
{% endfor %}

And also link to assets relative to root using the assets function or absolute URLs using absolute_url:

{# the image lives at "public/images/logo.png" #}
<img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>

{# the CSS file lives at "public/css/blog.css" #}
<link href="{{ asset('css/blog.css') }}" rel="stylesheet"/>

{# the JS file lives at "public/bundles/acme/js/loader.js" #}
<script src="{{ asset('bundles/acme/js/loader.js') }}"></script>

<img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!"/>

<link rel="shortcut icon" href="{{ absolute_url('favicon.png') }}">

.NET Core

.NET Core renders content using Razor. Here, we will discuss a specific implementation of that engine, the ASP.NET Core Razor. Razor Engin has many implementations that vary in the number of features and environments it runs in. Still, because we discussed the .NET Core, we will only concentrate on that version.

Another difference from Twig is that a Razor template can contain C# code. There is no need to define special extensions.

A template is defined in .cshtml file. Here is an example of a simple template. It looks very similar to the Twig template:

@model App.ViewModels.BrandNew
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to .NET Core!</title>
    </head>
    <body>
        <h1>@Model.Title</h1>

        @if (Model.User.IsLoggedIn)
        {
            <span>Hello @Model.User.Name</span>
        }
    </body>
</html>

The syntax is a bit different, and there are some new concepts (the "model"), but overall, it feels very like a Twig template.

Global variables

There is one global variable (an AppValriable) accessible in every template, which can give us info about the current request, user, or environment:

<p>Username: {{ app.user.username ?? 'Anonymous user' }}</p>
{% if app.debug %}
    <p>Request method: {{ app.request.method }}</p>
    <p>Application Environment: {{ app.environment }}</p>
{% endif %}

However, we can also define and access global variables within a template. This is achieved using Symfony's configuration files:

# config/packages/twig.yaml
twig:
    globals:
        my_variable: 'My Value'

Rendering templates

Basics

We obviously need to render the template. The simplest and most common way is to call a helper methodcontent element in that template from a controller that extends from the AbstractController. We have a few options here:

  • We return a response object with the rendered content like this:
return $this->render('product/index.html.twig', [
    'category' => '...',
    'promotions' => ['...', '...'],
]);
  • We render the content into a variable like this:
$contents = $this->renderView('product/index.html.twig', [
    'category' => '...',
    'promotions' => ['...', '...'],
]);

The above methods render an entire template. However, Twig can also take a whole template and render only part of it. This is done using template blocks.

// index.html.twig
{% block head %}
    <link rel="stylesheet" href="style.css"/>
    <title>{% title %}{% endblock %} - My Webpage</title>
{% endblock %}

{% block footer %}
    &copy; Copyright 2011 by <a href="https://example.com/">you</a>.
{% endblock %}

We can then render just one block out of this template.

return $this->renderBlock('index.html.twig', 'head', ['title' => 'My Title']);

// or like this
$content = $this->renderBlockView('index.html.twig', 'head', ['title' => 'My Title']);

Though we can still render from anywhere (including a controller that does not extend from the AbstractController) by injecting the Twig service:


use Twig\Environment;

class MyController
{
    public function __construct(private readonly Environment $twig){}

    public function index()
    {
        return $this->twig->render('product/index.html.twig');
    }

    public function blog()
    {
        $content = return $this->twig->renderView('product/index.html.twig');
    }
}

If we are inside a controller, a nice addon is the [Template] attribute, which we can put on a method in a controller to indicate what template we want to render and then return an array of variables to go into the template:

#[Template('product/index.html.twig')]
public function index(): array
{
    return [
        'category' => '...',
        'promotions' => ['...', '...'],
    ];
}

Rendering templates from a route

Symfony has an interesting feature: We can rent a static template from within a route configuration. The following example from Symfony's documentation illustrates the usage:

# config/routes.yaml
acme_privacy:
    path:          /privacy
    controller:    Symfony\Bundle\FrameworkBundle\Controller\TemplateController
    defaults:
        # the path of the template to render
        template:  'static/privacy.html.twig'

        # the response status code (default: 200)
        statusCode: 200

        # special options defined by Symfony to set the page cache
        maxAge:    86400
        sharedAge: 86400

        # whether or not caching should apply for client caches only
        private: true

        # optionally you can define some arguments passed to the template
        context:
            site_name: 'ACME'
            theme: 'dark'

Checking templates for errors

Because a Twig template will be compiled into PHP on the fly (and cached), we need a way to tell if it does not contain any syntax errors and can compile. Symfony has a console command for that.

php bin/console lint:twig

The above command will check all templates within our project. However, we can also check templates within a folder or a single template:

php bin/console lint:twig templates/email/
php bin/console lint:twig templates/article/recent_list.html.twig

Reusing templates

The simplest way to reuse a template is to include it in another template using the {{ include() }} function. An included template will see the same variables as the parent template (although this can be disabled per include). Here is a simple example of such an include:

# blog/_user_profile.html.twig
<div class="user-profile">
    <img src="{{ user.profileImageUrl }}" alt="{{ user.fullName }}"/>
    <p>{{ user.fullName }} - {{ user.email }}</p>
</div>
# blog/index.html.twig

{{ include('blog/_user_profile.html.twig') }}

We can also pass variables explicitly into the included template, for example, to rename a variable:

{{ include('blog/_user_profile.html.twig', {user: blog_post.author}) }}

This method of reuse has a drawback. The parent must already provide all the variables that the included template needs. So, the pattern always needs to know what will be included (also when we have a deep nested inclusion).

Another option is to render a controller. This allows us to encapsulate some logic (or database interactions) in one place and reuse it as a template fragment.

There are two ways to do it. The first one is to use reference a controller using a route:

{{ render(path('latest_articles', {max: 3})) }}
{{ render(url('latest_articles', {max: 3})) }}

In the above example, the latest_articles is a route name, and {max: 3} is a list of arguments for that route.

Another option is to reference a controller directly:

{{ render(controller(
    'App\\Controller\\BlogController::recentArticles', {max: 3}
)) }}

Frontend helpers

There is a way to render controllers asynchronously using dedicated JavaScript helper functions. Those helpers allow us to render a placeholder, and then Javascript will call the specified controller/route to load additional content asynchronously.

{{ render_hinclude(controller('...')) }}
{{ render_hinclude(url('...')) }}

Inheritance and layout

When building web pages, we often need to structure our templates. Our templates will have components like headers, footers, navigation, sidebars, and so on. On top of that, we can have content and other page elements that we want to keep dynamic based on the page we are on. A typical page would look as follows.

At the highest level, we will have a base template that defines some elements inherited by all pages.

{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}My Application{% endblock %}</title>
        {% block stylesheets %}
            <link rel="stylesheet" type="text/css" href="/css/base.css"/>
        {% endblock %}
    </head>
    <body>
        {% block body %}
            <div id="sidebar">
                {% block sidebar %}
                    <ul>
                        <li><a href="{{ path('homepage') }}">Home</a></li>
                        <li><a href="{{ path('blog_index') }}">Blog</a></li>
                    </ul>
                {% endblock %}
            </div>

            <div id="content">
                {% block content %}{% endblock %}
            </div>
        {% endblock %}
    </body>
</html>

The block element is the element that the child template can overwrite.

Our page default layout could look like this:

{# templates/blog/layout.html.twig #}
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Blog</h1>

    {% block page_contents %}{% endblock %}
{% endblock %}

The extends element tells us we inherit the base template and will replace the content element. Everything else stays the same.

The last level is the page itself. There, we extend from the blog/layout.html.twig template and overwrite title block (which was defined in templates/base.html.twig) and page_contents block (which was defined in templates/blog/layout.html.twig).

{# templates/blog/index.html.twig #}
{% extends 'blog/layout.html.twig' %}

{% block title %}Blog Index{% endblock %}

{% block page_contents %}
    {% for article in articles %}
        <h2>{{ article.title }}</h2>
        <p>{{ article.body }}</p>
    {% endfor %}
{% endblock %}


This content originally appeared on DEV Community and was authored by Aleksander Wons


Print Share Comment Cite Upload Translate Updates
APA

Aleksander Wons | Sciencx (2024-07-29T17:17:06+00:00) Symfony 7 vs. .NET Core 8 – Templating. Retrieved from https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/

MLA
" » Symfony 7 vs. .NET Core 8 – Templating." Aleksander Wons | Sciencx - Monday July 29, 2024, https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/
HARVARD
Aleksander Wons | Sciencx Monday July 29, 2024 » Symfony 7 vs. .NET Core 8 – Templating., viewed ,<https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/>
VANCOUVER
Aleksander Wons | Sciencx - » Symfony 7 vs. .NET Core 8 – Templating. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/
CHICAGO
" » Symfony 7 vs. .NET Core 8 – Templating." Aleksander Wons | Sciencx - Accessed . https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/
IEEE
" » Symfony 7 vs. .NET Core 8 – Templating." Aleksander Wons | Sciencx [Online]. Available: https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/. [Accessed: ]
rf:citation
» Symfony 7 vs. .NET Core 8 – Templating | Aleksander Wons | Sciencx | https://www.scien.cx/2024/07/29/symfony-7-vs-net-core-8-templating/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.