Generate PDF with different encodings using iTextSharp

Let’s assume a following scenario in iTextSharp:
You need to generate a PDF, which would contain text in two or three languages, each of them having its own special characters – for example: German, Italian and Polish (or another Eastern European language).
If you use the standard way of getting fonts, like:

[csharp]
BaseFont baseFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, false);
[/csharp]

…you will stumble upon a problem, that, using the CP1252 encoding (Latin), the Italian characters with accents are displayed, the German „Umlaute” are displayed, but the Eastern European special characters are not rendered at all in the PDF. On the other hand, using CP1250 encoding (Eastern European), displays the Eastern European characters, but not the Italian special characters with accents, for instance.

First idea would be to use some Unicode or UTF-8 to solve the problem – the „Identity-H” encoding option of iText sounds interesting here. But not with the Helvetica that’s shipped by default.

We could give Arial Unicode a try then, but it is not listed in the enumeration of BaseFont of iText.

Let’s load the font from disk then, and embed it into the document, making sure that all clients will be able to read it properly.

Here is my code snippet that does the trick:

[csharp]
String encoding = BaseFont.IDENTITY_H;
String fontName = "ARIALUNI.TTF";
String fontProjectPath = "MyProject.Namespace.Path." + fontName; // this path contains first the default namespace of your assembly, then the folder path within the project

// loading font from an embedded resource file, in order to avoid any file-system dependencies
using (Stream fontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fontProjectPath))
{
using (MemoryStream ms = new MemoryStream())
{
fontStream.CopyTo(ms);
BaseFont baseFont = BaseFont.CreateFont(fontName, encoding, BaseFont.EMBEDDED, BaseFont.CACHED, ms.ToArray(), null);
}
}
[/csharp]

What happens here? It loads the file from the assembly, into memory, and creates the font from the loaded file, embedding it additionally into the PDF document.
Now the document contains both Eastern European and Latin special characters.

Hope this helps.
Lukasz

Exposing SharePoint calendars for iCalendar clients

Hello there,

since Sharepoint offers out-of-the-box calendar and scheduling capabilities, it is a nice option for teams in terms of improved collaboration and sharing common agendas. By default, users can access the MOSS calendars using a browser or by syncing it with their Microsoft Outlook clients. Additionally, a RSS subscription is also available.
So far, so good, but if we need to subscribe to the calendar using a Mac or an iPhone, iPad, or any other client supporting the iCalendar specification, we have to use a custom solution.

Simply put, a file with the content type text/calendar needs to be generated, and it has to more or less comply with the RFC 2445 specification. Of course you may want to implement it your way from scratch, but there’s a nice iCal Exporter kit from CodePlex. It has been developed as a MOSS feature, but you can easily adapt it to serve users in a different way, for example as a custom http handler. So one can create a class inheriting the IHttpHandler interface, deploy the class library onto the Sharepoint application, and register the handler in web.config:

<add verb="GET,POST" path="*/ical.ics" type="MyLibrary.MyIcalHandler" />

Then we could access the generated file for example via http://myhost/mysite/lists/calendar/ical.ics , and subscribe to the calendar in a corresponding client software. In the handler itself, based on the request URL, we can fetch the corresponding Sharepoint list, iterate through its items and generate the proper entries, then flush it to the browser. The iCal Exporter kit also deals with recurring calendar entries, deleted event series’ occurences, all-day events, so that almost every case is covered.

One thing worth mentioning: when generating an event entry, be careful using the DTSTAMP property. In order for iCal clients to properly recognize changes made to calendar entries, one has to assign the last-modified-date property of the corresponding SPListItem as DTSTAMP value:

 foreach (SPListItem item in calendarList.Items)
 {
    // write vevent start...
    DateTime modified = Convert.ToDateTime(item["Modified"]);
    String dtstamp = "DTSTAMP:" + modified.ToString("yyyyMMddTHHmmssZ");
    // write dtstamp, other properties, vevent end
 }

Otherwise you may encounter a problem that the client won’t fetch a calendar entry’s changes.

Hope this helps,
Łukasz

Finding Active Directory user’s group membership in C#

Hi there,

there are a couple of ways to programmatically find a user and groups he belongs to in Active Directory. Recently I tested a few of them and here are some thoughts of what I found out.

DirectorySearcher

The System.DirectoryServices namespace provides us with a DirectorySearcher class. Filter property of that class can be used in order to specify the search query on the entire directory. An example filter for a user with login name ‘lkarolak’ could look like this:

(&(objectClass=user)(SAMAccountName=lkarolak))

If search is successful, the FindOne() method of the DirectorySearcher class should return an object of type DirectoryEntry. Finding this object’s membership requires iterating through its properties, finding the ones with name ‘memberOf’, and then (if needed) also performing some recursion in order to find out the nested group membership. After all, a bit complicated and quite resource-costly.

SearchRequest

Similar approach could be to use the SearchRequest and SearchResponse objects (this time from System.DirectoryServices.Protocols namespace), which are executed within a LDAP connection. The filter for the query looks just as in the previous example. Also in this case one has to recursively search within the result class’ (SearchResultEntry) attributes in order to get all the user’s nested groups.

Here’s a small example:

// establish a connection to LDAP
LdapDirectoryIdentifier id = new LdapDirectoryIdentifier(domain, port);
LdapConnection _connection = new LdapConnection(id);
_connection.SessionOptions.SecureSocketLayer = secureConnection;
_connection.AuthType = AuthType.Basic;
_connection.Credential = new NetworkCredential(ldapUser, ldapPassword);
_connection.Bind();

// distinguished name of the object 
// at which to start the search.
String _target = "dc=EXAMPLE,dc=COM";

String filter = "(&(objectCategory=person)(SAMAccountName=lkarolak))";
String[] attributesToReturn = { "SAMAccountName", "memberOf", "cn" };

SearchRequest searchRequest = new SearchRequest(_target, filter,
      SearchScope.Subtree, attributesToReturn);
SearchResponse response =
      (SearchResponse)_connection.SendRequest(searchRequest);

if (response.Entries.Count == 1)
{
  SearchResultEntry entry = response.Entries[0];
  for (int index = 0; index < entry.Attributes["memberOf"].Count; index++)
  {
      // get the group name, for example:
      String groupName = entry.Attributes["memberOf"][index].ToString();
  }
}

GroupPrincipal

The most interesting and straightforward solution to me was, however, another approach. Within the namespace System.DirectoryServices.AccountManagement we can find an easy way to find a user in AD and check his group memberships. Without having to recursively loop over the parent groups, we’re able to fetch the groups of the user, an much more data. The only constraint is that the code has to be run on a machine that is located within the domain. Let’s have a look at this sample code:

// "company" is the domain we would like to search in
PrincipalContext pc = new PrincipalContext(ContextType.Domain, "COMPANY");

// get the user of that domain by his username, within the context
UserPrincipal up = UserPrincipal.FindByIdentity(pc, username);

// fetch the group list
PrincipalSearchResult groups = up.GetAuthorizationGroups();
GroupPrincipal[] filteredGroups = (from p in groups
           where p.ContextType == ContextType.Domain
           && p.Guid != null
           && p is GroupPrincipal
           && ((GroupPrincipal)p).GroupScope == GroupScope.Universal
           select p as GroupPrincipal).ToArray();

The last lines actually do the trick. The GetAuthorizationGroups() method would fetch all the security groups of the user. If we would also like to have the distribution groups of the user, we’d have to use the GetGroups() method instead. Of course one could want to filter out some groups, like „Everyone” etc., maybe with help of a LINQ query like here, or in another way.
Anyway, the GroupPrincipal object returned contains the properties we need in order to get the name of the groups of a user (first of all, the DistinguishedName property).

After some unit tests done, it seems also that this last method is the fastest approach of those three mentioned here, probably also due to lack of recursive functions.

Hope this helps,
Łukasz

Custom tags and attributes in a SyndicationItem

Hello again,

for those of you who are not quite satisfied with the standard syndication functionality of .Net, here is a small tip on how to extend the XML generated from the SyndicationFeed class in C#.

Let’s assume we would like to have a RSS feed, which in fact would serve as a podcast, e.g. for iTunes. The software from Apple uses some information from their custom-defined RSS-tags, with an “itunes” prefix, for example:

<itunes:author>Anonymous One</itunes:author>

Without this prefix it’s very easy. Our SyndicationItem class provides us a functionality to extend the standard item’s elements:

SyndicationItem item = new SyndicationItem();
item.ElementExtensions.Add("customTag", String.Empty, "My test");

The second attribute is the namespace which comes into play in the next step. In order to add the tag prefix as mentioned before, one has start with adding the namespace to the feed instance:

SyndicationFeed feed = new SyndicationFeed();
XmlQualifiedName n=new XmlQualifiedName("itunes","http://www.w3.org/2000/xmlns/");
String itunesNs = "http://www.itunes.com/dtds/podcast-1.0.dtd";
feed.AttributeExtensions.Add(n, itunesNs);

Now that we have added the new namespace to the feed, we can start adding custom item elements within that namespace.

SyndicationItem item = new SyndicationItem();
item.ElementExtensions.Add(new SyndicationElementExtension("author",
     itunesNs, "Famous author"));

That should solve the issue with custom tags with custom prefixes. One more thing.. it may be quite useful for those which attach media files to their podcasts: the enclosure tag.
In order to add such an element to the SyndicationItem, we can use the SyndicationLink object as follows:

SyndicationLink enclosure =
           SyndicationLink.CreateMediaEnclosureLink(
               new Uri("http://example.com/01.mp3"), "audio/mpeg", 1200);
item.Links.Add(enclosure);

The method takes three arguments: the URL , type (MIME-type) and length of media attachment.

Hope this helps.