All posts by Jonas

Am i the only one who rewrites the same project over and over again? :)

Am I the only one why keeps rewriting the same code, over and over again, while learning new languages or plattforms?

For me, it’s mainly because i hate to run Hello world examples or create meaningless projects. Because of that, my hobby project NautiCalc (you can find i web version right here) has been rewritten in PHP, Objective-C, Java, Javascript  on plattforms like iOS, Android, Web and OS X.

And now, it’s time again! Last week I attended to DevSum 2016, and I got totally hooked by Xamarin! Time to build a cross-plattform version, running on iOS and Android!

After 3-4 days of coding with Xamarin on my Mac, my first reactions are:

  • Xamarin studio is surprisingly  complete, and supports pretty much everything you need!
  • Creating UI in XAML is really nice! I’ve missed it since i dropped Silverlight.
  • Xamarin.Forms is really nice and makes it easy to write cross-plattform apps. The best thing is that it maps to native components for each plattform.
  • The built-in dependency injection adds to that!

NautiCalc in Xamarin

So, my main screen is built up by a TabbedPage built with XAML. In this page all i do is adding my namespace, so i can reference separate pages for each tab, instead of have one huge XAML file.

<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:me="clr-namespace:NautiCalc;assembly=NautiCalc" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="NautiCalc.TabbedMainPage">
	<TabbedPage.Children>
		<me:NavigatePage />
		<me:WeatherPage />
		<me:CalculatePage />
		<me:AboutPage />
	</TabbedPage.Children>
</TabbedPage>

The TabbedPage render native navigation menus for each plattform. IT works with Windows as well, but who cares about Windows phones?

Below you can see the about page on Android and iOS, where the navigation is created by the XAML in my TabbedPage. The Android version lacks graphics at this moment,

AndroidAboutScreen

iOSAboutScreen

The XAML for the about page above is also the same on both plattforms. There’s only one thing thats different on each plattform, and that is the margins:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage Icon="Nauticalc_24.png" Title="AboutPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="NautiCalc.AboutPage">
	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness">
			<OnPlatform.iOS>
				15,120,5,0
			</OnPlatform.iOS>
			<OnPlatform.Android>
				15,100,5,0
			</OnPlatform.Android>
		</OnPlatform>
	</ContentPage.Padding>
	<StackLayout>
		<StackLayout Padding="15,0,15,0">
			<Image Source="Nauticalc_72.png" />
			<Label FontSize="24" Margin="0,25,0,25" FontAttributes="Bold" Text="NautiCalc" HorizontalOptions="Center" VerticalOptions="Center" />
			<Label Font="Small" Text="Helps you navigate at sea. Keeps track at your speed, course and position and helps you calculate how fast you need to go, how far you can reach in a certain time and what distance you cover at a certain speed and time" />
		</StackLayout>
		<StackLayout Padding="15,50,15,0" Orientation="Horizontal">
			<Label Text="Twitter:" VerticalOptions="Center" />
			<Label Text="@jonlin76" TextColor="Blue" x:Name="JonikaTwitter" VerticalOptions="Center" HorizontalOptions="EndAndExpand" />
		</StackLayout>
		<StackLayout Padding="15,0,15,0" Orientation="Horizontal">
			<Label Text="E-mail:" VerticalOptions="Center" />
			<Label Text="jonas@jonika.nu" TextColor="Blue" x:Name="JonikaMail" VerticalOptions="Center" HorizontalOptions="EndAndExpand" />
		</StackLayout>
		<StackLayout Padding="15,0,15,0" Orientation="Horizontal">
			<Label Text="Blogg:" VerticalOptions="Center" />
			<Label Text="http://jonika.nu/JonasBlogg/" x:Name="JonikaBlogg" TextColor="Blue" VerticalOptions="Center" HorizontalOptions="EndAndExpand" />
		</StackLayout>
	</StackLayout>
</ContentPage>

And, off course, there’s a small code behind as well. We need to add a GestureRecognizer to enable click on the hyperlinks:

		public AboutPage ()
		{
			InitializeComponent ();

			TapGestureRecognizer tapRec = new TapGestureRecognizer ();
			tapRec.Tapped += (object sender, EventArgs e) => {
				if (sender.Equals (JonikaTwitter))
					Device.OpenUri (new Uri ("https://twitter.com/jonlin76"));
				if (sender.Equals (JonikaMail))
					Device.OpenUri (new Uri ("mailto:jonas@jonika.nu"));
				if (sender.Equals (JonikaBlogg))
					Device.OpenUri (new Uri ("http://www.jonika.nu/JonasBlogg"));
			};

			JonikaBlogg.GestureRecognizers.Add (tapRec);
			JonikaMail.GestureRecognizers.Add (tapRec);
			JonikaTwitter.GestureRecognizers.Add (tapRec);
		}

My NavigationPage (the main screen in the app) uses Xamarin.Forms.Maps. This package has to be installed from NuGet, and it also requires some initiation and for Android som configuration like API key. When that is done, map component renders Apple Maps on iOS, Google Maps on Android and Bing Maps on Windows. All with the same code!

iOSNavigationScreen

AndroidNavigationPage

Cross-plattform have never been easier, and ism deeply impressed by both Xamarin Studio and the framework!

Time will tell if the app finishes, or maybe i’ll change my mind and rewrites this project before it gets done. Who knows 🙂

How to create Websites (and more) with PowerShell?

If you, like me, are maintaining lots of websites, you probably want to script as much as you can. It’s time consuming to change a setting for 20+ websites, and it’s also a great chance you miss something in the process.

PowerShell is really neat in these cases, and it gets better for every new release as well.

This is a script i use to do the following:

  1. Create a new application pool, with the preferred .NET version, identity and a few other settings
  2. Create a folder to hold the website
  3. Create the website
  4. Configure a scheduled task to rotate and archive log files.

This script is highly customized for my needs and my environment. As you can se there’s a lot of assumptions in the script, that you may want to change, but i may be a nice start for you!

#
# PowerShell Script used to create new websites. Requires Eleveted PowerShell Console!
# This script: 
# 	- Creates a new Applicatiopn Pool with our preferred settings
#	- Creates a new website using that pool
#	- Create a scheduled job to manage logfiles
#
# Make sure to edit these parameters before you run the script:
# - $AppPoolName Sets the name of AppPool and WebSite
# - $AppPoolIdentityName and $AppPoolIdentityPwd to set the identity
# - $SiteDirectory is the base path to the folder holding websites
#

#########################
# Import modules needed #
#########################

Import-Module WebAdministration

#####################
# Usefull Functions #
#####################

#Get the next website-id from IIS. You should override this if you use random ids, or if your sites isn't in order!
Function Get-NextSiteId {
	$MaxId = Get-Website | Measure-Object -Property ID -Maximum
	return $id.Maximum + 1	
}

######################
# Set all parameters #
######################

#AppPool parameters

$AppPoolName 			= "Test.Domain.Com"	#Name of ApplicationPool, Should be the same as the name of the website (Eg. Soot.Msb.Se)!
$AppPoolDotNetVersion 	= "v4.0"						#.NET version for pool
$AppPoolIdentityName 	= "domain\sysAccount"			#Identity to execute pool
$AppPoolIdentityPwd 	= "P@ssw0rd"					#Identity password

#WebSite parameters
$SiteName 				= $AppPoolName					#WebSite name, by default the same as name of pool
$SiteDirectory 			= "C:\WebSite\Sites\"	#Base folder, where the site should be created. Site folder is created automatically
$SiteId					= Get-NextSiteId				#Get next free ID, change this manually if needed, eg. if you are using random id.

#Scheduled task, LogRotate
#Theese values shouldn't be neccesarry to edit. 
#Make sure LogRotate exists under Program Files and that you WebSite-folder point to the correct location.
$TaskName 				= "LogRotate W3SVC$($SiteId)"
$TaskDescription 		= "Archives old logfiles for site $($SiteName)"
$TaskActionCommand		= "C:\Program Files\LogRotate\LogRotator.App.exe"
$TaskArguments			= "C:\WebSite\Logs\W3SVC$($SiteId) C:\WebSite\Logs\Backup W3SVC$($SiteId) *.log 31 true"
$TaskPath				= "LogRotate"

###########################
# Create application pool #
###########################

#Create Pool and store it in $AppPool
New-WebAppPool –Name $AppPoolName
$AppPool = Get-Item IIS:\AppPools\$($AppPoolName) 

#Stop AppPool
$AppPool | Stop-WebAppPool 

#Set additional properties
#Uncomment these three lines to user AppPoolIdentity
$AppPool.processModel.identityType = 3
$AppPool.ProcessModel.Username = $AppPoolIdentityName 
$AppPool.ProcessModel.Password = $AppPoolIdentityPwd

$AppPool.ProcessModel.IdleTimeout = "0"
$AppPool.ManagedRuntimeVersion = $AppPoolDotNetVersion

#Save and start AppPool
$AppPool | Set-Item
$AppPool | Start-WebAppPool

##################
# Create website #
##################

#Create Website Directory
New-Item -ItemType directory -Path "$($SiteDirectory)$($SiteName)"

#Create Website and store it in $WebSite
New-Website -Name $SiteName -PhysicalPath "$($SiteDirectory)$($SiteName)" -ApplicationPool $AppPoolName -Port "170$($SiteId)" -HostHeader "localhost"
$WebSite = Get-Item IIS:\Sites\$($SiteName) 

#Start Website
$WebSite | Start-WebSite

######################################
# Create scheduled job for LogRotate #
# This only works on Win2012         #
######################################

$PsHost = host
$OsVer = [environment]::OSVersion.Version

If(($OsVer.Major -ge 6) -and ($OsVer.Minor -ge 2) -and ($PsHost.Version.Major -ge 4))
{
    Import-Module ScheduledTasks 
	$TaskAction = New-ScheduledTaskAction -Execute $TaskActionCommand -Argument $TaskArguments
	$TaskTrigger = New-ScheduledTaskTrigger -Daily -AT "05:00"
	$Task = New-ScheduledTask -Action $TaskAction -Trigger $TaskTrigger -Description $TaskDescription
	Register-ScheduledTask $TaskName -InputObject $Task -TaskPath $TaskPath
}
Else
{
	"You need a PowerShell 4 and Windows Server 2012 to create Scheduled Task!"
	"You'll need to configure LogRotate manually."
}

 

Resolving links in XhtmlStrings, EPiServer 7.16.1

Did you ever try to store a XhtmlString in a variable and use it in your code? Maybe you noticed that some stuff, like links, isn’t very useful because they are internal.

I noticed this when i upgraded a site from CMS6, and on that site i had a working code that rendered dynamic content so the code i passed to my variable actually was useful, but it broke when i upgraded the site. What i did find than, was that XhtmlStrings are made up of Fragments. And that’s really useful!

One thing you can do with these fragments is resolving internal links, and this is because links are stored as a UrlFragment. A typical XhtmlString could look like:

As you can see, we have regular content, UrlFragments and a ContentFragment. So now, we can write some code to do stuff with different fragements:

StringBuilder sBuilder = new StringBuilder();
foreach (IStringFragment sFragment in Xhtml.Fragments)
{
  if (sFragment is ContentFragment)
  {
  }
  if (sFragment is UrlFragment)
  {
  }
  sBuilder.Append(sFragment.GetViewFormat());
}

Maybe you would like to do something with a block?

if (sFragment is ContentFragment)
{
  ContentFragment cFragment = sFragment as ContentFragment;
  var Content = Repository.Get<ContentData>(cFragment.ContentLink);
}

Or, if you would like to resolve links in your XhtmlString:

if(sFragment is UrlFragment) 
{
  UrlFragment uFragment = sFragment as UrlFragment;
  UrlBuilder uBuilder = new UrlBuilder(uFragment.InternalFormat);
  Global.UrlRewriteProvider.ConvertToExternal(uBuilder, null, Encoding.UTF8);
  sBuilder.Append(uBuilder.Uri);
  continue;
}

The complete code i use to resolve links before passing the content to a javascript:

public static string ParseXhtmlString(XhtmlString Xhtml)
{
    StringBuilder sBuilder = new StringBuilder();
    if (!(Xhtml == null || Xhtml.IsEmpty))
    {
        foreach (IStringFragment sFragment in Xhtml.Fragments)
        {
            UrlFragment uFragment = sFragment as UrlFragment;
            if(uFragment != null) 
            {
                try
                {
                    UrlBuilder uBuilder = new UrlBuilder(uFragment.InternalFormat);
                    Global.UrlRewriteProvider.ConvertToExternal(uBuilder, null, Encoding.UTF8);
                    sBuilder.Append(uBuilder.Uri);
                    continue;
                }
                catch (Exception)
                { }
            }
            sBuilder.Append(sFragment.GetViewFormat());
        }
    }
    return sBuilder.ToString();
}

 

Exact searches with Lucene (and searches with special characters)

There’s off course thousands of ways to solve this issue. I decided to go old-school 🙂

So whats the problem? Well, if you really want to search for an exact value, and perhaps it contains strange characters (like formulas, algorithms), maybe it’s to short to be indexed correctly… and I’m sure there are more reasons.

What can you do? One way to address this is to construct a unique string, store it in the index and search that column instead of the column containing the clear text value.

An example, lets say you have values like A(b)-c in a column, and you really only want to find exact matches. Parentheses and hyphens makes this hard, and if you replace these you risk get other matches as well. Want you can do is pass the value to a base64-encoder, which will give you a unique string without strange characters, in this case you’ll get: QShiKS1j

This has to be done when you create your index. So now you have two columns, side-by-side, one with the actual value and one with encoded value.

So, when a user enters A(b)-c in the search field, all you have to do in base64-encode it before using it in your search query, and of course search in the encoded field rather than the clear text one.

The code to encode and decode strings:

public static string Base64Encode(string text)
{
  return text != null ? Convert.ToBase64String(Encoding.UTF8.GetBytes(text)) : "";
}
public static string Base64Decode(string text)
{
  return text != null ? Encoding.UTF8.GetString(Convert.FromBase64String(text)) : "";
}

The code to create an index

IndexWriter Writer = new IndexWriter(
  FSDirectory.Open(@"C:\temp\index"),
  new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), 
  true, 
  IndexWriter.MaxFieldLength.LIMITED
);

Document doc = new Document();
doc.Add(new Field("value", "A(b)-c", Field.Store.YES, Field.Index.ANALYZED));
doc.Add(new Field("encodedvalue", Base64Encode("A(b)-c"), Field.Store.YES, Field.Index.ANALYZED));

Writer.AddDocument(doc);
Writer.Optimize();
Writer.Dispose();

And here’s one way to search the index

FSDirectory SearchIndex = FSDirectory.Open(@"c:\temp\index");
StandardAnalyzer Analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
            
IndexReader Reader = IndexReader.Open(SearchIndex, true);
IndexSearcher Searcher = new IndexSearcher(Reader);
            
QueryParser Parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "Alias", Analyzer);
string SpecialQueryText = string.Format("encodedvalue:{0}", Base64Encode("A(b)-c"));

Query SpecialQuery = Parser.Parse(SpecialQueryText);
TopDocs Hits = Searcher.Search(SpecialQuery, null, 1000);
Document[] Result = Hits.ScoreDocs.Select(s => Searcher.Doc(s.Doc)).ToArray<Document>();

Result = Hits.ScoreDocs.Where(x => x.Score >= 1.1F).Select(s => Searcher.Doc(s.Doc)).ToArray<Document>();

 

 

EPiServer CMS 6: Exporting lots and lots of files

Exporting lots of files from EPiServer CMS 6 can be a pain. Picking the files, one at a time, can take ages and when your done and press “Export”-button, session has probably times out and you have to do it all over again.

On top of that, if you upload-folder are huge (this one is 21Gb), for some reason the file browser dialog won’t open at all. (I’m not absolutely sure the size is the issue, but i think it is). So what to do?

You could create i job that reads all links from tblPageSoftLink, resolve their names, save the file and so on. That’s a nice solution off course, but i wanted i quick solution. So what to do?

I decided to take trick EPiServer, using FireBug, to work around the file browser dialog. this is what i did:

First, i created a list of the files i needed to export. Using your favorite editor it should be easy to generate a list of options with the file names and paths

<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/annotations.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/ast_alert.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/ast_loader.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/ast_navicons.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/ast_notes.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/ast_remote.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/ast_vidpresenter.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/attach.html">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/blank.html">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/button.gif">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/flashcommand.js">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/mod_colorizer.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/mod_engage.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/mod_exit.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/mod_quiz.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/null.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/PassVar.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/playershell.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/slidegroup.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/stealthray.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/utils.js">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/VideoPlayer.html">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/VideoPlayer.swf">
<option value="/Upload/Forebyggande/Informationssakerhet/Utbildning_kryptering/player/zoom.html">

I then went to the admin-mode, “Export data”, checked the “Export files”-option. Now, instead of pressing “Add files”, start FireBug and use the inspect element-tool and select the listbox:

inspectelement

Right-click the selected HTML-code, and choose edit html:

edithtml

Paste your list between <select> and </select>, when done press the “edit”-button in the top left to close the edit.

pastelist

Using the inspect element-tool again, you can see there’s a lot of files in the listbox. You could off course add the file names again, after the option tag to make them visible in the list.

inspectfiles

Press export and save the file. Locate the file on the disk, rename ExportedFile.episerverdata to ExportedFile.zip and open it. Inside the ZIP, there’s a folder with the same name as your upload, in my case it’s called SiteUpload.

exportedfile

In that folder there’s a folder structure similar to the one on the site. Just navigate through the folders and there’s your files!

files