Home - About me - Browse by categories

[ASPNET MVC] Utilisation du plugin jQuery BBQ pour la gestion du bouton back et la navigation par fragment dans une SPA

Lorsque l’on développe une Single Page Application (SPA) en JavaScript, il est souvent recommandé de mettre en place la gestion du bouton back pour l’historique de navigation, ainsi que de la navigation par fragment, permettant de conserver des URL propres à chaque ressource et permettant ainsi aux utilisateur d’utiliser la SPA comme n’importe qu’elle autre application.

Comprendre la navigation par fragment

Il s’agit en fait des éléments que l’on rajoute à une URL de page, après le caractère #. On parle aussi d’ancre dans la page. Par exemple, le lien suivant http://www.monsite.com/page1.html#menu, permet de rediriger l’utilisateur directement vers une section de le page.

Ce système existant dans HTML depuis de nombreuses années a été ré-exploité en JavaScript pour de la navigation par fragment. Par exemple, l’équivalent de la page http://www.monsite.com/customers/32, qui affiche le détail du client ayant 32 pour id, peut se retrouver sous la forme de http://www.monsite.com/customers/32 afin d’être exploité directement depuis du code JavaScript. Les éléments qui suivent le caractère # représente le hash de l’URL.

Il suffit donc d’avoir un mécanisme en JavaScript pour détecter les changements de hash et permettre à l’utilisateur de passer de ressource en ressource à l’aide des boutons précédent / suivant du navigateur, de mettre une ressource en favoris, d’envoyer une ressource par mail à un autre utilisateur, etc…

C’est ce que permet de faire le plugin jQuery BBQ : http://benalman.com/projects/jquery-bbq-plugin/

Mise en place de jQuery BBQ

Commencez par télécharger les sources du plugin ici ou ici (en version minified). Ajoutez ensuite le fichier à votre projet de développement et référencez le dans la/les page(s) dans la(les)quelle(s) vous souhaitez l’utiliser.

<script src="/Scripts/jquery.ba-bbq.js"></script>

Une fois ceci fait, il est possible de s’abonner à un événement hashchange sur la window. Cet événement sera levé à chaque fois que le hash de l’URL sera modifié :

<div id="menu">

- @Html.ActionLink("Clients", "Index", "Customers", null, new { @class = "menu-link" })

- @Html.ActionLink("Achats", "Index", "Orders", null, new { @class = "menu-link" })

<div id="contentDiv">

<div id="currentFragment">

@section scripts{
<script type="text/javascript">
$(document).ready(function () {
$(window).bind("hashchange", function (e) {
//récupération du fragment
var fragment = e.fragment;
$("#currentFragment").html("" + fragment + "

");
});
});
</script>
}

Dès lors, dès que le fragment change dans l’URL, le hash est affiché sur la page, dans le conteneur currentFragment :

image

A présent, il suffit de remplacer les liens directs des différentes sections de l’application par des liens fragmentés :

$(".menu-link").each(function(index, item) {
var href = $(item).attr("href");
var newHref = thisUrl + "#" + href;
$(item).attr("href", newHref);
});

Et de charger le contenu représenter par le fragment lorsque le hash change :

$(window).bind("hashchange", function (e) {
//récupération du fragment
var fragment = e.fragment;
$("#currentFragment").html("" + fragment + "

");

//chargement du contenu
$("#contentDiv").load(fragment);
});

Enfin, pour faire en sorte que le contenu soit également chargé lorsque l’utilisateur accède directement à votre application web via une URL fragmentée, il est nécessaire de forcer l’évènement hashchange sur le document ready :

$(window).trigger("hashchange");

Et voilà, le tour est joué. Vous pouvez récupérer les sources de l’exemple ici.

Enjoy Clignement d'œil

Julien

read more

Utilisez Razor dans vos scripts JavaScript

Dans certains cas, il peut être intéressant de pouvoir formater du JavaScript avec Razor, pour tirer profit des données du modèle directement dans vos scripts JavaScript.

Pour cela, il suffit d’utiliser la notation <text></text> comme ci-dessous :

<script type="text/javascript">

//code JavaScript
var customers = new Array();

//code Razor/C#
@foreach(var customer in Model.Customers)
{
//code javascript + razor/c#
<text>
customers.push({
name: "@customer.Name",
firstname: "@customer.FirstName",
birth: "@customer.BirthDate"
});
</text>
}

$(document).ready(function(){
//ect...
});

</script>

Et voilà : simple, mais efficace !

A+

Julien

read more

Création d’un multipart file stream provider personnalisé avec ASPNET Web API

La gestion de l’upload de fichier dans un service ASP.NET Web API se fait à l’aide d’un MultipartFileStreamProvider. Il est possible de créer ses propres providers pour enregistrer un fichier d’une manière particulière, par exemple dans un blob azure ou une base de données SQL Server. C’est le deuxième exemple que j’ai choisi pour illustrer ce post.

Utilisation basique du MultipartFileStreamProvider

Pour cet exemple, on se place dans un ApiController tout ce qu’il y a de plus classique, dans une méthode appelée en POST :

[HttpPost]
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent("form-data"))
return Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, "contenu non supporté");

//le multipart provider va télécharger les fichiers dans le répertoire temporaire
var multipartProvider = new MultipartFileStreamProvider(Path.GetTempPath());
var files = await Request.Content.ReadAsMultipartAsync(multipartProvider)
.ContinueWith(t =>
{
if (t.IsFaulted)
throw t.Exception;

//retourne la liste des fichiers qui ont été téléchargés sur le systeme de fichier local
//dans le répertoire temporaire
return t.Result.FileData.ToList();
});

foreach (var file in files)
{
//chemin du fichier en local :
string path = file.LocalFileName;

//nom du fichier envoyé :
string fileName = file.Headers.ContentDisposition.FileName;

//contentType du fichier
string contentType = file.Headers.ContentType.MediaType;
}

return Request.CreateResponse(HttpStatusCode.OK);
}

Il est d’abord nécessaire de vérifier que le POST a bien été fait avec un mime content en form-data. Si ce n’est pas le cas, on retourne en indiquant que le format du média n’est pas supporté.

Dans un second temps, il suffit d’instancier un MultipartFileStreamProvider en lui passant un répertoire de base pour télécharger les fichiers (ici un dossier temporaire) et d’appeler la méthode ReadAsMultipartAsync sur l’objet Request. Il est alors possible de récupérer les différents fichiers, leur chemin d’accès local, leur content type, leur nom… Il est alors ensuite possible d’appliquer n’importe quel traitement sur le fichier pour l’exploiter (envoie dans un blob azure, stockage dans une base de données…). Ce qui va devenir vraiment intéressant est de factoriser ces différents traitements dans des multipart providers dédiés à ces tâches spécifiques !

Personnalisation d’un MultipartFileStreamProvider

Il est possible de dériver la classe MultipartFileStreamProvider afin de surcharger la méthode ExecutePostProcessingAsync, en charge de traiter les différents fichier qui sont envoyés sur le serveur. Dans le cas présent, cette méthode va être surchargée pour envoyer automatiquement les fichiers dans une base de données SQL Serveur.

Pour commencer, on créé un constructeur qui permet de passer la chaîne de connexion vers la base de données ainsi que le fournisseur à utiliser :

public class MultipartSqlFileStreamProvider : MultipartFileStreamProvider
{
private const string SQL = "INSERT INTO Files (FileId, ContentType, FileName, FileContent) VALUES (@FileId, @ContentType, @FileName, @FileContent)";

private readonly string _connectionString;
private readonly DbProviderFactory _dbProviderFactory;

public List<Guid> FileIDs{ get; set; }

public MultipartSqlFileStreamProvider(string connectionString, string providerName)
: base(Path.GetTempPath())
{
_connectionString = connectionString;
FileIDs = new List<Guid>();
_dbProviderFactory = DbProviderFactories.GetFactory(providerName);
}
}

Ensuite, on surcharge la méthode et on parcourt la liste de fichier afin de les enregistrer dans la base SQL :

public async override Task ExecutePostProcessingAsync()
{
using (var sqlConnection = _dbProviderFactory.CreateConnection())
{
sqlConnection.ConnectionString = _connectionString;
sqlConnection.Open();

foreach (var file in FileData)
{
using (var dbCommand = sqlConnection.CreateCommand())
{
dbCommand.CommandText = SQL;

Guid fileId = Guid.NewGuid();

var fileIdParameter = dbCommand.CreateParameter();
fileIdParameter.ParameterName = "FileId";
fileIdParameter.Value = fileId;
fileIdParameter.DbType = System.Data.DbType.Guid;
dbCommand.Parameters.Add(fileIdParameter);

var contentTypeParameter = dbCommand.CreateParameter();
contentTypeParameter.ParameterName = "ContentType";
contentTypeParameter.Value = file.Headers.ContentType.MediaType;
contentTypeParameter.DbType = System.Data.DbType.StringFixedLength;
dbCommand.Parameters.Add(contentTypeParameter);

var fileNameParameter = dbCommand.CreateParameter();
fileNameParameter.ParameterName = "FileName";
fileNameParameter.Value = file.Headers.ContentDisposition.FileName.Replace("\"","");
fileNameParameter.DbType = System.Data.DbType.StringFixedLength;
dbCommand.Parameters.Add(fileNameParameter);

var fileContentParameter = dbCommand.CreateParameter();
fileContentParameter.ParameterName = "FileContent";
fileContentParameter.Value = File.ReadAllBytes(file.LocalFileName);
fileContentParameter.DbType = System.Data.DbType.Binary;
dbCommand.Parameters.Add(fileContentParameter);

await dbCommand.ExecuteNonQueryAsync();

FileIDs.Add(fileId);
}
}

sqlConnection.Close();
}
}

Pour utiliser le multipart provider personnalisé, il suffit de l’instancier et de le passer à la méthode ReadAsMultipartAsync, comme vu en introduction de cet article.

var connectionString = WebConfigurationManager.ConnectionStrings["DataConnectionString"];

var multipartSqlProvider = new MultipartSqlFileStreamProvider(connectionString.ConnectionString, connectionString.ProviderName);
var fileIDs = await Request.Content.ReadAsMultipartAsync(multipartSqlProvider)
.ContinueWith(t =>
{
if (t.IsFaulted)
{
throw t.Exception;
}

var provider = t.Result;
return provider.OutputFileIDs.First().ToString();
}
);

Dès lors, les fichiers sont automatiquement envoyés dans la base de données et vous récupérez en output la liste des IDs des fichiers !

Enjoy Clignement d'œil

Julien

read more

Déployer automatiquement vos applications ASPNET MVC dans Azure avec Team Foundation Service

Team Foundation Service (anciennement TFS Preview) est la version hébergée de Team Foundation Server. Pour en savoir plus sur ce service proposé par Microsoft, je vous invite à lire cet article et cet article sur le blog d’cet article !

Une des fonctionnalité très sympa lorsque l’on développe des applications destinées à être hébergées dans Windows Azure, est la possibilité d’associer automatiquement un projet d’équipe Team Foundation Service à un Cloud Service Azure. Cela permet de faire en sorte qu’à chaque check-in depuis Visual Studio, le code soit compilé et automatiquement déployé dans Azure.

Pour associer un projet d’équipe à un service de compute Azure, il faut se connecter sur l’interface de management Windows Azure puis se rendre sur le tableau de bord du service en question :

image

Cliquez sur Configurer la publication TFS. La popup qui s’ouvre vous permet soit d’utiliser un compte TFService déjà existant, soit de créer un compte sur visualstudio.com :

image

Saisissez le nom de votre compte de service si vous en avez déjà un. Si ce n’est pas le cas, cliquez sur Créez un compte TFS maintenant. La popup qui s’ouvre vous permet de créer un compte TFS Service :

image

Créez votre compte. Pour les utilisateur existant, cliquez sur le lien Autorisez maintenant.

Une fenêtre s’ouvre alors pour vous demander d’autoriser votre service Windows Azure à accéder à TFS, un peu comme cela pourrait être pour n’importe quel membre de l’équipe :

image

Si vous êtes d’accord, acceptez Sourire Vous pouvez à présent choisir le projet d’équipe à associer :

image

Patientez pendant la liaison du projet d’équipe à votre service Windows Azure :

image

Une fois ceci fait, ouvrez Visual Studio et connectez- vous à votre projet d’équipe. Dans le Team Explorer, affichez les définitions Builds configurées. Comme vous pouvez le constater, une nouvelle Build est présente. Elle porte le nom de votre service Cloud Azure, suffixé par _CD pour Continuous Deployment :

image

Si vous ouvrez la définition de la build, vous pourrez voir que celle ci est configurée pour être exécuté à chaque check-in (Continuous Integration), dans l’onglet Trigger. Si vous allez dans l’onglet Process, vous constaterez que le process template utilisé est un spécifique à Windows Azure : AzureContinuousDeployment.11.xaml. En ouvrant le workflow de build, et en fouillant un peu dans les différentes activités, vous finirez par trouver la partie qui concerne le déploiement dans Azure :

image

Concrètement, vous n’avez rien à modifier pour que ça marche Clignement d'œil

Afin d’avoir la main sur le profil de publication qui doit être utilisé par la build lors du déploiement, vous devez en créer un (ou utiliser un existant, si vous en avez déjà un). Pour se faire, faites un clic droit sur votre projet Cloud dans Visual Studio et cliquez sur Publish…

Dans la fenêtre qui s’ouvre, choisissez votre souscription Azure :

image

Configurez ensuite les paramètres en choisissez votre Cloud Service, l’environnement sur lequel vous souhaitez déployer (Production / Staging), la build configuration et la service configuration à utiliser :

image

Sur la page de résumé, enregistrez le profile, mais ne cliquez pas sur Publish, cliquez sur Cancel pour quitter :

image

Visual Studio a créé un fichier XML qui représente ce profil, dans le dossier Profiles du projet Windows Azure. Retournez dans la définition de la build, dans l’onglet Process et déroulez la section** 5. Publishing – Azure Cloud Service. **Modifiez le paramètre Alternate Publish Profile pour le faire pointer sur le fichier de profil que vous venez de créer :

image

Enregistrez les paramètres, et lancez la build. Le tour est joué.

Note : pour le moment le SDK 1.8 de Windows Azure n’est pas pris en charge par l’agent de build hosté sur Team Foundation Service, mais cela devrait arriver. En attendant, il est possible d’indiquer que la build doit s’exécuter sur un agent de build configuré on-premise, par vos soins.

Enjoy Clignement d'œil

Julien

read more

Azure Service Bus, BrokeredMessage et opérations longues

Le service de messaging d’Azure Service Bus n’est à l’origine pas prévu pour exécuter des long-running process. Cependant le SDK 1.8 sorti en Octobre dernier apporte une petite nouveauté sur ce point. En effet, la classe BrokeredMessage offre désormais une méthode RenewLock qui permet de demander le renouvèlement d’un verrou sur le brokered message afin qu’il ne soit pas remis à disposition dans la file d’attente.

Du coup, en se basant sur la propriété LockedUntilUtc, on peut réussir à exécuter une opération longue sans avoir de problème de perte de verrou sur le message :

//création d'un CTS pour lancer un task en charge de renouveller le verrou du message
var brokeredMessageRenewCancellationTokenSource = new CancellationTokenSource();

try
{
//reception du message
var brokeredMessage = _client.Receive();

var brokeredMessageRenew = Task.Factory.StartNew(() =>
{
while (!brokeredMessageRenewCancellationTokenSource.Token.IsCancellationRequested)
{
//on se base sur la propriété LockedUntilUtc pour savoir si le verrou expire bientôt
if (DateTime.UtcNow > brokeredMessage.LockedUntilUtc.AddSeconds(-10))
{
//si oui, on renouvelle le message
brokeredMessage.RenewLock();
}

Thread.Sleep(500);
}
}, brokeredMessageRenewCancellationTokenSource.Token);

//exécution d'une opération longue
RunLongRunningOperation();

//on marque le message comme terminé
brokeredMessage.Complete();
}
catch(MessageLockLostException)
{
//le verrou a expiré
}
catch(Exception)//autre exception
{
//autre erreur : on abandonne le message
brokeredMessage.Abandon();
}
finally
{
//on arrête la task de renouvellement du verrou
brokeredMessageRenewCancellationTokenSource.Cancel();
}

Et voilà ! Clignement d'œil

Julien

read more