Utiliser Rss20FeedFormatter et Atom10FeedFormatter avec ASP .net core 3

De base si l'on tente de générer un flux RSS via ASP .net core 3 et un SyndicationFeed, on utilise un code synchrone. Ce qui n'est pas du gout de core 3 et qui va produire des Exceptions.

Voici le code d'un ActionResult personnalisé qui fonctionne parfaitement avec .net core 2.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;

namespace MyLib.Web.Results
{
    public sealed class FeedResult : ActionResult
    {
        #region Declarations

        private const String AtomContentType = "application/atom+xml";
        private const String RssContentType= "application/rss+xml";

        public enum Type
        {            
            Atom,
            Rss
        }

        private readonly SyndicationFeed _feed;
        private readonly Type _type;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="feed"></param>
        public FeedResult(SyndicationFeed feed, Type type)
        {
            _feed = feed;
            _type = type;
        }

        #endregion

        #region Methodes

        public override void ExecuteResult(ActionContext context)
        {
            // Get the response
            HttpResponse response = context.HttpContext.Response;

            // Add settings
            var settings = new XmlWriterSettings
            {
                Encoding = new UTF8Encoding(false)
            };

            // Write
            using (XmlWriter writer = XmlWriter.Create(response.Body, settings))
            {
                if (_type == Type.Atom)
                {
                    response.ContentType = AtomContentType;
                    Atom10FeedFormatter atomformatter = new Atom10FeedFormatter(_feed);
                    atomformatter.WriteTo(writer);
                }
                else
                {
                    response.ContentType = RssContentType;
                    Rss20FeedFormatter rssformatter = new Rss20FeedFormatter(_feed);
                    rssformatter.WriteTo(writer);
                }
            }
        }

        #endregion
    }
}

Pour l'utiliser en réponse d'un Controller, rien de très compliqué

return new FeedResult(yourFeed, FeedResult.Type.Rss);

Pour rendre ce code opérationnel avec .net core 3, il faut que la réponse soit produite par une méthode Async. La solution la plus simple et d'utiliser la méthode WriteAsync de Response et de lui passer une String. Pour cela, on peut passer par un StringBuilder qui va récupérer le produit du XmlWriter. Et pour rester dans l'asynchrone, on peut lui demander d'utiliser ces options asynchrones.

Ce qui donne :

public override async void ExecuteResult(ActionContext context)
{
    // Get the response
    HttpResponse response = context.HttpContext.Response;

    // Add settings
    var settings = new XmlWriterSettings
    {
        Encoding = new UTF8Encoding(false),
        Async = true
    };

    // Write
    String content;
    using (StringWriter stringWriter = new StringWriter())
    using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
    {
        if (_type == Type.Atom)
        {
            response.ContentType = AtomContentType;
            Atom10FeedFormatter formatter = new Atom10FeedFormatter(_feed);
            formatter.WriteTo(xmlWriter);
        }
        else
        {
            response.ContentType = RssContentType;
            Rss20FeedFormatter formatter = new Rss20FeedFormatter(_feed);
            formatter.WriteTo(xmlWriter);
        }
        await xmlWriter.FlushAsync();
        content = stringWriter.ToString();
    }
    await response.WriteAsync(content);
}

Voilà ;)

Jérémy Jeanson

Contourner l'erreur .net core 3 "Synchronous operations are disallowed..."

Avez ASP .net core 3 vous êtes peut être déjà tombé sur l'erreur suivante :

Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.

Celle-ci se produit quand vous souhaitez interagir de avec la Request ou la Response, sans utiliser de méthodes asynchrones. L'exception levée est parfaitement légitime et reflète une volonté forte de l'équipe ASP .net core de réduire les problèmes de performance et de libération des ressources (je reviendrai dessus dans un prochain article).

Il n'est pas conseillé de passer outre ce changement. Cependant, vous pouvez souhaitez utiliser .net core 3 et ses nouveauté à court terme sans changer trop en profondeur le code de vos sites. Vous pouvez donc temporairement contourner la configuration à défaut.

Méfiez-vous du "temporaire qui dure".

Pour contourner le problème. Dans un premier temps, il faut modifier le comportement de Kestrel via la méthode ConfigureKestrel du builder. Cela se passe dans le fichier Program.cs de votre application.

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel(options =>
        {
            options.AllowSynchronousIO = true;
        });
}

Si vous utilisez l'intégration à IIS, il faudra utiliser la méthode Configure<IISServerOptions> de IServiceCollection.

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<IISServerOptions>(options =>
        {
            options.AllowSynchronousIO = true;
        });
    }
}

Cela se passe dans le fichier Program.cs de votre application.

Quand votre code n'utilisera plus que du code asynchrone pour manipuler la Request ou la Response, vous pourrez supprimer ces options.

Jérémy Jeanson

Erreurs avec le "deselectAll" de bootstrap-select

Bootstrap-select est un complément bien sympathique à Bootstrap (pour ceux qui ne connaissent pas, il se trouve ici). Malheureusement, sa dernière version a inclue un changement qui peut poser problème (version 1.13.10).

Si vous utilisez la méthode selectpicker avec l’argument "deselectAll", et que l’utilisateur n’a rien sélectionné dans votre liste, vous déclencherez une exception.

Pour éviter le problème, il faut :

  • Utiliser selectpicker("refresh") quand votre liste est vide.
  • Utiliser selectpicker("deselectAll") quand votre liste n’est pas vide.

Exemple avec désactivation du control :

var ctrl = $("#controlId");
ctrl.prop("disabled", true);
if (ctrl.val().length === 0) {
    ctrl.selectpicker("refresh");
}
else {
    ctrl.selectpicker("deselectAll");
}
Jérémy Jeanson

Créer des liens Bootstrap utilisables avec et sans JavaScript

La documentation de Bootstrap fait souvent état de l’usage d’ancres utilisant un # dans son href.


<a href="#" onclick="Foo()">...</a>

Niveau accessibilité, si on vous demande d’avoir un site qui fonctionne sans JavaScript, le lien sera inutilisable (oui, on peut encore vous demander cela en 2019).

Je n’entends pas refaire une explication ici des différents concepts de sémantique HTML, rôle ARIA … etc …. L’objectif est uniquement de rappeler que l’on peut avoir un lien fonctionnel avec et sans JavaScript.

Pour que le lien soit utilisable, il faut prévoir deux comportements :

  • Le premier avec JavaScript : ouvrira certainement une boite de dialogue.
  • Le second sans JavaScript : déclenchera la navigation vers une page.

Pour un site .net MVC, cela est plutôt facile. Il suffit de prévoir deux vues :

  • Une vue partielle pour l’encapsulation dans la boite de dialogue
  • Une vue qui embarque la vue partielle pour permettre la navigation de l’utilisateur sans JavaScript.

Côté HTTML de l’ancre, cela n’est pas compliqué.


<a href="/Controller/Action" onclick="Foo(); return false">...</a>

Comme cela, c’est un peu plus clair et cela fonctionne avec et sans JavaScript :

  • Le lien sert de bouton.
  • Avec JavaScript, au clavier, il fonctionnera comme un bouton.
  • Avec JavaScript, il fonctionne au click.
  • Sans JavaScript, on lance une navigation.

Old school, mais utilisable.

Pour une écriture plus propre, la fonction Foo() peut directement retourner false.

Jérémy Jeanson

Non, la dématérialisation n’est pas LA solution pour l’accessibilité aux démarches administratives !

Avant d’en dire plus long, sachez que je suis un fervent défenseur de la dématérialisation. Je trouve louable, toute initiative qui conduit à rendre une démarche possible à toute heure de la journée et sans avoir à utiliser papiers et enveloppes. Si en plus cela concerne des services publics, je suis très enthousiaste.

Malheureusement, je suis aussi défenseur de l’accessibilité. C’est ce qui me conduit à écrire cet article.