WIP, psts in place, parsing mostly correclty

This commit is contained in:
2020-09-21 21:56:11 -05:00
parent 8de94aa769
commit d274352b5f
87 changed files with 4820 additions and 11 deletions

View File

@@ -6,5 +6,15 @@
module.exports = {
/* Your site config here */
plugins: [`gatsby-plugin-sass`],
plugins: [
`gatsby-plugin-sass`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
`gatsby-transformer-remark`
],
}

43
gatsby-node.js Normal file
View File

@@ -0,0 +1,43 @@
const path = require(`path`);
const { createFilePath } = require(`gatsby-source-filesystem`);
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
var slug = createFilePath({ node, getNode, basePath: `pages` });
createNodeField({
node,
name: `slug`,
value: slug,
});
}
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`);
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/post.js`),
context: {
slug: node.fields.slug
}
});
});
};

9
gatsby-ssr.js Normal file
View File

@@ -0,0 +1,9 @@
import React from "react";
export const onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script src="https://platform.linkedin.com/badges/js/profile.js" async defer></script>,
<script>var clicky_site_ids = clicky_site_ids || []; clicky_site_ids.push(101254860);</script>,
<script src="//static.getclicky.com/js" async defer></script>
])
}

847
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,11 @@
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
},
"dependencies": {
"disqus-react": "^1.0.10",
"gatsby": "^2.24.61",
"gatsby-plugin-sass": "^2.3.13",
"gatsby-source-filesystem": "^2.3.30",
"gatsby-transformer-remark": "^2.8.35",
"node-sass": "^4.14.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"

22
src/components/footer.js Normal file
View File

@@ -0,0 +1,22 @@
import React from "react";
export default function Footer() {
const date = new Date();
const timestamp = date.toISOString();
const year = date.getYear() + 1900;
return (
<footer class="footer">
<div class="container">
<small>
&copy; <time datetime={timestamp}>{year}</time>.
All rights reserved.
</small>
</div>
<div class="container">
<hr />
<div class="LI-profile-badge" data-version="v1" data-size="medium" data-locale="en_US" data-type="horizontal" data-theme="dark" data-vanity="benjaminstevenramey"><a class="LI-simple-link" href='https://www.linkedin.com/in/benjaminstevenramey?trk=profile-badge'>Benjamin Ramey</a></div>
</div>
</footer>
);
}

View File

@@ -5,17 +5,16 @@ export default function Header() {
<header>
<div class="container">
<div class="d-flex justify-content-between align-item-center">
<a href="site.baseurl/" class="d-flex justify-content-start align-items-center mr-2" title="Home">
<a href="/" class="d-flex justify-content-start align-items-center mr-2" title="Home">
<img src="https://en.gravatar.com/userimage/738990/32d8e61848233a67f5e02dcc43be0ff8.jpeg"
alt="Gravatar" />
<span class="ml-2">site.title</span>
<span class="ml-2">Ben Ramey's Blog</span>
</a>
<div class="d-none d-sm-flex flex-column justify-content-around ml-2">
<small>site.tagline</small>
<small>Scripture, programming problems, solutions and stories.</small>
</div>
</div>
</div>
</header>
);
}

View File

@@ -1,11 +1,19 @@
import React from "react";
import Header from './header';
import Footer from './footer';
import Nav from './nav';
export default function Layout({ children }) {
return (
<body>
<div>
<Header />
<Nav />
<main>
<div className="container">
{children}
</body>
</div>
</main>
<Footer />
</div>
);
}

17
src/components/nav.js Normal file
View File

@@ -0,0 +1,17 @@
import React from "react";
export default function Nav() {
return (
<nav class="main">
<div class="container d-flex justify-content-center">
{/* {% for page in site.pages %}
{% if page.title != null %}
{% if page.layout == "page" %} */}
<a href="page.url | relative_url">page.title</a>
{/* {% endif %}
{% endif %}
{% endfor %} */}
</div>
</nav>
);
}

View File

@@ -1,10 +1,10 @@
import React from "react";
import Layout from "../components/layout"
import Page from "../templates/page";
export default function Home() {
return (
<Layout>
<Page>
<div>hello world!</div>
</Layout>
</Page>
);
}

View File

@@ -0,0 +1,20 @@
---
layout: post
title: "Replacing newline characters in VB.NET strings"
date: 2012-06-12
description: "Replacing newline characters in VB.NET strings"
categories: [programming]
tags: [vb.net]
---
Im not very familiar (which I am actually very happy for!) with VB.NET and so I had a hard time figuring out how to trim newline characters from a string in a Reporting Services report I was working on.
Turns out none of the “regular” methods really work. The Trim function only removes spaces and anything like Replace(“string”, “\n”, “”) didnt work.
You have to use the Chr(13) or Chr(10) functions to actually get the character values for the newline characters. So, my solution looked like this:
{% highlight csharp %}
Replace(Replace(stringVariable, Chr(10), “”), Chr(13), “”)
{% endhighlight %}
Thanks to this forum post for providing the clues:
[http://www.daniweb.com/forums/thread54797.html](http://www.daniweb.com/forums/thread54797.html)

View File

@@ -0,0 +1,18 @@
---
layout: post
title: "Converting a Hexadecimal Color to a System.Drawing.Color Object"
date: 2009-09-24
description: "Converting a Hexadecimal Color to a System.Drawing.Color Object"
categories: [programming]
tags: [.net]
---
I try to avoid setting styles in my code as much as possible. However, I ran across a case using a DevExpress PageControl control where there was no possible way to set a certain border by using CSS. This is because DevExpress wisely (read sarcasm) uses “border-color: #whatever !important” in the elements style attribute. So, there is no way to override that style except by setting it on the control in code (or your aspx page).
The particular border I needed is the ContentStyle.Border border of the ASPxPageControl and I needed to set the BorderColor property which is a System.Drawing.Color object. There isnt any straightforward way to get a Color object from a hexadecimal color using the Color object itself. You have to use the ColorTranslator (also in the System.Drawing namespace) which gives you a FromHtml method and returns the correct Color object.
{% highlight csharp %}
Color hexColor = ColorTranslator.FromHtml(“#666666″);
{% endhighlight %}
Thanks to this guy for the quick answer:
[http://www.akxl.net/labs/articles/converting-a-hexadecimal-color-to-a-system.drawing.color-object/](http://www.akxl.net/labs/articles/converting-a-hexadecimal-color-to-a-system.drawing.color-object/)

View File

@@ -0,0 +1,13 @@
---
layout: post
title: "Vertical text with CSS"
date: 2010-06-29
description: "Vertical text with CSS"
categories: [programming]
tags: [css]
---
I found a nice blog post today describing how to do vertical text with CSS. Instead of reposting his information, Ill just give the link.
[http://scottgale.com/blog/css-vertical-text/2010/03/01/](http://scottgale.com/blog/css-vertical-text/2010/03/01/)
Its a nice technique using various proprietary methods since there is no standard way to get vertical text across the major browsers right now.

View File

@@ -0,0 +1,26 @@
---
layout: post
title: "Visual Studio Post Build Event to Copy DLLs"
date: 2010-06-29
description: "Visual Studio Post Build Event to Copy DLLs"
categories: [programming]
tags: [visual-studio]
---
While doing SharePoint development, we use a lot of pre- and post-build events in Visual Studio projects to do various things like build and deploy SharePoint solutions to our development environments, activate features automatically and other similar things to make the process of building solutions for SharePoint as automated as possible.
One helpful way Ive been able to use post-build events is to copy the compiled DLLs for a given project to the inetpub directory for the SharePoint site I am currently working on. As Im building a new feature or debugging an existing one, I like to build a lot of see my changes as I go. Well, building and deploying the entire SharePoint solution is a rather lengthy process its only really necessary if youre changing ASPX pages or updating a feature XML file or something like that. If all I did was come C# code changes, thats too much to wait through to see my changes. So, what I do is put a small post-build event in my projects that Im working with that copies the DLLs for that project to my SharePoint sites inetpub directory and I skip the whole SharePoint solution build and deployment step.
Adding a post-build event couldnt be much simpler either. In Visual Studio just right-click on the project you want to add the post-build event for in Solution Explorer and select “Properties”. You can either do that or, with the project selected in Solution Explorer, hit Alt-Enter. The project properties window will display. On the left-hand side, click the “Build Events” tab. Youll see two boxes there. One is for pre-build events and one is for post-build events.
{% highlight text %}
In the post-build events textbox add the following:
IF NOT ($(ConfigurationName)) == (Debug) GOTO END
cd $(ProjectDir)
copy /y bin\debug\*.dll C:\inetpub\wwwroot\wss\VirtualDirectories\{YOUR SHAREPOINT SITE DIRECTORY}\bin
copy /y bin\debug\*.pdb C:\inetpub\wwwroot\wss\VirtualDirectories\{YOUR SHAREPOINT SITE DIRECTORY}\bin
:END
{% endhighlight %}
These are just basic Windows batch commands like youd find in any Windows batch file. This is a very basic setup, but it should be a good starting point for you to work from. All it does is check whether you are building with your Debug build configuration, if you are, then it moves to your project directory and copies the DLLs in your bin\debug folder to the bin folder of your SharePoint site.
Easy enough!

View File

@@ -0,0 +1,20 @@
---
layout: post
title: "Determine SharePoint MOSS 2007 Service Pack Version"
date: 2010-10-06
description: "Determine SharePoint MOSS 2007 Service Pack Version"
categories: [programming]
tags: [sharepoint]
---
Determining the service pack that you have installed for a particular
SharePoint 2007 installation turned out to be a surprisingly opaque process.
I found this very clear and helpful blog post dealing with just this
determination:
[http://techpunch.wordpress.com/2008/10/15/sharepoint-2007-moss-how-to-determine-service-pack-version/](http://techpunch.wordpress.com/2008/10/15/sharepoint-2007-moss-how-to-determine-service-pack-version/)
To give a quick overview, the right way to determine the installed service pack is to look up the version of SharePoint that you have installed and match it against a table (available at the link above) of version numbers to service packs.
Determine your SharePoint version by logging into your Central Administration site and going to Operations > Servers In Farm > Database Schema Version. Your SharePoint version will be listed in the table of servers in the farm.
Note that the link above only has version numbers listed through SP1 (the post is from October 2008). Use this link to get a more up-to-date listing of version numbers:
[http://www.sharepointdesignerstepbystep.com/Blog/Articles/How%20To%20find%20the%20SharePoint%20version.aspx](http://www.sharepointdesignerstepbystep.com/Blog/Articles/How%20To%20find%20the%20SharePoint%20version.aspx)

View File

@@ -0,0 +1,29 @@
---
layout: post
title: "SharePoint 2007 to 2010 in-place upgrade serviceHostingEnvironment error"
date: 2010-10-06
description: "SharePoint 2007 to 2010 in-place upgrade serviceHostingEnvironment error"
categories: [programming]
tags: [sharepoint]
---
Im testing an in-place SharePoint 2007 to SharePoint 2010 upgrade today and I came across this odd error during the update process.
I successfully installed SharePoint 2010 and proceeded to run the configuration wizard which updates 2007 to 2010. The configuration process would always fail with an error in the Central Administration web.config file. Viewing the configuration log file (the configuration wizard provided the location of it) turned up the error.
Apparently, at some point in the process the configuration wizard looks for the serviceHostingEnvironment element under the `<system.serviceModel>` section in the Central Administration web.config.
{% highlight xml %}
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
{% endhighlight %}
If it doesnt find it adds it itself, but curiously, it wraps it in an incomplete system.serviceModel tag so it looks like this:
{% highlight xml %}
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
</system.serviceModel>
{% endhighlight %}
This then caused the configuration wizard to bomb out.
The simple solution was to just add the `<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />` element under my existing `<system.serviceModel>` section. The wizard completed just fine after this.

View File

@@ -0,0 +1,15 @@
---
layout: post
title: "Error occurred in deployment step Recycle IIS Application Pool: The local SharePoint server is not available. Check that the server is running and connected to the SharePoint farm."
date: 2010-10-08
description: "Error occurred in deployment step Recycle IIS Application Pool: The local SharePoint server is not available. Check that the server is running and connected to the SharePoint farm."
categories: [programming]
tags: [sharepoint]
---
I got the above error today while trying to deploy a SharePoint 2010 project in Visual Studio 2010. Very frustrating!!
The answer turned out to be simple enough, though no clues are really provided in the error message (typical). I had to add my user account to the db_owner role for the SharePoint_Admin database that the site I was deploying to was using.
Once I did that, the deployment in Visual Studio did just great.
The following post led me to the solution: [http://social.technet.microsoft.com/Forums/en-US/sharepoint2010programming/thread/a97a8413-a0ae-47f2-aab7-fae1cf40595a](http://social.technet.microsoft.com/Forums/en-US/sharepoint2010programming/thread/a97a8413-a0ae-47f2-aab7-fae1cf40595a)

View File

@@ -0,0 +1,13 @@
---
layout: post
title: "Cookie.Expires from ASP.NET Request Object Always DateTime.MinValue"
date: 2010-10-12
description: "Cookie.Expires from ASP.NET Request Object Always DateTime.MinValue"
categories: [programming]
tags: [.net,asp.net]
---
Ive been working on a problem today involving cookies with one of our web applications. The cookie is keeping track of a users authenticated session and its timing people out too soon. So, I was checking the expiration time of the cookie coming in from the browser and it was returning 1/1/0001 12:00AM every time. I thought this might be the source of my problem, but resetting the cookie and the expiration time did not help the next round-trip to the server. The cookie still read the DateTime.MinValue time.
I found this article: [http://www.eggheadcafe.com/tutorials/aspnet/198ce250-59da-4388-89e5-fce33d725aa7/aspnet-cookies-faq.aspx](http://www.eggheadcafe.com/tutorials/aspnet/198ce250-59da-4388-89e5-fce33d725aa7/aspnet-cookies-faq.aspx)
The articles explains why this happens. The summary: the browser maintains the cookie and handles its expiration. Therefore, it does not send this information back to the server when making a request. So, you can never read the expiration time of a cookie on the server from the Request object.

View File

@@ -0,0 +1,17 @@
---
layout: post
title: "Add SharePoint 2010 Central Admin Web Applications Page Ribbon Button"
date: 2010-11-02
description: "Add SharePoint 2010 Central Admin Web Applications Page Ribbon Button"
categories: [programming]
tags: [sharepoint]
---
I finally figured out today how to add a button to the Central Admin sites ribbon for the WebApplicationsList page and have it behave like the other ribbon buttons on this page. The problem wasnt so much adding the buttonthat part was easy. The problem was getting the button to behave like the other buttons. That is, getting it to enable and disable when a web application was selected from the list.
It was pretty easy to find the methods for the new SharePoint 2010 javascript API to see if a list item is selected in a list and then make decisions based on that. The SP.UI.ListOperation.Selection methods are great for that. However, the WebApplicationList (http:///_admin/WebApplicationList.aspx) page list in the Central Administration site is not a list and so these methods dont work.
I had to do some digging in the SharePoint 2010 javacript files to find what I was looking for. I found what I was looking for in the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Admin.debug.js file. This is the debug version of the SP.UI.Admin.js file (so its possible to read it) that gets included on these pages. It includes a set of methods beginning with “SP.UI.Admin.WebApplicationPageComponent” that are used for the WebApplicationsList page.
What you want is the SP.UI.Admin.WebApplicationPageComponent.get_selectedItem method. This method returns an object containing information about the selected web application on the page.
Here is my final CustomAction XML for the button that enables the button when a web application is selected from the list and then opens up a dialog window when the button is clicked, passing the web application ID to the page that opens in the dialog box.

View File

@@ -0,0 +1,13 @@
---
layout: post
title: "Blank SharePoint Site Problem"
date: 2011-12-01
description: "Blank SharePoint Site Problem"
categories: [programming]
tags: [sharepoint]
---
## Problem
A SharePoint site renders completely blank in the browser. It shows up immediately without any “processing” time and when looking at the HTML source for the page, it is completely blankno HTML tags whatsoever.
## Solution
In my case, the web.config file for the SharePoint application had duplicate entries in the system.webServer/handlers and system.webServer/modules elements. Once I removed those, the site rendered as expected.

View File

@@ -0,0 +1,17 @@
---
layout: post
title: "Duplicating SPWebConfigModifications"
date: 2010-12-10
description: "Duplicating SPWebConfigModifications"
categories: [programming]
tags: [sharepoint]
---
## Problem
Despite everything looking OK (no duplicate SPWebConfigModifications for your web application), your SPWebConfigModifications are adding duplicate elements to your web.config file.
## Solution (at least one possible one)
I ran into this problem today and discovered that in the Name property of the SPWebConfigModification I was not wrapping an attribute value in single quotes. Since the actual Value property of the SPWebConfigModification had quotes around the attribute value, whenever the SPWebConfigModification was applied, it could not find the existing element and therefore would add a second, duplicate element.
In other words, this is wrong for the SPWebConfigModification Name: “add[@name=valueOfAttribute]“.
This is correct: “add[@name=valueOfAttribute]“

View File

@@ -0,0 +1,52 @@
---
layout: post
title: "Opening all SharePoint 2010 Documents in a new window"
date: 2011-04-25
description: "Opening all SharePoint 2010 Documents in a new window"
categories: [programming]
tags: [sharepoint,javascript]
---
We wanted an easy solution to open every document in a SharePoint from a document library in a new window. This way, the user stays on the page he is originally on and the document (PDF, Word doc, Excel fileswhatever) will open in a blank window.
I dont know of any way to do this via a setting in SharePoint, but a little JavaScript employing jQuery does the job just perfectly.
{% highlight javascript %}
$(document).ready(
function () {
// has to be on an interval for grouped doc libraries
// where the actual links are loaded only once a group
// is expanded
setInterval(
function () {
$("a[onclick*=return DispEx][target!=_blank]")
.attr("target", "_blank")
.removeAttr("onclick");
// document type icons
$("td.ms-vb-icon>img[onclick]:not([documentUrl])")
.click(function (e) {
window.open($(this).attr("documentUrl"), "_blank");
e.stopPropagation();
e.preventDefault();
return false;
})
.each(function () {
$(this).attr(
"documentUrl",
$.trim(String($(this).attr("onclick"))
.split("=")[1]
.replace(/["'{}]/g, "")
.split(";")[0])
);
this.onclick = null;
});
},
500
);
}
);
{% endhighlight %}
This JavaScript takes into account that some Document Libraries are grouped and so not all the document links (or icon links) will appear on the page load. This is the reason for the half-second interval. At the most half a second after the group is expanded the newly loaded links will be altered to also open in a new window.
Youll notice that there are two things happening on each interval. The first line does any Title links for the document. The second line (and following) handles the document type icons that are also links to the document. Note that if you want only one or the other link to open in a new window (for example, on the document type icon opens in a new window and the Title link opens in the same window) then all you need to do is remove the appropriate line.

View File

@@ -0,0 +1,19 @@
---
layout: post
title: "File Name Sanitizer One-Liner"
date: 2011-08-24
description: "File Name Sanitizer One-Liner"
categories: [programming]
tags: [command-line]
---
I find myself often having to sanitize file names and paths because Im creating files from some kind of user input that Im unsure about. I found this handy way of doing this in one line today:
{% highlight text %}
fileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (name, c) => name.Replace(c, _));
{% endhighlight %}
Note that the same goes for paths. The only thing that changes is the static method you use from Path:
{% highlight text %}
filePath = Path.GetInvalidPathChars().Aggregate(filePath , (name, c) => name.Replace(c, _));
{% endhighlight %}

View File

@@ -0,0 +1,34 @@
---
layout: post
title: "ANSI/UTF-8 Encoding Problem"
date: 2011-11-14
description: "ANSI/UTF-8 Encoding Problem"
categories: [programming]
tags: []
---
## Problem
I ran into a problem in a project I was working on recently. The basics of the project were to take data out of a MySQL database and transform that data into an XML file schema for a
[Day CQ5 CMS](http://day.com/day/en/products.html)
[Java Content Repository (JCR)](http://www.ibm.com/developerworks/java/library/j-jcr/).
I kept running into an issue with certain text fields of user-entered data where I would get all these funky, special characters in the text. I knew it had to be some kind of encoding issue where I wasnt outputting or getting the data with the right character encoding.
I dont know much about character encoding, except that there is a difference between them!
## Solution
After some digging around and playing with Notepad++ (where I could switch the encoding of the text), I found at least a glimmer of hope. When I had the character encoding set to ANSI in Notepad++, pasted in the troublesome text, and then switched the encoding to UTF-8, all of my problems disappeared.
So, eventually, I came up with these few lines of code which convert the encoding of my text and fixed all of my problems. Woohoo!
{% highlight csharp %}
Encoding targetEncoding = Encoding.GetEncoding(1252);
byte[] utf8Bytes = targetEncoding.GetBytes(text);
byte[] ansiBytes = Encoding.Convert(Encoding.UTF8,
targetEncoding,
utf8Bytes);
return targetEncoding.GetString(ansiBytes);
{% endhighlight %}
What this simple code does is first get the Encoding for the ANSI character encoding (1252 is ANSI, apparently). It then gets the bytes of the string with the ANSI encoding, converts them from the ANSI encoding to UTF-8 encoding and then gets the string with the ANSI encoding.
So, quite honestly, Im not 100% sure why this works. The Notepad++ experiment would seem to indicate the opposite: that I had to convert from ANSI to UTF-8. But, that only compounded the problem when I wrote it that way in code. When I reversed the conversion, it worked as I was hoping.

View File

@@ -0,0 +1,21 @@
---
layout: post
title: "jQuery Tools Overlay v1.2.5 Bug"
date: 2011-11-21
description: "jQuery Tools Overlay v1.2.5 Bug"
categories: [programming]
tags: [jquery]
---
## Problem
I noticed a bug in the jQuery Tools library today, specifically in version 1.2.5 and the Overlay plugin. I should say, Im assuming its a bug. This is an existing website I am working on where I am not in a position to upgrade the jQuery Tools library to the latest version (1.2.6 is out, it looks like) and I dont know how much of the problem may be due to how existing code is possibly interfering with the library.
In any case, the bug showed up when I had an existing overlay showing and wanted to show another overlay. By default, the library allows one overlay at a time. This was fine with me as I needed to close the existing overlay before showing the new one. The library handles this automatically. However, when the second overlay was displayed, the mask would not display and the z-index was not set, so it showed up behind other content.
## Solution
While its somewhat of a hack, the best solution I found was to simply remove the div that Overlay creates with id “exposeMask” before I display the second overlay.
{% highlight text %}
$(“#exposeMask”).remove();
{% endhighlight %}
I believe this is what is happening: when the first overlay is closed by the library, it doesnt properly handle the existing exposeMask div and so when the second overlay shows it misunderstands the mask to be displaying properly and doesnt calculate its own display values properly. Therefore, its z-index is wrong (or non-existing) and the mask remains hidden (because the library hid it when it closed the first overlay).

View File

@@ -0,0 +1,34 @@
---
layout: post
title: "Formatting multiple XML files at a time"
date: 2011-12-14
description: "Formatting multiple XML files at a time"
categories: [programming]
tags: [xml]
---
I recently had a situation where I needed to compare many XML files generated by a program of one version to the same set of XML files produced by a previous version of the same program. Unfortunately, the sets of XML files were formatted differently and so doing a file comparison with Beyond Compare (a GREAT file comparison tool, by the way) was going to be useless.
So, I started looking for a way to quickly format all the files in each set the same way with one program. I looked into using Notepad++ which has a great XML Tools plugin (look for it under Plugins > Plugin Manager). I tried combining the plugins formatting commands with a macro that would format the XML file, save it and close it. So, I could easily open the few hundred files I had to format in Notepad++ (one set at a time), then run the macro multiple times (Macro > Run a Macro Multiple Times…). This would run through each file until all were formatted and closed. However, after working with it for a while, I couldnt get the Notepad++ macro system to actual perform the XML Tools plugin format command. The macro would successfully run, saving and closing the file. But, when I checked the files they had not been formatted. I worked with it for a while, but could not figure out what the matter was.
I knew the real solution had to be some type of command-line utility and a batch file. So, I started looking into that. The solution I ended up with was just that.
First of all, I found HTML Tidy which I could run from the Windows command line to format a file. Using a configuration file for the tidy.exe (placed in the same directory as tidy.exe and named tidcfg.inialthough neither matters, see below) that looked like this:
{% highlight text %}
indent:yes
indent-attributes:yes
{% endhighlight %}
I got the formatting I wanted.
Now, all I had to do was brush up on my Windows batch command skills to run tidy.exe on multiple files. Easy enough! This is what my batch file looked like:
{% highlight text %}
for /d %%X in (C:\<path_to_parent_directory>\*) do (c:\<path_to_tidy.exe>\tidy.exe -m -xml -config c:\<path_to_tidy.ini_file>\tidycfg.ini %%X\<xml_file_name>.xml)
{% endhighlight %}
I had a folder structure where there were hundreds of directories inside this one parent directory. Each of the child directories had a single XML file in it. Therefore, I needed the C:\<path_to_parent_directory>\* wildcard.
So, what this batch file does is simply look at each child directory (with the /d switch) in my parent directory. In each directory it runs (do) the tidy.exe program, tells it to modify the input file itself (-m) instead of saving the formatted XML to another file, tells it that the input file is valid XML (-xml) and then tells it where the tidycfg.ini file is (-config). Finally, it tells tidy.exe to take the current directory (%%X) and use the <xml_file_name>.xml file as the input file to format.
This little set up worked very well and quickly formatted all of my files in the same way so that I could successfully compare them with Beyond Compare.

View File

@@ -0,0 +1,56 @@
---
layout: post
title: "How to Create a Brightcove Player Logo Overlay"
date: 2012-02-03
description: "How to Create a Brightcove Player Logo Overlay"
categories: [programming]
tags: [brightcove]
---
As simple as it ended up being, it took me quite a while to figure out how to correctly add a simple overlaying graphic to a Brightcove video player yesterday. It involves a couple of steps which I did not find clearly laid out anywhere I looked. So, I thought a nice little step-by-step blog post would be helpful to anyone else looking for the same thing.
First, Brightcove has a nice and easy method (I say that because it looks easy…I didnt try this method) of adding logo overlays to an individual video. You just:
1. Log into your video cloud Brightcove Studio
1. Click on the Media icon in the top navigation bar
1. Find and select your video (by clicking on it)
1. Click on the Edit button at the bottom of the list of videos
1. Follow the process outlined on the Logo Overlay tab of the Edit popup
This isnt what I wanted though. I needed these videos to appear logo-free in certain instances. So, the best solution was to use one player in the instance where I didnt need a logo and then use a different player that included the logo overlay for my other scenario. That required add an overlay to a player, not a video.
So, on to the solution!
## Step One: Create a new player template
This part is easy. Actually, all the parts are easy. You just need to know what to do.
1. Under the Publishing tab in your Brightcove Studio, click on All Templates in the left sidebar.
1. Select one of the templates that Brightcove gives you and duplicate it (use the Duplicate button at the bottom of the list of templates)
1. Select your new template and click on the Edit button
1. In the Layout field (the XML that describes the player), add an Image element inside the Layout element. See the BEML reference documentationfor details. Here is what my Image element looks like:
{% highlight xml %}
<Image id="logoOverlay"
width="50"
height="50"
scaleMode="exactFit"
visible="{!videoPlayer.menu.open}"/>
{% endhighlight %}
All of the attributes are optional except the id attribute. It doesnt have to be named “logoOverlay”, but you have to have something there. Notice the value for my “visible” attribute. This ensures that my logo disappears when the video is done and doesnt awkwardly overlay any of the video menu UI elements. Remove it if you want your logo to display the whole time. Also note the “x” and “y” values. Change these to your liking. You might have to wait to finish Step Two below when youve added a logo and then you can test a video with your player to see where the logo fits best. From there, you can keep adjusting your logo until youre satisfied.
1. Now click on All Players in the left sidebar and then click on the New Player button at the bottom
1. Create a new player with whatever name is appropriate from the template you just created
Thats it for the template! Youre done with Step One.
## Step Two: Add Your Logo
I figured out step one all on my own pretty quickly. I figured out step two all on my own too, but it look a lot longer!
1. Under the Publishing tab (youre probably already there), click on All Players in the left sidebar
1. Select the player you just created in Step One and click on the Styles button at the bottom of the page.
1. If you gave your Image element an id (you should have!), then you should see an item underneath Editable Areas in the right sidebar with the same name as your Image id. Click on it.
1. Just below (also in the right sidebar), you should now see an Image Selection tab in the Edit pane
1. Click on Upload and upload your logo (or whatever image).
1. Add a URL that the image links to and a tooltip to show when you hover over the image, if you want
1. Click on Save & Close
Youre done! Now, any video you display with that player will have a logo overlay.

View File

@@ -0,0 +1,21 @@
---
layout: post
title: "Install SharePoint on Windows 7"
date: 2012-02-03
description: "Install SharePoint on Windows 7"
categories: [programming]
tags: [sharepoint]
---
If you try to do a SharePoint 2010 installation on your client machine (that is, not a Windows Server OS) then youll get an error explaining that SharePoint 2010 cant be installed on your operating system.
Never fear, CodeProject is here. Check out this helpful post to get it installed (it couldnt be easier):
[CodeProject article](http://www.codeproject.com/Articles/44210/Installing-SharePoint-Server-2010-on-Windows-7-x64)
Even though the post is from 2009, it still works great.
For more detailed steps (including the ones outlined in the link above) following these instructions:
[Microsofts detailed steps](http://msdn.microsoft.com/en-us/library/ie/ee554869.aspx)
They will help you get the prerequisites installed as well.

View File

@@ -0,0 +1,19 @@
---
layout: post
title: "Ektron License Key Update"
date: 2012-02-27
description: "Ektron License Key Update"
categories: [programming]
tags: [ektron]
---
## Problem
We use a temporary license key with our Ektron development website here at work. Because its temporary, we have to update it every-so-often. That happened today.
I got the new key and copy-pasted it into the right License Key field under Configuration / Settings. I clicked on “Save” and thought I was done. When I tried to log back in with my regular account (the way you are notified of the license expiration is a license violation error when you try log in) and it still gave me the license violation error. So, I logged back in with the built-in account (which lets you log in with limited features so that you can update the license key) and found out that I had copy-pasted the license key wrong. It was missing part of the domain name.
Naturally, I tried simply updating the license key domain name, but Ektron threw a Javascript error which prevented me from saving the page saying that I had updated the license key in a way that would make it invalid. What?! I tried re-pasting the key in, thinking I had it wrong still. I didnt work.
## Solution
Turns out this has to be an Ektron bug. I ended up trying something random: I changed the last number of the license key to something arbitrary (along with making my domain name change) and it updated fine. Strange, I know…but this IS Ektron.
Of course, that is an invalid key, so once the page saved, I changed that last digit back to the original digit from the valid license key and it let me save it and I could log in again.

View File

@@ -0,0 +1,168 @@
---
layout: post
title: "Grouping into Rows with XSLT"
date: 2012-03-05
description: "Grouping into Rows with XSLT"
categories: [programming]
tags: [xslt]
---
Lets say you have a simple XML document that looks something like this:
{% highlight xml %}
<root>
<Pod id="1"></Pod>
<Pod id="2"></Pod>
<Pod id="3"></Pod>
<Pod id="4"></Pod>
</root>
{% endhighlight %}
The content or meaning of the pods is irrelevant. The idea is that you have a “list” of elements (pods in this case) in your XML. How, then, do you use XSLT to group these pods into rows of two, three or whatever number of pods across? Something like this:
{% highlight xml %}
<div>
<div class="row">
<div class="pod"> pod 1 </div>
<div class="pod"> pod 2 </div>
<div class="pod"> pod 3 </div>
</div>
<div class="row">
<div class="pod"> pod 4 </div>
</div>
</div>
{% endhighlight %}
This example would required three pods per row. The example XSLT below will work for any number of pods per row with a simple edit (explained below).
My first thought was from a very normal programmers perspective. Id use some kind of looping (with xsl:for-each) and just start a new row for each x number of pods. So, I did it that way and it worked. It was pretty ugly XSLT, but I didnt really think there was a better way to do it. But then a colleague looked over my shoulder and chuckled. “I did it that way at first too”, he said, “but thats not the XSLT way to do it.” He gave me a little bit of a clue of how it really should be done then he left, leaving me determined to find the “XSLT way” of doing it!
Turns out, it really isnt difficult. You just have to think a little differently and start using xsl:template, xsl:call-template and xsl:apply-templates effectively along with their select and match attributes. So, take a look at this XSLT file:
{% highlight xml %}
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method="html"
version="1.0"
omit-xml-declaration="yes"
indent="yes"
encoding="utf-8" />
<xsl:template match="/root">
<xsl:apply-templates select="Pod[position() mod 3 = 1]" />
</xsl:template>
<xsl:template name="PodRow"
match="Pod[position() mod 3 = 1]">
<div class="row">
<xsl:choose>
<xsl:call-template name="PodWrapper"/>
<xsl:apply-templates
select="following-sibling::Pod[position() &lt; 3]"/>
</xsl:choose>
</div>
</xsl:template>
<xsl:template name="PodWrapper"
match="Pod[position() mod 3 &gt; 1]">
<div class="pod">
<xsl:call-template name="PodContent"/>
</div>
</xsl:template>
<xsl:template name="PodContent">
<! whatever content your "pods" contain can be marked up here >
</xsl:template>
</xsl:stylesheet>
{% endhighlight %}
Heres whats going on: first, we call xsl:apply-templates on the first and then every third Pod using “Pod[position() mod 3 = 1]”. This will select the Pod at the start of every row: Pods 1 and 4. Notice that XSLT indexing starts at 1 and NOT 0. The PodRow template will match these selected Pods.
Inside the PodRow template we have the HTML for the row. Inside the row HTML, we explicitly call the PodWrapper template for the current Pod. We then call xsl:apply-templates on the following siblings of the current Pod, but only on the next two of them. This grabs the remaining Pods in the row. This xsl:apply-templates select will match the PodWrapper template which creates the HTML for a single Pod.
Easy as pie! To change the number of pods in a row, update all of the “position() mod 3″s by replacing 3 with however many pods you want in a row. Then also make sure to update the xsl:apply-templates select attribute inside PodRow to be “position() < n-1″ where n is the number of Pods in a row.
## Identifying the last cell of every row
I needed another feature from my XSLT though. I needed every last pod in each row to have a special CSS class. This meant determining which pod was the last one in each row whether the row was full or not. This made the XSLT significantly more complicated, but not overwhelmingly so.
I needed my HTML to look like this (notice the additional “last” class):
{% highlight xml %}
<div>
<div class="row">
<div class="pod"> pod 1 </div>
<div class="pod"> pod 2 </div>
<div class="pod last"> pod 3 </div>
</div>
<div class="row">
<div class="pod last"> pod 4 </div>
</div>
</div>
{% endhighlight %}
So, this is what I had to do:
{% highlight xml %}
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method="html"
version="1.0"
omit-xml-declaration="yes"
indent="yes"
encoding="utf-8" />
<xsl:template match="/root">
<xsl:apply-templates select="Pod[position() mod 3 = 1]" />
</xsl:template>
<xsl:template name="PodRow"
match="Pod[position() mod 3 = 1]"
priority="2">
<div class="row">
<xsl:choose>
<xsl:when test="count(following-sibling::Pod) = 0">
<xsl:call-template name="PodWrapperLast"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="PodWrapper"/>
<xsl:apply-templates
select="following-sibling::Pod[position() &lt; 3]"/>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:template>
<xsl:template name="PodWrapper"
match="Pod[position() mod 3 &gt; 1]"
priority="0">
<div class="pod">
<xsl:call-template name="PodContent"/>
</div>
</xsl:template>
<xsl:template name="PodWrapperLast"
priority="1"
match="Pod[position() mod 3 = 0 or position() = last()]">
<div class="pod last">
<xsl:call-template name="PodContent"/>
</div>
</xsl:template>
<xsl:template name="PodContent">
<! whatever content your "pods" contain can be marked up here >
</xsl:template>
</xsl:stylesheet>
{% endhighlight %}
This version of the XSLT functions exactly the same as the previous one. The only additions are for identifying the last pod in each row for all cases. So, here is what has changed.
The xsl:apply-templates call inside of PodRow now matches one of two templates: PodWrapper or PodWrapperLast. You guessed it, PodWrapperLast just has an extra “last” class on the Pod div and a fancy match attribute. “Pod[position() mod 3 = 0 or position() = last()]” will match Pod numbers 3, 6, etc (in other words, the last Pod of every row) or the last Pod in the XML (no matter what number it is).
Notice the new xsl:choose inside of our PodRow template. This handles the case where the last Pod in the XML is also the first Pod in a row.
Notice also the “priority” attributes on the templates now. This prevents the PodWrapperLast template from matching the first Pod in a row if that first Pod is also the last Pod in the XML.

View File

@@ -0,0 +1,10 @@
---
layout: post
title: "Easy Way to Make Huge Maps with Google Maps"
date: 2012-03-07
description: "Easy Way to Make Huge Maps with Google Maps"
categories: [programming]
tags: [google-maps]
---
[Easy way to make huge maps with Google Maps](http://www.metacafe.com/watch/1073912/google_maps_hack_how_to_save_large_maps/)

View File

@@ -0,0 +1,69 @@
---
layout: post
title: "Change the color of an icon with Gimp"
date: 2012-03-15
description: "Change the color of an icon with Gimp"
categories: [programming]
tags: [gimp]
---
Fairly often, I run across the perfect icon that I need for a website, but its just the wrong color. I want to keep the nice blending to the background of the icon around the edges, the shape and everything that makes the icon look spiffy. I just want the main color to be something else.
It took me a long time to figure out a good method (Im no graphic designer!), but I eventually came a cross a really easy method that works well using trusty Gimp. Ill walk you through it.
## 1. Find an Icon
The first step, of course, is to find an icon. There are billions of free ones out there on the web. This is mine.
![Blue phone icon](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/icon-phone-blue.png)
I love it. Its just what I needbut what a terrible color! I need something else. Its a PNG and when I open it in Gimp, heres what it looks like.
![Original in Gimp](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/original_in_gimp-300x236.png)
You can see the edges have been blended into a white background which would work great if I only ever put it on a white background. Well, I just might be doing that, but Im too much of a perfectionist to use it like that. So, lets get to work.
## 2. Create a color layer
Add a new layer to the image by selecting Layer > New Layer. Call it “color”. Make sure its on top of the “Background” layer.
![Adding a color layer](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/color_layer-149x300.png)
Fill this new layer with the color you want to change the icon to. Im using this lovely puke color (not my choice!): #afbd0a.
Now, hide this new layer so we can work on the “Background” layer by clicking on the eye button just to the left of the layer thumbnail.
## 3. Manipulate the icon colors
Select the “Background” layer. Desaturate the image by selecting Colors > Desaturate. I choose “Lightness” from the dialog box that pops up.
Now, invert the image by selecting Colors > Invert. Next, create another layer called “mask” and fill it with black. Move this layer below the “Background” layer. Your layers should look like the image below.
![Creating the mask layer](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/mask_layer-209x300.png)
Right-click on the “Background” layer and select “Merge down”. Now you should have something similar to the image below.
![After merging down](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/after_merge_down-300x156.png)
## 4. Adjust the curves
Now, with the “mask” layer selected, select Colors > Curves. In the dialog, drag the very middle of the line straight up to the top-center of the graph. Gimp should look like the screenshot below. Click “OK”.
![Adjusting the curves](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/curves-300x189.png)
## 5. Create a layer mask on the “color” layer
Now, with the “mask” layer selected, copy the entire layer by selecting Select > All. Then select Edit > Copy.
Next, right-click on the “color” layer and select “Add layer mask”. Choose “Black (full transparency)” and make sure “Invert mask” is checked.
Paste what youve copied to the layer mask by selecting Edit > Paste. Youll notice a new layer called “Floating selection (Pasted layer)”. Right-click on this new layer and select “Anchor layer”. The pasted layer will now be merged with your anchor layer.
## 6. Youre done!
Now hide your “mask” layer and show your “color” layer. Perfect!
![Completed image](/assets/images/2012-03-15-change-the-color-of-an-icon-with-gimp/done-300x163.png)
You now have a new icon with a completely different color, but with the nice blending at the edges and perfect shape of the original icon. Dont believe me? Well, test it! Select your “mask” layer and delete everything on it. Fill it with any background color you want and see how it looks. Make sure to show your “mask” layer again by click on the eye button.
Hope this was helpful! You can apply your layer mask on the “color” layer if you want. But, I like to keep it as it is and save the file as an xcf for later use. This way, if you ever need the icon in another color, all you have to do is change the fill color of the “color” layer and youre done.

View File

@@ -0,0 +1,22 @@
---
layout: post
title: "Limit Selected Items on Sitecore Multilist Field"
date: 2012-06-12
description: "Limit Selected Items on Sitecore Multilist Field"
categories: [programming]
tags: [sitecore]
---
## Problem
You want to limit the number of items a user can choose for a multilist field in a data template in sitecore.
## Solution
The solution is easy enough. Just add some validation to the field that will “count” the number of GUIDs in the raw value of the MultiList field.
1. Open up the template in the tree navigation and click on the MultiList field you want to limit
![sitecore_template_property_nav](/assets/images/2012-06-12-limit-selected-items-on-sitecore-multilist-field/sitecore_template_property_nav.png)
2. Scroll down to the Validation field and enter this: ^(\{[^}]+\}\|?){0,5}$. This regular expression simply makes sure you have between 0 and 5 GUIDs in the value of the MultiList, effectively limiting the number of items you select in the list. The {0,5} is the range. So, change the 0 to limit the lower bound and change the 5 to limit the upper bound.
![sitecore_template_validation_field](/assets/images/2012-06-12-limit-selected-items-on-sitecore-multilist-field/sitecore_template_validation_field.png)
3. Enter an appropriate message that explains why the validation failed in the ValidationText field.
![sitecore_template_validationtext_field](/assets/images/2012-06-12-limit-selected-items-on-sitecore-multilist-field/sitecore_template_validationtext_field.png)

View File

@@ -0,0 +1,43 @@
---
layout: post
title: "Grace & Me"
date: 2012-07-19
description: "Grace & Me"
categories: [personal]
tags: []
---
Our story really begins somewhere around the beginning of 2007. Thats when Graces brother Nathan came down to Kansas City for a couple of Bible conferences that I (Ben) also happened to go to. I barely remember meeting Nate, but he must have made an impression because we started to become good friends soon afterward. On a whim one time as Nate and I were chatting on Facebook, he invited me up to a regular mens Bible study in Colfax, WI led by Warren Henderson. Thats eight hours away from Kansas City and so Nate never thought Id actually say yes. But, what Nate did not know was this was a tremendous answer to prayer for me. Id been praying that the Lord would bring some good, solid young men who were my age into my life. This seemed like the perfect place to see that prayer answered. Little did I know that it would some day lead to me marrying Nates sister!
Grace and I dont really know for sure when we first met. We have our first, real evidence of being in the same place at the same time at the 2008 Vessels of Honor conference in Parkville, MO. But, it was probably a little before that, on a Sunday morning in Chippewa Falls, WI after one of those mens Bible studies at the River Valley Christian Fellowship where Nathan, Grace and their family went to church.
Its pretty safe to say that neither Grace nor I really took too much notice of each other back then! I was just some friend of Nathans, driving up ridiculous distances for a one-day Bible study, and she was just Nathans younger sister.
As time went on, Nathan got me involved in Story Book Lodge Bible Camp in Gilbert, MN. Thats even further northalmost as far north as you can get in Minnesota. Counseling at camp in the summer and attending Winter Retreat up at camp became a yearly event. Grace and I slowly got to know each other better as we attended the same camps and conferences over the next four years.
Still, while we gradually became friends, as late as June of 2011 we would never have thought anything beyond friendship was in our future.
As the summer of 2011 rolled along, Grace and I were both having some gracious and immensely valuable lessons from the Lord sink into our minds and hearts. Through completely separate, but similar situations we were both learning what trusting in the true goodness of our Lord meant. It was clear to both of us that we sure wanted to get married. But, who would it be to? When would it be? Would it ever happen? We had to trust the Lord that if He is good (and He is), then being single our whole lives would be the very best life we could imagine. If, on the other hand, being married some day was what the Lord had in store for us, the timing He had planned would be the only right timing there was.
Then one day in the middle of that summer, Grace came to my mind. At the same time, I was nowhere near Graces mind! I was planning on counseling at Story Book Lodges 2nd Teen camp week and hoping Grace would be there. I was hoping I could just get to know her better. I prayed a lot about it. I asked the Lord that He would make things clear if I should take the next step after this week at camp. Amazingly, others were secretly praying at this same time specifically for me and Grace tooI just didnt know about it!
Meanwhile, Grace wasnt even sure if she would make it to camp that year. It wasnt clear if she could get off of work in time. But, at the last minute, she found out it was going to work and she was able to counsel that same week.
I spent the week concentrating on the spiritual needs of my campers, but also taking what opportunities presented themselves (or that I could generate!) to be around Grace and talk with her. She, by her own admission, was completely oblivious to this while I was taking each smile or glance as a sure sign that she felt the same way about me that I was beginning to feel about her.
The week was full of great happenings: exciting conversations with Grace, forceful proddings from her brother-in-law Cory, knowing looks from her sister Beth and finally suspicion on Graces part too. Then, the week came to a close. Camp is often a perception altering vortex of emotion. So, I wanted to be sure what I was thinking and feeling wasnt a product of the strange air at Story Book (they dont call it that for nothing, you know).
So, I waited a week after camp to just think and pray. Meanwhile, Grace was wondering if her own suspicions were part of the Story Book vortex. She was praying for clear thinking about it too.
The following Sunday though, I got up the courage to call her Dad and ask his permission to start pursuing Grace. I was nervous, but he gladly said yes and I called Grace a few minutes later. Thankfully, she said yes too!
What followed were some of the happiest months of our lives so far. We traveled to Eau Claire or Kansas City as much as we could, trying to spend as much time together as possible. It seemed like things just went from good to better. We agreed on just about everything we could think to talk about and slowly but surely we fell rather deeply in love with each other.
As February of 2012 rolled around, I knew I wanted to marry this girl. I bought a ring and made my plan to visit the Hansons over a week from the 17th to the 26th while the newly engaged David and Rachel (now) Hanson were visiting from California. A fishing trip was planned for that first Saturday which in Wisconsin means youre getting up at 4am. But, I couldnt wait any longer. I asked Joel (Graces Dad) if I could talk to him that morning before we left for fishing and he seemed to know what might be coming. So, we both got up around 3:30 and I asked him if I could marry his daughter. Yes, was the answer! Now I had a little more planning to do.
I found a fire tower not too far away where Grace and I could climb up and watch the sunrise together. I imagined a beautiful morning, no clouds and a little nice, wooden room on top of the fire tower where I could propose and we could eat a little breakfast together. What happened was slightly less perfect than I had hoped! It was a freezing cold morning with a frigid wind whipping through the broken windows of the years-unused fire tower. We even had a tired onlookera racoon making its bed in the ceiling! But, the morning really was crystal clear and the sunrise was beautiful. We read some Psalms together and I told Grace for the first time that I loved her. I asked her to marry me and she quietly said yes.
And so, the wedding planning began and here we now are, less than two months from the day! What we thought were wonderful days while we were dating paled in comparison to the joy of being engaged, looking forward every moment to the day of our wedding and beyond, all the while getting to know each other in a deeper and closer way.
Grace and I are so thankful for the goodness of the Lord. That very goodness that He so graciously and patiently taught both of us has now been poured out to us in ways we hardly know how to express. He has given us salvation through His Son, Jesus Christ. He has given us wonderful, strong families who love us and care for us. He has given us so many good, encouraging friendships. He has given us to each other. What a wonderful God we serve!
Grace and I can think of no better way to return our thanks to the Lord than to serve Him with whole hearts for the rest of our lives. Whatever the Lord has for us, may His glory be shown and His honor upheld through our lives together! That is our desire for our marriage.

View File

@@ -0,0 +1,25 @@
---
layout: post
title: "Nested User Controls Not Rendering"
date: 2012-07-25
description: "Nested User Controls Not Rendering"
categories: [programming]
tags: [sitecore]
---
## Problem
I got caught today in a stupid problem. But, it wasnt obvious (at least to me) at first what was causing it. I was doing something simple: nesting one UserControl inside of another. But, the nested UserControl wasnt showing up. I couldnt, for the life of me, figure out what I had done wrong. If I didnt nest the control, it worked fine.
## Solution
The solution was just correctly a stupid oversight on my part. I use ReSharper, so what I would do is just type the control prefix, colon then the name of the control and ReSharper would give me the suggestion to add the right Register tag. Well, ReSharper looks at it like a WebControl, not a UserControl. So, the Register tag is adds looks like this:
{% highlight csharp %}
<%@ Register TagPrefix="ogden" Namespace="Ogden.Web.controls" Assembly="Ogden.Web" %>
{% endhighlight %}
It should look like this:
{% highlight csharp %}
<%@ Register TagPrefix="ogden" TagName="BlogComments" src="BlogComments.ascx" %>
{% endhighlight %}
So, if youre dumb like me, make sure you have the right Register tag if youre nesting a UserControl (or using a UserControl anywhere really).

View File

@@ -0,0 +1,22 @@
---
layout: post
title: "Group List Into Sub-lists by Index"
date: 2012-07-27
description: "Group List Into Sub-lists by Index"
categories: [programming]
tags: [.net]
---
## Problem
Everyone once-in-a-while I need to take a flat list (or array, or whatever) of items and divide it up into chunks. It doesnt have to be in any real order, necessarily, it just has to be in groups of two, three or four or however many items. Most recently, I needed this to divide up a list of blog items into chunks of four to display in sets of “pages” that a user would page through with Javascript.
## Solution
I found a great, compact solution on Stack Overflow here: [http://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq](http://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq). Take a look at answer number one and make sure to pay attention to the warnings others gave about performance for large sets of items. Im working on a small set, so its not too important in my case.
Heres the code I used to set a Repeater DataSource:
{% highlight csharp %}
PodsRepeater.DataSource = Data.Items
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(obj => obj.Index / 4)
.Select(obj => obj.Select(v => v.Value).ToList());
{% endhighlight %}

View File

@@ -0,0 +1,43 @@
---
layout: post
title: "Rendering must have placeholder chrome as its parent. Got rendering instead"
date: 2012-08-09
description: "Rendering must have placeholder chrome as its parent. Got rendering instead"
categories: [programming]
tags: [sitecore]
---
## Problem
I got the error message (in my Firebug console) in the Sitecore Page Editor on a page where I had a nested sublayout. I had a basic two-column layout for the main sublayout of the page. Then, in the right column, I had another sublayout that further divided up the right column as I needed. Here is what the nested sublayout looked like:
{% highlight html %}
<sc:Placeholder runat="server" Key="blogRightColumnTop"/>
<div class="sidebar-blog">
<sc:Placeholder runat="server" Key="blogRightColumnBottomLeft"/>
</div>
<div class="sidebar-ads">
<sc:Placeholder runat="server" Key="blogRightColumnBottomRight"/>
</div>
{% endhighlight %}
This was a Javascript error that was preventing the Page Editor from fully loading the editor buttons for the edit frames I had on the page.
## Solution
The error message (surprise, surprise) was a little mystifying, but it gave me enough of a clue to think that there was some type of nesting issue. I noticed that I had a sc:Placeholder element at the top of my nested sub-layout with nothing wrapping it. Perhaps this was the issue? I tried this edit to my nested sub-layout:
{% highlight html %}
<div>
<sc:Placeholder runat="server" Key="blogRightColumnTop"/>
<div class="sidebar-blog">
<sc:Placeholder runat="server" Key="blogRightColumnBottomLeft"/>
</div>
<div class="sidebar-ads">
<sc:Placeholder runat="server" Key="blogRightColumnBottomRight"/>
</div>
</div>
{% endhighlight %}
Notice the single, wrapping div. This worked! So, Im not sure why, but apparently it solves this nesting problem. Perhaps Sitecore just does not expect a sc:Placeholder to be directly inside of another sc:Placeholder and so wrapping it solves that direct nesting issue.

View File

@@ -0,0 +1,13 @@
---
layout: post
title: "'Specified method is not supported' error when deleting SharePoint site"
date: 2012-09-25
description: "'Specified method is not supported' error when deleting SharePoint site"
categories: [programming]
tags: [sharepoint]
---
I was getting a “Specified method is not supported” error when I tried to delete some test sites in SharePoint 2010 today. I found a great link that gave several solutions to deletion errors. Issue #1 under this link is what I needed:
[SharePoint 2010: Unable to delete site/web after SP1](http://www.benramey.com/2012/09/25/specified-method-is-not-supported-error-when-deleting-sharepoint-site/#)
Even though I got an error executing the Upgrade-SPContentDatabase PowerShell command, it still fixed the problem with deleting the sites.

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Deploying SharePoint 2010 Page Layouts with Visual Studio 2010"
date: 2012-09-26
description: "Deploying SharePoint 2010 Page Layouts with Visual Studio 2010"
categories: [programming]
tags: [sharepoint]
---
[Deploying SharePoint 2010 Page Layouts with Visual Studio 2010](http://www.benramey.com/2012/09/26/deploying-sharepoint-2010-page-layouts-with-visual-studio-2010/#)

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "SharePoint Page Layout Error: Only Content controls are allowed directly in a content page that contains Content controls"
date: 2012-09-26
description: "SharePoint Page Layout Error: Only Content controls are allowed directly in a content page that contains Content controls"
categories: [programming]
tags: [sharepoint]
---
[SharePoint Page Layout Error: Only Content controls are allowed directly in a content page that contains Content controls](http://www.benramey.com/2012/09/26/sharepoint-page-layout-error-only-content-controls-are-allowed-directly-in-a-content-page-that-contains-content-controls/#)

View File

@@ -0,0 +1,78 @@
---
layout: post
title: "Cannot remove file “master_page_name_here”. Error Code: 158"
date: 2012-09-27
description: "Cannot remove file “master_page_name_here”. Error Code: 158"
categories: [programming]
tags: [sharepoint]
---
UPDATE (9/19/2013):
Please see Jeffs comment below for a better solution to this issue using best-practices for iterating through a SPSites AllWebs collection.
There are multiple places online that provide an answer to the error in the title of this post. The scenario is pretty simple: deleting a master page file previously deployed by a feature in the FeatureDeactivating method of a FeatureReceiver.
Posts like this one [http://sharepoint.stackexchange.com/questions/41358/delete-custom-master-page-error-on-feature-deactivation](http://sharepoint.stackexchange.com/questions/41358/delete-custom-master-page-error-on-feature-deactivation) give the right answer (even if its sloppyyou dont need to loop through EACH SPWeb and try to delete the files) but you can still get the error if youre not careful (like I wasnt!).
Here was my specific case. In my FeatureDeactivating method I was setting all the SPWebs back to the original v4.master like this:
{% highlight csharp %}
foreach (SPWeb site in siteColl.AllWebs)
{
site.MasterUrl = masterUrl;
site.CustomMasterUrl = masterUrl;
site.Update();
}
{% endhighlight %}
Notice how I am looping through the SPSite.AllWebs SPWebCollection and updating each SPWeb after I reset the master page URLs. Now, here is what I was doing to try to delete the master page file from the _catalogs/masterpage library:
{% highlight csharp %}
string fileUrl = SPUrlUtility.CombineUrl(
siteColl.ServerRelativeUrl,
file.FullRelativeUrl);
SPFile spFile = siteColl.RootWeb.GetFile(fileUrl);
try
{
if (spFile.Exists)
{
spFile.Delete();
spFile.Update();
}
}
catch { }
{% endhighlight %}
When I would get to the spFile.Delete() line, it would catch the exception whose error message is the title of this post. But why?? The master page wasnt being referenced anywhere anymore! As I would discover, the problem is on this line:
{% highlight csharp %}
SPFile spFile = siteColl.RootWeb.GetFile(fileUrl);
{% endhighlight %}
Do you see it? I didnt at first either. The RootWeb SPWeb reference isnt pointing to the same object that the same SPWeb in the SPSite.AllWebs collection is pointing to. So, technically, the RootWeb object still thinks its master page URLs are pointing to my custom master that Im trying to delete because it hasnt had Update() called on it.
To fix it, I had to get my SPFile from one of the SPWebs in SPSite.AllWebs, like this:
{% highlight csharp %}
SPFile spFile = siteColl.AllWebs.First().GetFile(fileUrl);
{% endhighlight %}
That enables me to delete the file with no errors.
## Jeff's comment
Hi Ben,
Great post but I did run into the same issue you were experiencing today. You can fix this issue also by using recommended best practice ([http://msdn.microsoft.com/en-us/library/aa973248(v=office.12).aspx](http://msdn.microsoft.com/en-us/library/aa973248(v=office.12).aspx))
Good Coding Practice #2
When iterating through SPWebs dispose of items with the using statement. This will cleanup any issues left in memory
So it might look like this:
{% highlight csharp %}
using(SPSite siteColl = properties.Feature.Parent as SPSite)
{
foreach (SPWeb site in siteColl.AllWebs)
{
site.MasterUrl = masterUrl; site.CustomMasterUrl = masterUrl; site.Update();
}
}
{% endhighlight %}

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Endless Authentication Prompts for SharePoint Site Collection"
date: 2012-09-27
description: "Endless Authentication Prompts for SharePoint Site Collection"
categories: [programming]
tags: [sharepoint]
---
[Fix for endless authentication prompts in SharePoint on localhost with host header](http://www.benramey.com/2012/09/27/endless-authentication-prompts-for-sharepoint-site-collection/#)

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Check for a Publishing SPWeb or SPSite"
date: 2012-09-28
description: "Check for a Publishing SPWeb or SPSite"
categories: [programming]
tags: [sharepoint]
---
[Check for a Publishing SPWeb or SPSite](http://www.dhirendrayadav.com/2011/09/checking-if-site-is-publishing-site.html)

View File

@@ -0,0 +1,104 @@
---
layout: post
title: "Retrieve Dynamic Module Choice Field Value Options in Sitefinity"
date: 2012-10-05
description: "Retrieve Dynamic Module Choice Field Value Options in Sitefinity"
categories: [programming]
tags: [sitefinity]
---
I am testing out Sitefinity right now for an upcoming project. So far, I really like everything I see. Its a great CMS and should fit out needs just perfectly (for pretty cheap too).
As with all software though, you will inevitably run into things you cant do so easily (or, at least, as easily as you think you should be able to). I ran into one of those issues yesterday and finally hacked my way to a solution this morning.
## Problem
My problem was this: how do I retrieve the available options for a Choices field in a dynamic module (built with the Module Builder). Honestly, Im not sure it really matters. That is, I dont think Sitefinity validates the value of a Choices field against what is technically a “valid” option. I think I read somewhere that it can store any option. However, I wanted to present a drop-down to users that had values that were essentially controlled by the available choices for this field. Heres the dialog Im talking about:
![Choices field choice options](/assets/images/2012-10-05-retrieve-dynamic-module-choice-field-value-options-in-sitefinity/choices_dialog.png)
## Solution
Finding a way to retrieve this options through the Sitefinity API (which, in all other respects Ive investigated so far is really good) was impossible. I started decompiling Sitefinity DLLs to see how it was done and still didnt really get anywhere. All I could see was the choices being picked up from a config file.
As I read more, I realized this was a bigger clue than I initially thought. The dynamic module information is stored in the App_Data/Sitefinity/Configuration directory in the DynamicModulesConfig.config file. Lo and behold, the options were stored in therein no less than three places too. Sitefinity has the Config.Get interface for its configuration files. However, to get the DynamicModulesConfig.config information, I would have had to call something like Config.Get(). But, the DynamicModulesConfig (in Telerik.Sitefinity.DynamicModules.Configuration) is an internal class. So, I couldnt do that. I decided upon a “get config file and parse the xml” approach.
Here is my eventual solution. Unless they change the file location or configuration XML structure, its pretty safe, albeit a little hacky. I created a simple DynamicModulesHelper class.
{% highlight csharp %}
public static class DynamicModuleHelper
{
public const string DynamicModulesConfigRelativePath =
"~/App_Data/Sitefinity/Configuration/DynamicModulesConfig.config";
public static KeyValuePair<string, string>[] GetChoiceFieldOptions(
string contentType,
string dataFieldName)
{
XmlNodeList choicesNodes = GetChoiceNodes(contentType, dataFieldName);
if (choicesNodes == null)
{
return new KeyValuePair<string, string>[0];
}
var choices = new List<KeyValuePair<string, string>>();
foreach (XmlNode node in choicesNodes)
{
ParseChoiceElement(choices, node);
}
return choices.ToArray();
}
private static void ParseChoiceElement(
List<KeyValuePair<string, string>> choices,
XmlNode node)
{
if (node == null || node.Attributes == null)
{
return;
}
string key, value;
key = value = string.Empty;
if (node.Attributes["text"] != null)
{
key = node.Attributes["text"].Value;
}
if (node.Attributes["value"] != null)
{
value = node.Attributes["value"].Value;
}
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
{
choices.Add(new KeyValuePair<string, string>(key, value));
}
}
private static XmlNodeList GetChoiceNodes(
string contentType,
string dataFieldName)
{
string dynamicModulesConfigPath = HttpContext.Current.Server.MapPath(
DynamicModulesConfigRelativePath);
XmlDocument xml = new ConfigXmlDocument();
string xpath = string.Concat(
"/dynamicModulesConfig/contentViewControls",
string.Format("/contentViewControl[@contentType='{0}]", contentType),
"//view[@displayMode=Read]",
string.Format("//field[@dataFieldName='{0}]", dataFieldName),
"/choicesConfig/element"
);
xml.Load(dynamicModulesConfigPath);
XmlNodeList choicesNodes = xml.SelectNodes(xpath);
return choicesNodes;
}
}
{% endhighlight %}
To get the Choices field options, you call the GetChoiceFieldOptions method, passing in the dynamic modules content type string (like this one: “Telerik.Sitefinity.DynamicTypes.Model.TestModule.TestModule”) and the name of the Choices field (like this: “RandomChoices”). So, my particular call for just a goofy test module I created was this:
{% highlight csharp %}
KeyValuePair<string, string>[] choices = DynamicModuleHelper.GetChoiceFieldOptions(
"Telerik.Sitefinity.DynamicTypes.Model.TestModule.TestModule",
"RandomChoices");
{% endhighlight %}

View File

@@ -0,0 +1,239 @@
---
layout: post
title: "Sitefinity Configuration File Changes When Creating and Activating a Dynamic Module"
date: 2012-10-10
description: "Sitefinity Configuration File Changes When Creating and Activating a Dynamic Module"
categories: [programming]
tags: [sitefinity]
---
After you create and activate a dynamic module with Module Builder in Sitefinity several configuration files are changes inside of App_Data/Sitefinity/Configuration. Heres a list of what is changed.
## ContentViewConfig.config
A config:link element is added under contentViewControls. Example:
{% highlight xml %}
<config:link definitionName="Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChangeBackendDefinition" path="dynamicModulesConfig/contentViewControls/Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChangeBackendDefinition" module="ModuleBuilder" />
{% endhighlight %}
## DynamicModulesConfig.config
And entire contentViewControl section is added under the contentViewControls element. Example:
{% highlight xml %}
<contentViewControl contentType="Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" managerType="Telerik.Sitefinity.DynamicModules.DynamicModuleManager, Telerik.Sitefinity" useWorkflow="True" definitionName="Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChangeBackendDefinition">
<views>
<view gridCssClass="sfPagesTreeview" searchFields="Title" doNotBindOnClientWhenPageIsLoaded="False" allowPaging="True" allowUrlQueries="True" disableSorting="False" itemsPerPage="50" canUsersSetItemsPerPage="False" sortExpression="LastModified DESC" detailsPageId="00000000-0000-0000-0000-000000000000" webServiceBaseUrl="~/Sitefinity/Services/DynamicModules/Data.svc/" templateEvaluationMode="None" itemsParentId="00000000-0000-0000-0000-000000000000" renderLinksInMasterView="True" enableSocialSharing="False" displayMode="Read" useWorkflow="True" title="Config changes" viewType="Telerik.Sitefinity.DynamicModules.Web.UI.Backend.DynamicContentMasterGridView" viewName="Config changeBackendList" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.MasterGridViewElement, Telerik.Sitefinity">
<decisionScreens>
<add decisionType="NoItemsExist" displayed="False" messageText="No config changes have been created yet" messageType="Neutral" name="NoItemsExistScreen">
<actions>
<add commandName="create" commandButtonType="Create" isFilter="False" permissionSet="Configchangetests-ConfigChange" actionName="Create" relatedSecuredObjectTypeName="Telerik.Sitefinity.DynamicModules.Builder.Model.DynamicModuleType" relatedSecuredObjectId="95f17af4-12b4-491f-bfbd-f4cf8c02c0b0" cssClass="sfCreateItem" text="Create a config change" wrapperTagKey="Unknown" isSeparator="False" name="Create" />
</actions>
</add>
</decisionScreens>
<dialogs>
<add name="ContentViewInsertDialog" openOnCommand="create" height="100%" width="100%" initialBehaviors="Maximize" behaviors="None" autoSizeBehaviors="Default" isfullscreen="False" visiblestatusbar="False" visibletitlebar="False" params="?ControlDefinitionName=Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChangeBackendDefinition&amp;ViewName=Config changeBackendInsertView" ismodal="False" destroyOnClose="False" ReloadOnShow="False" cssclass="sfMaximizedWindow" id="ContentViewInsertDialog on create" />
<add name="ContentViewEditDialog" openOnCommand="edit" height="100%" width="100%" initialBehaviors="Maximize" behaviors="None" autoSizeBehaviors="Default" isfullscreen="False" visiblestatusbar="False" visibletitlebar="False" params="?ControlDefinitionName=Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChangeBackendDefinition&amp;ViewName=Config changeBackendEditView&amp;Id={{Id}}" ismodal="False" destroyOnClose="False" ReloadOnShow="False" cssclass="sfMaximizedWindow" id="ContentViewEditDialog on edit" />
<add name="ContentViewEditDialog" openOnCommand="preview" height="100%" width="100%" initialBehaviors="Maximize" behaviors="None" autoSizeBehaviors="Default" isfullscreen="False" visiblestatusbar="False" visibletitlebar="False" params="?ControlDefinitionName=Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChangeBackendDefinition&amp;ViewName=Config changeBackendPreviewView" ismodal="False" destroyOnClose="False" ReloadOnShow="False" cssclass="sfMaximizedWindow" id="ContentViewEditDialog on preview" />
<add name="ModulePermissionsDialog" openOnCommand="permissions" height="100%" width="100%" initialBehaviors="Maximize" behaviors="None" autoSizeBehaviors="Default" isfullscreen="False" visiblestatusbar="False" visibletitlebar="False" params="?moduleName=ModuleBuilder&amp;typeName=Telerik.Sitefinity.DynamicModules.Builder.Model.DynamicModuleType&amp;securedObjectId=95f17af4-12b4-491f-bfbd-f4cf8c02c0b0&amp;backLabelText=Back to items&amp;title=Permissions&amp;permissionSetName=Configchangetests-ConfigChange" ismodal="False" destroyOnClose="False" ReloadOnShow="False" cssclass="sfMaximizedWindow" id="ModulePermissionsDialog on permissions" />
</dialogs>
<viewModes>
<add EnableDragAndDrop="False" EnableInitialExpanding="False" Name="Grid" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.GridViewModeElement, Telerik.Sitefinity">
<columns>
<add clientTemplate="&lt;a sys:href=javascript:void(0); sys:class=&quot;{{ sf_binderCommand_edit sfItemTitle sf + Lifecycle.WorkflowStatus.replace( ,”).toLowerCase()}}&quot;&gt;&lt;strong&gt;{{Title}}&lt;/strong&gt;&lt;span class=sfStatusLocation&gt;{{Lifecycle.WorkflowStatus}}&lt;/span&gt;&lt;/a&gt;" headerCssClass="sfTitleCol" headerText="Title" itemCssClass="sfTitleCol" width="0" disableSorting="False" name="Title" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.DataColumnElement, Telerik.Sitefinity" />
<add headerCssClass="sfMoreActions" headerText="Actions" itemCssClass="sfMoreActions" width="0" disableSorting="False" name="Actions" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.ActionMenuColumnElement, Telerik.Sitefinity">
<mainAction commandButtonType="Standard" isFilter="False" wrapperTagKey="Unknown" isSeparator="False" />
<menuItems>
<menuItem commandName="delete" commandButtonType="Standard" isFilter="False" cssClass="sfDeleteItm" text="Delete" wrapperTagKey="Li" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="Delete" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
</menuItems>
</add>
<add clientTemplate="&lt;span&gt;{{Author}}&lt;/span&gt;" resourceClassId="Labels" headerCssClass="sfAuthor" headerText="Author" itemCssClass="sfAuthor" width="0" disableSorting="False" name="Author" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.DataColumnElement, Telerik.Sitefinity" />
<add clientTemplate="&lt;span&gt;{{ (PublicationDate) ? PublicationDate.sitefinityLocaleFormat(dd MMM, yyyy hh:mm:ss): - }}&lt;/span&gt;" resourceClassId="ModuleBuilderResources" headerCssClass="sfDateAndHour" headerText="PublicationDate" itemCssClass="sfDateAndHour" width="0" disableSorting="False" name="PublicationDate" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.DataColumnElement, Telerik.Sitefinity" />
</columns>
</add>
</viewModes>
<links>
<add navigateUrl="[node:70aef8e7-1bf8-49d0-a7c9-21b90fb9c7a6]/fa2e4a73-929b-4050-a4ac-6030808f41cf" commandName="goBackToContentTypes" name="NavigateToContentTypesLink" />
</links>
<toolbar wrapperTagKey="Unknown">
<sections>
<section titleWrapperTagKey="Unknown" wrapperTagKey="Unknown" visible="True" name="toolbar">
<items>
<item commandName="create" commandButtonType="Create" isFilter="False" permissionSet="Configchangetests-ConfigChange" actionName="Create" relatedSecuredObjectTypeName="Telerik.Sitefinity.DynamicModules.Builder.Model.DynamicModuleType" relatedSecuredObjectId="95f17af4-12b4-491f-bfbd-f4cf8c02c0b0" cssClass="sfMainAction" text="Create a Config change" wrapperTagKey="Unknown" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="CreateItemWidget" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="groupDelete" commandButtonType="Standard" isFilter="False" permissionSet="Configchangetests-ConfigChange" actionName="Delete" relatedSecuredObjectTypeName="Telerik.Sitefinity.DynamicModules.Builder.Model.DynamicModuleType" relatedSecuredObjectId="95f17af4-12b4-491f-bfbd-f4cf8c02c0b0" text="Delete" wrapperTagKey="Unknown" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="DeleteItemWidget" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item text="More actions" wrapperTagKey="Li" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.ActionMenuWidget" isSeparator="False" name="MoreActionsItemWidget" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.ActionMenuWidgetElement, Telerik.Sitefinity">
<mainAction commandButtonType="Standard" isFilter="False" wrapperTagKey="Unknown" isSeparator="False" />
<menuItems>
<item commandName="groupPublish" commandButtonType="Standard" isFilter="False" cssClass="sfPublishItm" text="Publish" wrapperTagKey="Unknown" isSeparator="False" name="PublishItemWidget" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="groupUnpublish" commandButtonType="Standard" isFilter="False" cssClass="sfUnpublishItm" text="Unpublish" wrapperTagKey="Unknown" isSeparator="False" name="UnpublishItemWidget" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
</menuItems>
</item>
<item persistentTypeToSearch="Telerik.Sitefinity.GenericContent.Model.Content" mode="NotSet" commandName="search" commandButtonType="Standard" isFilter="False" text="Search" wrapperTagKey="Unknown" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.SearchWidget" isSeparator="False" name="SearchItemWidget" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.SearchWidgetElement, Telerik.Sitefinity" />
</items>
</section>
</sections>
</toolbar>
<sidebar title="Manage Config changes" wrapperTagKey="Unknown">
<sections>
<section title="Filter Config change" titleWrapperTagKey="Unknown" wrapperTagKey="Unknown" cssClass="sfFirst sfWidgetsList sfSeparator sfModules" visible="True" name="Filter">
<items>
<item commandName="showAllItems" commandButtonType="SimpleLinkButton" isFilter="False" buttonCssClass="sfSel" text="All Config changes" wrapperTagKey="Unknown" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="AllItems" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="showMyItems" commandButtonType="SimpleLinkButton" isFilter="False" text="My Config change" wrapperTagKey="Unknown" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="MyItems" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
</items>
</section>
<section title="Settings" titleWrapperTagKey="Unknown" wrapperTagKey="Unknown" cssClass="sfWidgetsList sfSettings" resourceClassId="ModuleBuilderResources" visible="True" name="Settings">
<items>
<item commandName="goBackToContentTypes" commandButtonType="SimpleLinkButton" isFilter="False" text="Content types" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="NavigateToContentTypes" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="permissions" commandButtonType="SimpleLinkButton" isFilter="False" text="Permissions" resourceclassid="ModuleBuilderResources" wrapperTagKey="Unknown" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="Permissions" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
</items>
</section>
</sections>
</sidebar>
<contextBar wrapperTagKey="Unknown" />
<scripts>
<script scriptLocation="Telerik.Sitefinity.Resources.Scripts.jquery.shorten.js, Telerik.Sitefinity.Resources" />
<script loadMethodName="OnModuleMasterViewLoaded" scriptLocation="Telerik.Sitefinity.DynamicModules.Web.UI.Backend.Script.MasterGridViewGeneratorExtensions.js, Telerik.Sitefinity" />
</scripts>
<commentsSettingsDefinition postRights="None" />
</view>
<view showTopToolbar="True" webServiceBaseUrl="~/Sitefinity/Services/DynamicModules/Data.svc/?itemType=Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" showNavigation="False" createBlankItem="True" unlockDetailItemOnExit="True" isToRenderTranslationView="False" doNotUseContentItemContext="False" multilingualMode="Automatic" showSections="True" masterPageId="00000000-0000-0000-0000-000000000000" dataItemId="00000000-0000-0000-0000-000000000000" enableSocialSharing="False" displayMode="Write" useWorkflow="True" title="Create a Config change" viewType="Telerik.Sitefinity.DynamicModules.Web.UI.Backend.DynamicContentDetailFormView" viewName="Config changeBackendInsertView" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.DetailFormViewElement, Telerik.Sitefinity">
<toolbar wrapperTagKey="Unknown">
<sections>
<section titleWrapperTagKey="Unknown" wrapperTagKey="Div" cssClass="sfWorkflowMenuWrp" visible="True" name="BackendForm">
<items>
<item commandName="save" commandButtonType="Save" isFilter="False" text="Create Config change" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="SaveChangesWidgetElement" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="cancel" commandButtonType="Cancel" isFilter="False" text="Back to Press Releases" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="CancelWidgetElement" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="preview" commandButtonType="Standard" isFilter="False" text="Preview" resourceclassid="Labels" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="PreviewWidgetElement" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
</items>
</section>
</sections>
</toolbar>
<sections>
<sections cssClass="sfFirstForm" wrapperTag="Div" isHiddenInTranslationMode="False" name="MainSection">
<fields>
<field rows="1" id="TitleControl" dataFieldName="Title" displayMode="Write" wrapperTag="Li" title="Title" fieldType="Telerik.Sitefinity.Web.UI.Fields.TextField" cssClass="sfFormSeparator" fieldName="Title" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.TextFieldDefinitionElement, Telerik.Sitefinity">
<expandableDefinition expanded="True" />
<validator expectedFormat="None" maxLength="0" minLength="0" required="True" maxLengthViolationMessage="The input is too long" messageCssClass="sfError" minLengthViolationMessage="The input is too short" requiredViolationMessage="This field is required!" validateIfInvisible="True" />
</field>
</fields>
<expandableDefinition expanded="True" />
</sections>
<sections cssClass="sfExpandableForm" title="More Options" wrapperTag="Div" isHiddenInTranslationMode="False" name="MoreOptions">
<fields>
<field regularExpressionFilter="[^\p{L}\-\!\$\(\)\=\@\d_\\.]+|\.+$" replaceWith="-" mirroredControlId="TitleControl" enableChangeButton="True" toLower="True" trim="True" rows="1" id="UrlNameFieldControl" dataFieldName="UrlName.PersistedValue" displayMode="Write" wrapperTag="Li" title="UrlNameTitle" example="UrlNameExample" fieldType="Telerik.Sitefinity.Web.UI.Fields.MirrorTextField" resourceClassId="ModuleBuilderResources" cssClass="sfFormSeparator" fieldName="UrlName" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.MirrorTextFieldElement, Telerik.Sitefinity">
<expandableDefinition expanded="True" />
<validator expectedFormat="None" maxLength="-1" minLength="-1" regularExpression="^[\p{L}\-\!\$\(\)\=\@\d_\~\.]*[\p{L}\-\!\$\(\)\=\@\d_\~]+$" required="True" messageCssClass="sfError" regularExpressionViolationMessage="The URL contains invalid symbols." requiredViolationMessage="Url name cannot be empty." validateIfInvisible="True" />
</field>
</fields>
<expandableDefinition expanded="False" />
</sections>
<sections cssClass="sfItemReadOnlyInfo" wrapperTag="Div" isHiddenInTranslationMode="False" name="SidebarSection">
<fields>
<field displayMode="Write" wrapperTag="Li" fieldType="Telerik.Sitefinity.Web.UI.Fields.ContentWorkflowStatusInfoField" fieldName="ItemWorkflowStatusInfoField" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.ContentWorkflowStatusInfoFieldElement, Telerik.Sitefinity">
<validator expectedFormat="None" maxLength="-1" minLength="-1" required="False" validateIfInvisible="True" />
<expandableDefinition expanded="True" />
</field>
</fields>
<expandableDefinition expanded="True" />
</sections>
</sections>
<commentsSettingsDefinition postRights="None" />
</view>
<view showTopToolbar="False" webServiceBaseUrl="~/Sitefinity/Services/DynamicModules/Data.svc/?itemType=Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" showNavigation="True" createBlankItem="True" unlockDetailItemOnExit="True" doNotUseContentItemContext="False" multilingualMode="Automatic" showSections="True" masterPageId="00000000-0000-0000-0000-000000000000" dataItemId="00000000-0000-0000-0000-000000000000" enableSocialSharing="False" displayMode="Read" useWorkflow="False" title="Preview a Config change" viewType="Telerik.Sitefinity.DynamicModules.Web.UI.Backend.DynamicContentDetailFormView" viewName="Config changeBackendPreviewView" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.DetailFormViewElement, Telerik.Sitefinity">
<toolbar wrapperTagKey="Unknown" />
<sections>
<sections cssClass="sfFirstForm" wrapperTag="Div" isHiddenInTranslationMode="False" name="MainSection">
<fields>
<field rows="1" id="TitleControl" dataFieldName="Title" displayMode="Read" wrapperTag="Li" title="Title" fieldType="Telerik.Sitefinity.Web.UI.Fields.TextField" cssClass="sfFormSeparator" fieldName="Title" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.TextFieldDefinitionElement, Telerik.Sitefinity">
<expandableDefinition expanded="True" />
<validator expectedFormat="None" maxLength="0" minLength="0" required="True" maxLengthViolationMessage="The input is too long" messageCssClass="sfError" minLengthViolationMessage="The input is too short" requiredViolationMessage="This field is required!" validateIfInvisible="True" />
</field>
</fields>
<expandableDefinition expanded="True" />
</sections>
<sections cssClass="sfExpandableForm" title="More Options" wrapperTag="Div" isHiddenInTranslationMode="False" name="MoreOptions">
<fields>
<field regularExpressionFilter="[^\p{L}\-\!\$\(\)\=\@\d_\\.]+|\.+$" replaceWith="-" mirroredControlId="TitleControl" enableChangeButton="True" toLower="True" trim="True" rows="1" id="UrlNameFieldControl" dataFieldName="UrlName.PersistedValue" displayMode="Read" wrapperTag="Li" title="UrlNameTitle" example="UrlNameExample" fieldType="Telerik.Sitefinity.Web.UI.Fields.MirrorTextField" resourceClassId="ModuleBuilderResources" cssClass="sfFormSeparator" fieldName="UrlName" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.MirrorTextFieldElement, Telerik.Sitefinity">
<expandableDefinition expanded="True" />
<validator expectedFormat="None" maxLength="-1" minLength="-1" regularExpression="^[\p{L}\-\!\$\(\)\=\@\d_\~\.]*[\p{L}\-\!\$\(\)\=\@\d_\~]+$" required="True" messageCssClass="sfError" regularExpressionViolationMessage="The URL contains invalid symbols." requiredViolationMessage="Url name cannot be empty." validateIfInvisible="True" />
</field>
</fields>
<expandableDefinition expanded="False" />
</sections>
</sections>
<commentsSettingsDefinition postRights="None" />
</view>
<view showTopToolbar="True" webServiceBaseUrl="~/Sitefinity/Services/DynamicModules/Data.svc/?itemType=Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" showNavigation="False" createBlankItem="True" unlockDetailItemOnExit="True" isToRenderTranslationView="False" doNotUseContentItemContext="False" multilingualMode="Automatic" showSections="True" masterPageId="00000000-0000-0000-0000-000000000000" dataItemId="00000000-0000-0000-0000-000000000000" enableSocialSharing="False" displayMode="Write" useWorkflow="True" title="Edit a Config change" viewType="Telerik.Sitefinity.DynamicModules.Web.UI.Backend.DynamicContentDetailFormView" viewName="Config changeBackendEditView" type:this="Telerik.Sitefinity.Web.UI.ContentUI.Views.Backend.Master.Config.DetailFormViewElement, Telerik.Sitefinity">
<toolbar wrapperTagKey="Unknown">
<sections>
<section titleWrapperTagKey="Unknown" wrapperTagKey="Div" cssClass="sfWorkflowMenuWrp" visible="True" name="BackendForm">
<items>
<item commandName="save" commandButtonType="Save" isFilter="False" text="Create Config change" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="SaveChangesWidgetElement" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="cancel" commandButtonType="Cancel" isFilter="False" text="Back to Press Releases" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="CancelWidgetElement" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
<item commandName="preview" commandButtonType="Standard" isFilter="False" text="Preview" resourceclassid="Labels" wrapperTagKey="Span" widgetType="Telerik.Sitefinity.Web.UI.Backend.Elements.Widgets.CommandWidget" isSeparator="False" name="PreviewWidgetElement" type:this="Telerik.Sitefinity.Web.UI.Backend.Elements.Config.CommandWidgetElement, Telerik.Sitefinity" />
</items>
</section>
</sections>
</toolbar>
<sections>
<sections cssClass="sfFirstForm" wrapperTag="Div" isHiddenInTranslationMode="False" name="MainSection">
<fields>
<field rows="1" id="TitleControl" dataFieldName="Title" displayMode="Write" wrapperTag="Li" title="Title" fieldType="Telerik.Sitefinity.Web.UI.Fields.TextField" cssClass="sfFormSeparator" fieldName="Title" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.TextFieldDefinitionElement, Telerik.Sitefinity">
<expandableDefinition expanded="True" />
<validator expectedFormat="None" maxLength="0" minLength="0" required="True" maxLengthViolationMessage="The input is too long" messageCssClass="sfError" minLengthViolationMessage="The input is too short" requiredViolationMessage="This field is required!" validateIfInvisible="True" />
</field>
</fields>
<expandableDefinition expanded="True" />
</sections>
<sections cssClass="sfExpandableForm" title="More Options" wrapperTag="Div" isHiddenInTranslationMode="False" name="MoreOptions">
<fields>
<field regularExpressionFilter="[^\p{L}\-\!\$\(\)\=\@\d_\\.]+|\.+$" replaceWith="-" mirroredControlId="TitleControl" enableChangeButton="True" toLower="True" trim="True" rows="1" id="UrlNameFieldControl" dataFieldName="UrlName.PersistedValue" displayMode="Write" wrapperTag="Li" title="UrlNameTitle" example="UrlNameExample" fieldType="Telerik.Sitefinity.Web.UI.Fields.MirrorTextField" resourceClassId="ModuleBuilderResources" cssClass="sfFormSeparator" fieldName="UrlName" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.MirrorTextFieldElement, Telerik.Sitefinity">
<expandableDefinition expanded="True" />
<validator expectedFormat="None" maxLength="-1" minLength="-1" regularExpression="^[\p{L}\-\!\$\(\)\=\@\d_\~\.]*[\p{L}\-\!\$\(\)\=\@\d_\~]+$" required="True" messageCssClass="sfError" regularExpressionViolationMessage="The URL contains invalid symbols." requiredViolationMessage="Url name cannot be empty." validateIfInvisible="True" />
</field>
</fields>
<expandableDefinition expanded="False" />
</sections>
<sections cssClass="sfItemReadOnlyInfo" wrapperTag="Div" isHiddenInTranslationMode="False" name="SidebarSection">
<fields>
<field displayMode="Write" wrapperTag="Li" fieldType="Telerik.Sitefinity.Web.UI.Fields.ContentWorkflowStatusInfoField" fieldName="ItemWorkflowStatusInfoField" type:this="Telerik.Sitefinity.Web.UI.Fields.Config.ContentWorkflowStatusInfoFieldElement, Telerik.Sitefinity">
<validator expectedFormat="None" maxLength="-1" minLength="-1" required="False" validateIfInvisible="True" />
<expandableDefinition expanded="True" />
</field>
</fields>
<expandableDefinition expanded="True" />
</sections>
</sections>
<commentsSettingsDefinition postRights="None" />
</view>
</views>
</contentViewControl>
{% endhighlight %}
## SecurityConfig.config
A permission element is added underneath the permissions node. Example:
{% highlight xml %}
<permission title="Config changes permissions" description="Represents the most common application security permissions." loginUrl="~/Sitefinity/Login" ajaxLoginUrl="~/Sitefinity/Login/Ajax" name="Configchangetests-ConfigChange">
<actions>
<add title="View Config changes" description="Allows or denies viewing Config changes." type="View" name="View" />
<add title="Create Config changes" description="Allows or denies the creation of new Config changes." type="Create" name="Create" />
<add title="Modify Config changes" description="Allows or denies changes to existing Config changes." type="Modify" name="Modify" />
<add title="Delete Config changes" description="Allows or denies deleting Config changes." type="Delete" name="Delete" />
<add title="Change a Config changes permissions" description="Allows or denies changing the permissions of Config changes." type="ChangePermissions" name="ChangePermissions" />
</actions>
</permission>
{% endhighlight %}
## ToolboxesConfig.config
A tool is added in the tools section. Example:
{% highlight xml %}
<add enabled="True" type="Telerik.Sitefinity.DynamicModules.Web.UI.Frontend.DynamicContentView, Telerik.Sitefinity" title="Config changes" cssClass="sfNewsViewIcn" moduleName="Config change tests" DynamicContentTypeName="Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" DefaultMasterTemplateKey="844f4eff-9a33-4799-84e5-2973d7a3db9b" DefaultDetailTemplateKey="7fd83546-0785-4050-998f-06428e7c6fa1" visibilityMode="None" name="Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" />
{% endhighlight %}
## WorkflowConfig.config
A workflow type is added in the workflowTypes section. Example:
{% highlight xml %}
<add title="Config change" moduleName="Config change tests" contentType="Telerik.Sitefinity.DynamicTypes.Model.Configchangetests.ConfigChange" />
{% endhighlight %}
## Deactivation
No configuration changes are made in the Sitefinity configuration files when you just deactivate a dynamic module with Module Builder.
Interestingly, when you delete a dynamic module, all of the changes mentioned above are completely reverted EXCEPT the ContentViewConfig.config file. It keeps the config:link element that was added when you added and activated the dynamic module originally. This is curious as it points to a configuration element in DynamicModulesConfig.config that is removed when you delete the dynamic module.

View File

@@ -0,0 +1,32 @@
---
layout: post
title: "Custom Error Pages with Sitefinity"
date: 2012-10-12
description: "Custom Error Pages with Sitefinity"
categories: [programming]
tags: [sitefinity,.net]
---
I had a considerably hard time today getting just a simple 404 error page (custom) to show up in all cases for a Sitefinity installation. It probably has nothing to do with Sitefinity and a lot more to do with my lack of experience doing this simple task. But, anyhow, here are my findings.
You have to setup the error pages in two places in the applications web.config to cover all of your bases.
## system.web/customErrors
You have to setup your error pages in the system.web/customErrors section of your web.config to cover calls to non-existent *.aspx pages (and probably other ASP.NET-handled pages as well). My customErrors section looks like this. It will be expanded to cover other error codes.
{% highlight xml %}
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="~/Static/404.htm"/>
</customErrors>
{% endhighlight %}
## system.webServer/httpErrors
The next section you need is the system.webServer/httpErrors section. I never knew this section exists, but it corresponds to the Errors panel in IIS Manager 7.5 for your website. This section covers request for non-existing folders and static files (like /bogusFolder or /bogusFile.htm). Mine looks like this and it will likewise be expanded for other error codes.
{% highlight xml %}
<httpErrors errorMode="Custom" defaultResponseMode="File">
<remove statusCode="404"/>
<error statusCode="404" path="Static\404.htm"/>
</httpErrors>
{% endhighlight %}
Because IIS has default settings for this stuff, you need to use the remove element before adding your own error messages. You can find more specifics on this configuration element here: [http://www.iis.net/configreference/system.webserver/httperrors](http://www.iis.net/configreference/system.webserver/httperrors).
You have other options than the defaultResponseMode=”File” for the httpErrors section. See the link above for more information about this. You can redirect to *.aspx files and stuff for more dynamically handled pages. I wanted very simple, static HTML pages to ensure maximum possibility of the page rendering and displaying a legible error to the user.

View File

@@ -0,0 +1,113 @@
---
layout: post
title: "Indexing Taxonomy Fields For Search in Sitefinity 5.1"
date: 2012-10-19
description: "Indexing Taxonomy Fields For Search in Sitefinity 5.1"
categories: [programming]
tags: [sitefinity]
---
## Problem
We recently ran into a rather surprising issue with a Sitefinity 5.1 installation. We wanted a pretty simple feature: when searching and using Lucene, we wanted the values of any taxonomy fields to be indexed with Sitefinitys Lucene back-end so that items could be searched by their attached taxonomy. This is apparently impossible with the basic installation because you can only search short text and long text fields.
So, I began on a quest to implement this somehow and ran into another wall: the Sitefinity documentation on their search API is horrendously non-existent. We mostly had to rely on forum posts and decompiling Telerik assemblies to see how stuff worked. But, this did eventually lead to a solid solution.
## Solution
It took me about 13 hours of research to figure out what to do and then just about an hour to actually implement. So, the good news is that its a pretty simple solution!
When you create a search index in Sitefinitys back-end, you can add a comma-separated list of extra fields to index. Sitefinity indexes a standard set (like Title). But, if you create custom dynamic modules with other fields you want to index you have to add them in this Additional Fields box. Here here:
![Additional Fields](/assets/images/2012-10-19-indexing-taxonomy-fields-for-search-in-sitefinity-51/addtionalfields.png)
Looking through the de-compiled code for Sitefinitys SearchModule, I found that it registered a special outbound pipe that updates the Lucene indexes (not directly, but through Sitefinitys abstraction, it appears) when you publish an item. It also registered various Translator classes with PipeTranslatorFactory.RegisterTranslator. Digging through the code in that SearchIndexOutboundPipe and the various translator classes it registered, I finally found that when the SearchIndexOutboundPipe runs, it reads that comma-separated list of additional fields from your index and ties them to the ConcatenationTranslator through some type of mapping. It looked like these translators were being used by the SearchModule to translate content values into index-able values for the Lucene indexing system.
Turns out, I was right. With some more trial and error from that point on, I came up with this solution.
Override ConcatenationTranslator
First of all, you need to create a new class that inherits from ConcatenationTranslator. This is a public class in the Telerik.Sitefinity.Publishing.Translators namespace. I called my class CustomConcatenationTranslator. IN the base class, the Translate method turns an array of object values into a space-separated string. Ill show the code in a minute, but the first thing you need to do after creating the class is register it with the pipeline. In Global.asax.cs, add these lines (around any other code you have there):
{% highlight csharp %}
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Initialized += Bootstrapper_Initialized;
}
private void Bootstrapper_Initialized(object sender, Telerik.Sitefinity.Data.ExecutedEventArgs e)
{
if (e.CommandName == "Bootstrapped")
{
PipeTranslatorFactory.RegisterTranslator(new CustomConcatenationTranslator());
}
}
{% endhighlight %}
You dont have to unregistered the existing ConcatenationTranslator. Your registration will replace it as long as you dont rename the translator by overriding its Name property.
What I wanted to do was check if the data being translated was a TrackedList<Guid> type. This is what your taxonomy fields are in code. Here is my code for the CustomConcatenationTranslator class.
{% highlight csharp %}
public class CustomConcatenationTranslator : ConcatenationTranslator
{
private readonly TaxonomyManager _taxonomyManager;
public CustomConcatenationTranslator()
{
_taxonomyManager = TaxonomyManager.GetManager();
}
public override object Translate(object[] data, IDictionary<string, string> translationSettings)
{
if (data.Length <= 0)
{
return string.Empty;
}
StringBuilder concatedStr = new StringBuilder();
ConcatValues(data, concatedStr);
return concatedStr.ToString();
}
private static bool IsTrackedList<T>(object data)
{
return data is TrackedList<T>;
}
private void ConcatValues(object[] data, StringBuilder concatedStr)
{
for (int i = 0; i < data.Length; i++)
{
string str;
if (IsTrackedList<Guid>(data[i]))
{
str = TranslatedTaxonomies(data, i);
}
else
{
str = GetString(data[i]);
}
concatedStr.Append(str);
if (i + 1 < data.Length)
{
concatedStr.Append( );
}
}
}
private string TranslatedTaxonomies(object[] data, int i)
{
List<string> taxNames = new List<string>();
foreach (Guid guid in ((TrackedList<Guid>)data[i]))
{
var taxon = _taxonomyManager.GetTaxon(guid);
taxNames.Add(taxon.Name);
}
return string.Join(" ", taxNames);
}
}
{% endhighlight %}
It should be pretty simple to follow. Basically, if I find a data value is a TrackedList<Guid>, I get the Taxon items with that Guid ID, get the name of that Taxon and then concatenate those values, separated by a space. If the data object is anything other than a TrackedList<Guid>, I used the default ConcatenationTranslator behavior.
## Conclusion
Thats really all you need to do. Just go into your Sitefinity back-end, re-index your search index and you can now search your content by any taxonomy it has attached to it.

View File

@@ -0,0 +1,114 @@
---
layout: post
title: "Setting Selected Items on Sitefinity 5.1 PagesSelector in Javascript"
date: 2012-10-25
description: "Setting Selected Items on Sitefinity 5.1 PagesSelector in Javascript"
categories: [programming]
tags: [sitefinity]
---
## Problem
Today I nearly tore my hair out trying to get set the selected items (having a list of GUIDs) on the PagesSelector control in Sitefinity through the Javascript API to the control.
You are supposed to be able to use the set_selectedItemIds method to do this, but it doesnt work. Nothing happens after you call this method.
After doing some debugging through Sitefinitys javascript, I found a bug which, when fixed, causes the set_selectedItemIds method to work as expected.
## Solution
I am using a WidgetDesigner in which I have the PagesSelector control. My goal is pretty simple: I want the content editors to be able to select a set of pages for which to show links in the footer of their website. When they edit the footer widget, the PagesSelector should show up, let them choose pages (showing the currently selected ones) and let them save those selections.
Everything works fine. Except, once I save a selection in the PagesSelector, on subsequent edits, the currently selected items would not show up, as described above.
Here is the bug I found in the Sitefinity code. The set_selectedItemIds function is really simple. It looks like this:
{% highlight javascript %}
set_selectedItemIds: function(ids) {
this._selectedItemIds = ids;
this._updateGridSelection();
this._updateTreeSelection();
}
{% endhighlight %}
It updates a property (_selectedItemIds) and then updates the grid (not sure what that is) and then the tree (of page nodes). The _updateTreeSelection method is what were looking forthis is where the bug is. The method looks like this:
{% highlight javascript %}
_updateTreeSelection: function () {
if (this._treeIsBound == false) {
this._treeMustBeUpdated = true;
return;
}
this._treeMustBeUpdated = false;
var tree = this.get_itemsTree();
var binder = tree.getBinder();
if (this._selectedItemId) {
binder.setSelectedValues([this._selectedItemId], true, true);
} else {
var selectedItems = tree.get_selectedItems();
if (selectedItems) {
binder.clearSelection();
binder.setSelectedItems(selectedItems, true, true);
} else {
if (this._selectedItemIds) {
binder.setSelectedValues(this._selectedItemIds, true, true);
}
}
}
this._raiseSelectionApplied(this, {});
}
{% endhighlight %}
The problem is on line 13. tree.get_selectedItems() returns an empty array when there are no items selected. So, the expression if (selectedItems) { } will always be truebut we want to get to the else statement, as you can see, to set the selection from the this._selectedItemIds array.
The beauty (and danger!) of javascript is that you can easily fix something like this in your own code by simply redefining their method. Here is my code that fixes this bug:
{% highlight javascript %}
var wrapper = this.get_pagesSelector();
var selector = this.get_pagesSelector().get_pageSelector();
var selecting = false;
wrapper.add_selectionApplied(function () {
if (selecting) {
return;
}
selecting = true;
var pageIds = controlData.FooterPages;
if (pageIds) {
var pages = pageIds.split(",");
var oldMethod = selector._updateTreeSelection;
selector._updateTreeSelection = function () {
if (this._treeIsBound == false) {
this._treeMustBeUpdated = true;
return;
}
this._treeMustBeUpdated = false;
var tree = this.get_itemsTree();
var binder = tree.getBinder();
if (this._selectedItemId) {
binder.setSelectedValues([this._selectedItemId], true, true);
} else {
var selectedItems = tree.get_selectedItems();
if (selectedItems && selectedItems.length > 0) {
binder.clearSelection();
binder.setSelectedItems(selectedItems, true, true);
} else {
if (this._selectedItemIds) {
binder.setSelectedValues(this._selectedItemIds, true, true);
}
}
}
this._raiseSelectionApplied(this, {});
};
selector.set_selectedItemIds(pages);
selector._updateTreeSelection = oldMethod;
}
});
{% endhighlight %}
First, I attach to the selectionApplied method. If you try to do the above code before that is done, your selection will just be overridden (or just not worknot sure which). This event will get called when you call set_selectedItemIds. To avoid an infinite loop, then, I use the selecting flag at the top of the event function I am attaching.
Second, I parse out my ids into an array.
Third, I save the old version of the _updateTreeSelection method and then override it with my own. Notice, the fix on line 13 of that method. I have added ” && selectedItems.length > 0″.
Fourth, now that I have a fixed method, I call selector.set_selectedItemIds, passing in my array of ids and it works!
Lastly, I change the method back to the original (just in case the seemingly faulty if expression is what they intended in other situations).
Hope that helps!

View File

@@ -0,0 +1,36 @@
---
layout: post
title: "LINQ to Ektron Search Library"
date: 2013-01-15
description: "LINQ to Ektron Search Library"
categories: [programming]
tags: [ektron]
---
I am currently working on a project at VML that will make use of Ektrons 3-tier architecture. It will be an MVC 4.0 site that basically uses an existing Ektron site as a datasource. It pulls in various Ektron content and then feeds it into the MVC application (a mobile version of the Ektron site).
I wanted to take time on this project to make everything “right”. I am using MVC as my presentation layer, I have a distinct business logic layer and underneath that I have a nice data layer. This data layer is what is interacting with Ektron. The data model for the application is defined separately through a set of interfaces that describe different data objects. The presentation and business logic layers know nothing about the concrete implementations of these interfaces. So, my project structure looks like this:
- Project.Web my MVC application
- Project.Data the set of interfaces that define my data model and repositories
- Project.Data.Ektron the implementation of the Project.Data interfaces with Ektron (data model and repositories)
- Project.BusinessLayer the service layer that sits on top of Project.Data.Ektron (with no direct knowledge of it)
I tie it all neatly together with dependency injection using Autofac (thats how my business layer doesnt need to know about Project.Data.Ektron).
I thought about a few designs for how my repositories would receive queries for data. Of course, nothing beats LINQ for this purpose. The problem is, Ektron doesnt implement anything like LINQ-to-Ektron to query its data. So, what to do?
I really didnt want to move away from LINQ. Anything else was going to be too rigid and cumbersome to use. I started looking into my own implementation of LINQ-to-Ektron. Ektron already implements a LINQ-like expression tree search via its AdvancedSearchCriteria search API. So, my thinking was to take LINQ queries and translate them into those AdvancedSearchCriteria expressions.
As I started to look into what it took to create a LINQ QueryProvider, I was overwhelmed. I read through the first few posts of Matt Warrens “tutorial” here: [http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx](http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx). I didnt even understand half of what he was talking about, but I copied his code and started tinkering. I gave up a couple of times, thinking this would take way too long to implement. But, I always came back to wanting to use LINQ for my repositories. This meant I had to do some kind of LINQ-to-Ektron search translation and that meant I had do a LINQ query provider for it.
I actually got to the point where I had a working query provider that successfully performed searches through Ektron. It was pretty exciting. Somewhere along the way, however, I discovered Remotions re-linq library and I knew I needed to rewrite my entire library. re-linq does a great job of pre-parsing any LINQ expression into a more consistent format to translate into whatever you want to translate it into. You still have to do the actual work of translating the query into something useful, but the gazillions of tiny tasks of parsing the query, evaluating evaluatable parts, etc are all taken care of for you. In addition, it gives real structure to your query parsing code.
What I ended up with, I decided to put on GitHub as an open-source project so that other Ektron developers (which I do NOT claim to be!) could benefit from it. Ive also created a NuGet package out of it to easily include in your own projects.
I need users and contributors to beef it up and test it out. So far, Ive only had time to test it with the one project I developed it for (which is an Ektron 8.5 installation). So, my domain of testing has been pretty limited. I need it tested/expanded for 8.6 and for all sorts of different uses.
Check out the library here:
GitHub: [https://github.com/benjaminramey/GoodlyFere.Ektron.Linq](https://github.com/benjaminramey/GoodlyFere.Ektron.Linq)
NuGet: [http://nuget.org/packages/GoodlyFere.Ektron.Linq](http://nuget.org/packages/GoodlyFere.Ektron.Linq)
PS A note on the name “Goodly Fere”. Goodly Fere is the name given to Christ in a ballad by Ezra Pound. Read it here: [http://www.bartleby.com/265/295.html](http://www.bartleby.com/265/295.html). My faith in Christ defines everything I do and say (at least, it shouldoften I fall short of that ideal). Colossians 3:17 says to do everything in the name of the Lord Jesus. Thats quite a standard! After all, Paul started off that letter to the Colossians talking about how all of Creation was made by, through and for Christ. I think Creation is a pretty high standard of quality. I desire to reflect that standard of quality (as much as I can) in everything I do tooincluding writing code libraries.

View File

@@ -0,0 +1,74 @@
---
layout: post
title: "Converting an Interface Expression to a Concrete Expression"
date: 2013-01-23
description: "Converting an Interface Expression to a Concrete Expression"
categories: [programming]
tags: [.net]
---
I had a case recently where I needed to convert a LINQ expression of the type of an interface and I needed to convert it to be based on a concrete implementation of that interface. I came up with the following solution, using an ExpressionVisitor and a simple helper method.
First, the helper method in a static class.
{% highlight csharp %}
internal static Expression<Func<TConcrete, bool>> ConvertToConcreteExpression<TConcrete, TInterface>( Expression<Func<TInterface, bool>> interfaceExpression)
{
if (!typeof(TInterface).IsAssignableFrom(typeof(TConcrete)))
{
throw new Exception("TInterface must be assignable from TConcrete to convert an expression.");
}
return TransformVisitor<TConcrete, TInterface>.Transform(interfaceExpression);
}
{% endhighlight %}
Here is the TransformVisitor class.
{% highlight csharp %}
internal class TransformVisitor<TConcrete, TInterface> : ExpressionVisitor
{
private readonly ParameterExpression _param = Expression.Parameter(typeof(TConcrete), "param_0");
public static Expression<Func<TConcrete, bool>> Transform(Expression expression)
{
var visitor = new TransformVisitor<TConcrete, TInterface>();
var newLambda = (Expression<Func<TConcrete, bool>>)visitor.Visit(expression);
return newLambda;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T).IsAssignableFrom(typeof(Func<TInterface, bool>)))
{
return Expression.Lambda<Func<TConcrete, bool>>(
Visit(node.Body),
_param
);
}
return base.VisitLambda(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType.IsAssignableFrom(typeof(TInterface)))
{
return Expression.MakeMemberAccess(
Visit(node.Expression),
typeof(TConcrete).GetProperty(node.Member.Name));
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type.IsAssignableFrom(typeof(TInterface)))
{
return _param;
}
return base.VisitParameter(node);
}
}
{% endhighlight %}

View File

@@ -0,0 +1,45 @@
---
layout: post
title: "Ektron Paths"
date: 2013-03-15
description: "Ektron Paths"
categories: [programming]
tags: [ektron]
---
For future reference, here is how you have to construct paths for finding Ektron content items, folders and taxonomy items.
## FolderManager and ContentManager
Use forward slashes (/)
Do NOT include an initial slash
DO include a trailing slash
Example: “folder1/folder2/otherfolder/”
Given this code:
{% highlight csharp %}
FolderManager fm = new FolderManager();
FolderCriteria folderCrit = new FolderCriteria();
folderCrit.AddFilter(
FolderProperty.FolderPath,
CriteriaFilterOperator.EqualTo,
folderPath);
FolderData folder = fm.GetList(folderCrit).FirstOrDefault();
{% endhighlight %}
Then the “folderPath” variable needs to be in this format: “folderName1/folderName2/”. The same would apply for finding all the content in a folder via the contents path.
## TaxonomyManager
Use backslashes (\)
DO use an initial slash
Do NOT use a trailing slash
Example: “\taxonomy1\taxonomy2\othertaxonomy”
So, given this code:
{% highlight csharp %}
ITaxonomyManager _taxManager = ObjectFactory.GetTaxonomyManager();
TaxonomyData tax = _taxManager.GetItem(taxPath);
{% endhighlight %}
The “taxPath” variable needs to be in this format: “\rootTaxonomyName\subTaxonomyName1\subTaxonomyName2″.

View File

@@ -0,0 +1,43 @@
---
layout: post
title: "TaxonomyManager WCF Error in Ektron 8.5 3-tier Setup"
date: 2013-03-15
description: "TaxonomyManager WCF Error in Ektron 8.5 3-tier Setup"
categories: [programming]
tags: [ektron]
---
## Problem
Im currently working on a project to import data into an existing Ektron 8.5 SP3 installation. It is pulling existing product data from a database, transforming it into the smart form XML we need and then shoving that data into the Ektron installation. In addition to creating content items, the import tool also updates metadata and taxonomy fields.
The update the taxonomy fields, I use the TaxonomyManager to get the taxonomy by name, then set the taxonomy IDs on TaxonomyItemData objects. This import tool isnt running inside of an Ektron installationits a standalone toolso it takes advantage of Ektron 8.5s 3-tier setup. This means the TaxonomyManager calls are going over the wire via the WCF services.
The TaxonomyManager calls were throwing WCF errors, however, saying that the ITaxonomyManager interface was violating the WCF rules of not having any same-named methods. If you look, the ITaxonomyManager interface defines multiple same-named methods, including two versions of GetList.
This was pretty confusing to me because I couldnt believe that Ektron would release a major version like 8.5, touting their 3-tier capabilities, without having tested one of the major content managers liket he TaxonomyManager.
## Solution
Whatever the case may be, I started snooping around on the net for others having experienced this issue. As usual though (also a mystery), I couldnt find any info on it. Why does there seem to be so little content on the net about Ektron??
I started snooping around with decompiling the Ektron.Cms.ObjectFactory DLL code where the ITaxonomyManager interface is defined. Everything looked goodit had the same-named methods, but was using the OperationContract attribute which lets you define the action name. The two GetList actions were defined with different names, so something wasnt matching up with the error I was seeing in my import project. You can see that the two GetList methods have different Action values, making it OK for WCF.
{% highlight csharp %}
[OperationContract(Action="GetList")]
List<TaxonomyData> GetList(TaxonomyCriteria criteria);
[OperationContract(Action="GetListByCustomProperty", Name="GetListByCustomPropertyCriteria")]
List<TaxonomyData> GetList(TaxonomyCustomPropertyCriteria criteria);
{% endhighlight %}
When I decompiled the Ektron.Cms.ObjectFactory DLL that I was using in my import project, I found the problem. You can see it here:
{% highlight csharp %}
[OperationContract(Action="GetList")]
List<TaxonomyData> GetList(TaxonomyCriteria criteria);
[OperationContract(Action="GetList")]
List<TaxonomyData> GetList(TaxonomyCustomPropertyCriteria criteria);
{% endhighlight %}
Notice how the Action is defined with the same name. I checked the DLL versions of my DLL and the DLL in the Ektron 8.5 SP3 site I was pushing content too. They were both the same: 8.5.0.356. So, somewhere along the line, Ektron fixed the issue without updating the AssemblyFileVersion to signify a bug fix/updated code. Once I copied the DLL from the SP3 sites bin folder to my import project, it worked just fine.
So, if you have the same problem somewhere, look for an updated Ektron.Cms.ObjectFactory 8.5 DLLprobably in SP3.

View File

@@ -0,0 +1,21 @@
---
layout: post
title: "Ektron Html Encodes Certain Content Title Characters"
date: 2013-03-20
description: "Ektron Html Encodes Certain Content Title Characters"
categories: [programming]
tags: [ektron]
---
I found out today, while trying to search for certain content items in Ektron, that Ektron HTML encodes certain characters in content titlesbut does not, apparently, HTML encode the entire title.
Here is a list of characters Ive found so far that Ektron encodes in the title:
- / (forward slash)
- (apostrophe single quote)
- & (ampersand)
Here is what I know they do NOT encode in titles:
® (registered trademark symbol)
So, if you need to perform a search for Ektron content with a title like “Miners Hat”, then you need to encode it as “Miner&#39;s Hat” to find it.

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "ASP.NET MVC 3.0 Redirect to Account/Login with Windows Authentication"
date: 2013-04-23
description: "ASP.NET MVC 3.0 Redirect to Account/Login with Windows Authentication"
categories: [programming]
tags: [.net,asp.net,mvc]
---
[ASP.NET MVC 3 Windows Authentication problem redirects to Account/Login](http://www.benramey.com/2013/04/23/asp-net-mvc-3-0-redirect-to-accountlogin-with-windows-authentication/#)

View File

@@ -0,0 +1,13 @@
---
layout: post
title: "Sitecore Rocks and SharePoint Package Interference"
date: 2013-05-08
description: "Sitecore Rocks and SharePoint Package Interference"
categories: [programming]
tags: [sitecore]
---
I ran into an interesting problem today while working in Visual Studio 2010 on a SharePoint 2010 project.
You are supposed to be able to open the Package.package files to edit the Features that are included and their order, etc. However, when I double-clicked on the Package.package file to open it, I got a strange dialog box that said the document was already open, would I like to close it? If I said “yes”, another “Package Connection” dialog box would open with all the IIS sites I had setup listed. However, none of my SharePoint sites were listed and choosing any of the other sites just failed.
I have no idea why, honestly, but apparently Sitecore Rocks is causing this issue. I had the Visual Studio extension enabled (I had worked on a Sitecore project a few months ago). When I disabled it and restarted Visual Studio, everything worked as I expected when opening Package files.

View File

@@ -0,0 +1,71 @@
---
layout: post
title: "SharePoint 2010 401 Unauthorized Error"
date: 2013-05-23
description: "SharePoint 2010 401 Unauthorized Error"
categories: [programming]
tags: [sharepoint]
---
There are many, many reasons why SharePoint will throw a 401 error. Most of them are hard to track down and have absolutely no bearing on reality unless youre a genius. So, your only hope is to track down obscure registry updates (hacks), scour the internet for random things other people have found or just give up. :-)
I just about pulled my hair out yesterday trying to get to the bottom of a 401 Unauthorized error I kept getting for my anonymous access-enabled web application. Im not sure why, but I never tried debugging my application to see where the error was actually being thrown. I assumed it was on the IIS level or some SharePoint level, so I never tried debugging.
So, lesson #1 when getting 401 in SharePoint: debug your application.
Lesson #2: apparently accessing the DefaultPage property of a PublishingWeb requires elevated privileges. Here was my code:
{% highlight csharp %}
private string GetDefaultPageUrl()
{
if (!PublishingWeb.IsPublishingWeb(SPContext.Current.Web))
{
return string.Empty;
}
PublishingWeb web = PublishingWeb.GetPublishingWeb(SPContext.Current.Web);
if (web == null)
{
return string.Empty;
}
return web.DefaultPage == null ? string.Empty : web.DefaultPage.Url;
}
{% endhighlight %}
That last line was throwing the 401. So, the solution was to wrap it in an SPSecurity.RunWithElevatedPrivileges call, like this:
{% highlight csharp %}
private string GetDefaultPageUrl()
{
string url = string.Empty;
SPSite currentSite = SPContext.Current.Site;
SPWeb currentWeb = SPContext.Current.Web;
SPSecurity.RunWithElevatedPrivileges(
() =>
{
// reopen site & web, otherwise defaultpage will throw an error
using (SPSite site = new SPSite(currentSite.ID))
using (SPWeb web = site.OpenWeb(currentWeb.ID))
{
if (!PublishingWeb.IsPublishingWeb(web))
{
return;
}
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);
if (web == null)
{
return;
}
url = pubWeb.DefaultPage == null ? string.Empty : pubWeb.DefaultPage.Url;
}
});
return url;
}
{% endhighlight %}
Thanks to this post for the answer about re-opening the site and web inside the RunWithElevatedPrivileges code:
[http://henry-chong.com/2010/11/sharepoint-anonymous-access-and-publishing-web-default-page](http://henry-chong.com/2010/11/sharepoint-anonymous-access-and-publishing-web-default-page).

View File

@@ -0,0 +1,13 @@
---
layout: post
title: "SharePoint LINQ (SPMetal) in Anonymous User Context"
date: 2013-05-23
description: "SharePoint LINQ (SPMetal) in Anonymous User Context"
categories: [programming]
tags: [sharepoint]
---
Refer to the links below to fix this issue of SPMetal LINQ queries failing in an anonymous user context in SharePoint 2010.
[Access Denied with LINQ-to-SharePoint](http://blog.tylerholmes.com/2011/04/access-denied-with-linq-to-sharepoint.html)
[Making Linq to SharePoint work for Anonymous users](http://jcapka.blogspot.com/2010/05/making-linq-to-sharepoint-work-for.html)

View File

@@ -0,0 +1,15 @@
---
layout: post
title: "SharePoint Unknown Error After Database Attach"
date: 2013-06-06
description: "SharePoint Unknown Error After Database Attach"
categories: [programming]
tags: [sharepoint]
---
There are many reasons you might get one of those blank screens with just the text “An unknown error has occurred” when working with SharePoint. Just search for it online and youll find many fixes.
I found another one last night. One of our system admins setup a SharePoint web application for me for development purposes. We attached a database from a backup of our clients production content. But, I couldnt get to the site. It gave me the unknown error screen.
I noticed in the ULS logs that the page request was immediately being redirected to the Access Denied page, but that wasnt rendering either.
As it turns out, the system admin that setup the site was let go a couple of weeks ago and as a result his Windows domain account was deactivated. However, he was the lone Site Collection Administrator on the site. Once I removed him and added myself, the site came up as expected.

View File

@@ -0,0 +1,74 @@
---
layout: post
title: "Error on /_vti_bin/owssvr.dll?cs=65001 When Creating or Editing a SharePoint List View"
date: 2013-06-13
description: "Error on /_vti_bin/owssvr.dll?cs=65001 When Creating or Editing a SharePoint List View"
categories: [programming]
tags: [sharepoint]
---
> UPDATE (6/14/2013):
> After some further issues I encountered (unrelated to the OWSSVR.dll error, but on this module) I found out that the problem is with accessing query string parameters (at least). However, accessing the HttpRequest objects Url property, for example, doesnt cause this error.
> So, I moved my code back to the BeginRequest event (to solve my other problem) and then I just did a simple HttpContext.Current.Request.Url.AbsoluteUri.Contains(“owssvr”) check to go on and check the query string parameters or not.
## Problem
I was recently very frustrated by the dreaded “Cannot complete this action” error on a SharePoint 2010 project when trying to edit or create a new view for any list. I looked around and found several causes that others had pinpointed, but nothing really related to the specific situation I was seeing. I wasnt seeing this on a particular list or set of lists. I could create an out-of-the-box SharePoint Document Library and get this error when trying to edit or create a view.
What finally got me on the right track was this link here that a co-worker of mine found:
[MSDN forum thread](http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopmentprevious/thread/ea0b1380-480f-4b2e-afde-77ed06995bb0)
In a response to the question in that thread, it is mentioned that accessing the SPContext static object in an HttpModule causes this error when the /_vti_bin/owssvr.dll is accessed. Great! There was only one problem for me: my HttpModule was not accessing the SPContext object anywhere. Nevertheless, when I removed my HttpModules from the web.config, the list editing and creation worked. So, it was definitely something with my modules.
Still, the post mentioned above got me on the right track. By commenting out code a bit at a time, I narrowed the problem down to a check for the HttpContext.Current.Request object that I was performing. This is probably the same error that is caused by the SPContext object, since, as I understand it, the SPContext object just wraps the HttpContext object.
I had the following code in my HttpModule. Here is what Init method looked like:
{% highlight csharp %}
public void Init(HttpApplication context)
{
context.BeginRequest += SetupOutputFilter;
context.PreSendRequestHeaders += WritePdfHeaders;
context.PreSendRequestContent += WritePdfToOutput;
}
{% endhighlight %}
Commenting out the tie into the BeginRequest event fixed the issue, so it had to be something there. SetupOutputFilter looked like this:
{% highlight csharp %}
private void SetupOutputFilter(object sender, EventArgs e)
{
if (!IsPdfRequest)
{
return;
}
HttpResponse response = HttpContext.Current.Response;
_pdfStream = new PdfMemoryStream(response.Filter);
response.Filter = _pdfStream;
}
{% endhighlight %}
Commenting out the if expression also fixed the issue. My IsPdfRequest property looked like this:
{% highlight csharp %}
private static bool IsPdfRequest
{
get
{
return !string.IsNullOrEmpty(HttpContext.Current.Request["as"])
&& HttpContext.Current.Request["as"] == "pdf";
}
}
{% endhighlight %}
And there you have my access to the HttpContext object. I kind of needed this though, so I thought it might be a timing issue. Maybe accessing this on BeginRequest was the problem. I took a look at the ordering of the HttpModule events to find a good replacement. This StackOverflow thread helped me there:
[StackOverflow thread](http://stackoverflow.com/questions/441421/httpmodule-event-execution-order)
## Solution
By switching my SetOutputFilter method from the BeginRequest event to the PostReleaseRequestState event, I was able to fix the issue.
So, the lesson here is this: if you need HttpModules for your SharePoint 2010 project, make sure they do not access the HttpContext object anywhere in the BeginRequest event.

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Properly Setting Up SharePoint 2010 Search PDF Indexing"
date: 2013-06-26
description: "Properly Setting Up SharePoint 2010 Search PDF Indexing"
categories: [programming]
tags: [sharepoint]
---
[Adobe PDF IFilter Indexing with SharePoint 2010](http://nickgrattan.wordpress.com/2010/06/14/adobe-pdf-ifilter-indexing-with-sharepoint%C2%A02010/)

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Querying a Lookup Field with CAML"
date: 2013-07-15
description: "Querying a Lookup Field with CAML"
categories: [programming]
tags: [sharepoint,caml]
---
[Caml Query with Lookup field](http://forums.asp.net/t/1127580.aspx/1)

View File

@@ -0,0 +1,18 @@
---
layout: post
title: "SharePoint 2010 Page Layout Button Javascript Error"
date: 2013-09-25
description: "SharePoint 2010 Page Layout Button Javascript Error"
categories: [programming]
tags: [sharepoint]
---
I ran across an issue today in SharePoint 2010 where the Page Layout button would not open. It would throw the following error in Internet Explorer:
{% highlight javascript %}
SCRIPT5007: Unable to get property nodeName of undefined or null reference
cui.js, line 2 character 6422
{% endhighlight %}
The following link gave me the answer.
[SharePoint 2010 Quick Fix for Ribbon Page Layout switch JavaScript error](http://johnliu.net/blog/2010/12/22/sharepoint-2010-quick-fix-for-ribbon-page-layout-switch-java.html)

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "How Not to Validate Email Addresses"
date: 2013-10-14
description: "How Not to Validate Email Addresses"
categories: [programming]
tags: [email]
---
[How Not to Validate Email Addresses](http://mdswanson.com/blog/2013/10/14/how-not-to-validate-email-addresses.html)

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Installing SharePoint 2010 Language Packs on Windows 7"
date: 2013-11-05
description: "Installing SharePoint 2010 Language Packs on Windows 7"
categories: [programming]
tags: [sharepoint]
---
[Installing Language Packs on Windows 7 for SharePoint Server 2010 RC](http://johnnyharbieh.wordpress.com/2010/04/08/installing-language-packs-on-windows-7-for-sharepoint-server-2010-rc/)

View File

@@ -0,0 +1,9 @@
---
layout: post
title: "Better Gruntfiles"
date: 2014-02-23
description: "Better Gruntfiles"
categories: [programming]
tags: [grunt]
---
[More maintainable Gruntfiles](http://www.thomasboyt.com/2013/09/01/maintainable-grunt.html)

View File

@@ -0,0 +1,82 @@
---
layout: post
title: "NServiceBus with EntityFramework Persisters"
date: 2014-03-07
description: "NServiceBus with EntityFramework Persisters"
categories: [programming]
tags: [.net,nservicebus]
---
## Update 1/13/2016
This code (much refactored and improved) is now available as a [NuGet package](http://www.nuget.org/packages/GoodlyFere.NServiceBus.EntityFramework).
Please see documentation and the code over at the [GitHub repository](https://github.com/benjaminramey/GoodlyFere.NServiceBus.EntityFramework).
## Summary
NServiceBus is easily configurable to use NHibernate, RavenDB or in-memory persistence. RavenDB is built in and is the default. You can configure it to use in-memory persistence. You can install a NuGet package to use NHibernate which opens the door to many data stores.
So, for a recent project in which we were using NServiceBus, I decided to use NHibernate as the ORM for our simple domain model. I might just be dumb, but I ran into all sorts of problems with NHibernate and MS DTC when I used a remote SQL server. Try as I might, I just couldn't get NHibernate to work to persist my domain model even though it persisted the NServiceBus stuff just fine.
I decided to switch over to EntityFramework for my domain model. NHibernate and EntityFramework coexisted just fine, but I wanted to use the same ORM for both NServiceBus and my domain model just to make things nice and clean. The problem is, there isn't a prepackaged solution (none that I found at least) out there to use EntityFramework with NServiceBus. I had to implement my own persistence classes for NServiceBus. As it turns out, that wasn't too hard. Below is a description of what you will need to do to get EntityFramework working as NServiceBus' persistence layer. Much of it was modeled directly off of the NHibernate persistence classes found here: https://github.com/Particular/NServiceBus.NHibernate.
Below are all the classes and interfaces I used to implement these persisters. Note that I have EF abstracted away through an IDataContext interface which the persisters use.
- IPersistSagas
- ISubscriptionStorage
- IPersistTimeouts
Actually, this solution isn't tied specifically to EntityFramework. I have EF abstracted away through an IDataContext interface which the persisters use.
## IDataContext
<script src="https://gist.github.com/benjaminramey/9421173.js" type="text/javascript">
</script>
## BaseDataContext
<script src="https://gist.github.com/benjaminramey/9421291.js">
</script>
## EFDataContext
<script src="https://gist.github.com/benjaminramey/9421303.js">
</script>
## IRepository
<script src="https://gist.github.com/benjaminramey/9421272.js">
</script>
## EFRepository
Not every method is implemented because I didn't need them all on this project. Using TDD, I only implemented methods as I needed them.
<script src="https://gist.github.com/benjaminramey/9421323.js">
</script>
## EFDbContext
This is the project-specific EntityFramework DbContext.
<script src="https://gist.github.com/benjaminramey/9421353.js">
</script>
## EFSagaPersister
<script src="https://gist.github.com/benjaminramey/9421364.js">
</script>
## EFSubscriptionPersister
<script src="https://gist.github.com/benjaminramey/9421372.js">
</script>
## EFTimeoutPersister
<script src="https://gist.github.com/benjaminramey/9421383.js">
</script>
## TimeoutDataEntity
<script src="https://gist.github.com/benjaminramey/9421403.js">
</script>
## Subscription
<script src="https://gist.github.com/benjaminramey/9421461.js">
</script>
## SagaData
<script src="https://gist.github.com/benjaminramey/9421470.js">
</script>
## Criteria classes
<script src="https://gist.github.com/benjaminramey/9421428.js">
</script>

View File

@@ -0,0 +1,14 @@
---
layout: post
title: "The Dumbest Code I Ever Wrote"
date: 2014-03-21
description: "The Dumbest Code I Ever Wrote"
categories: [programming]
tags: []
---
Here it is, folks: some of the dumbest code Ive ever written and I just now noticed it.
{% highlight csharp %}
string.Concat("attachment; filename=\"", string.Concat(pagePdf.Name, ".pdf"), "\"");
{% endhighlight %}

View File

@@ -0,0 +1,32 @@
---
layout: post
title: "EPiServer 7 “The file /link/GUID.aspx does not exist.” Error"
date: 2014-03-27
description: "EPiServer 7 “The file /link/GUID.aspx does not exist.” Error"
categories: [programming]
tags: [episerver]
---
I ran into the following error while working with an installation of EPiServer 7 this week. It took me a long time to finally figure out the solution.
First, heres the error and stack trace:
{% highlight text %}
2014-03-27 12:12:43,396 [6] ERROR EPiServer.Global: 1.2.5 Unhandled exception in ASP.NET
System.Web.HttpException (0x80004005): The file /link/cc299342a697494c8a4bc47717210bf0.aspx does not exist.
at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetVirtualPathObjectFactory(VirtualPath virtualPath, HttpContext context, Boolean allowCrossApp, Boolean throwIfNotFound)
at System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(VirtualPath virtualPath, Type requiredBaseType, HttpContext context, Boolean allowCrossApp)
at System.Web.Routing.PageRouteHandler.GetHttpHandler(RequestContext requestContext)
at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
{% endhighlight %}
As it turns out, I did not have a TemplateDescriptor attribute on the page template that I was trying to access. Adding the following TemplateDescriptor attribute to the code-behind class for my page template saved me.
{% highlight csharp %}
[TemplateDescriptor(Path = "~/Templates/PageTemplates/Home.aspx")]
public partial class Home : TemplatePage<HomePage>
{% endhighlight %}

View File

@@ -0,0 +1,47 @@
---
layout: post
title: "EPiServer 7: Cannot decrypt password"
date: 2014-04-17
description: "EPiServer 7: Cannot decrypt password"
categories: [programming]
tags: [episerver]
---
## Problem
Ive been dealing with an issue in EPiServer 7 recently. I got this error when trying to log into the admin back-end of the site.
{% highlight text %}
Cannot decrypt password
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Cannot decrypt password
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[InvalidOperationException: Cannot decrypt password]
EPiServer.Common.Security.HMACPasswordProvider.DecryptPassword(Byte[] ciphertext) +60
EPiServer.Common.Web.Authorization.Integrator.SynchronizeUser(MembershipUser membershipUser, String password, Boolean enableCreateNew) +1116
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165
{% endhighlight %}
## Scenario
We originally installed the Relate+ site for our EPiServer installation. Through various bad decisions, we didnt keep the custom code and templates we wrote separate from the Relate+ stuff. So, we inevitably created a mess where we did not need a ton of the Relate+ templates, page types, block types, etc but we could not longer easily pull our custom stuff out from the tangled mess.
Well, we undertook the disentangling anyway (a good decision) and ended up with a much cleaner code base. This meant re-installing EPiServer for a clean (without Relate+) beginning. The database would remain the same to keep our content.
We ran into this error when we got the new, cleaned-up site installed and running and then tried to log into the administration portion of the site.
## Solution
At first, I found this post online: http://world.episerver.com/Modules/Forum/Pages/Thread.aspx?id=77776 which suggested changing over to the multiplexing membership and role providers and described the new hashing mechanism in EPiServer 7.
At first, this seemed to work. Yay! But, the joy didnt last long. I soon got the error again even after using the multiplexing providers. I dug a little deeper into the issue.
What I found was (in retrospect) pretty obvious. When we did the new, clean installation of EPiServer, it also created a new web.config with new attributes on the machineKey element. These properties are used with forms authentication to encrypt and decrypt information. Well, we didnt make sure to keep the same values between the old install and the new install. So, when it tried to decrypt login information created with the old descryptionKey using the new decryptionKey, it obviously didnt work.
Luckily, our trusty Systems Admins always keep backups of stuff they replace. We just went into the old web.config file from the previous site, copied the entire <machineKey> element and overwrite it in the new web.config. After an app pool recycle, the login was working great.

View File

@@ -0,0 +1,133 @@
---
layout: post
title: "Create a List Selector Field in a Widget Designer in Sitefinity 6.2"
date: 2014-05-21
description: "Create a List Selector Field in a Widget Designer in Sitefinity 6.2"
categories: [programming]
tags: [sitefinity]
---
> [9/25/14 update] See Hardy's comments below for necessary updates to my steps when using Sitefinity 7.1 and up. Thanks, Hardy!
## Scenario
I am designing a fairly simple widget in Sitefinity (version 6.2, but this probably applies to other versions as well) that will display a simple FAQ list. The FAQ list has two major parts: 1) a "table of contents" type ordered-list at the top that lists each question and links to the answer further down the page, and 2) the list of questions with the answers below them.
## Approach
I wanted to create a simple Sitefinity list that would contain the FAQ content (questions and answers). The default fields for a Sitefinity list are Title and Content which are really all I needed to collect a Question and Answer for a FAQ item.
So, my widget, after being placed on a page, would only need to allow a content author to simply select the FAQ list they had created earlier. The widget would then find the list, collect the items in the list and display them.
## Solution
My solution would involved a widget designer control and a customized FlatSelector field. Here's an overview with details below.
### Overview
1. Create a Sitefinity MVC widget with a single "List" field that will contain the list GUID
1. Create a Sitefinity widget designer for this MVC widget with a single FlatSelector field
1. Update the FlatSelector ItemType field
1. Update the FlatSelector ServiceURL field
1. Update the FlatSelector DataMembers
1. Update the widget designer Javascript to choose the list ID
### Details
For some reason, Sitefinity widget designer documentation is really hard for me to find. Maybe I'm the only one. Maybe Sitefinity needs to put some more dollars toward good documentation! Anyway, after some searching and some trial and error, I found this page giving me an idea of how I should accomplish what I was trying to do: [Sitefinity article][sf-article]
To summarize, it tells you how to create a widget designer with Sitefinity Thunder and then change the generated control to point to some of the generic content types (like Lists). However, it didn't have all the details I needed. I had to troubleshoot and that's why I'm writing this post.
#### Step 1: Create the Widget
Below are the details for how I setup my MVC widget. Translate the various tasks for the same effect if you prefer a WebForms widget.
Using Sitefinity Thunder, create your widget. Don't create the designer at this time. I like using MVC widgets but there's no reason this won't work with a WebForms widget. I called my widget "FAQList".
##### Step 1.1: Update controller properties
Remove whatever properties Thunder automatically adds on the MVC controller and add your own List property of type Guid.
{% highlight csharp %}
[Category("Widget Properties")]
public Guid List { get; set; }
{% endhighlight %}
##### Step 1.2: Update widget view model
I added a simple POCO to describe each FAQ item (containing a question and answer) and then added a list of these POCOs as the only property on my view model.
{% highlight csharp %}
public class FAQListModel
{
public List<FAQItem> Items { get; set; }
}
public class FAQItem
{
public string Answer { get; set; }
public string Question { get; set; }
}
{% endhighlight %}
##### Step 1.3: Update Index action
Update the Index action to use the List property to access the content-author-chosen list, pull the items and stick them in the model.
{% highlight csharp %}
public ActionResult Index()
{
var model = new FAQListModel();
if (List != Guid.Empty)
{
model.Items = App.WorkWith()
.List(List)
.ListItems()
.Get()
.Where(li => li.Status == ContentLifecycleStatus.Live)
.Select(li => new FAQItem { Question = li.Title, Answer = li.Content })
.ToList();
}
else
{
model.Items = new List<FAQItem>();
}
return View("Default", model);
}
{% endhighlight %}
#### Step 2: Create widget designer/public
Once your widget is created, you can create your widget designer. Using Thunder, create a widget designer for an existing widget. Name your widget designer. I use the name of the widget and append "Designer".
![FAQItemDesigner widget designer naming](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-naming.png)
Click "Next" on the next screen. Choose your FAQItemController from the list on the next screen.
##### Step 2.1: Choose widget designer fields
Next, choose the List property from your controller and set it up as follows. Especially note two things. First, choose the DynamicContentSelector. Second, update the "Select the content type for the selector" to 'Telerik.Sitefinity.Lists.Model.List'. Click on "Add".
![Create a widget designer](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-fields1.png)
##### Step 2.2: Update generated widget designer files
In the generated .ascx file, update the 'ServiceUrl' property on the FlatSelector control to '/Sitefinity/Services/Lists/ListService.svc/?managerType=&providerName=&itemType=Telerik.Sitefinity.Lists.Model.List&provider=&sortExpression=LastModified%20DESC&skip=0&take=50'.
![Service URL 1](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-serviceurl1.png)
In the generated .cs file, remove line 135 that sets the ConstantFilter property to 'Visible=true'.
![FAQItemDesigner remove line 135](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-removeline135.png)
Finally, in the generated .js file, change the property that is used as the value of the selected list from 'OriginalContentId' to 'Id'. This code can be found in the '_ListDoneSelecting' method.
![FAQItemDesigner update js file](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-changejsfile.png)
At this point, you should be done! Build your project, plop your new widget on a page and test it out! Here's what mine looks like.
The edit dialog:
![FAQItemDesigner edit screen](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-editscreen.png)
The list select screen:
![FAQItemDesigner select screen](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-selectscreen.png)
The selected item screen:
![FAQItemDesigner selected screen](/assets/images/create-a-list-selector-field-in-a-widget-designer-in-sitefinity-6-2/widget-designer-selectedscreen.png)
[sf-article]: http://www.sitefinity.com/documentation/documentationarticles/change-your-dynamic-content-selector-to-choose-from-generic-content

View File

@@ -0,0 +1,32 @@
---
layout: post
title: "Sitefinity Thunder Boolean Widget Designer Field Auto-Generation is Flawed"
date: 2014-05-23
description: "Sitefinity Thunder Boolean Widget Designer Field Auto-Generation is Flawed"
categories: [programming]
tags: [sitefinity]
---
## Problem
When you use Sitefinity Thunder (a great help, by the way) to generate a Widget Designer for an existing widget that has a boolean property, you get this javascript in the auto-generated javascript file (lets say my boolean field is named HasSubMenu):
{% highlight javascript %}
/* RefreshUI HasSubMenu */
jQuery(this.get_hasSubMenu()).attr("checked", controlData.HasSubMenu);
{% endhighlight %}
This code is in the refreshUI method. Its supposed to mark a checkbox as checked if HasSubMenu is true and not check it if its false. The problem is, for some reason, HasSubMenu is a string in Javascript instead of a real boolean. So, HasSubMenu will be either “true” or “false”both of which are true values in Javascript. That is, because “true” and “false” are both non-empty strings, they evaluate to true when used as an expression.
This means, no matter what you do with the checkbox, the next time you click “Edit” for this widget, the checkbox will be checked and your data is effectively corrupted.
## Solution
The solution is pretty simple. Instead of using controlData.HasSubMenu for the expression by itself, update your Javascript so that you check if the string is “true” or not: controlData.HasSubMenu === "true". The line in the refreshUI method should now look like this:
{% highlight javascript %}
/* RefreshUI HasSubMenu */
jQuery(this.get_hasSubMenu()).attr("checked", controlData.HasSubMenu === "true");
{% endhighlight %}
Now your checkbox will keep its value as you would expect.

View File

@@ -0,0 +1,30 @@
---
layout: post
title: "IE Date Parsing Doesnt Work Like Chrome"
date: 2014-06-05
description: "IE Date Parsing Doesnt Work Like Chrome"
categories: [programming]
tags: [ie,chrome]
---
I noticed a goofy issue today while working with a jQuery countdown plugin. This plugin allows you to set an “until” date so that you counter counts down to zero at a certain date and time.
I was setting the date like this:
{% highlight javascript %}
element.countdown({
until: new Date(10/13/14 00:00:00)
});
{% endhighlight %}
In Chrome, this worked just fine. The string “10/13/14 00:00:00″ parsed to a Date object for midnight on October 13th, 2014.
In IE, however (versions10, 9 and 8 at least) it parsed as October 13th, 1914. Why? Who knows. Updating my string to “10/13/2014 00:00:00″ fixed it. My final javascript looked like this:
{% highlight javascript %}
element.countdown({
until: new Date(10/13/2014 00:00:00)
});
{% endhighlight %}
So, be aware of this oddity! It caused my whole countdown widget to screw up because the current time was already past the “until” date.#

View File

@@ -0,0 +1,10 @@
---
layout: post
title: "Apply Git Patches to SVN Repository"
date: 2014-09-23
description: "Apply Git Patches to SVN Repository"
categories: [programming]
tags: [git]
---
[Creating Subversion patches with Git](http://codeprairie.net/blogs/chrisortman/archive/2008/01/14/creating-subversion-patches-with-git.aspx)

View File

@@ -0,0 +1,188 @@
---
layout: post
title: "Active Directory Authentication in ASP.NET MVC 5 with Forms Authentication and Group-Based Authorization"
date: 2014-10-20 9:03:00
description: "How to integrate MVC authorization attributes with Active Directory."
categories: [programming]
tags: [mvc,asp.net,active-directory]
---
I know that blog post title is sure a mouth-full, but it describes the whole problem I was trying to solve in a recent project.
## The Project
Let me outline the project briefly. We were building a report dashboard-type site that will live inside the client's network. The dashboard gives an overview of various, very important information that relates to how the company is performing on a hourly basis. So, the dashboard is only available to a certain group of directors.
To limit the solution to the these directors, authentication and authorization would go through their existing Active Directory setup by putting the authorized users in a special AD group.
## The Problem
Getting authentication to work was a snap. Microsoft provides the System.Web.Security.ActiveDirectoryMembershipProvider
class to use as your membership provider. Putting an `[Authorize]` attribute on my action methods or entire controllers was all I needed to get it working (besides, of course, the system.web/authentication web.config updates and a controller to show my login form and handle the submit credentials).
Here's my relevant web.config setup:
{% highlight xml %}
<connectionStrings>
<add name="ADConnectionString" connectionString="<ldap connection string here>" />
</connectionStrings>
<authentication mode="Forms">
<forms name=".AuthCookie" loginUrl="~/login"/>
</authentication>
<membership defaultProvider="ADMembershipProvider">
<providers>
<clear/>
<add name="ADMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider"
connectionStringName="ADConnectionString"
attributeMapUsername="sAMAccountName"/>
</providers>
</membership>
{% endhighlight %}
The tough part came when I wanted to limit access to users in that AD group. Microsoft doesn't provide a RoleProvider along with its ActiveDirectoryMembershipProvider. So, what to do?
I tried several methods I found online. Most of them were based on creating my own custom RoleProvider and querying AD to iterate through the user's groups (treating them like roles) and seeing if one of them matched my AD group I was looking for. However, I could never get it to work. Each code example I found eventually gave me this AD error when I iterated through the current user's AD groups:
{% highlight text %}
The specified directory service attribute or value does not exist.
{% endhighlight %}
## The Solution
Eventually, I found a solution online that worked. Instead of setting up a custom RoleProvider, all it involved was creating a custom AuthorizeAttribute for your MVC controllers (or action methods) that checked the user's .IsMemberOf method to see if the member belonged the sought after group (or groups). I don't know why this method does not cause the same AD error as describe above, but I'm glad it doesn't! All I can assume is that it queries AD in a more friendly way.
Here is my custom AuthorizeAttribute:
{% highlight c# %}
public class AuthorizeADAttribute : AuthorizeAttribute
{
private bool _authenticated;
private bool _authorized;
public string Groups { get; set; }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
if (_authenticated && !_authorized)
{
filterContext.Result = new RedirectResult("/error/notauthorized");
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
_authenticated = base.AuthorizeCore(httpContext);
if (_authenticated)
{
if (string.IsNullOrEmpty(Groups))
{
_authorized = true;
return _authorized;
}
var groups = Groups.Split(',');
string username = httpContext.User.Identity.Name;
try
{
_authorized = LDAPHelper.UserIsMemberOfGroups(username, groups);
return _authorized;
}
catch (Exception ex)
{
this.Log().Error(() => "Error attempting to authorize user", ex);
_authorized = false;
return _authorized;
}
}
_authorized = false;
return _authorized;
}
}
{% endhighlight %}
Notice that I also included a little code to distinguish between the user not being authenticated (which the call to base.AuthorizeCore takes care of) and not being authorized. Without the code in HandleUnauthorizedRequest, if the user successfully logs in but is not in the AD group, he just sees the log in screen again which doesn't communicate the problem very well.
The this.Log() code uses a Nuget package called this.Log. The LDAPHelper class is something I wrote. The code is below:
{% highlight c# %}
public static class LDAPHelper
{
public static string GetLDAPContainer()
{
Uri ldapUri;
ParseLDAPConnectionString(out ldapUri);
return HttpUtility.UrlDecode(ldapUri.PathAndQuery.TrimStart('/'));
}
public static string GetLDAPHost()
{
Uri ldapUri;
ParseLDAPConnectionString(out ldapUri);
return ldapUri.Host;
}
public static bool ParseLDAPConnectionString(out Uri ldapUri)
{
string connString = ConfigurationManager.ConnectionStrings["ADConnectionString"].ConnectionString;
return Uri.TryCreate(connString, UriKind.Absolute, out ldapUri);
}
public static bool UserIsMemberOfGroups(string username, string[] groups)
{
/* Return true immediately if the authorization is not
locked down to any particular AD group */
if (groups == null || groups.Length == 0)
{
return true;
}
// Verify that the user is in the given AD group (if any)
using (var context = BuildPrincipalContext())
{
var userPrincipal = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName,
username);
foreach (var group in groups)
{
if (userPrincipal.IsMemberOf(context, IdentityType.Name, group))
{
return true;
}
}
}
return false;
}
public static PrincipalContext BuildPrincipalContext()
{
string container = LDAPHelper.GetLDAPContainer();
return new PrincipalContext(ContextType.Domain, null, container);
}
}
{% endhighlight %}
My code is mostly based on example code I found on a very helpful [StackOverflow post][so-post].
To use this code, all you have to do is use your custom AuthorizeAttribute instead of the built-in one. Something like this:
{% highlight c# %}
[AuthorizeAD(Groups="Some AD group name")]
public class HomeController : Controller
{
}
{% endhighlight %}
[so-post]: http://stackoverflow.com/questions/4342271/asp-net-mvc-forms-authorization-with-active-directory-groups/4383502#4383502

View File

@@ -0,0 +1,25 @@
---
layout: post
title: "Permutations with Iterators in C#"
date: 2015-09-03 12:27:00
description: "Link to great code for generator permutations of objects with iterators"
categories: [programming]
tags: [.net]
---
I recently ran across a need to generate all permutations of an array of objects
in some unit testing I was doing.
The specific situation was testing that, no matter what the order, when certain messages
were picked up by an [NServiceBus][nsbus] [Saga][sagas], that a certain state was consistent after all
messages were received. Since I explicitly wanted to test the state no matter what order
the messages arrived in, I needed all permutations of those messages so I could send
them to the Saga in each order and test the resulting state.
I found this super article on the subject that gave all the code I needed: [Generating Permutations with C# Iterators][article].
Thanks IanG on Tap!
[nsbus]:http://particular.net/
[article]:http://www.interact-sw.co.uk/iangblog/2004/09/16/permuterate
[sagas]:particular.net/articles/sagas-in-nservicebus

View File

@@ -0,0 +1,16 @@
---
layout: post
title: "Working with x.509 Certificates in .NET"
date: 2015-12-02 16:35:00
description: "Link to helpful article on working with certificates in Windows and .NET"
categories: [programming]
tags: [.net,certificates]
---
I found a lot of very helpful details about certificate storage and usage in Windows and
.NET in this article.
Particularly, the explanation of where certificates and private keys can be stored for UserStore
certificates was very helpful in debugging some issues I'm currently having on a production server.
[Paul Stovell's blog post](http://paulstovell.com/blog/x509certificate2)

View File

@@ -0,0 +1,43 @@
---
layout: post
title: "Adding a RabbitMQ Configuration File When Running as a Windows Service"
date: 2016-01-04 15:26:00
description: "Instructions to add a RabbitMQ configuration file after having installed the Windows Service already."
categories: [programming]
tags: [rabbitmq]
---
## A little context
I was having a hard time adding a rabbitmq.config file today. I added it in the right location, restarted
the RabbitMQ Windows service and the logs still showed that the configuration file was not found.
If you're experiencing the same problem, you'll see a similar error message in the RabbitMQ log file after
you restart the Windows service.
{% highlight text %}
=INFO REPORT==== <date here>
node : rabbit@<server>
home dir : C:\Windows
config file(s) : c:/path/to/config/rabbitmq.config (not found)
...some other stuff...
{% endhighlight %}
This will happen if you just install your RabbitMQ server with the normal, default installation process
and then try to add a configuration file later. By default, RabbitMQ doesn't install configuration file and just
uses all it's defined defaults.
## Solution
I read the [documentation](http://www.rabbitmq.com/configure.html) a little more closely
and finally came upon this line: "Windows service users will need to re-install the service after adding
or removing a configuration file."
Oops. Guess I should have read the documentation more closely the first time around! The easiest way to
do this is as follows (start a command prompt as administrator):
{% highlight text %}
> cd "C:\Program Files (x86)\RabbitMQ Server\rabbitmq_server-3.6.0\sbin"
> .\rabbitmq-service.bat remove
> .\rabbitmq-service.bat install
> .\rabbitmq-service.bat start
{% endhighlight %}
Of course, The path you cd into will depend on the version of RabbitMQ you have installed.

View File

@@ -0,0 +1,86 @@
---
layout: post
title: "You Can't Subscribe to IEvent with NServiceBus"
date: 2016-02-25 15:56:00
description: "The problems with trying to subscribe to IEvent with NServiceBus"
categories: [programming]
tags: [.net,nservicebus]
---
The reason you might want to subscribe to `IEvent` is pretty straightforward. I
wanted to do it for the simple reason of wanting to be able to choose (based
on configuration) whether I wanted to notify anyone of any event that
could be published on my bus.
In other words, whenever an event of any type was published, I wanted one particular
endpoint to subscribe to that event and then decide whether it should do
anything with it.
I thought the code to do that should be pretty simple. Since all published
events *should* implement the `IEvent` interface and NServiceBus supports
polymorphism in its message handling, creating a message handler that
implemented `IHandleMessages<IEvent>` should be all I should have needed!
I was testing this with only partial success and so I thought it was working.
{% highlight csharp %}
public class EventHandler : IHandleMessages<IEvent>
{
public void Handle(IEvent message)
{
...
}
}
{% endhighlight %}
## My Partial Success
The reason I had partial success was because I was also *explicitly* handling
other events in the same endpoint on a special saga I had setup. So, when
the endpoint started up, it would subscribe to these events. At first, I only
wanted to send notifications for these events anyway, so it all seemed to be
working. My special `IEvent` would work because the `Saga` subscribed this same
endpoint to those few events explicitly.
## My Full Failure
I realized I had only partially succeeded when I started wanting to send notifications
for other events that were not in that `Saga`. I dug into the issue for a few
hours and finally began to wonder if NServiceBus (or maybe the RabbitMQ transport)
was explicitly ignoring `IEvent` when it setup subscriptions.
Sure enough, it does.
To build a list of types to subscribe to, NServiceBus uses the `Conventions`
class in the `NServiceBus.Core` namespace. One of the checks that code does
is to filter the list of potential types with the `IsEventType` method. This
method checks if the `Type` is in a Particular (NServiceBus) DLL. See the
code here: [IsEventType](https://github.com/Particular/NServiceBus/blob/e4bc405509e3b9c3fc91e21a56333bb40ac54a60/src/NServiceBus.Core/Conventions.cs#L154)
## My Solution
The solution is simple enough. Instead of listening to `IEvent`, listen
to a custom interface that you implement on all of your events. This is probably
more close to what you are trying to do anyway--listen to all events that your
system produces.
So, I simply created a marker interface called `ICustomEvent`.
{% highlight csharp %}
public interface ICustomEvent : IEvent { }
{% endhighlight %}
Then, on all of my bus event classes, instead of implementing `IEvent` directly,
I implemented `ICustomEvent`.
My handler then looked like this.
{% highlight csharp %}
public class EventHandler : IHandleMessages<ICustomEvent>
{
public void Handle(ICustomEvent message)
{
...
}
}
{% endhighlight %}
Now, the right subscriptions are all set up and my single handler gets
every single event published by my entire bus.

View File

@@ -0,0 +1,74 @@
---
layout: post
title: "Using NuGetPackager 0.5.5 with GitVersionTask 3.4.1"
date: 2016-03-03 15:04:00
description: "A quick fix to use NuGetPackager 0.5.5 with GitVersionTask 3.4.1"
categories: [programming]
tags: [nuget,git]
---
I'm using both [NuGetPackager](https://www.nuget.org/packages/NuGetPackager/)
with [GitVersionTask](https://www.nuget.org/packages/GitVersionTask/) on
my project that provides EntityFramework persisters for NServiceBus:
[GoodlyFere.NServiceBus.EntityFramework](https://github.com/benjaminramey/GoodlyFere.NServiceBus.EntityFramework).
NuGetPackager seems to work seamlessly with GitVersionTask 2.0.0. However,
GitVersionTask is up to version 3.4.1 and I just hate sitting at a version that
far behind!
So, I updated my NuGet package to the latest version of GitVersionTask. That's
3.4.1 right now. Too bad, so sad! That caused this error:
{% highlight xml %}
The "CreatePackages" task was not given a value for the required parameter "Version". 1
{% endhighlight %}
## The Problem
The problem turns out that NuGetPackager 0.5.5 is looking for a MSBuild property
called "GfvNuGetVersion". See the code in the .targets file [here](https://github.com/Particular/NuGetPackager/blob/0.5.6/src/NuGetPackager/NuGetPackager.targets#L5).
GitVersionTask 3.4.1 (somewhere in a release since 2.0.0) changed that property
name to "GitVersion_NuGetVersion". See the code [here](https://github.com/GitTools/GitVersion/blob/master/src/GitVersionTask/NugetAssets/GitVersionTask.targets#L68).
## The Solution
So, how to fix this? NuGetPackager will probably be updated some day, but for
now you can just translate the property in your .csproj file before the
CreatePackage task is called in NuGetPackager.
Open up your .csproj file directly in some text editor like Notepad++. Find
these two lines at the bottom of the file:
{% highlight xml %}
<Import Project="..\packages\GitVersionTask.3.4.1\Build\dotnet\GitVersionTask.targets"
Condition="Exists('..\packages\GitVersionTask.3.4.1\Build\dotnet\GitVersionTask.targets')" />
<Import Project="..\packages\NuGetPackager.0.5.5\build\NuGetPackager.targets"
Condition="Exists('..\packages\NuGetPackager.0.5.5\build\NuGetPackager.targets')" />
{% endhighlight %}
Before those two lines, add a target and a property group. The target will
create a task that translates the new property to the new property. The
property group wil define a BuildDependsOn to call that target before the
CreatePackages target is called.
{% highlight xml %}
<Target Name="TranslateNugetVersion"
Condition="'$(Configuration)' == 'Release'">
<CreateProperty
Value="$(GitVersion_NuGetVersion)">
<Output
TaskParameter="Value"
PropertyName="GfvNuGetVersion" />
</CreateProperty>
</Target>
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
TranslateNugetVersion
</BuildDependsOn>
</PropertyGroup>
<Import Project="..\packages\GitVersionTask.3.4.1\Build\dotnet\GitVersionTask.targets"
Condition="Exists('..\packages\GitVersionTask.3.4.1\Build\dotnet\GitVersionTask.targets')" />
<Import Project="..\packages\NuGetPackager.0.5.5\build\NuGetPackager.targets"
Condition="Exists('..\packages\NuGetPackager.0.5.5\build\NuGetPackager.targets')" />
{% endhighlight %}
Now, when you build your project, everything should work great!

View File

@@ -0,0 +1,92 @@
---
layout: post
title: "OWIN Integration Testing with OAuth Bearer Tokens"
date: 2016-04-11 13:34:00
description: "How to generate an OAuth bearer token to use with OWIN integration tests."
categories: [programming]
tags: [owin,.net]
---
## Situation
The situation is pretty simple. We have a project written with Web API 2.2
that we have running with OWIN. The API requires user authorization. To
accomplish that, we use the OWIN OAuth libraries.
Upon until recently, the OAuth authorization server (the thing that takes in
the user credentials and issues the token) and the API (the resource server) were
hosted in the same site and web project.
Integration testing was pretty easy at this point. For the authorized calls,
we simply wrote helper methods that logged a test user in through the authorization
server (hosted along with the API in the OWIN test server), got a bearer token
and then made the integration test calls.
## Problem
When we recently decided to split the authorization server and resource server
code up into two separate code bases (so that they could be hosted separately)
we ran into issues with the authorization and API calls no longer being possible within a
single integration test. The code to generate the bearer token was now no
longer accessible in the test server for the API integration tests because the authorization
server was now designed to be hosted separately. We'd have
to somehow generate the bearer token without making a call with the OWIN test
server.
## Solution
The solution is to generate a valid bearer token in your test code and send it
along with your test API call as you would have previous. How to generate that
valid bearer token though?? It turns out to be easier than I thought.
How you use OWIN's test server is beyond the scope of this blog post, but
eventually you'll have some code resembling this:
var server = TestServer.Create<Startup>();
You'll have to use a overload of the `Create()` method to capture a reference
to the server's `IDataProtector`. This interface defines what OWIN OAuth will
use to encrypt a `ClaimsIdentity` into a valid bearer token.
Update your server creation code like this:
{% highlight csharp %}
var dataProtector;
var server = TestServer.Create(app =>
{
var s = new Startup();
s.Configuration(app);
dataProtector = app.CreateDataProtector(
typeof(OAuthBearerAuthenticationMiddleware).Namespace,
"Access_Token", "v1");
});
{% endhighlight %}
You have to get the `dataProtector` this way because it's the same way that
the OWIN OAuth libraries get it before trying to decrypt the bearer token.
See the source for the OWIN OAuth code here:
[OAuthBearerAuthenticationMiddleware.cs](http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthBearerAuthenticationMiddleware.cs)
Once you've "captured" the `dataProtector`, in your integration test (or in some
helper code, probably) you can generate your `ClaimsIdentity` and then your
bearer token with the `dataProtector`.
{% highlight csharp %}
// inside an integration test
using (server)
{
// create your valid identity any way you need to here
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity("username"));
// these classes are defined in the OWIN OAuth libraries.
var properties = new AuthenticationProperties();
var ticket = new AuthenticationTicket(identity, properties);
var format = new TicketDataFormat(dataProtector);
string bearerToken = format.Protect(ticket);
// ... call your API through the test server with the bearer token...
var response = server.CreateRequest("/api/someendpoint")
.AddHeader("Authorization", "Bearer " + bearerToken)
.GetAsync()
.Result;
}
{% endhighlight %}

View File

@@ -0,0 +1,133 @@
---
layout: post
title: "Castle.Windsor TypedFactoryFacility and Unexpected Property Injection Behavior"
date: 2016-08-24 12:37:00
description: "Interesting (and unexpected) behavior of Windsor's property injection when using TypedFactoryFacility."
categories: [programming]
tags: [.net,castle-windsor]
---
I finally got to the bottom of an issue today that came down to the way Castle.Windsor
does property injection (injecting dependencies into public properties) when
you are also using their TypedFactoryFacility. It was a tough one to track down, so
I thought I'd put the solution out there for everyone else's benefit.
## The Problem
Here's what the problem looked like.
### The Error Message
First things first, this is what the symptom looked like:
{% highlight text %}
[ComponentNotFoundException: No component for supporting the service System.Threading.Tasks.Task was found]
Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, IDictionary arguments, IReleasePolicy policy) +120
Castle.Facilities.TypedFactory.Internal.TypedFactoryInterceptor.Resolve(IInvocation invocation) +147
Castle.Facilities.TypedFactory.Internal.TypedFactoryInterceptor.Intercept(IInvocation invocation) +123
Castle.DynamicProxy.AbstractInvocation.Proceed() +448
Castle.Proxies.Func`2Proxy.Invoke(OAuthMatchEndpointContext arg) +168
...
{% endhighlight %}
This is actually a pretty standard error when you're using Castle.Windsor. It usually
means Windsor tried to resolve some dependency but discovered that it was missing a
registration. Translation: you forgot to register a component or type in your Windsor
setup. Usually it's an easy fix; just register your dependency properly.
In this case, however, as you can see, the missing registration was of type
System.Threading.Tasks.Task. I didn't have any dependencies on the `Task` class and so
I didn't want to "fix" any Windsor setup to register it. This wasn't making sense.
### Step One: TypedFactoryInterceptor?
The first oddity I noticed was that this error was happening somewhere in the flow
of the TypedFactoryInterceptor. I could tell where the error was coming from in my
code (from the rest of the stack trace that I didn't include above). The problem
was that it had nothing to do with any TypedFactoryFacility.
So, I inspected my registrations a little closer. I basically had a setup like this.
{% highlight csharp %}
public class ChildClass : ParentClass
{
public override Task DoSomething(Options options)
{
...
}
}
public class ParentClass : IInterface
{
public Func<Options, Task> OnDoSomething { get; set; }
public Func<Options, Task> OnDoSomethingElse { get; set; }
public virtual Task DoSomething(Options options)
{
return OnDoSomething(options);
}
public virtual Task DoSomethingElse(Options options)
{
return OnDoSomethingElse.Invoke(options);
}
}
{% endhighlight %}
Notice a couple of things.
1. ParentClass has several virtual methods. I'm only overriding one.
2. ParentClass is setup to allow you to override the virtual methods or to just supply
it with Funcs.
3. The implementation of the virtual methods is to simply call the corresponding Funcs.
### Finding the Location of the Problem
After lots of testing and debugging, I finally figured out that somehow a FuncProxy
(see line 6 in the error stack trace above) was being injected into all of the
public `Func<,>` properties on ParentClass. Then, when methods were called that
I did not override in my ChildClass, the Func would be called in the ParentClass
and that's where Windsor was hooking in and eventually causing an error when it
eventually tried to resolve a dependency on `Task`.
### One More Clue
As I mentioned above, the presence of a TypedFactoryInterceptor in the stack trace
was strange to me. I wasn't using Windor's TypedFactoryFacility to register my ChildClass.
So, why was it sticking its nose into my class somehow?
I did some simple debugging and turned off the TypedFactoryFacility in my Windsor setup.
Just like magic,
the error I was seeing went away. So, my conclusion: somehow TypedFactoryFacility was
inserting itself into my ChildClass registration and injecting `FuncProxy`s into the
ParentClass public `Func<,>` properties.
## The Solution
This looked an aweful lot like property injection to me. I started searching related to
TypedFactoryFacility and property injection. I pretty quickly came upon this documentation:
[https://github.com/castleproject/Windsor/blob/master/docs/how-properties-are-injected.md](https://github.com/castleproject/Windsor/blob/master/docs/how-properties-are-injected.md).
That page describes how Windsor does property injection, which I actually thought was
an opt-in feature of Windsor. In fact, Windsor does it by default, as describe on that page.
Combining this revelation (to me) with the fact that TypedFactoryFacility also allows you
to take a dependency on a simple `Func` and turn it into a factory call into your Windsor
container, I concluded that, by turning on the TypedFactoryFacility in Windsor, I was opening
up public properties that had `Func<>` types to property injection by Windsor.
Now, I hate property injection, so I was fine with just turning it off completely. Luckily,
the documentation link above also included instructions how to do it. I've included that code
here.
{% highlight csharp %}
var propInjector = Kernel.ComponentModelBuilder
.Contributors
.OfType<PropertiesDependenciesModelInspector>()
.Single();
Kernel.ComponentModelBuilder.RemoveContributor(propInjector);
{% endhighlight %}
I added this code into my Windsor container setup and tried again. Everything worked just
as expected.
## Lessons Learned
A couple of quick takeaways:
1. Windsor does property injection by default. Check out the documentation link above for
the criteria it uses to determine when it should or not.
2. Using TypedFactoryFacility is fantastic, but turn off property injection if you have
public `Func<>` properties anywhere.

View File

@@ -0,0 +1,40 @@
---
layout: post
title: "I Like Languages That Expect Me To Be a Great Programmer"
date: 2017-01-20 20:05:00
description: "Languages that are getting too 'safe'!"
categories: [programming]
tags: []
---
A co-worker of mine recently turned me on to Uncle Bob's blog. Yup, that's Uncle Bob Martin.
Here's one of his latest posts that sparked a thought in me: [The Dark Path](http://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath.html).
Make sure to read his follow-up post too: [Types and Tests](http://blog.cleancoder.com/uncle-bob/2017/01/13/TypesAndTests.html).
When I got done reading those posts, this was my simple thought: I like programming languages
that expect me to be a great programmer.
Think of anyone you know who is *great* at something. Is it an artist? A politician? A teacher?
I just watched _American Sniper_ so I'm thinking of someone like Chris Kyle. He was a great
sniper. His skill with a rifle was amazing.
Now, think of why that person is considered great at what they do. Is it because what they
do is easy? Probably not. It's probably because what they do is extremely difficult, but
they still do it well. Or, it may be something that's easy to do, but very difficult to
do *really* well. Think of shooting a rifle. It's very easy to shoot a rifle. Make sure it's
loaded. Aim it somewhere (safe). Pull the trigger. That's it. But how about hitting a target
100 yards away? How about 1000 yards? Yeah, that gets really hard. Only a *great*
marksman could do that.
The reason it's hard to be a great marksman is because of the flexibility of a rifle. It's
relatively simple design lets you take it almost anywhere and lets you aim it at almost anything.
The only way to make a rifle that even a novice could use to hit targets 1000 yards away
would be to mount it on some kind of stand, add advanced aiming software somehow and let it
pull the trigger. But, that makes it hard to use, doesn't it? You now at the least have a bulky tripod to
lug around a need for a power source for the computer to aim the thing.
That's how overly restrictive programming languages have become for the sake of safety. They're
getting unwieldy. I want a flexible language that may be harder to use when I'm a novice, but
once I'm an expert, it lets me do amazing things.

View File

@@ -0,0 +1,147 @@
---
layout: post
title: "Psalm 130"
date: 2020-05-12
description: "Psalm 130 is a plan for returning your thoughts to the Lord from being in the depths."
categories: [scripture]
tags: [psalms]
---
## Introduction
Psalm 130 records the thoughts of a believer who goes through a remarkable transformation: from the depths of despair to proclaiming hope in the Lord to his nation.
How does this transformation happen?
This is not a psalm about a unique event or a special tragedy or an especially remarkable man accomplishing a gargantuan feat that no one can again accomplish. This is not a super-Christian battling forces of darkness with fantastic spiritual super powers.
No, instead, it is the straightforward revelation of a man's internal thoughts as they transform from deep darkness to light; of despair to hope for the future; of desperation for his own rescue to confidence that others can be rescued as well.
What we see in this psalm are the thought patterns of a mature believer; patterns we ought to emulate and repeat in our own lives.
Now, when I say "mature believer", I want to make sure you've got the same picture that I do. I don't mean someone who "has it all figured out". I definitely don't mean someone who has reached some "higher spiritual plane". All I mean is someone who has walked with the Lord for a long time and has trained his thoughts in the patterns we'll see. He falls. He fails. He's done it before and he's experienced enough to recognize it and know how to get back up again.
If we copy these patterns of thought, they will drive us to strengthen our upward confidence in the Lord and then mature us to expand our thoughts outward toward our fellow men. But, it begins where it must: our inward thoughts, thoughts directed toward ourselves.
## Inward
Verses 1-2.
The author of this psalm begins by revealing the state of his mind: his inward thoughts. He uses only two words to do this: "the depths".
### He is in trouble
By themselves "the depths" might conjure up enough for us to imagine that he is in some kind of trouble. He's not feeling good. But, this word isn't just a word for how deep a hole might be. Everywhere else it's used in Scripture, it's used to describe deep waters and especially the depths of the sea. That's another kind of "depths" altogether.
If you've ever taken a swim you can begin to imagine the analogy he is painting. You've probably felt the slight panic to get back to the surface when you stayed under the water a little too long. You've perhaps felt the water pressure when you dove down to the deep end of the pool.
But, the swimming pool is nothing like the depths of the sea. When you get down deep, the light disappears. The sunlight fades until there is only utter blackness. If help was on the way to pull you out, you couldn't see it coming. You can't even see your hand in front of your face. You're surrounded by darkness. There are no waypoints. No landmarks to recognize. No signs to point the way of escape.
All comfort of heat vanishes the deeper you sink. Before much depth, the cold becomes unbearable.
As the waters above increase, the pressure mounts until it would easily crush you.
Then, of course, you cannot breath. In the depths of the sea you are far from the surface, far from breath. You will suffocate in a matter of minutes--certainly before you make it to the top on your own.
Darkness, cold, pressure, suffocation--it paints a desperate, hopeless, life-threatening situation. This man is in great trouble.
### He admits he's in trouble
But, notice the first thought pattern we can recognize of a mature believer. It's the one important thing he does first: he admits he's in trouble. There's no denial here. There's no trying to make the situation not seem so bleak. He's in trouble he cannot escape. He knows it and he says so. This is the first thing we can note as the sign of a mature believer. The mature believer makes honest confession of his desperate need. He frankly admits his trouble and makes no attempt to hide it from himself or from the Lord.
### He calls out to the Lord
The second thought pattern of a mature believer is quickly noticed next. Admitting his situation has freed him ask for help.
What would you do if you were sinking, with no one in sight to save you?
I think my first instinct would be to call out as loudly as I could for someone--anyone at all--to come rescue me!
My second oldest, Gideon, is not quite able to dress himself yet. When he gets stuck in his shirt or can't quite work the button on his pants, he has the habit of desperately calling out "Somebody! Anybody!". He doesn't care if me or Grace or even his brother (sometimes) helps him. He just needs someone to get this shirt off!
But, this is not exactly what he does here. He cries out. But he specifically calls out to only one person: the Lord. And there's no doubt who he means. He uses the name of the Lord, Yahweh. He pleads with Him to hear his voice, to hear the voice of his supplications; or, "pleas for mercy" as the ESV translates it.
It seems counterintuitive to call out for a specific person. If he's truly desperate wouldn't any help be welcome, no matter who it came from? But, this too is a thought pattern of a mature believer. He knows the One who not only can save him, but is willing to save him. There is only One such person and He is the Lord.
He is the one who will put it on the heart of your spouse to say the right kind word at the right time. Or burden a preacher with an exhortation from the Word that encourages you. Or embolden your brother to give you the strong admonition you need.
An interesting thing happens next. From a dire situation, his thoughts begin to shift. His calling out to the Lord has shifted his focus from his circumstances to the Lord Himself. He now begins to drift away from his inward thoughts and toward upward thoughts.
## Upward
Verses 3-6.
Notice how his thoughts are still on himself, but no longer inward. His thoughts have turned to who he is in light of his relationship to a holy God. They have turned upward.
### Sin and Forgiveness
What would it be like if the Lord marked our iniquities? What if he kept a record and closely watched them? It's obvious--our case would be hopeless! We'd have no chance of standing at that judgment.
Think of the things this reveals about his view of God in relation to himself:
- First, he knows he has sinned and does not hide it
- He knows his sin is serious, and it has fearful consequences
- He also knows the Lord knows these sins
- Beyond this, he knows he ought to be rightfully accountable to the Lord for these sins
- But, he also believes that the Lord does not keep a record of these sins
- More than that, he knows it's not a matter of the Lord misplacing the official sin record, but the marks are gone because there is forgiveness with the Lord
- As a result of this forgiveness, he fears the Lord
So, we can notice the third and fourth thought patterns of a mature believer.
First, he knows he is a sinner, forgiven by a loving God. This is how he stands, even though he has sinned. This is both a humbling admission and a strengthening encouragement and they are in a kind of balance. Thinking only of ourselves as sinners neglects the truth of God's work to forgive us. Thinking only of the forgiveness we have in the Lord neglects the truth of our sinfulness that necessitated His work in the first place.
Second, rather than a license to sin, this forgiveness leads him to fall fearfully before the Lord in humble worship. Perhaps, like me, you don't normally associate God's forgiveness with fearing Him. I, for one, am much more likely to associate it with relief or gratefulness or happiness.
But the connection is perhaps closer than we think. When we acknowledge that God is the one who forgives our sin, at the same time, we're acknowledging that He has the authority and the power to do so. The natural reaction to such a thought is to fear, follow and worship such a God who wields such power over us, but choses instead to forgive us.
The forgiveness of the Lord also frees a man to leave the things behind him (his sins, weaknesses and failures) and press on in the work of the Lord. In other words, it leads to a greater desire to follow the Lord. As we follow Him, we know Him better. As we know Him better, we can only fear Him more. This certainly seemed to be Paul's mindset in Philippians 3:
> **Philippians 3:1217**
>
> 12 Not that I have already obtained it or have already become perfect, but I press on so that I may lay hold of that for which also I was laid hold of by Christ Jesus.
> 13 Brethren, I do not regard myself as having laid hold of it yet; but one thing I do: forgetting what lies behind and reaching forward to what lies ahead,
> 14 I press on toward the goal for the prize of the upward call of God in Christ Jesus.
> 15 Let us therefore, as many as are perfect, have this attitude; and if in anything you have a different attitude, God will reveal that also to you;
> 16 however, let us keep living by that same standard to which we have attained.
> 17 Brethren, join in following my example, and observe those who walk according to the pattern you have in us.
Notice the connection and progression from leaving what lays behind to striving for the upward call to keeping an attitude of holy living.
### Waiting and Hoping
Earlier, in verses 1-2, the psalmist honestly admits his circumstances and this leads him to action: to call out to the Lord for rescue. Here too, having turned his thoughts upward to the Lord's forgiveness of his sins, he now takes action. He trains his thoughts on waiting for the Lord and hoping in His word.
There are at least two things to notice in these two verses. First, the repetition and second, the comparison to the watchmen.
It's interesting how he repeats, to himself, how he will wait for the Lord. It's almost as if he needs more than one reminder to wait for Him. Perhaps he is retraining his thoughts away from fearfulness and despair to an expectant, waiting hopefulness.
Keep in mind that we have no indication that his circumstances have changed at all. But his thinking is brightening considerably. His concentration is no longer on his circumstances, but on the Lord who certainly will rescue him.
Second, we should ask ourselves what the waiting of a watchman for the morning is like. Even for a new watchman, on his first day on the job, I can't imagine he has any question or uncertainty about the morning coming! There is a confidence that the sun will rise and the watch will end.
The fifth thought pattern of a mature believer now emerges. He trains his thoughts to confidently hope in the Lord, waiting on His rescue, no matter the circumstances.
As before, now another interesting things happens. As he moves away from inward thinking to upward thinking, he grows in his confidence to trust the Lord as he remembers the lovingkindness of the Lord. In that confidence his thoughts now turn completely away from himself and outward, towards his fellow countrymen.
## Outward
Verses 7-8.
Now, the mood of the psalmist has completely changed from where he started. Far from crying out in desperation for help, he is now calling out to his countrymen to hope in the Lord!
I think we can notice two more thought patterns of the psalmist to emulate.
### Seeing the needs of others
The first is a sense of responsibility to share his confidence in the Lord with others.
His thinking has been so transformed that, though we still have no indication that he has been rescued, he sees the need of his fellow man for the same trusting hope in the Lord. His own issues are no longer the only ones he sees. Others are in similar circumstances, similar "depths". So, he calls on them to hope based on two characteristics of the Lord: His lovingkindness and his abundant redemption.
### Confidence in the future
The second is a confidence in the future actions of the Lord.
How does he know that God will redeem Israel from all his iniquities?
I think the answer is relatively simple. He has seen the past actions of the Lord in his life. He has observed the character of the Lord worked out in his own life. Having drawn closer to the Lord, he knows Him better and can look out into the future, confident that the Lord will do the same then as He has done in the past.
## Conclusion
To summarize this psalm, I think we could say this. The mature believer is not someone who has reached a higher plane of Christian living, never finding himself in the depths, but always thinking upward and outward. I'm confident that every believer finds himself in the depths at some time or another and most likely even quite regularly.
Remember, the one writing this psalm, who I see as a very mature believer, is one who has fallen! But remember what Proverbs 24:16 says:
> **Proverbs 24:16**
>
> 16 For a righteous man falls seven times, and rises again, But the wicked stumble in time of calamity.
Rising again is what the mature believer does. But how? That is what this Psalm spells out for us.
The mature believer is the one whose has learned certain patterns of thought to a consistent way of thinking that does one simple thing: it always turns him to the Lord for rescue. This then enables him to grow his thoughts from necessary introspection (those inward thoughts), upward, to the joy of the reality of his relationship with the Lord. It also emboldens him to reach outward to share with those around him the bounty of hope and lovingkindness available in the Lord.

View File

@@ -0,0 +1,462 @@
---
layout: post
title: "Iterating All EPiServer Catalog MetaObjects"
date: 2020-05-27
description: "C# iterators to walk through all EPiServer catalog node and entry content meta objects."
categories: [programming]
tags: [episerver,csharp,.net]
---
If you're maintaining an e-commerce-enabled site in EPiServer and you're using the built-in catalog, then
you have probably found the need to incorporate external data into the catalog, perhaps from various sources.
Such is the case for me on an EPiServer site I've been working on over the last (almost) four years. The primary
data for the catalog comes into EPiServer from an external PIM system. But, then there is all sorts of ancillary
data that needs to calculated from existing fields or pulled from external sources. These external data sources
don't really provide a way to incrementally apply deltas for the data they provide. The only option to make updates is to walk
through the entire catalog and see if anything needs updating when matched against the external data I've pulled.
This external data is stored in the catalog as custom EPiServer MetaFields. So,
a need arose to easily walk through the entire catalog, pick up every MetaObject (to update the custom MetaFields)
in every available catalog culture and make the appropriate updates.
Below are the iterators I wrote to accomplish this. They let you walk the entire catalog tree (in a breadth-first manner)
starting from the top as if you were iterating a simple C# array or list.
## High-Level Usage
Because all we're doing is writing iterators, the high-level usage looks just like you were interating through
an `IEnumerable` in C# like an array or a `List<T>`.
The iterators are implemented as extension methods on the `ICatalogSystem` interface. So, iterating through all
of the catalog entry MetaObjects looks like this.
{% highlight csharp %}
using Mediachase.Commerce.Catalog;
using Mediachase.MetaDataPlus;
ICatalogSystem catalog = CatalogContext.Current;
MetaDataContext mdc = MetaDataContext.Instance;
foreach ((MetaObject metaObject, MetaDataContext metaDataContext) entryMO in catalog.AllCatalogSystemEntryMetaObjects(mdc))
{
entryMO.metaObject["SomeCustomMetaFieldName"] = "external data source value here";
entryMO.metaObject.AcceptChanges(entryMO.metaDataContext);
}
{% endhighlight %}
I implemented the separate iterators for NodeContent MetaObjects and the EntryContent
MetaObjects. You easily use them in combination to iterate through the MetaObjects of the entire catalog tree.
Note a couple of things from the example code above:
1. You have to give the iterator the MetaDataContext. As we'll see in the iterator code, it will copy this MDC for each catalog language so that you get a MetaObject for every culture present in your catalog.
1. Because of #1 above, the current MetaDataContext is passed to you during iteration so that you know which catalog culture this MetaObject is for.
## Extension Method Code
The extension method code for AllCatalogSystemEntryMetaObjects is pretty simple. It uses four iterators in nested foreach loops to loop through
1. All catalogs
1. All nodes
1. All entries
1. All meta objects
{% highlight csharp %}
public static IEnumerable<(MetaObject metaObject, MetaDataContext metaDataContext)> AllCatalogSystemEntryMetaObjects(
this ICatalogSystem catalogSystem,
MetaDataContext metaDataContext)
{
foreach (var catalog in new CatalogSystemCatalogs(catalogSystem))
{
foreach (var node in new CatalogSystemNodes(catalogSystem, catalog.CatalogId))
{
foreach (var entry in new CatalogSystemEntries(catalogSystem, catalog.CatalogId, node.CatalogNodeId))
{
foreach (var tuple in new CatalogSystemEntryMetaObjects(metaDataContext, catalog, entry))
{
yield return tuple;
}
}
}
}
}
{% endhighlight %}
I broke the iteration down into separate iterators in this fashion so that I could easily iterate through all the entries (for example) in a single node, if I knew from the beginning which node I wanted to start at. If that was the case, I could easily write another extension method that took a node ID (or node code) as a parameter, use the ICatalogSystem to find that node and then pass the node ID to the CatalogSystemEntries iterator to iterate just the entries under that node.
## Iterators
We have four iterators in use:
1. CatalogSystemCatalogs - iterates through all catalogs
1. CatalogSystemNodes - iterates through all nodes, in a breadth-first fashion, in a catalog
1. CatalogSystemEntries - iterates through all entries in a node
1. CatalogSystemEntryMetaObjects - iterates through all MetaObjects for an entry
### CatalogSystemCatalogs Iterator
The code is pretty simple. We implement an IEnumerable which uses our implementation of an IEnumerator.
The IEnumerator simply gets all the catalogs from the ICatalogSystem and, as you call MoveNext() (usually done for you by the foreach loop), increments an index value to give you the current CatalogDto.CatalogRow to work with.
{% highlight csharp %}
public class CatalogSystemCatalogs : IEnumerable<CatalogDto.CatalogRow>
{
private readonly CatalogSystemCatalogEnumerator _enumerator;
public CatalogSystemCatalogs(ICatalogSystem catalogSystem)
{
_enumerator = new CatalogSystemCatalogEnumerator(catalogSystem);
}
public IEnumerator<CatalogDto.CatalogRow> GetEnumerator() => _enumerator;
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
}
public class CatalogSystemCatalogEnumerator : IEnumerator<CatalogDto.CatalogRow>
{
private int _currentIndex = 0;
private readonly ICatalogSystem _catalogSystem;
private Lazy<Lst<CatalogDto.CatalogRow>> _catalogs;
public CatalogSystemCatalogEnumerator(ICatalogSystem catalogSystem)
{
_catalogSystem = catalogSystem;
_catalogs = new Lazy<Lst<CatalogDto.CatalogRow>>(GetCatalogs);
}
public CatalogDto.CatalogRow Current
{
get
{
return _catalogs.Value[_currentIndex];
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
Reset();
}
public bool MoveNext()
{
if (_catalogs.IsValueCreated)
{
_currentIndex++;
}
if (_currentIndex >= _catalogs.Value.Count)
{
return false;
}
return true;
}
public void Reset()
{
_catalogs = new Lazy<Lst<CatalogDto.CatalogRow>>(GetCatalogs);
_currentIndex = 0;
}
private Lst<CatalogDto.CatalogRow> GetCatalogs()
=> _catalogSystem.GetCatalogDto().Catalog.Freeze();
}
{% endhighlight %}
### CatalogSystemNodes Iterator
For the nodes iterator, things get a little more complicated. Nodes can be nested, so we have tree structure we have to navigate. We use a Queue to walk through the nodes in a breadth-first fashion.
I like to use as many functional programming techniques as I can in C# these days. I use the great [LanguageExt](https://github.com/louthy/language-ext) library for this. This library is where the `Que<T>`, `Option<T>` and `Lst<T>` data structures come from. I don't want to go in depth about what they are, but to understand the code below, you should now this: `Que<T>` is an immutable `Queue<T>`, `Lst<T>` is an immutable `List<T>` and `Option<T>` is basically the functional way to do the null-object pattern (instead of dealing with nulls, use an object to represent "I don't have a value").
If you're not familiar with breadth-first tree traversals, it's not too complicated. What we do first is get all the nodes at the top level (in our case, all the nodes that are direct children of the catalog we pass in) and put them in the queue. Then, we iterate through each of the nodes we put in the queue and add their child nodes to the queue. We do this until we run out of nodes to process in the queue which indicates we've gone through all nodes in the tree.
{% highlight csharp %}
public class CatalogSystemNodes : IEnumerable<CatalogNodeDto.CatalogNodeRow>
{
private readonly CatalogSystemNodeEnumerator _enumerator;
public CatalogSystemNodes(ICatalogSystem catalogSystem, int catalogId, int startingParentNodeId = 0)
{
_enumerator = new CatalogSystemNodeEnumerator(catalogSystem, catalogId, startingParentNodeId);
}
public IEnumerator<CatalogNodeDto.CatalogNodeRow> GetEnumerator() => _enumerator;
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
}
public class CatalogSystemNodeEnumerator : IEnumerator<CatalogNodeDto.CatalogNodeRow>
{
private Que<int> _nodeQue;
private readonly ICatalogSystem _catalogSystem;
private readonly int _catalogId;
private readonly int _startingParentNodeId;
private Option<CatalogNodeDto.CatalogNodeRow> _current;
// We have a parameter for the starting node ID (startingParentNodeId) which defaults to 0. If you
// pass in a node ID, we'll start iterating child nodes of that node. If you don't pass a value (keep
// the default of 0), then we'll iterate through all nodes in the catalog.
public CatalogSystemNodeEnumerator(ICatalogSystem catalogSystem, int catalogId, int startingParentNodeId = 0)
{
_catalogSystem = catalogSystem;
_catalogId = catalogId;
_startingParentNodeId = startingParentNodeId;
_nodeQue = Prelude.Queue(_startingParentNodeId);
_current = Option<CatalogNodeDto.CatalogNodeRow>.None;
}
// ValueUnsafe() lets us get the value of _current (which is an Option<T>)
// or null if the Option<T> has no value.
public CatalogNodeDto.CatalogNodeRow Current => _current.ValueUnsafe();
object IEnumerator.Current => Current;
public void Dispose()
{
Reset();
}
public bool MoveNext()
{
// end-case scenario--we've run out of nodes in the queue, so we're
// done traversing the node tree
if (!_nodeQue.Any())
{
return false;
}
int nextNodeId = GetNextNodeIdAndUpdateQue();
// special starting condition when we want to do the entire tree
// and no other starting node was passed in
if (nextNodeId == 0)
{
// special case of there being a catalog node with no category nodes beneath it
if (!_nodeQue.Any())
{
return false;
}
nextNodeId = GetNextNodeIdAndUpdateQue();
}
_current = GetNode(nextNodeId);
return _current.IsSome;
}
private int GetNextNodeIdAndUpdateQue()
{
// get the next node ID from the queue
int nextNodeId = _nodeQue.Peek();
// remove the retrieved node from the queue
_nodeQue = _nodeQue.Dequeue();
// get all of this node's children and add their IDs to the queue
_nodeQue = GetChildren(nextNodeId)
.Fold(_nodeQue, (q, nodeRow) => q.Enqueue(nodeRow.CatalogNodeId));
return nextNodeId;
}
private Option<CatalogNodeDto.CatalogNodeRow> GetNode(int nodeId)
=> _catalogSystem.GetCatalogNodeDto(nodeId).CatalogNode.HeadOrNone();
private Lst<CatalogNodeDto.CatalogNodeRow> GetChildren(int nodeId)
=> _catalogSystem.GetCatalogNodesDto(_catalogId, nodeId).CatalogNode.Freeze();
public void Reset()
{
_nodeQue = Prelude.Queue(_startingParentNodeId);
_current = Option<CatalogNodeDto.CatalogNodeRow>.None;
}
}
{% endhighlight %}
### CatalogSystemEntries Iterator
Since entries cannot be arrange in a nested fashion, this iterator is a little less complicated than the nodes iterator. All we do is take in a catalogId and a catalogNodeId and retrieve all the entries below the given node ID, passing them to you one at a time as you call MoveNext().
The only mildly complex thing here is that we use a `Lazy<T>` so that we're not retrieving the entries immediately when you instanstiate the IEnumerator. We only load the entries when you MoveNext() and access Current which indicates you really do want some entries.
{% highlight csharp %}
public class CatalogSystemEntries : IEnumerable<CatalogEntryDto.CatalogEntryRow>
{
private readonly CatalogSystemEntryEnumerator _enumerator;
public CatalogSystemEntries(ICatalogSystem catalogSystem, int catalogId, int catalogNodeId)
{
_enumerator = new CatalogSystemEntryEnumerator(catalogSystem, catalogId, catalogNodeId);
}
public IEnumerator<CatalogEntryDto.CatalogEntryRow> GetEnumerator() => _enumerator;
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
}
public class CatalogSystemEntryEnumerator : IEnumerator<CatalogEntryDto.CatalogEntryRow>
{
private int _currentIndex = 0;
private readonly ICatalogSystem _catalogSystem;
private Lazy<Lst<CatalogEntryDto.CatalogEntryRow>> _entries;
private readonly int _catalogNodeId;
private readonly int _catalogId;
// we take in the catalogNodeId to start at so that you could use this IEnumerator
// to collect entries at any starting node in the catalog.
public CatalogSystemEntryEnumerator(ICatalogSystem catalogSystem, int catalogId, int catalogNodeId)
{
_catalogSystem = catalogSystem;
// we use a Lazy<T> object here so that we don't do a database call
// as soon as you instantiate this class. We only do it once you
// access Current, indicating you really want to iterate the entries now
_entries = new Lazy<Lst<CatalogEntryDto.CatalogEntryRow>>(GetEntries);
_catalogNodeId = catalogNodeId;
_catalogId = catalogId;
}
public CatalogEntryDto.CatalogEntryRow Current
{
get
{
return _entries.Value[_currentIndex];
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
Reset();
}
public bool MoveNext()
{
// the first MoveNext() call will be when when _entries doesn't have
// a value yet (but will after Current is accessed). So, this leaves
// the _currentIndex at 0 the first time MoveNext() is called.
if (_entries.IsValueCreated)
{
_currentIndex++;
}
// we just incremented _currentIndex to the count of the _entries list,
// so we return false to indicate there are no more values. We really could
// use == here instead of >=. I guess >= feels safer for some reason.
if (_currentIndex >= _entries.Value.Count)
{
return false;
}
return true;
}
public void Reset()
{
_entries = new Lazy<Lst<CatalogEntryDto.CatalogEntryRow>>(GetEntries);
_currentIndex = 0;
}
private Lst<CatalogEntryDto.CatalogEntryRow> GetEntries()
=> _catalogSystem.GetCatalogEntriesDto(_catalogId, _catalogNodeId).CatalogEntry.Freeze();
}
{% endhighlight %}
### CatalogSystemEntryMetaObjects Iterator
The MetaObjects iterator is a different than the other iterators in that it doesn't really iterate through a list or tree of MetaObjects. Instead, it iterates through all fo the MetaObject instances for a particular entry--one instance for reach culture available for the catalog.
So, as you'll see in the code below, one of the first things we do is grab all of the languages for the catalog so that we know what we need to interate through.
{% highlight csharp %}
public class CatalogSystemEntryMetaObjects : IEnumerable<(MetaObject metaObject, MetaDataContext metaDataContext)>
{
private readonly CatalogSystemEntryMetaObjectsEnumerator _enumerator;
public CatalogSystemEntryMetaObjects(MetaDataContext metaDataContext, CatalogDto.CatalogRow catalog, CatalogEntryDto.CatalogEntryRow entry)
{
_enumerator = new CatalogSystemEntryMetaObjectsEnumerator(metaDataContext, catalog, entry);
}
public IEnumerator<(MetaObject metaObject, MetaDataContext metaDataContext)> GetEnumerator() => _enumerator;
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
}
public class CatalogSystemEntryMetaObjectsEnumerator : IEnumerator<(MetaObject metaObject, MetaDataContext metaDataContext)>
{
private int _currentIndex = 0;
private readonly CatalogEntryDto.CatalogEntryRow _entry;
private readonly MetaDataContext _metaDataContext;
private Lazy<Lst<CultureInfo>> _catalogLanguages;
private readonly CatalogDto.CatalogRow _catalog;
public CatalogSystemEntryMetaObjectsEnumerator(MetaDataContext metaDataContext,
CatalogDto.CatalogRow catalog,
CatalogEntryDto.CatalogEntryRow entry)
{
_catalog = catalog;
_entry = entry;
_metaDataContext = metaDataContext;
_catalogLanguages = new Lazy<Lst<CultureInfo>>(GetCatalogLanguages);
}
public (MetaObject metaObject, MetaDataContext metaDataContext) Current
{
get
{
// what we're really iterating through is the catalog languages.
// as we iterate through them, we grab the associate MetaObject for the current
// language for the given entry
CultureInfo currentCultureInfo = _catalogLanguages.Value[_currentIndex];
var mdc = _metaDataContext.Clone();
mdc.UseCurrentThreadCulture = false;
mdc.Language = currentCultureInfo.Name;
return (MetaObject.Load(mdc, _entry.CatalogEntryId, _entry.MetaClassId), mdc);
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
Reset();
}
public bool MoveNext()
{
// As with the Lazy<T> for iterating through the entries, the first tie MoveNext() is called
// the _catalogLanguages Lazy<T> will not have a value. It will after the first Current access.
// So, for the first MoveNext(), we want to keep the _currentIndex at 0, which is what this if-statement
// accomplishes.
if (_catalogLanguages.IsValueCreated)
{
_currentIndex++;
}
if (_currentIndex >= _catalogLanguages.Value.Count)
{
return false;
}
return true;
}
public void Reset()
{
_catalogLanguages = new Lazy<Lst<CultureInfo>>(GetCatalogLanguages);
_currentIndex = 0;
}
private Lst<CultureInfo> GetCatalogLanguages()
=> _catalog.GetCatalogLanguageRows()
.Map(lr => new CultureInfo(lr.LanguageCode))
.Freeze();
}
{% endhighlight %}
## Warning
Even though the above code will give you the convenience of iterating through your entire EPiServer catalog(s), be warned! You could easily forget what the IEnumerators really do above and do something like this:
{% highlight csharp %}
// REALLY BAD CODE, DON'T DO
List<(MetaObject metaObject, MetaDataContext mdc)> allTheMetaObjects = _catalogSystem.AllCatalogSystemEntryMetaObjects(mdc).ToList();
{% endhighlight %}
I hope you see the danger of this. If you have a large catalog, you're loading the ENTIRE set of MetaObjects into memory in your `allTheMetaObjects` list. This probably won't be good for your application. If you have a small catalog, this might not be a big deal. But, keep this in mind!

16
src/posts/organize.ps1 Normal file
View File

@@ -0,0 +1,16 @@
$posts = gci ./ -filter *.md
$posts | % {
$filename = $_.Name
$parts = $filename.split("-")
$year = $parts[0]
$month = $parts[1]
$day = $parts[2]
$filename = $filename -replace "$year-$month-$day-",""
$newDir = "./$year/$month/$day"
new-item $newDir -itemtype directory -ErrorAction SilentlyContinue
move-item $_.FullName "$newDir/$filename"
}

13
src/templates/page.js Normal file
View File

@@ -0,0 +1,13 @@
import React from "react";
import Layout from "../components/layout"
export default function Page({ children }) {
return (
<Layout>
<article class="page">
<h1 class="page-title">page.title</h1>
{children}
</article>
</Layout>
);
}

71
src/templates/post.js Normal file
View File

@@ -0,0 +1,71 @@
import React from "react";
import { graphql } from "gatsby";
import Layout from "../components/layout";
import { DiscussionEmbed } from 'disqus-react';
export default function Post({ data }) {
const post = data.markdownRemark;
var tags = post.frontmatter.tags.map((tag) => {
return (<a href={'/tags/' + tag }>#{tag}</a>);
});
var categories = post.frontmatter.categories.map((category) => {
return (<a href={'/categories/' + category }>#{category}</a>);
});
var postDate = new Date(post.frontmatter.date);
console.log(postDate);
var isoDate = postDate.toISOString();
var visibleDate = postDate.toDateString();
return (
<Layout>
<div>
<article className="post">
<h1 className="post-title">{post.frontmatter.title}</h1>
<div className="post-info d-flex justify-content-start">
<time datetime={isoDate} className="post-date">{visibleDate}</time>
<nav className="post-tags">
{tags}
</nav>
<nav className="post-categories">
<span>posted in:</span>
{categories}
</nav>
</div>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
<DiscussionEmbed
shortname='fishybusiness'
config={
{
url: "https://" + data.site.host + ":" + data.site.port,
identifier: post.slug,
title: post.frontmatter.title,
language: 'en_US'
}
}/>
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
date
categories
tags
}
}
site {
host
port
}
}
`