Tuesday, 9 December 2014

How to Change the Default Font in Blogger

If you are tired of writing your blog posts in Times New Roman font and don't want to add bloated HTML by choosing the font and size from Blogger's post editor, this post is right place for you. The solution is simple if you know where to look. Below are two ways you can try.
Chrome Browser Settings
As Blogger is a part of Google family, changing the font is actually a Chrome setting, not a Blogger setting.

To change your font setting do the following
1. Chrome browser
2. Settings
3. Show Advanced Settings
4. Web Content: Customize Fonts
Here is a bonus part, though it does set your default font & size in Blogger, it also changes it all over Google.

CSS Way
Alternatively if you don't want to change chrome browser settings, you can make CSS work for you.

1. Sign in to your blogger account.
2. Select your blog.
3. On right hand menu click Templates and then Edit HTML.
4. Locate or search for <b:skin> and copy/paste the following CSS code inside.

* { font-family: Arial!important;}
or
body {
font-family: Arial!important;
}

5. Click Save template.

Now all the old and new blog posts in your blog will be in the font you specified through CSS.


/Adnan
December 09, 2014

Saturday, 6 December 2014

How to Fix HttpContextBase Errors in Facebook OAuth for ASP.NET

This fix applies to legacy ASP.NET Web Forms or ASP.NET MVC applications running on .NET Framework and using DotNetOpenAuth. If you are building a new application on ASP.NET Core, use the built-in external authentication providers instead of this older pattern.

Problem

I was helping a friend wire up Facebook OAuth login in an older ASP.NET application using the DotNetOpenAuth extensions installed from NuGet.

The code looked like this:

Uri ui = new Uri("~/Login.aspx", UriKind.Relative);

var fbClient = new DotNetOpenAuth.AspNet.Clients.FacebookClient("***", "***********");
fbClient.RequestAuthentication(context, ui);

The problem is that RequestAuthentication expects an instance of HttpContextBase.

If you try to pass HttpContext.Current directly, it fails because HttpContext.Current is a HttpContext, not a HttpContextBase.

Why This Happens

HttpContextBase was introduced as an abstraction over HttpContext. This makes ASP.NET code easier to test and easier to work with in components that should not depend directly on the concrete runtime context.

To bridge the gap between the two types, ASP.NET provides HttpContextWrapper.

Solution

Wrap HttpContext.Current in a HttpContextWrapper before calling RequestAuthentication:

var httpContextBase = new HttpContextWrapper(HttpContext.Current);
fbClient.RequestAuthentication(httpContextBase, ui);

Explanation

HttpContextWrapper acts as an adapter. It takes the current ASP.NET request context and exposes it as a HttpContextBase, which is exactly what the DotNetOpenAuth API expects.

So if you are maintaining a legacy ASP.NET application and run into a type mismatch between HttpContext and HttpContextBase, this wrapper is the correct fix.

Modern Note

For new applications, this is no longer the recommended approach. In modern ASP.NET Core applications, external login providers such as Facebook are configured through the built-in authentication middleware, and System.Web, HttpContextBase, and HttpContextWrapper are not part of that model.

December 06, 2014

Thursday, 27 November 2014

How to Render ASP.NET MVC Views to an HTML String

A common need I have in my ASP.NET MVC based projects is to render a complete "View" or "PartialView" to string instead of the HTTP response and then present it or embed it in another rendered view.

You can implement the following code in shared controller or preferably in base Controller so that you can access this function in all controllers across your project. You can access the ControllerContext within controller and pass it to the function. It will return the rendered view in HTML string, the usage is self-explanatory.
public static string RenderViewToString(string viewName, object model) 
{
 if (string.IsNullOrEmpty(viewName)) 
     viewName = ControllerContext.RouteData.GetRequiredString("action");

 ViewData.Model = model;
 using(StringWriter sw = new StringWriter()) 
 {
  ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
  ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
  viewResult.View.Render(viewContext, sw);
  return sw.GetStringBuilder().ToString();
 }
}

ControllerContext can be access using following method.

ControllerContext.RouteData.GetRequiredString("action");

If you want to put the function in a helper class you have to pass the ControllerContext from controller to the function.

public static string RenderViewToString(ControllerContext context, string viewName, object model) 
{
 if (string.IsNullOrEmpty(viewName)) 
     viewName = context.RouteData.GetRequiredString("action");
 
 var viewData = new ViewDataDictionary(model);
 using(var sw = new StringWriter()) 
 {
  var viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
  var viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
  viewResult.View.Render(viewContext, sw);
  return sw.GetStringBuilder().ToString();
 }
}

Call the function in your Action Method

//Somewhere in HomeController
public ActionResult Index() 
{
 //Second parameter(model) can be null
 var context = ControllerContext.RouteData.GetRequiredString("action");
 var content = RenderViewToString(context, "profile", new ProfileModel());
 //var content = RenderViewToString("profile", new ProfileModel());

 
        //Do something with the content, e.g.get profile specific template and send it to e-mail


 //This does nothing to do with rendered string
 return View();
}


/Adnan

November 27, 2014

Friday, 21 November 2014

How to Copy Text to the Clipboard Using JavaScript

In a recent web project, I needed to create a button that would copy text from textbox onto the user's clipboard. The obvious approach is to use jQuery or JavaScript to trigger onclick() event of the button and copy text to clipboard. It sounds easy and convenient and should be achieved with few lines of code but it is not. 
During the code generation process, I found that JavaScript copy to clipboard was not available because of security which also meant that jQuery would not be able to copy the text to clipboard. Actually you can still use JavaScript, but it prompts the user to allow the application to copy text on clipboard, and hence voids the whole idea of providing user convenience. This means I had to find another way around, but I still wanted to use JavaScript.

After spending some time on Google search, luckily I found a jQuery library called ZeroClipboard. This library provides an easy way to copy text to the clipboard using a pinch of Invisible Adobe Flash movie, and touch of JavaScript. Flash can access your computer's clipboard because you have to install flash and agree to the security settings. We can use JavaScript as an interface to flash so we can start this off with a click event on a button.

Note: Before we continue to tutorial/demo there are some things to consider. Due to security issues, flash cannot access the clipboard unless the action originates from a click (or user interaction) with a flash object.
You cannot copy paste code in html file and open it with browser and expect ZeroClipboard to run. You will not able to click button. So you have to host the HTML page in your local IIS website e.g http://localhost:8080/zeroclipbaord.html to make it work.

How to Use ZeroClipboard?
You can download ZeroClipboard from http://zeroclipboard.org/ or use it the from the public content distribution network (CDN) cdnjs: http://cdnjs.com/libraries/zeroclipboard
To start using ZeroClipboard simply include following JavaScript file in your page.

<script src="//cdnjs.cloudflare.com/ajax/libs/zeroclipboard/2.1.6/ZeroClipboard.js" type="text/javascript"></script>

The following example shows you two common ways to copy text on clipboard
1 - Copy the text by Setting Target Area
2 - Copy the text with a HTML data-attribute

1- Copy the Text by Setting Target Area
This method allows you to define a HTML element that you can get the text from to copy. The value that it will use can either be the value of the element, the innerHTML or the textContent. This works off a data attribute of data-clipboard-target with a value of the ID of element you want to copy.

<button data-clipboard-target="clipboard-text" id="btn-To-Copy">Copy To Clipboard</button>

<textarea cols="20" id="clipboard-text" name="clipboard-text" onclick="this.select();" rows="20">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus mattis lacus nibh, ac sollicitudin sapien accumsan in. Mauris euismod posuere tellus luctus sodales.
Fusce a consectetur massa, non tincidunt mauris. Phasellus a rutrum libero. Praesent tempus urna et nisi aliquam convallis. Fusce porttitor justo condimentum orcieuismod, pulvinar congue magna vestibulum.
Sed gravida eleifend justo, id ultrices tellus porttitor nec. Nam porttitor gravida tempor. In libero ante, euismod ac fermentum nec, gravida ut dolor. Nullam a pulvinar ligula.
</textarea>

<div id="responsecopy" style="display: none; position: relative;">
</div>

We setup the ZeroClipboard client to be attached to the btn-To-Copy button. ZeroClipboard will search for the data-clipboard-target attribute and use this value to get the text to copy on clipboard.

2- Copy the Text with a HTML data-attribute
You provide button with the text and use Html data-attribute (data-clipboard-text) to tell ZeroClipboard to copy value from button to Clipboard.

<button data-clipboard-text="This text will be copied to Clipboard" id="btn-To-Copy" name="btn-To-Copy">Copy To Clipboard</button>
<div id="responsecopy" style="display: none; position: relative;">
</div>

Both above examples use the same following JavaScript

<script type="text/javascript">
        var client = new ZeroClipboard(document.getElementByIdid("btn-To-Copy"));

        client.on("ready", function (readyEvent) {
            // alert( "ZeroClipboard SWF is ready!" );

            client.on("aftercopy", function (event) {
                // `this` === `client`
                // `event.target` === the element that was clicked

                var msgBox = document.getElementById("responsecopy");
                msgBox.innerHTML = "Copied '" + event.data["text/plain"] + "' to clipboard";
                msgBox.style.display = 'block';

               //alert("Copied text to clipboard: " + event.data["text/plain"]);
           });
        });
    </script>

ZeroClipboard uses a Flash movie, and so your users obviously need to have Adobe Flash installed. You do need to handle the case where it is not present.

ZeroClipboard Documentation
Need to learn more how to Use ZeroClipboard? You can go though the latest version documentation of ZeroClipboard on their Github Project page.


/Adnan
November 21, 2014

Sunday, 12 October 2014

How to Get Combinations of Rows from Multiple SQL Tables

Getting combinations of rows from database tables is simple. First you have to understand the difference between Cartesian product and Permutation before we go any further.

I have manually printed out all the combinations in example containing three tables with respective values for the understanding.

Example
Table1     Table2     Table3
a1             b1           c1
a2             b2           c2
a3                            c3
                                c4
The results should be as follow

a1,b1,c1
a1,b1,c2
a1,b1,c3
a1,b1,c4

a1,b2,c1
a1,b2,c2
a1,b2,c3
a1,b2,c4

a2,b1,c1
a2,b1,c2
a2,b1,c3
a2,b1,c4

a2,b2,c1
a2,b2,c2
a2,b2,c3
a2,b2,c4

a3,b1,c1
a3,b1,c2
a3,b1,c3
a3,b1,c4

a3,b2,c1
a3,b2,c2
a3,b2,c3
a3,b2,c4
The above results are not permutation, because you need the combinations to always follow the unique format. So, in conclusion the Cartesian product concept is the right way to go.

Solution
We will use Cartesian Join or Cross Join for the solution of above example. Cross Join returns the Cartesian product of rows from tables in the join. Each row in the first table is matched with every row in the second table and so on.
select *
from
  table1
  cross join table2
  cross join table3
Same thing as implicit cross join:
select *
from
  table1, table2, table3



/Adnan

October 12, 2014

Thursday, 2 October 2014

How to Get the Current Page in a Block Controller or Action Filter in Optimizely CMS

If you need to get the current routed page inside a block controller, view component, or action filter in Optimizely CMS, the usual approach is to use IPageRouteHelper.

If you want the current routed content more generically, use IContentRouteHelper.

Modern Approach

In newer Optimizely CMS projects, it is better to use constructor injection instead of ServiceLocator.Current.

Get the Current Page

using EPiServer.Web.Routing;

public class MyService
{
    private readonly IPageRouteHelper _pageRouteHelper;

    public MyService(IPageRouteHelper pageRouteHelper)
    {
        _pageRouteHelper = pageRouteHelper;
    }

    public PageData GetCurrentPage()
    {
        return _pageRouteHelper.Page;
    }
}

Get the Current Page Reference

using EPiServer.Web.Routing;

public class MyService
{
    private readonly IPageRouteHelper _pageRouteHelper;

    public MyService(IPageRouteHelper pageRouteHelper)
    {
        _pageRouteHelper = pageRouteHelper;
    }

    public PageReference GetCurrentPageReference()
    {
        return _pageRouteHelper.PageLink;
    }
}

Get the Current Routed Content

If you do not specifically need a page, use IContentRouteHelper instead:

using EPiServer.Web.Routing;

public class MyService
{
    private readonly IContentRouteHelper _contentRouteHelper;

    public MyService(IContentRouteHelper contentRouteHelper)
    {
        _contentRouteHelper = contentRouteHelper;
    }

    public IContent GetCurrentContent()
    {
        return _contentRouteHelper.Content;
    }

    public ContentReference GetCurrentContentReference()
    {
        return _contentRouteHelper.ContentLink;
    }
}

Using It in an Action Filter

If you need the current page inside an action filter, you can resolve the helper from the request services:

using EPiServer.Web.Routing;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;

public class MyActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var pageRouteHelper = context.HttpContext.RequestServices.GetRequiredService<IPageRouteHelper>();
        var currentPage = pageRouteHelper.Page;
        var currentPageReference = pageRouteHelper.PageLink;

        base.OnActionExecuting(context);
    }
}

Legacy Example

If you are working with older code, you may still see ServiceLocator.Current being used:

using EPiServer.ServiceLocation;
using EPiServer.Web.Routing;

var pageRouteHelper = ServiceLocator.Current.GetInstance<IPageRouteHelper>();
var currentPage = pageRouteHelper.Page;
var pageReference = pageRouteHelper.PageLink;

Important Note

Page and PageLink are only available when the current request is actually routed to a page. In some non-page contexts, such as certain preview scenarios, they may be null. If you need a more general routed object, IContentRouteHelper is the safer choice.

Final Thought

If your code only needs the routed content, prefer IContentRouteHelper. If your code specifically depends on a page, use IPageRouteHelper.

October 02, 2014

Friday, 26 September 2014

How to Make a Conference or Group Call Using Rebtel

If you are a Rebtel user and want to make a conference / group call, then this post is the right place for you. Rebtel is a great, cheap way to make international calls to your loved ones with crystal clear voice quality. I love it and I'm sure that you do too. So when you get what you need without much effort you want more, that’s human nature and in Rebtel's case more is possible in the form of Conference / Group calls.

Rebtel don't officially offer and support conference calling as a service. This can be a useful feature if you want to call your friends and family at once over PSTN without caring about what device they have. For a receiver it is as simple as just picking up the call and talking. You do not have to bother getting online, downloading plugins or software to just engage in a simple call. This is basically a hustle free solution. Conference call over data is great, but there is one catch that everyone you want to be in group / conference call should be using it too, and be online on certain application like Skype. So how can you actually make Conference / Group call using your beloved Rebtel, It requires some work but it’s not difficult at all.

Before we start with the details, please note that there are some limitations of this process. This will only work if you are registered in one of Rebtel Countries. If you don't know what the Rebtel countries are? Read here. The reason to be in a Rebtel country is to use Rebtel famous and brilliant feature Local Numbers. Confused about Local Numbers? Have a quick peak here

We will use the phones native conference call functionality. You have to use a Smartphone, any smart phone with the Add Call button on the dialer while on call indicates the phone supports conference call natively (e.g. Android, iPhone or Windows Phone). As we will not use the Rebtel app you have to bare network charges for every call. So if you have an unlimited or free minutes on your price plan from your operator this process will be much cost effective.


Every number you add in My Local Numbers by login in http://my.rebtel.com, Rebtel assigns a Local Access Number against the contact so as an alternative of using Rebtel app, you can directly call Local Number and Rebtel automatically connects you with the friend you want to call. FYI, Rebtel Local Numbers are personal and cannot be shared.

The following steps are based on Android native dial pad:

1. Go to http://my.rebtel.com on a menu click "My Local Numbers", and create Local Numbers for the contacts you want to do a group call, and save in your save Rebtel generated local access number to your phone contacts. You will get a SMS and email of the number when you created the local access number so you can easily save it in your phone contacts.

2. Now all you have to do is make a regular call from your phone native dialer to the Local Number with whom you want to have conference / group call to initiate local minutes based Rebtel call.

3. Once the call is established, press the add people button on a dialer and select contact (with Rebtel local number) to initiate the second call. 


At this point the other party will be on hold automatically so inform them beforehand not to disconnect the call. You can manually put them on hold as well just to be on safe side.



4. Now you have two calls established one on hold and the second active, simply tap Merge Calls on dial pad to create a conference call.



Repeat step 3 to 4 for each additional personal whom you want in on the conference call.

You can add up to 5 people on conference call on Android and iPhone by repeating the same process.

Bonus Tip: If your friend calls you while you are on a call you can merge him in conference call as well. So instead you call people, they can call you and you add them in your conference call. Great for people who want to save network and Rebtel charges.

Go ahead and make conference / group call.

Happy Calling

/Adnan

September 26, 2014

Wednesday, 24 September 2014

How to Delete a Language Branch from All Pages in Optimizely CMS

Suppose your EPiServer CMS site contains a large number of pages and supports multiple languages, for example English (en), French (fr), and Italian (it).

You want to remove one language branch, such as French, from all pages across the site, but you do not know exactly how many pages currently use that language.

If you try to remove the language directly from the CMS admin UI, you will usually hit an error because the language is still being used by content or language settings.

Before You Start

  • Take a database backup first.
  • Test the process in a lower environment before running it in production.
  • Disable the target language in Admin/Settings > Manage Website Languages so editors cannot keep creating new versions while the cleanup is running.
  • This approach will not delete pages where the language you want to remove is the master language. Those must be handled separately.

Small Sites vs Large Sites

If only a handful of pages use the language, the built-in Versions gadget is usually enough.

But if the site is large, deleting language branches manually page by page quickly becomes painful. In that case, a scheduled job is the safer and more practical option.

Modern Approach

In newer EPiServer / Optimizely CMS solutions, the cleanest approach is to:

  • Loop through the site start pages and their descendants.
  • Check whether a page has the language branch you want to remove.
  • Skip pages where that language is the master language.
  • Delete the branch with IContentRepository.DeleteLanguageBranch.

Scheduled Job Example

using System;
using System.Collections.Generic;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using EPiServer.PlugIn;
using EPiServer.Scheduler;
using EPiServer.Security;
using EPiServer.Web;

[ScheduledPlugIn(
    DisplayName = "Delete French Language Branch From All Pages",
    Description = "Deletes the fr branch from all pages where fr is not the master language.",
    SortIndex = 100)]
public class DeleteLanguageBranchFromAllPagesJob : ScheduledJobBase
{
    private readonly IContentLoader _contentLoader;
    private readonly IContentRepository _contentRepository;
    private readonly ISiteDefinitionRepository _siteDefinitionRepository;

    private bool _stopRequested;

    private const string LanguageToDelete = "fr";

    public DeleteLanguageBranchFromAllPagesJob(
        IContentLoader contentLoader,
        IContentRepository contentRepository,
        ISiteDefinitionRepository siteDefinitionRepository)
    {
        _contentLoader = contentLoader;
        _contentRepository = contentRepository;
        _siteDefinitionRepository = siteDefinitionRepository;

        IsStoppable = true;
    }

    public override string Execute()
    {
        var scanned = 0;
        var deleted = 0;
        var skippedMasterLanguage = 0;
        var failed = 0;
        var visited = new HashSet<int>();

        foreach (var site in _siteDefinitionRepository.List().Where(x => !ContentReference.IsNullOrEmpty(x.StartPage)))
        {
            var references = new[] { site.StartPage }.Concat(_contentLoader.GetDescendents(site.StartPage));

            foreach (var contentLink in references)
            {
                if (_stopRequested)
                {
                    return $"Stopped. Scanned: {scanned}, Deleted: {deleted}, Skipped master language: {skippedMasterLanguage}, Failed: {failed}";
                }

                if (!visited.Add(contentLink.ID))
                {
                    continue;
                }

                scanned++;

                try
                {
                    var languageBranches = _contentRepository
                        .GetLanguageBranches<IContent>(contentLink)
                        .OfType<PageData>()
                        .ToList();

                    if (!languageBranches.Any())
                    {
                        continue;
                    }

                    var branchToDelete = languageBranches.FirstOrDefault(x =>
                        string.Equals(x.Language.Name, LanguageToDelete, StringComparison.OrdinalIgnoreCase));

                    if (branchToDelete == null)
                    {
                        continue;
                    }

                    if (branchToDelete is ILocalizable localizable &&
                        localizable.MasterLanguage != null  &&
                        string.Equals(localizable.MasterLanguage.Name, LanguageToDelete, StringComparison.OrdinalIgnoreCase))
                    {
                        skippedMasterLanguage++;
                        continue;
                    }

                    _contentRepository.DeleteLanguageBranch(contentLink, LanguageToDelete, AccessLevel.Delete);
                    deleted++;
                }
                catch (Exception ex)
                {
                    failed++;
                    OnStatusChanged($"Failed for content ID {contentLink.ID}: {ex.Message}");
                }
            }
        }

        return $"Completed. Scanned: {scanned}, Deleted: {deleted}, Skipped master language: {skippedMasterLanguage}, Failed: {failed}";
    }

    public override void Stop()
    {
        _stopRequested = true;
    }
}

How It Works

The job goes through each site start page and all descendant pages beneath it. For every page, it checks whether the target language exists.

If the page contains the target branch and that branch is not the master language, the job removes it. If the target language is the master language, the page is skipped.

After the Job

  • Review the skipped pages. These are typically pages where the language you want to remove is the master language.
  • Try removing the language from Manage Website Languages again.
  • If the CMS still reports that the language is used in language settings, clear that language from the affected start pages or content language settings and retry.

Final Note

If you only need to remove language branches from a few pages, use the UI. If you need to clean up hundreds or thousands of pages, a scheduled job like the above is a much more realistic approach.

If you also want to remove the same language from blocks or media, the same idea can be extended beyond PageData to other localizable content types.

September 24, 2014

Thursday, 11 September 2014

How to Get All Page Types in Optimizely CMS

If you want to get all page types in Optimizely CMS, the recommended modern approach is to use IContentTypeRepository.

This is the cleaner replacement for older patterns such as PageTypeRepository or PageType.List().

Modern approach

In newer Optimizely CMS projects, use IContentTypeRepository and filter the result to PageType:

using System.Linq;
using EPiServer.DataAbstraction;

var contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();

var pageTypes = contentTypeRepository
    .List()
    .OfType<PageType>()
    .OrderBy(x => x.DisplayName ?? x.Name)
    .ToList();

This returns the page type definitions configured in the CMS.

Recommended approach in application code

If you are writing new code, constructor injection is better than using ServiceLocator.Current:

using System.Collections.Generic;
using System.Linq;
using EPiServer.DataAbstraction;

public class PageTypeService
{
    private readonly IContentTypeRepository _contentTypeRepository;

    public PageTypeService(IContentTypeRepository contentTypeRepository)
    {
        _contentTypeRepository = contentTypeRepository;
    }

    public IList<PageType> GetAllPageTypes()
    {
        return _contentTypeRepository
            .List()
            .OfType<PageType>()
            .OrderBy(x => x.DisplayName ?? x.Name)
            .ToList();
    }
}

About PageTypeRepository

You may still see older examples using PageTypeRepository:

using EPiServer.DataAbstraction;
using EPiServer.ServiceLocation;

var repository = ServiceLocator.Current.GetInstance<PageTypeRepository>();
var pageTypes = repository.List();

This still appears in older codebases, but the official Optimizely API marks PageTypeRepository as obsolete and recommends using IContentTypeRepository instead.

Legacy fallback

For older EPiServer projects, you may still find this approach:

var pageTypes = EPiServer.DataAbstraction.PageType.List();

This method is obsolete in later versions, but it can still be useful when maintaining legacy solutions.

Final note

Remember that this gives you the list of page type definitions, not the actual pages created from those types. If you need the pages themselves, you will need to query content separately.

September 11, 2014

Get Usage report of Content Types in Optimizely CMS

Large Optimizely or EPiServer projects tend to collect old page types over time. New templates are introduced, editors move on to newer content types, and some older page types quietly stop being used.

If you are cleaning up a long-running CMS solution, one of the first things worth checking is which page types are still in use and which ones have a page count of zero.

The SQL query below gives you a quick usage report for page types. It does not cover block types, but it is a useful first step when auditing legacy CMS projects.

Quick SQL Audit

Run the following query in SQL Server to list page type names, filenames, and the number of pages using each type:

SELECT
    pt.Name,
    pt.Filename,
    COUNT(p.pkID) AS PageCount
FROM tblPageType AS pt
LEFT JOIN tblPage AS p
    ON p.fkPageTypeID = pt.pkID
GROUP BY
    pt.Name,
    pt.Filename
ORDER BY
    PageCount DESC,
    pt.Name;

Why This Is Useful

This report helps you quickly spot:

  • page types that are heavily used
  • page types that are rarely used
  • page types with a PageCount of 0, which may be candidates for cleanup

Before You Run It

Empty the recycle bin first. Deleted content can still affect the result and make unused page types look active when they are not.

Important Note

A page count of zero is a strong signal, but it should not automatically mean “safe to delete”. Before removing a page type from code, double-check whether it is still referenced by old templates, import jobs, migrations, or content type availability rules.

For older self-hosted EPiServer or Optimizely CMS solutions, this is a quick and practical way to start a cleanup exercise without building a custom report first.

September 11, 2014

Tuesday, 9 September 2014

DNN: TreeView File Manager nodes and Module "Action" menu not working

I upgraded a client's DotNetNuke version to DotNetNuke 5.1 to 5.6.3. The DNN Portal which was running DotNetNuke Version 5.6.3, while I was working on it, all of a sudden the filemanager stopped working properly "Spinner in the Root TreeView". Same problem was with the modules "Action" menu. It was a mess to do anything on the website in edit mode.


The problem for DNN file manager treeview looks like this:





The treeview of the folder control seems to be in a endless loop and the treeview does not open the folder structure.

In some cases, an error message displayed, other wise there was no error message.
.
"Runtime error in Microsoft JScript: Sys.ArgumentException: Cannot deserialize empty string. Parameter name: data"


The problem seemed to be in any case the same.

So If you are experiencing this problem check the Compression Setting which you can find in the "Performance Settings" section under the host setting.




If the GZip Compression is selected, you must change the setting to "no compression"

Save the change, and try the file manager, I'm pretty sure, it will work :)

Hope that helps.

As far as I know,  the problem is already exists for a long time in the various different versions of DotNetNuke.

So if you have this problem in an older version, check the setting and look what happens.

September 09, 2014

How to Change a DNN Username Using SQL Server

Sometime users request to change their usernames, the reason could be they don't want to lose their activities in the system. There can be many other reasons but the fact is I have deal with these request time to time.So here is a solutions.

If you are going to change the username from SQL Server, make it repeatable, you might want to wrap the syntax in a transaction. You don't want want the two tables to be out of sync. 

Here is sample series of T-Sql statements. Should be easy to convert to a stored procedure

declare @oldName nvarchar(128)
declare @newName nvarchar(128)
declare @error_var int, @rowcount_var int
declare @newNameCount int

select @oldName = 'someUsername'
select @newName = 'newUsername'


begin transaction

select @newNameCount = count(*)
  from Users
  where Username = @newName
if @newNameCount > 0
begin
  RAISERROR('Username already exists. @newName=%s', 10, 1, @newName)
  ROLLBACK TRANSACTION
  RETURN
end

update Users
set Username = @newName
where Username = @oldName

SELECT @error_var = @@ERROR, @rowcount_var = @@ROWCOUNT
IF @rowcount_var <> 1 OR @error_var <> 0
BEGIN
  RAISERROR('Could not Update User.Username. @oldName=%s', 10, 1, @oldName)
  ROLLBACK TRANSACTION
  RETURN
END


update aspnet_Users
set
  Username = @newName,
  LoweredUserName = LOWER(@newName)
where LoweredUserName = LOWER(@oldName)

SELECT @error_var = @@ERROR, @rowcount_var = @@ROWCOUNT
IF @rowcount_var <> 1 OR @error_var <> 0
BEGIN
  RAISERROR('Could not Update aspnet_Users.Username. @oldName=%s', 10, 1, @oldName)
  ROLLBACK TRANSACTION
  RETURN
END

Commit transaction
go 
September 09, 2014

How to Programmatically Assign a Role to a User in DNN

Assigning role to user programmatically can easily be done using DotNetNuke's RoleController in your code. It means without storing role info in database, and programmatically assign a role rights to the user. You can call AddUserRole function in RoleController to perform this action the below code might help you in achieving this

RoleController objRoles = new RoleController();
RoleInfo objRole = new RoleInfo;

// autoassign user to portal roles

var arrRoles = objRoles.GetPortalRoles(user.PortalID);

foreach (var obJrole in arrRoles) {
if (objRole.AutoAssignment == true) {
objRoles.AddUserRole(user.PortalID, user.UserID, objRole.RoleID, Null.NullDate, Null.NullDate); }
}

objRoles.AddUserRole(user.PortalID, user.UserID, 5, Null.NullDate, Null.NullDate); 
September 09, 2014

How to Programmatically Log In a User in DNN


In DotNetNuke if  you end up in a need to login user programmatically. You can use the following code
var loginStatus = new UserLoginStatus();

var objUser = UserController.ValidateUser(0, "host", "dnnhost", "", "", "0.0.0.0", ref loginStatus);

if (loginStatus != UserLoginStatus.LOGIN_FAILURE || loginStatus != UserLoginStatus.LOGIN_USERNOTAPPROVED)
{
   UserController.UserLogin
   (this.PortalId,
    objUser, PortalSettings.PortalName,
    HttpContext.Current.Request.UserHostAddress, false);
}

September 09, 2014

How to Get SMTP Settings from DNN

In your module, if you need to access to the SMTP settings specified in the HOST settings of the DNN portal, you can use the following function to retrieve it and use it in your code.
You can pass the following parameters in the function to retrieved the required setting value

- SMTPAuthentication
- SMTPEnableSSL
- SMTPPassword
- SMTPServer
- SMTPUsername

var hostSettings = DotNetNuke.Entities.Host.Host.GetHostSettingsDictionary();
string SMTPServer = hostSettings["SMTPServer"];
string SMTPAuthentication = hostSettings["SMTPAuthentication"];
September 09, 2014

How to Remove Skin and Container from an ASCX Control in DNN

I was stuck in an annoying problem while working of one of my modules in DNN. The Ideas was to load a usercontrol while click on button, But instead of clean skinless form, DNN automatically add the container and skin into it. That bugged me a lot. So how can you open a .ascx user control in a popup/ new window in DotNetNuke without a Skin and Container. 


Here is the solution of the problem.

On the button onClick server side action use the following code

Globals.NavigateURL(
 TabId,
 "ControlName", "Param1", ParamValue,
 "SkinSrc=[G]" + Globals.QueryStringEncode( DotNetNuke.UI.Skins.SkinInfo.RootSkin + "/" + 
 Globals.glbHostSkinFolder + "/" + "No Skin" ),
 "ContainerSrc=[G]" + Globals.QueryStringEncode( DotNetNuke.UI.Skins.SkinInfo.RootContainer +
 "/" + Globals.glbHostSkinFolder + "/" + "No Container" )
);
or for simple not showing skin you can use

NavigateURL(
 TabId,
 "ControlName",  
 "Param1", 
 ParamValue,
 SkinSrc=[G]" + Globals.QueryStringEncode( DotNetNuke.UI.Skins.SkinInfo.RootSkin +
 "/" + Globals.glbHostSkinFolder + "/" + "No Skin" )
)


Explanation: [G] is used in Dotnetnuke as a placeholder for current portal location of the specific folder. No Skin is a skin file (No Skin.ascx) in Skin folder in path \portals. There is even a No Container.ascx in container folder if you dont want to use Container.
September 09, 2014

How to Find Duplicate Records in SQL Server

Here's a handy query for finding duplicates in a table. Suppose you want to find all email addresses in a table name users that exist more than once:
SELECT email, 
COUNT(email) AS NumOccurrences
FROM users
GROUP BY email
HAVING ( COUNT(email) > 1 )


You could also use this technique to find rows that occur exactly once:

SELECT email
FROM users
GROUP BY email
HAVING ( COUNT(email) = 1 )
September 09, 2014

SQL Server Security Updates for Supported Versions

Microsoft released a security bulletin covering several issues that could potentially affect SQL Server; these exploits include remote code execution, denial of service, information disclosure and elevation of privilege. You should test these patches on all machines running SQL Server, including those running only client tools (e.g. Management Studio or Management Studio Express). The updates affect the following versions of SQL Server:
  • SQL Server 2005 SP3
  • SQL Server 2005 SP4
  • SQL Server 2008 SP1
  • SQL Server 2008 SP2
  • SQL Server 2008 R2
So, depending on your SQL Server version (run SELECT @@VERSION;), here is what you should do:

If you are running... And your build number is... Your best course of action is probably to...
SQL Server 2005 Less than 9.0.4035
 
Upgrade to Service Pack 3 (9.0.4035) or Service Pack 4 (9.0.5000), then come back for the GDR
Exactly 9.0.4035 (SP3) Install the SP3 GDR (9.0.4060) from KB #2494113
 
Between 9.0.4036 and 9.0.4339 (a) Upgrade to Service Pack 4 (9.0.5000), then come back for the GDR
OR
(b) Install the SP3 QFE (9.0.4340) from KB #2494112
 
Exactly 9.0.5000 (SP4) Install the SP4 GDR (9.0.5057) from KB #2494120
 
Greater than 9.0.5000
 
Install the SP4 QFE (9.0.5292) from KB #2494123
 
SQL Server 2008 Less than 10.0.2531
 
Upgrade to Service Pack 1 (10.0.2531) or Service Pack 2 (10.0.4000), then come back for the GDR
Exactly 10.0.2531 (SP1) Install the SP1 GDR (10.0.2573) from KB #2494096
 
Between 10.0.2532 and 10.0.2840 (a) Upgrade to Service Pack 2 (10.0.4000), then come back for the GDR
OR
(b) Install the SP1 QFE (10.0.2841) from KB #2494100
 
Exactly 10.0.4000 (SP2) Install the SP2 GDR (10.0.4064) from KB #2494089
 
Greater than 10.0.4000 Install the SP2 QFE (10.0.4311) from KB #2494094
 
SQL Server 2008 R2 Exactly 10.50.1600 (RTM) Install the GDR (10.50.1617) from KB #2494088
 
Between 10.50.1601 and 10.50.1789 Install the QFE (10.50.1790) from KB #2494086
 
Greater than 10.50.1790
(e.g. 10.50.2418 or 10.50.2425)
 
Wait for the final release of Service Pack 1
Watch for cumulative update or updates to MS11-049
At this time there is no fix for the CTP of SQL Server 2008 R2 SP1


What is the difference between a GDR and a QFE? 

A GDR (general distribution release) is one that Microsoft support deems is necessary for all systems running SQL Server. A QFE (quick fix engineering) is one that does not affect everyone. Why are there two releases for this important fix? Well, one reason is that after a QFE is installed, it is no longer possible to install a GDR. So, if you have a system that has had previous cumulative updates or QFEs applied, the GDR might not work for you. If you have a system that is exactly at one of the levels described above, then the GDR is probably the better choice, because it will allow you to install either a GDR or a QFE in the future, whereas installing a QFE on such a system kind of paints you into a corner.
There is also a GDR available if you are running Management Studio Express 2005 (but none seem to be listed at this time for the 2008 or 2008 R2 versions):

KB #2546869
September 09, 2014

How to Clean SQL Server Tables and Reset Identity Columns

I had a problem while back to clean up my database and reset identity columns in all tables. But as we all know the chaos of the database, trace relations and delete records from child tables before parent tables because of foreign key constrains. By manually doing it could take so much time even the database is not that huge. Through some research on Google and books I was able to come up with a solution to achieve my target in few steps. The solution was to use built in stored procedure sp_MSforeachtable (which I already discussed in my previous blog post). So here is the following code bellow  for how I re-zeroed my Database:

/*Disable Constraints & Triggers*/
exec sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
exec sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'

 /*Perform delete operation on all table for cleanup*/
exec sp_MSforeachtable 'DELETE ?'

/*Enable Constraints & Triggers again*/
exec sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
exec sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL'

/*Reset Identity on tables with identity column*/
exec sp_MSforeachtable 'IF OBJECTPROPERTY(OBJECT_ID(''?''), ''TableHasIdentity'') = 1 BEGIN DBCC CHECKIDENT (''?'',RESEED,0) END' 
September 09, 2014

How to List SQL Server Tables and Their Row Counts

I was trying to identify which tables were affected by an update though an application (3rd party). There were more than 300+ tables in the Database. I was hoping to avoid checking each of them for changes. To get the list of tables and their rows counts in a database you can use the following SQL query before and after the update.
SELECT
[TableName] = so.name,
[RowCount] = MAX(si.rows)
FROM
sysobjects so,
sysindexes si
WHERE
so.xtype = 'U'
AND
si.id = OBJECT_ID(so.name)
GROUP BY
so.name
ORDER BY
2 DESC

Note:
The sysindexes table is usually a little bit inaccurate, because it is not updated constantly. It will also include the 'dtproperties' table, which is one of those hybrid tables that falls neither under the 'system' nor 'user' category. It does not appear in Enterprise Manager's "Tables" view if you choose to hide system objects, but it shows up above.

In any case, it is generally not recommended to query against the system objects directly, so please only use the above for rough, ad-hoc guesstimates.
September 09, 2014

How to Delete or Truncate All Tables in SQL Server

To delete/drop/truncate all the tables from Database, you can use the following SQL commads to perform the desired function
EXEC sp_MSforeachtable @command1 = "DELETE FROM ?"
EXEC sp_MSforeachtable @command1 = "TRUNCATE TABLE ?"

Explanation

sp_MSforeachtable is a hidden Stored Procedure in sql server, that will execute for all the tables for database (no rollback)
@command1 is variable which will run against each table for connected database.
Whatever you will write in the double quotes, that will be act as a command for each table, where '?' is the name of the table.

Word of caution. Make sure execute these commands on test Database before actually executing in the desired database
September 09, 2014

How to Delete a Team Project from TFS 2005, 2008 and 2010

Deleting a team project from team foundation server is bit tricky. As there is no user interface available to do it in TFS, you have to use command line with a TfsDeleteProject.exe utility that ships with TFS 2005/2008/2010 to active this. Look complicated but very easy to do :)

You can do this from any computer in the network that has a access to team foundation server .

open Visual studio command prompt or windows command processor (cmd.exe)
and cd to the following path

In 32 Bit windows operating system

%program files%\Microsoft Visual Studio 9.0\Common7\IDE (2008/2010)
%program files%\Microsoft Visual Studio 8\Common7\IDE (2005)

In 64 Bit windows operating system

%Program Files (x86)%\Microsoft Visual Studio 9.0\Common7\IDE (2008/2010)
%Program Files (x86)%\Microsoft Visual Studio 8\Common7\IDE (2005)

then execute the following command

tfsdeleteproject /q /force /server: " "

Keep in mind: Your team foundation server User must have an Administrator rights to execute the above command successfully.  
September 09, 2014

Monday, 8 September 2014

How to Convert Culture-Specific Currency Strings to Decimal in C#

You can use the following functions to convert a decimal monetary value according to a specified culture and convert it back to a decimal.

// Convert a decimal monetary value to a culture-specific format,
// for example Swedish Kronor.
public static string FormatMoney(decimal value)
{
    return string.Format(
        System.Globalization.CultureInfo.CreateSpecificCulture("sv-SE"),
        "{0:C}",
        value
    );
}

// Convert a culture-formatted monetary value,
// for example Swedish Kronor, back to decimal.
public static decimal ParseMoney(string money)
{
    return decimal.Parse(
        money,
        System.Globalization.NumberStyles.Currency,
        System.Globalization.CultureInfo.CreateSpecificCulture("sv-SE")
    );
}

ParseMoney will throw an exception if the value is not correctly formatted for the specified culture. Use decimal.TryParse if you want to handle invalid values without throwing an exception.

September 08, 2014

How to Resize an Image While Maintaining Aspect Ratio in C#

This function allows to resize the image. It prevents skewed images and  also vertically long images caused by trying to maintain the aspect ratio on images who's height is larger than their width

public void ResizeImage(string OriginalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider)
{
 System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(OriginalFile);

 // Prevent using images internal thumbnail
 FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
 FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);

 if (OnlyResizeIfWider)
 {
  if (FullsizeImage.Width <= NewWidth)
  {
   NewWidth = FullsizeImage.Width;
  }
 }

 int NewHeight = FullsizeImage.Height * NewWidth / FullsizeImage.Width;
 if (NewHeight > MaxHeight)
 {
  // Resize with height instead
  NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;
  NewHeight = MaxHeight;
 }

 System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);

 // Clear handle to original file so that we can overwrite it if necessary
 FullsizeImage.Dispose();

 // Save resized picture
 NewImage.Save(NewFile);
}

September 08, 2014