Wednesday, March 16, 2016

Integrating Azure CDN into an ASP.Net MVC application

Using a CDN has a multitude of advantages. I won't go into details about them here. You can Google around for more info ;-)

In the examples one can find online on how to integrate a CDN into an MS MVC application, we see typical examples using the CDN of common libraries like jQuery:

See "Using a CDN" at http://www.asp.net/mvc/overview/performance/bundling-and-minification

It's plain and simple:
var jqueryCdnPath = "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js";

bundles.Add(new ScriptBundle("~/bundles/jquery",
                jqueryCdnPath).Include("~/Scripts/jquery-{version}.js"));
But what if you want to use your own scripts through a CDN like Azure? This works nicely (in BundleConfig):
bundles.Add(new ScriptBundle("~/bundles/jquery", CdnRoot + "/bundles/jquery")
                .Include("~/Content/Scripts/Lib/jquery-{version}.js"));
where CdnRoot is the path to the CDN endpoint, like: //my-endpoint.azureedge.net

This way, the CDN will load the resources from the bundle like in a normal case, store it in its cache, and return it the next times a user requests them. No need to upload the files to the CDN, and changes to the code behind the bundle is taken into account automatically.

Saturday, November 22, 2014

Using Lucene search in DotNetNuke / Evoq Content 7+

Intro

I've recently improved a DNN module to use the new search engine present in the latest version of DotNetNuke. Before, I implemented my own search tool using SQL queries, but it was not very efficient (with LIKEs) and users always found ways not to find what they where looking for (using diacritic and other special characters, using quotes a.s.o...). In short, it was not good ;-)

I googled a bit to find examples, but didn't find many. So instead, I looked up the in the DNN source code. Here is a summary of my findings...

There were 2 areas I wanted to cover:

  • submit items to the DNN search engine (there are examples of that);
  • use the engine to retrieve my items and display them in my module (didn't find any example);


Submitting items to crawl

To use Lucene, you have to use the latest "ModuleSearchBase" base class for your FeatureController instead of implementing the ISearchable interface as in older versions.
You then override the GetModifiedSearchDocuments method and return a list of SearchDocument:

using System;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Services.Search;
using DotNetNuke.Services.Search.Entities;
using DotNetNuke.Services.Search.Internals;

public class FeatureController : ModuleSearchBase
{
    private static readonly int ModuleSearchTypeId = SearchHelper.Instance.GetSearchTypeByName("module").SearchTypeId;

    public override IList GetModifiedSearchDocuments(ModuleInfo oModuleInfo, DateTime beginDate)
    {
        var oDocuments = new List();
        var oDocument = new SearchDocument();
        oDocument.Keywords.Add("keyword1", "value1");
        oDocument.UniqueKey = "some unique key to your app";
        oDocument.Title = "Title of the document";
        oDocument.Body = "Content of the document...";
        oDocument.AuthorUserId = oModuleInfo.LastModifiedByUserID;
        oDocument.ModuleId = oModuleInfo.ModuleID;
        oDocument.ModuleDefId = oModuleInfo.ModuleDefID;
        oDocument.PortalId = oModuleInfo.PortalID;
        oDocument.TabId = oModuleInfo.ParentTab.TabID;
        oDocument.SearchTypeId = ModuleSearchTypeId;
        oDocument.QueryString = "Parameters to pass to the page where my module is inserted into";
        oDocument.ModifiedTimeUtc = DocumentLastModifiedDate;

        // Important, if false, the document will be deleted from the search index
        oDocument.IsActive = true; 

        oDocuments.Add(oDocument);
        return oDocuments;
    }
}

You then run the site crawler job (or wait for it to run automatically) and you should be able to find your documents in the standard search. Clicking on an item will call the page where the module is located and add the querystring you specified for each document.

Note: you should take the "beginDate" parameter into account to only return new and modified documents, otherwise duplicate entries will show up. Unfortunately, if you purge the index in DNN, that date is not updated, and your module will return modified entries instead of returning all of them to refill the index... This is a bug I have reported to support, hopefully it will be fixed someday.

Using the DNN search in a module

A query is very simple to make. You make a new instance of SearchQuery, fill in some data, run it and display the results:

using DotNetNuke.Services.Search.Controllers;
using DotNetNuke.Services.Search.Entities;

protected void btnSearch_Click(object sender, EventArgs e)
{
    var oSearchQuery = new SearchQuery();
    oSearchQuery.KeyWords = "Search query";
    oSearchQuery.ModuleId = this.ModuleId;
    oSearchQuery.TabId = this.TabId;
    oSearchQuery.PortalIds = new List() { this.PortalId };
    oSearchQuery.WildCardSearch = true;

    var results = SearchController.Instance.ModuleSearch(oSearchQuery);

    if (results != null)
    {
        if (results.Results == null || results.Results.Count == 0)
            plhSearchResults.Controls.Add(new LiteralControl("<div class='alert alert-info'>" + LocalizeString("NoSearchResults") + "</div>"));
        else
        {
            plhSearchResults.Controls.Add(new LiteralControl("<ul style='list-style-type: none;'>"));
            foreach (var oRes in results.Results)
                plhSearchResults.Controls.Add(new LiteralControl(string.Format("<li><a href='{0}'>{1}</a><p>{2}</p></li>", oRes.Url, oRes.Title, oRes.Snippet)));
            plhSearchResults.Controls.Add(new LiteralControl("</ul>"));
        }
    }
}

Where plhSearchResults is an asp:placeholder receiving the list of results.

Hope that helps!

Thursday, March 6, 2014

ASP.Net Web API + OData + $inlinecount

So, I had a nice single page application querying data from a Web API backend. As I wanted to allow searching and paging, I used the OData extensions and all was well. Until I needed to know the total number of items in the dataset after having it filtered using $filter, $top and $skip. At the time, I didn't find any easy way to do it, so I implemented a second query to the API to return the total number of items. Problem is, this second query returned the total number of items and didn't take into account any filtering I had applied in the OData query (using $filter). I let that dormant for a while...

Then came the need to have a nice navigation footer, and I used this component: http://botmonster.com/jquery-bootpag/

This component just needs to know the number of pages and at which page it currently is. But then again, we need to know the number of items to know the number of pages to display. Back at square one ;-)

I looked for ways to implement this once again and found more info this time (technology matured or better search query in Google?). Some articles suggested to force the verbose mode of OData (by using "&$format=verbosejson" or adding an accept header with a value of "application/json;odata=verbose"), to no avail. I would always receive an array with the X items I requested using the $top parameter and nothing else.

Until I came across these 2 posts on SO:
http://stackoverflow.com/questions/18428763/web-api-odata-inlinecount-not-mapped
and http://stackoverflow.com/questions/18197041/reconstructing-an-odataqueryoptions-object-and-getinlinecount-returning-null

In short, here is what I had to change:

[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable Get()
{
return m_oItems.AsQueryable();
}

changed to:

public PageResult Get(ODataQueryOptions options)
{
IQueryable results = options.ApplyTo(m_oItems.AsQueryable());
return new PageResult(results as IEnumerable, Request.GetNextPageLink(), Request.GetInlineCount());
}

Then in my JS file, I added "&$inlinecount=allpages" to my query and where I would treat the data blob returned by $.getJSON as an array, I simply use data.Count to have the number of items that match my query, and data.Items as my array of items.

The solution looks so simple now, but took me some time to figure out. Hope this helps...

Friday, February 22, 2013

Tuesday, September 11, 2012

You play, we play

If you want to play Euromillions and increase your odds, a lottery syndicate is the best way to do just that. By pooling your ticket with 49 other players, your odds of winning something is multiplied by 50, for a price a tad higher than buying your own ticket in the shop.

I've played with the below syndicate and have won something on every single draw, albeit small prizes so far.

You also have the benefit of the British Euromillion draw that gives away 1 million pounds during each draw to a ticket sold in the UK. In fact, each draw gives you 50 changes to get that lucky ticket and share it between 50, which gives you 20.000£ in such a case. Gladly welcome.

Enjoy!

Wednesday, June 29, 2011

Journée en Famille 2011

Ce 26 juin 2011, Céline Asselborn animait l'atelier sur le portage respectueux à Volaiville. Buffets, stands d'information, activités diverses, et bien sûr, la 2ème édition de la Balade des enfants portés complétaient cette journée très ensoleillée !

Photos ici:


Tuesday, April 5, 2011

Anne-Lise & Fabien

Petite vidéo prise lors de la séance photos précédent la cérémonie de mariage d'Anne-Lise et Fabien, le samedi 2 avril 2011...



Anne-Lise & Fabien from Frédéric Mauroy on Vimeo.

Canon 5D MkII + Canon EF 200 1.8L

Les photos sont ici: