Improving the MediaElement.js Loading Animation

I’ve been using MediaElement.js recently, to display videos on a client’s site. One thing that bothered me was the loading animation that played. It looked unfinished – showing up in a small dark square – almost like it was supposed to be completely transparent, but wasn’t.  Here’s an image of how it is in the current release:

I looked into it, and the reason for it is because the loading animation is an animated gif. Gif’s don’t support alpha transparency, so the edges don’t blend well with the background video, which is the reason for the transparent box around it.

I updated the css for MediaElement.js to make the dark background square fill the video screen.  I found that helped the loading animation blend in better, and not seem so out of place. Here’s how it looks after my changes:

With MediaElement.js being open source and on GitHub, it made it very easy for me to share my changes. I forked the code, applied my changes and created a pull request. Hopefully these changes will make their way into a MediaElement.js release in the future. In the meantime, you can grab the updated version here:

https://github.com/ccoulson/mediaelement/commit/866b1d8ac725ed0997a12232fb4446041a249415

Easy Handling of Http Range Requests in ASP.NET

I was recently working on adding video playing capabilities to a client’s existing ASP.NET application. They had video files that had been uploaded by users, and they wanted to allow the users to be able to preview the files on the web. I found MediaElement.js to be a excellent solution for this: As long as there were WEBM and H.264 encoded versions of the video to be played, it would handle playing the video on most browsers/devices and would fall back to a Flash video player if html video was not supported.

I did up a sample page, encoded some sample videos and everything worked great. However when I finally implemented it on the ASP.NET application only the Flash fallback player would play the videos. The HTML 5 player would not play the video.  I finally narrowed down the culprit to the way the video files were returned to the browser. Chrome and the iPhone/iPad require Http Range Requests to be supported for HTML5 video support.  Range requests allow the browser to only request a portion of the file from the server. So for videos, the user could drag the seek slider to the middle of the video and the browser would request the video from that point on, instead of downloading the full file from the server.

IIS 7 supports range requests natively, so if you are simply serving the video files directly from IIS you won’t have any problems. In this case, all file requests were being served by an ASPX page – it looked up information in a database as well as did some authorization checking before returning the file.  Unfortunately range requests are not natively supported from aspx responses – they have to be handled manually.

Scott Mitchell wrote a great article and HTTP Handler to handle this exact scenario, and it looks like a great solution. It’s very detailed and looks like it handles pretty much every edge case. Unfortunately it would require a lot of changes to fit into my client’s existing solution (and I didn’t want to rewrite the client’s solution – as it worked well for every other case), so I searched for a simpler solution.

After a lot of searching I finally found the VideoStreamer project at CodePlex by cipto0382. It includes a great little function that handles Http Range Requests.  I was able to include it in my project with very few modifications and it worked great. Chrome, Iphone and the IPad now played the video served by the ASP.Net Application.

I’ve included the function for reference below. It checks if the HTTP Request is a range request and returns a partial file with the necessary headers if it is. If it’s not a range request, it simply returns the complete file:

RangeDownload by cipto0382


private void RangeDownload(string fullpath,HttpContext context)
{
	long size,start,end,length,fp=0;
	using (StreamReader reader = new StreamReader(fullpath))
	{

		size = reader.BaseStream.Length;
		start = 0;
		end = size - 1;
		length = size;
		// Now that we've gotten so far without errors we send the accept range header
		/* At the moment we only support single ranges.
		 * Multiple ranges requires some more work to ensure it works correctly
		 * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
		 *
		 * Multirange support annouces itself with:
		 * header('Accept-Ranges: bytes');
		 *
		 * Multirange content must be sent with multipart/byteranges mediatype,
		 * (mediatype = mimetype)
		 * as well as a boundry header to indicate the various chunks of data.
		 */
		context.Response.AddHeader("Accept-Ranges", "0-" + size);
		// header('Accept-Ranges: bytes');
		// multipart/byteranges
		// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2

		if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
		{
			long anotherStart = start;
			long anotherEnd = end;
			string[] arr_split = context.Request.ServerVariables["HTTP_RANGE"].Split(new char[] { Convert.ToChar("=") });
			string range = arr_split[1];

			// Make sure the client hasn't sent us a multibyte range
			if (range.IndexOf(",") > -1)
			{
				// (?) Shoud this be issued here, or should the first
				// range be used? Or should the header be ignored and
				// we output the whole content?
				context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
				throw new HttpException(416, "Requested Range Not Satisfiable");

			}

			// If the range starts with an '-' we start from the beginning
			// If not, we forward the file pointer
			// And make sure to get the end byte if spesified
			if (range.StartsWith("-"))
			{
				// The n-number of the last bytes is requested
				anotherStart = size - Convert.ToInt64(range.Substring(1));
			}
			else
			{
				arr_split = range.Split(new char[] { Convert.ToChar("-") });
				anotherStart = Convert.ToInt64(arr_split[0]);
				long temp = 0;
				anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : size;
			}
			/* Check the range and make sure it's treated according to the specs.
			 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
			 */
			// End bytes can not be larger than $end.
			anotherEnd = (anotherEnd > end) ? end : anotherEnd;
			// Validate the requested range and return an error if it's not correct.
			if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size)
			{

				context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
				throw new HttpException(416, "Requested Range Not Satisfiable");
			}
			start = anotherStart;
			end = anotherEnd;

			length = end - start + 1; // Calculate new content length
			fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
			context.Response.StatusCode = 206;
		}
	}
	// Notify the client the byte range we'll be outputting
	context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
	context.Response.AddHeader("Content-Length", length.ToString());
	// Start buffered download
	context.Response.WriteFile(fullpath, fp, length);
	context.Response.End();

}

Adding Breadcrumb Navigation to SharePoint 2010 Application Pages

The application pages in my SharePoint 2010 FBA Pack were not properly displaying the breadcrumb navigation. They didn’t include ‘Site Settings’ in the navigation – they went straight from Home to the application page.

FBA User Management Title Before Changes

I used the following blog entry to add the breadcrumb navigation:

http://weblogs.asp.net/jan/archive/2008/04/16/adding-breadcrumb-navigation-to-sharepoint-application-pages-the-easy-way.aspx

Unfortunately after making the changes, I still didn’t see a single change on the page.  I found a few other people on the forums wondering how to do breadcrumbs in SharePoint 2010, but nobody with a solution.  I decided to dig into the out of the box Site Settings application pages and see how they did it, as their breadcrumb navigation was displaying flawlessly, with the Site Settings as part of the navigation:

Site Columns Application Page Title

Looking at the Site Columns application page, mngfield.aspx, I realized the problems.  First, the SharePoint 2010 master page v4.master uses SPSiteMapProvider and SPContentMapProvider for the breadcrumb site map providers. These don’t build the breadcrumb from the layouts.sitemap file that define the breadcrumb in 2007.  The mngfield.aspx application page overrides v4.master and uses SPXmlContentMapProvider for the site map provider, which does read from the layouts.sitemap file. The second thing the out of the box application page does differently is override the PlaceHolderPageTitleInTitleArea content and hard codes the breadcrumb navigation. What I had mistaken for the bread crumb navigation was actually the title area.  The breadcrumb navigation in SharePoint 2010 is accessed with the ‘Navigate Up’ folder icon.

SharePoint 2010 Breadcrumb Control

SharePoint 2010 Title Area

So here are the steps required to get breadcrumb navigation working in SharePoint 2010:

SPFarm.Local.Services.GetValue<SPWebService>().
                    ApplyApplicationContentToLocalServer();
  • Add the following PlaceHolderTitleBreadcrumb section to your application page:
<asp:Content contentplaceholderid="PlaceHolderTitleBreadcrumb" runat="server">
  <SharePoint:UIVersionedContent UIVersion="3" runat="server"><ContentTemplate>
	<asp:SiteMapPath
		SiteMapProvider="SPXmlContentMapProvider"
		id="ContentMap"
		SkipLinkText=""
		NodeStyle-CssClass="ms-sitemapdirectional"
		RootNodeStyle-CssClass="s4-die"
		PathSeparator="&#160;&gt; "
		PathSeparatorStyle-CssClass = "s4-bcsep"
		runat="server" />
  </ContentTemplate></SharePoint:UIVersionedContent>
  <SharePoint:UIVersionedContent UIVersion="4" runat="server"><ContentTemplate>
	<SharePoint:ListSiteMapPath
		runat="server"
		SiteMapProviders="SPSiteMapProvider,SPXmlContentMapProvider"
		RenderCurrentNodeAsLink="false"
		PathSeparator=""
		CssClass="s4-breadcrumb"
		NodeStyle-CssClass="s4-breadcrumbNode"
		CurrentNodeStyle-CssClass="s4-breadcrumbCurrentNode"
		RootNodeStyle-CssClass="s4-breadcrumbRootNode"
		HideInteriorRootNodes="true"
		SkipLinkText="" />
  </ContentTemplate></SharePoint:UIVersionedContent>
</asp:Content>
  • Replace your PlaceHolderPageTitleInTitleArea section with the following:
<asp:Content ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
	<a href="settings.aspx"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,settings_pagetitle%>" EncodeMethod="HtmlEncode"/></a>&#32;<SharePoint:ClusteredDirectionalSeparatorArrow runat="server" />
	My Application Page Title
</asp:Content>

After those changes my application pages display as they should, with the proper breadcrumb navigation:

FBA User Management Title Area After

FBA User Management Breadcrumbs After

Bulk Delete SharePoint Site Users with PowerShell

Below is a PowerShell script for deleting a filtered list of users from a SharePoint site.  Simply copy the script to a .ps1 file, adjust the $SITEURL to the url of the site and adjust the $USERNAMEFILTER to a lowercase string that is contained in all of the usernames you would like to delete.

The script is based on a combination of the scripts from:

http://blogs.msdn.com/b/vijgang/archive/2009/04/26/powershell-script-to-remove-all-users-from-a-sharepoint-group.aspx

and

http://nikspatel.wordpress.com/2010/08/10/delete-orphaned-ad-users-from-the-site-collection/

################################################################################################################

[System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")

###########################
# "Enter the site URL here"
$SITEURL = "http://demo2010a:2114"

# "Enter the username filter (lowercase) here"
$USERNAMEFILTER = "member"

###########################

$site = new-object Microsoft.SharePoint.SPSite ( $SITEURL )
$web = $site.OpenWeb()
"Web is : " + $web.Title

$usersToDelete = @()

# Iterate through the site's users and add usernames to
# an array of users to delete if the name contains
# contains the username filter.
foreach ($user in $web.SiteUsers)
{
	if ($user.LoginName.ToLower().Contains( $USERNAMEFILTER ))
	{
		$usersToDelete += $user.LoginName
	}
}

# Delete each user selected from the SiteUsers array.
# The SiteUsers array can't be iterated through directly
# as it gets changed as users are removed from it.
foreach ($user in $usersToDelete)
{
	"Removing user : " + $user
	$web.SiteUsers.Remove($user);
}

$web.Update();

################################################################################################################

Bulk Delete ASP.NET Membership Users

Below is a SQL script that can be used to delete a list of users from an ASP.NET membership database.  It retrieves a list of users into a temporary table and then deletes the users from all the relevant tables in the database.

SELECT UserID, UserName
into #temp
FROM aspnet_Users
WHERE UserName in ('MEMBER10001','MEMBER10002','MEMBER10003','MEMBER10004','MEMBER10005')

-- Adjust the WHERE Clause to filter the users
-- for example WHERE UserName LIKE 'MEMBER%'
-- would delete all users whose username started with 'MEMBER'

DELETE FROM dbo.aspnet_Membership WHERE UserId IN (Select UserId from #temp)

DELETE FROM dbo.aspnet_UsersInRoles WHERE UserId IN (Select UserId from #temp)

DELETE FROM dbo.aspnet_Profile WHERE UserId IN (Select UserId from #temp)

DELETE FROM dbo.aspnet_PersonalizationPerUser WHERE UserId IN (Select UserId from #temp)

DELETE FROM dbo.aspnet_Users WHERE UserId IN (Select UserId from #temp)

Mixed Anonymous and Secure Content with SharePoint 2010

In this tutorial i’m going to step you through how to set up a SharePoint 2010 publishing portal with both anonymous and secure content.

1. Create a new Web Application in Central Admin. Make sure that you select ‘Yes’ for Allow Anonymous.  All other values can remain at the defaults. For this example I’m going to use FBA Claims based authentication.

2. Using central admin, create a site collection on the web application you just created.  In this example I’m creating a publishing site.

3. Visit the site you just created. You will be asked to authenticate.  This is because even though ‘Allow Anonymous’ was turned on at the web application level, it still has to be specified at the site level. You will be shown the default home page.

4. First we’ll create a new Home page that will be the page anonymous users will see when they first access the site.  From Site Actions, choose New Page and call it “Home”. When the new page appears, just type in a quick welcome message and save it.

5. With a publishing site the content will need to be both published and approved before it will be visible by anonymous users. To publish the page, click ‘Submit’ on the ‘Publish’ tab and follow the wizard.  Once the submission is complete, approve the page by clicking ‘Approve’ on the ‘Publish’ tab and follow the wizard.  Note that to approve the page, you will first have to add your user to the ‘Approvers’ group under ‘People and Groups’ in ‘Site Settings’. The page is now published, and will be visible to anonymous users once we enable anonymous access.

Note that any resources that have been added to the page, such as images from the Images library, will also have to be published and approved before they can be viewed by anonymous users.

6. Under ‘Site Settings’, ‘Welcome Page’ set the welcome page to be the new ‘Home’ page we just created.

Now when we got to our site root page, we’ll be redirected to the ‘Home’ page we just published. If you sign out and visit Pages/Home.aspx, you’ll notice that you’ll be prompted to enter your credentials, so we still need to enable anonymous access to the page.

7. Sign back in to the site.  Go to ‘Site Settings’, ‘Site Permissions’. Click on ‘Anonymous Access’. From the ‘Anonymous users can access: ‘ dialog, choose ‘Lists and Libraries’ and click OK. Alternatively you can click ‘Entire Web Site’, which will make everything available anonymously, however I prefer to define exactly which resources have anonymous access.  By choosing ‘Lists and Libraries’ your entire web site is still secured.  You have to directly configure each List/Document Library to enable anonymous access. If you sign out and visit Pages/Home.aspx, you will notice that you will still be prompted to authenticate to view the page.

8. Now anonymous access needs to be enabled on the ‘Pages’ library. Go to ‘View all site content’, ‘Pages’. Click ‘Library Permissions’ on the ‘Library’ tab. Click ‘Stop Inheriting Permissions’. Click ‘Anonymous Access’. Select ‘View Items’ in the ‘Anonymous Access’ dialog and click OK. Now all of the content in the ‘Pages’ library will be accessible anonymously.  Note that you can ‘Manage Permissions’ and ‘Stop Inheriting Permissions’ for individual items (including folders) in the library, if you want to prevent them from having anonymous access. Unfortunately you cannot enable anonymous access on individual items, only the whole library.

Now if you sign out of the site and visit Pages/Home.aspx, you’ll be able to view it and won’t be prompted to authenticate.

9. There’s another problem you might notice.  Home.aspx is our default page, and allows anonymous access, however if we visit the site root we’re still prompted to authenticate.  This is because we chose the more secure option of allowing anonymous access on ‘Lists and Libraries’ instead of the ‘Entire Web Site’. PowerShell needs to be used to allow anonymous access to the site while keeping the ‘Lists and Libraries’ settings. Run the following PowerShell commands (based on instructions from http://stackoverflow.com/questions/1338809/anonymous-access-to-a-sharepoint-site-root) – substitute your own SPWeb address:


[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$web = Get-SPWeb http://demo2010a:42076
$web.AnonymousState = [Microsoft.SharePoint.SPWeb+WebAnonymousState]::Enabled
$web.AnonymousPermMask64 = "Open, ViewPages"
$web.Update()

Note that if you look at ‘Anonymous Access’ under ‘Site Permissions’, it will say ‘Entire Web Site’ – however you will still need to explicitly specify which lists and libraries have anonymous access, just as if it was set to ‘Lists and Libraries’.

Now if you sign out and visit the root of the site, you should be redirected to Pages/Home.aspx and not asked to authenticate. We finally have controlled anonymous access!

10. The other part of this tutorial is to show you how to have secure content mixed in with your anonymous content.  Now that you can control which content is available anonymously, adding secure content is very straightforward.  I had mentioned earlier that you can secure content within a library with anonymous access by clicking ‘Stop Inheriting Permissions’.  The problem with this is that by default a document will have anonymous access, and you will have to specify exactly which documents are secure.  For that reason it’s better to keep secure pages in their own library with anonymous access turned off.  For this tutorial we’re going to create a child site called ‘User’.  It’s ‘Pages’ library will not allow anonymous access, so all of it’s content will be secured.

Select ‘Site Actions’,’New Site’ and create a new ‘Publishing Site with Workflow’ called ‘User’. Click Create.

11. Edit the default page and add some custom content. Save, Publish and Approve the page.

As the new site’s Pages library is secured by default, nothing additional has to be done to secure it.  Notice that if you sign out and visit the site root, you can still access Pages/Home.aspx (although you won’t see a link to the User site). If you attempt to access ‘User/Pages/default.aspx’, you will be asked to authenticate.

Congratulations! You now have a SharePoint 2010 site with both mixed and secure content.

If you’d like to learn how to access your mixed content over both http and https see: Mixed Http and Https Content with SharePoint 2010

Simplify Your Caching By Using a Generic Method

I needed to add caching to my data access routines to improve the performance of commonly accessed data.  This is pretty straightforward:

  • Check to see if the cache entry already exists
  • If the cache entry does exist, return the cached value
  • If the cached entry doesn’t exist, fetch the data and add it to the cache.

The problem was, I had a lot of data access routines that I wanted to add caching to.  Adding the 4 or 5 lines of code needed to do this to each routine would be tedious, and all of the repetition would be messy.  I knew I had to centralize my caching functionality in one place.  Since my data access routines returned many different types, I decided to use a generic method to perform the caching.  Since the data validation routines essentially had to be wrapped by this function (so that it’s only run if the value doesn’t exist in the cache), I call it using a statement lambda.

Here’s what I came up with:
//I use ASP.Net's cache object.  I get a reference to it with System.Web.HttpRuntime.Cache
//as I won't always be using it in the context of ASP.Net
private static System.Web.Caching.Cache DataCache = System.Web.HttpRuntime.Cache;

public static T CacheFetch<T>(string key, Func<T> fetcher) where T:class
{
	//Don't bother with the caching if the key is null or blank
	if ((key ?? "").Length <= 0)
	{
		return fetcher();
	}

	T result = DataCache[key] as T;

	if (result == null)
	{
		result = fetcher();

		if (result == null)
		{
			DataCache.Remove(key);
		}
		else
		{
			DataCache[key] = result;
		}
	}

	return result;
}

And here’s some examples of how I used it:

Original data access routine:
public static IEnumerable<Trustee> GetTrustees(int infoReturnID)
{
	InfoReturnEntities context = new InfoReturnEntities(new Uri(Common.InfoReturnDataServiceURL));

	IEnumerable<Trustee> trustees = from trustee in context.Trustees
									where trustee.InfoReturnID == infoReturnID
									select trustee;

	return trustees;
}
Updated data access routine:
public static IEnumerable<Trustee> GetTrustees(int infoReturnID)
{
	return Common.CacheFetch<IEnumerable<Trustee>>("Trustees_" + infoReturnID.ToString(), () =>
	{
		InfoReturnEntities context = new InfoReturnEntities(new Uri(Common.InfoReturnDataServiceURL));

		IEnumerable<Trustee> trustees = from trustee in context.Trustees
										where trustee.InfoReturnID == infoReturnID
										select trustee;

		return trustees;
	});
}
And another example:
public static IEnumerable<RevenueRange> GetRevenueRanges()
{
	return Common.CacheFetch<IEnumerable<RevenueRange>>("RevenueRanges", () => {
		InfoReturnEntities context = new InfoReturnEntities(new Uri(Common.InfoReturnDataServiceURL));

		IEnumerable<RevenueRange> revenueRanges = from e in context.RevenueRanges
												  where e.RevenueRangeCD != "?"
												  orderby e.LowerLimit
												  select e;

		return revenueRanges;
	});
}

My method was inspired by this.

Performance Profiling a SharePoint 2010 Project using EQATEC Profiler

I wanted to profile a SharePoint 2010 project i’d been working on, in order to find some of the slower methods so that they could be improved.  I decided to give EQATEC Profiler a try since it had some good recommendations on StackOverflow and was FREE.

EQATEC works by modifying and rebuilding your assemblies.  Since I use Visual Studio 2010’s built in package and deployment features, it needs to be run after the build, but before the project is packaged and deployed to the SharePoint server.

Here’s how I got it working:

  1. Start EQATEC Profiler. Under ‘Application to profile’ select your project’s bin\debug folder. Check-mark all of the assembly’s you would like to profile.
  2. Click ‘Build’.  This will rebuild the project with added profiling bits, as well as add ‘app.eqconfig’ and ‘EQATEC.Profiler.RuntimeFullNet.dll’ to your output folder.
  3.  The EQATEC assembly needs to be added to your project’s package for it to function properly.
    • Open your project in Visual Studio 2010.
    • Open your project’s package and click on the ‘Advanced’ tab.
    • Click ‘Add’ -> ‘Add Existing Assembly…’. Browse and select ‘EQATEC.Profiler.RuntimeFullNet.dll’ from your bin\debug folder.
    • You can leave the default location and selection of ‘Global Assembly Cache’.
    • Click OK.
    • Save the package.



  4. Open the project properties and select the ‘Build Events’ tab.  Add the following command to the Post-Build Commands:
    "C:\Program Files (x86)\EQATEC\EQATECProfiler\EQATECProfilerCmd" -build app.eqconfig

    This will cause your assemblies to be recompiled with EQATEC once the standard build is done.

  5. Deploy your project to SharePoint.
  6. Exercise your project in SharePoint.
  7. In EQATEC, click on the Run tab.  You should see messages that your profiled application has started.
  8. Once SharePoint has “warmed up” and your project is performing normally, click the ‘Reset Counters’ button in EQATEC.  This will reset the profiling so that you can begin collecting data.
  9. Exercise your project in SharePoint. Make sure you exercise all code you’d like profiled.
  10. Back in EQATEC, click the ‘Take Snapshot’ button.  This will create a report of your profiling session.
  11. Under ‘View snapshot reports’ double-click on the report you’d like to view.  This will open the view tab, where you can explore the results of your profiling session and find the slower methods in your project.
  12. Don’t forget when you’re done profiling to remove the EQATEC assembly from your package as well as remove the post-build step.  Leaving them in will decrease your project’s performance.