Alpha's Manifesto

A black and white figure's thought-hive

Crear sitios de SharePoint programáticamente

El objetivo

Estuve peleando estos días con la posibilidad de crear sitios de SharePoint programáticamente desde mi código. Sin embargo, aún cuando esto fue hecho para un proyecto interno, tomé tanto de la internet que realmente siento que debo devolver algo a la comunidad. Además, no voy a divulgar nada relacionado al proyecto, así que aquí vamos.

Primero, buscando en internet una forma de crear estos sitios programáticamente, me topé con un artículo del 2007 de WinSmarts.com, llamado Programatically create a Sharepoint site based on a site definition. Aquí está el código de ese post:

[sourcecode language=”csharp”]
public static bool CreateSite(string parentSiteURL, string siteURLRequested,
string siteTitle, string siteTemplateName)
{
bool returnCondition = false; // Assume failure.
const Int32 LOCALE_ID_ENGLISH = 1033;
using (SPSite siteCollection = new SPSite(parentSiteURL))
{
SPWeb parentWeb = siteCollection.OpenWeb();
SPWebTemplateCollection Templates = siteCollection.GetWebTemplates(Convert.ToUInt32(LOCALE_ID_ENGLISH));
SPWebTemplate siteTemplate = Templates[siteTemplateName];
if (parentWeb.Webs[siteURLRequested].Exists)
{
parentWeb.Webs.Delete(siteURLRequested);
}
parentWeb.Webs.Add(siteURLRequested, siteTitle, "", Convert.ToUInt32(LOCALE_ID_ENGLISH), siteTemplate, false, false);

// All is good?
returnCondition = true;
}
return returnCondition;
}
[/sourcecode]

Lo que yo quería cambiar de esta solución era:

  • Los Locales estaban hardcodeados
  • Los Site templates estaban hardcodeados
  • La descripción del sitio estaba hardcodeado (esa es la cadena vacía en la llamada a webs.add)

Removiendo literales…

Entonces fui a revisar la lista de locales: SharePoint Locale ID (LCID) Table, y una lista de los identificadores de site templates, en un artículo llamado Create a site programmatically in moss.

Bueno… aquí vamos (cuidado, vienen enumeraciones largas):

[sourcecode language=”csharp”]
public enum SPLocales: uint
{
Afrikaans /*af*/ = 1078,
Albanian /*sq*/ = 1052,
Arabic_UnitedArabEmirates /*ar-ae*/ = 14337,
Arabic_Bahrain /*ar-bh*/ = 15361,
Arabic_Algeria /*ar-dz*/ = 5121,
Arabic_Egypt /*ar-eg*/ = 3073,
Arabic_Iraq /*ar-iq*/ = 2049,
Arabic_Jordan /*ar-jo*/ = 11265,
Arabic_Kuwait /*ar-kw*/ = 13313,
Arabic_Lebanon /*ar-lb*/ = 12289,
Arabic_Libya /*ar-ly*/ = 4097,
Arabic_Morocco /*ar-ma*/ = 6145,
Arabic_Oman /*ar-om*/ = 8193,
Arabic_Qatar /*ar-qa*/ = 16385,
Arabic_SaudiArabia /*ar-sa*/ = 1025,
Arabic_Syria /*ar-sy*/ = 10241,
Arabic_Tunisia /*ar-tn*/ = 7169,
Arabic_Yemen /*ar-ye*/ = 9217,
Armenian /*hy*/ = 1067,
Azeri_Latin /*az-az*/ = 1068,
Azeri_Cyrillic /*az-az*/ = 2092,
Basque /*eu*/ = 1069,
Belarusian /*be*/ = 1059,
Bulgarian /*bg*/ = 1026,
Catalan /*ca*/ = 1027,
Chinese_China /*zh-cn*/ = 2052,
Chinese_HongKongSAR /*zh-hk*/ = 3076,
Chinese_MacauSAR /*zh-mo*/ = 5124,
Chinese_Singapore /*zh-sg*/ = 4100,
Chinese_Taiwan /*zh-tw*/ = 1028,
Croatian /*hr*/ = 1050,
Czech /*cs*/ = 1029,
Danish /*da*/ = 1030,
Dutch_TheNetherlands /*nl-nl*/ = 1043,
Dutch_Belgium /*nl-be*/ = 2067,
English_Australia /*en-au*/ = 3081,
English_Belize /*en-bz*/ = 10249,
English_Canada /*en-ca*/ = 4105,
English_Caribbean /*en-cb*/ = 9225,
English_Ireland /*en-ie*/ = 6153,
English_Jamaica /*en-jm*/ = 8201,
English_NewZealand /*en-nz*/ = 5129,
English_Phillippines /*en-ph*/ = 13321,
English_SouthAfrica /*en-za*/ = 7177,
English_Trinidad /*en-tt*/ = 11273,
English_UnitedKingdom /*en-gb*/ = 2057,
English_UnitedStates /*en-us*/ = 1033,
Estonian /*et*/ = 1061,
Farsi /*fa*/ = 1065,
Finnish /*fi*/ = 1035,
Faroese /*fo*/ = 1080,
French_France /*fr-fr*/ = 1036,
French_Belgium /*fr-be*/ = 2060,
French_Canada /*fr-ca*/ = 3084,
French_Luxembourg /*fr-lu*/ = 5132,
French_Switzerland /*fr-ch*/ = 4108,
Gaelic_Ireland /*gd-ie*/ = 2108,
Gaelic_Scotland /*gd*/ = 1084,
German_Germany /*de-de*/ = 1031,
German_Austria /*de-at*/ = 3079,
German_Liechtenstein /*de-li*/ = 5127,
German_Luxembourg /*de-lu*/ = 4103,
German_Switzerland /*de-ch*/ = 2055,
Greek /*el*/ = 1032,
Hebrew /*he*/ = 1037,
Hindi /*hi*/ = 1081,
Hungarian /*hu*/ = 1038,
Icelandic /*is*/ = 1039,
Indonesian /*id*/ = 1057,
Italian_Italy /*it-it*/ = 1040,
Italian_Switzerland /*it-ch*/ = 2064,
Japanese /*ja*/ = 1041,
Korean /*ko*/ = 1042,
Latvian /*lv*/ = 1062,
Lithuanian /*lt*/ = 1063,
FYRO_Macedonian /*mk*/ = 1071,
Malay_Malaysia /*ms-my*/ = 1086,
Malay_Brunei /*ms-bn*/ = 2110,
Maltese /*mt*/ = 1082,
Marathi /*mr*/ = 1102,
Norwegian_Bokmal /*no-no*/ = 1044,
Norwegian_Nynorsk /*no-no*/ = 2068,
Polish /*pl*/ = 1045,
Portuguese_Portugal /*pt-pt*/ = 2070,
Portuguese_Brazil /*pt-br*/ = 1046,
Raeto_Romance /*rm*/ = 1047,
Romanian_Romania /*ro*/ = 1048,
Romanian_RepublicOfMoldova /*ro-mo*/ = 2072,
Russian /*ru*/ = 1049,
Russian_RepublicOfMoldova /*ru-mo*/ = 2073,
Sanskrit /*sa*/ = 1103,
Serbian_Cyrillic /*sr-sp*/ = 3098,
Serbian_Latin /*sr-sp*/ = 2074,
Setsuana /*tn*/ = 1074,
Slovenian /*sl*/ = 1060,
Slovak /*sk*/ = 1051,
Sorbian /*sb*/ = 1070,
Spanish_Spain /*es-es*/ = 1034,
Spanish_Argentina /*es-ar*/ = 11274,
Spanish_Bolivia /*es-bo*/ = 16394,
Spanish_Chile /*es-cl*/ = 13322,
Spanish_Colombia /*es-co*/ = 9226,
Spanish_CostaRica /*es-cr*/ = 5130,
Spanish_DominicanRepublic /*es-do*/ = 7178,
Spanish_Ecuador /*es-ec*/ = 12298,
Spanish_Guatemala /*es-gt*/ = 4106,
Spanish_Honduras /*es-hn*/ = 18442,
Spanish_Mexico /*es-mx*/ = 2058,
Spanish_Nicaragua /*es-ni*/ = 19466,
Spanish_Panama /*es-pa*/ = 6154,
Spanish_Peru /*es-pe*/ = 10250,
Spanish_PuertoRico /*es-pr*/ = 20490,
Spanish_Paraguay /*es-py*/ = 15370,
Spanish_ElSalvador /*es-sv*/ = 17418,
Spanish_Uruguay /*es-uy*/ = 14346,
Spanish_Venezuela /*es-ve*/ = 8202,
Sutu /*sx*/ = 1072,
Swahili /*sw*/ = 1089,
Swedish_Sweden /*sv-se*/ = 1053,
Swedish_Finland /*sv-fi*/ = 2077,
Tamil /*ta*/ = 1097,
Tatar /*tt*/ = 1092,
Thai /*th*/ = 1054,
Turkish /*tr*/ = 1055,
Tsonga /*ts*/ = 1073,
Ukrainian /*uk*/ = 1058,
Urdu /*ur*/ = 1056,
Uzbek_Cyrillic /*uz-uz*/ = 2115,
Uzbek_Latin /*uz-uz*/ = 1091,
Vietnamese /*vi*/ = 1066,
Xhosa /*xh*/ = 1076,
Yiddish /*yi*/ = 1085,
Zulu /*zu*/ = 1077
}

public enum SPSiteTemplates
{
TeamSite,
BlankSite,
DocumentWorkspace,
BasicMeetingWorkspace,
BlankMeetingWorkspace,
DecisionMeetingWorkspace,
SocialMeetingWorkspace,
MultiPageMeetingWorkspace,
Wiki,
Blog,
/// <summary>
/// A central document management location for an enterprise
/// </summary>
DocumentCenter,
/// <summary>
/// A central location in which records managers can define routes for incoming files
/// </summary>
RecordsCenter1,
RecordsCenter2,
PublishingSite,
/// <summary>
/// A site for publishing web pages on a schedule with workflow features enabled
/// </summary>
PublishingSite2,
PressReleasesSite,
/// <summary>
/// A publishing site for web pages using approval workflows
/// </summary>
PublishingSiteWithWorkflow,
/// <summary>
/// A site for publishing news and articles
/// </summary>
NewsSite,
/// <summary>
/// A site for creating, managing, and delivering web pages, dashboards, and Key Performance Indicators (KPIs)
/// </summary>
ReportCenter,
/// <summary>
/// A starter hierarchy for an intranet divisional portal
/// </summary>
SPSPortal,
/// <summary>
/// A profile site that includes page layouts with zones
/// </summary>
ProfileSite,
/// <summary>
/// A site collection preconfigured for revision-controlled, secure content creation and publication
/// </summary>
PublishingPortal,
/// <summary>
/// Keep in mind that only one of these can be provisioned per Shared Services Provider
/// </summary>
MySiteHost,
/// <summary>
/// A site designed to deliver the search query and results experience
/// </summary>
SearchCenter,
/// <summary>
/// A superset of the previous; does not appear in navigation bars
/// </summary>
SearchCenter2
}
[/sourcecode]

Un poco de magia enum

Lo que nos queda es:

  • Convertir enums SPLocales a su representación correspondiente en uint
  • Convertir enums SPSiteTemplates a su representación en cadena correspondientes
  • Evitar intentar crear sitios duplicados

Para convertir enums SPLocales a su representación uint, sólo tenemos que hacer el casting correspondiente. Esta es la razón por la que hicimos al enum heredar de unit y setear el valor apropiado en cada elemento.

Para poder convertir enums SPSiteTemplates a cadenas, el método común es utilizar atributos y reflexión, pero personalmente no me gusta esa forma, así que hice una con diccionarios y métodos de extensión. La idea es proveer al enum la posibilidad de convertirse en una cadena, y estas cadenas de ser parseadas en el enum nuevamente. Para tener este tipo de relación en ambos sentidos, usé la implementación de BiDictionaryOneToOne del usuario de StackOverflow’s Joel in Go, en una pregunta de sobre cómo hacer un diccionario buscable key-value y buscable value-key: Bidirectional 1 to 1 Dictionary in C#.

[sourcecode language=”csharp”]
public static class SPSiteTemplateMethodExtension
{
private const string SP_SITE_TEMPLATE_TEAM_SITE = "STS#0";
private const string SP_SITE_TEMPLATE_BLANK_SITE = "STS#1";
private const string SP_SITE_TEMPLATE_DOCUMENT_WORKSPACE = "STS#2";
private const string SP_SITE_TEMPLATE_BASIC_MEETING_WORKSPACE = "MPS#0";
private const string SP_SITE_TEMPLATE_BLANK_MEETING_WORKSPACE = "MPS#1";
private const string SP_SITE_TEMPLATE_DECISION_MEETING_WORKSPACE = "MPS#2";
private const string SP_SITE_TEMPLATE_SOCIAL_MEETING_WORKSPACE = "MPS#3";
private const string SP_SITE_TEMPLATE_MULTI_PAGE_MEETING_WORKSPACE = "MPS#4";
private const string SP_SITE_TEMPLATE_WIKI = "WIKI#0";
private const string SP_SITE_TEMPLATE_BLOG = "BLOG#0";
private const string SP_SITE_TEMPLATE_DOCUMENT_CENTER = "BDR#0";
private const string SP_SITE_TEMPLATE_RECORDS_CENTER_1 = "OFFILE#0";
private const string SP_SITE_TEMPLATE_RECORDS_CENTER_2 = "OFFILE#1";
private const string SP_SITE_TEMPLATE_PUBLISHING_SITE = "CMSPUBLISHING#0";
private const string SP_SITE_TEMPLATE_PUBLISHING_SITE_2 = "BLANKINTERNET#0";
private const string SP_SITE_TEMPLATE_PRESS_RELEASES_SITE = "BLANKINTERNET#1";
private const string SP_SITE_TEMPLATE_PUBLISHING_SITE_WITH_WORKFLOW = "BLANKINTERNET#2";
private const string SP_SITE_TEMPLATE_NEWS_SITE = "SPSNHOME#0";
private const string SP_SITE_TEMPLATE_REPORT_CENTER = "SPSREPORTCENTER#0";
private const string SP_SITE_TEMPLATE_SPS_PORTAL = "SPSPORTAL#0";
private const string SP_SITE_TEMPLATE_PROFILE_SITE = "PROFILES#0";
private const string SP_SITE_TEMPLATE_PUBLISHING_PORTAL = "BLANKINTERNETCONTAINER#0";
private const string SP_SITE_TEMPLATE_MY_SITE_HOST = "SPSMYSITEHOST#0";
private const string SP_SITE_TEMPLATE_SEARCH_CENTER = "SRCHCENTERLITE#0";
private const string SP_SITE_TEMPLATE_SEARCH_CENTER_2 = "SRCHCENTERLITE#1";

private static BiDictionaryOneToOne<SPSiteTemplates, string> _templates;
private static BiDictionaryOneToOne<SPSiteTemplates, string> Templates {
get {
if (_templates == null) {
_templates = new BiDictionaryOneToOne<SPSiteTemplates, string>();
_templates.Add(SPSiteTemplates.BasicMeetingWorkspace, SP_SITE_TEMPLATE_BASIC_MEETING_WORKSPACE);
_templates.Add(SPSiteTemplates.BlankMeetingWorkspace, SP_SITE_TEMPLATE_BLANK_MEETING_WORKSPACE);
_templates.Add(SPSiteTemplates.BlankSite , SP_SITE_TEMPLATE_BLANK_SITE);
_templates.Add(SPSiteTemplates.Blog , SP_SITE_TEMPLATE_BLOG);
_templates.Add(SPSiteTemplates.DecisionMeetingWorkspace , SP_SITE_TEMPLATE_DECISION_MEETING_WORKSPACE);
_templates.Add(SPSiteTemplates.DocumentCenter , SP_SITE_TEMPLATE_DOCUMENT_CENTER);
_templates.Add(SPSiteTemplates.DocumentWorkspace , SP_SITE_TEMPLATE_DOCUMENT_WORKSPACE);
_templates.Add(SPSiteTemplates.MultiPageMeetingWorkspace , SP_SITE_TEMPLATE_MULTI_PAGE_MEETING_WORKSPACE);
_templates.Add(SPSiteTemplates.MySiteHost , SP_SITE_TEMPLATE_MY_SITE_HOST);
_templates.Add(SPSiteTemplates.NewsSite , SP_SITE_TEMPLATE_NEWS_SITE);
_templates.Add(SPSiteTemplates.PressReleasesSite , SP_SITE_TEMPLATE_PRESS_RELEASES_SITE);
_templates.Add(SPSiteTemplates.ProfileSite , SP_SITE_TEMPLATE_PROFILE_SITE);
_templates.Add(SPSiteTemplates.PublishingPortal , SP_SITE_TEMPLATE_PUBLISHING_PORTAL);
_templates.Add(SPSiteTemplates.PublishingSite , SP_SITE_TEMPLATE_PUBLISHING_SITE);
_templates.Add(SPSiteTemplates.PublishingSite2 , SP_SITE_TEMPLATE_PUBLISHING_SITE_2);
_templates.Add(SPSiteTemplates.PublishingSiteWithWorkflow , SP_SITE_TEMPLATE_PUBLISHING_SITE_WITH_WORKFLOW);
_templates.Add(SPSiteTemplates.RecordsCenter1 , SP_SITE_TEMPLATE_RECORDS_CENTER_1);
_templates.Add(SPSiteTemplates.RecordsCenter2 , SP_SITE_TEMPLATE_RECORDS_CENTER_2);
_templates.Add(SPSiteTemplates.ReportCenter , SP_SITE_TEMPLATE_REPORT_CENTER);
_templates.Add(SPSiteTemplates.SearchCenter , SP_SITE_TEMPLATE_SEARCH_CENTER);
_templates.Add(SPSiteTemplates.SearchCenter2 , SP_SITE_TEMPLATE_SEARCH_CENTER_2);
_templates.Add(SPSiteTemplates.SocialMeetingWorkspace , SP_SITE_TEMPLATE_SOCIAL_MEETING_WORKSPACE);
_templates.Add(SPSiteTemplates.SPSPortal , SP_SITE_TEMPLATE_SPS_PORTAL);
_templates.Add(SPSiteTemplates.TeamSite , SP_SITE_TEMPLATE_TEAM_SITE);
_templates.Add(SPSiteTemplates.Wiki , SP_SITE_TEMPLATE_WIKI);
}
return _templates;
}
}

public static string GetSharePointStringKey(this SPSiteTemplates siteTemplate)
{
return Templates.GetByFirst(siteTemplate);
}
}
[/sourcecode]

Sí, eso de ahí es un singleton y podría tener problemas potenciales de concurrencia, pero no estamos esperando agregar/remover/editar items. Además, yo habría ido con un constructor estático, pero los métodos de extensión tienen que implementarse en clases estáticas, y las clases marcadas como estáticas no tienen permitido tener constructores estáticos.

Toques extras

Con esto ya casi había terminado, pero resulta que si queremos verificar que un sitio existe, necesitamos usar su URL completa, pero si usamos el símbolo de los dos puntos ( : ) entonces vamos a tener una linda SPException diciéndonos que la URL es inválida. Lo que tenemos que hacer es usar URLs relativas a la URL del site collection, así que incluí un par de líneas para parsearlas.

Así que, aquí está la última versión del método CreateSite:

[sourcecode language=”csharp”]
public SPWeb CreateSite(string parentSiteURL, string siteURLRequested, string siteTitle, SPSiteTemplates siteTemplate, SPLocales locale, string description)
{
SPWeb resultWeb = null;

PerformActionOnSite(parentSiteURL, (siteCollection, parentWeb) =>
{
SPWebTemplateCollection Templates = siteCollection.GetWebTemplates(Convert.ToUInt32(locale));
SPWebTemplate spTemplate = Templates[siteTemplate.GetSharePointStringKey()];

var rootUrl = siteCollection.Url;
if (siteURLRequested.ToUpper().StartsWith(rootUrl.ToUpper()))
{
siteURLRequested = siteURLRequested.Remove(0, rootUrl.Length);
if (siteURLRequested.StartsWith("/"))
siteURLRequested = siteURLRequested.Remove(0, 1);

if (siteURLRequested.Length == 0) //they were equal, don’t create the root site
{
resultWeb = parentWeb;
}
else if (parentWeb.Webs[siteURLRequested].Exists) //it’s not the root site, but it exist anyway
{
resultWeb = parentWeb.Webs[siteURLRequested];
}
else
{
resultWeb = parentWeb.Webs.Add(siteURLRequested, siteTitle, description, Convert.ToUInt32(locale), spTemplate, false, false);
}
}
});

return resultWeb;
}
[/sourcecode]

Si se sienten confundidos por el método PerformActionOnSite, es un wrapper que creé para automáticamente usar y hacer dispose en los objetos SPSite y SPWeb .

[sourcecode language=”csharp”]
protected void PerformActionOnSite(string siteUrl, Action<SPSite, SPWeb> action)
{
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
action(site, web);
web.Close();
}
site.Close();
}
}
[/sourcecode]

Ok, entonces ya todo está en su lugar, deberían poder usarlo así::

[sourcecode language=”csharp”]
CreateSite("http://siteCollectionUrl", "http://siteCollectionUrl/newSite", SPSiteTemplates.PublishingSite, SPLocales.English_UnitedStates, "site description");
[/sourcecode]

Eso es todo. Espero que esto sea útil para ustedes. Subí una copia de todo este código en http://files.alphasmanifesto.com/Software/

¡Disfruten!

Referencias:

Link del día: Generación de código

Hace tiempo atrás publiqué  un link sobre generación automática de código, pero realmente hacía falta una explicación lenta y paso a paso de cómo se hace o cómo se pueden aprovechar las características que esto nos ofrece.

Un artículo muy bueno desde SwitchOnTheCode trata este mismo tema: C# Code Generation.

Piensen que combinando esto con programación procedural o algoritmos genéticos ([1], [2]) puede tener resultados muy interesantes.

Soy un zorrinito autogenerado.

Link of the day: Parallel programming with C#

I just found out that the “Start page” for Visual Studio can really provide nice interesting articles and news. It’s there where I found a link to an unfinished series of posts called Parallel Programming. These are part of the C# Frequently Asked Questions blog, one of the very interesting blogs around the MSDN.

These specific posts I mentioned are some techniques that we can take advantage of in C# (of course, VB also applies) to easily develop multi-thread applications without struggling with the bared thread objects. Of course, being based in .NET Framework 4, we have a lot of new features we can take advantage of.

Happy Friday to all!

I’m a parallel little skunk.

Link of the day: Graphics Programming Black Book

I don’t know if you agree with me, but graphics programming is something I always wanted to learn – and will eventually do it someday. For that, we need a book that is as explicative as possible and detailed, going through every concept and being insightful on the concepts.  Luckily, I found a web holding a full online version of Graphics Programming Black Book, a full featured book with really deep explanations and concepts.

It goes through optimization techniques and source code examples, even with 3D rendering and known examples as Doom or Quake graphic engines. You may see that the book is quite extensive, and good it is, for 70 short chapters we can really grasp the concepts and get hands to work on C++ and ASM in order to make our graphic programming.

I’m a graphic little skunk.

Link of the day: Google Security

Well… sort of. It’s not anything new these days, but a while ago, Google published it’s own security testing tool for website security testing, called Skipfish. Of course, this is not the first tool that Google releases for this purposes, as many related are already out there (like ratproxy, the Browser Security Handbook, and so on…). However, the new thing today is skipfish.

Skipfish is an active website scanner that will test the web applications for XSS, SQL Injection, Shell injection, XML Injection (that one’s new for me), SSL, insecure cookies, correct MIME headers, server errors, invalid links… etc. The complete list is enormous, and one of the main things is that this tool is being developed on C for high performance. It claims to be able to run with 2000 requests per second on remote servers (of course, if the servers answers in time).

I haven’t had the chance yet to give it a try but I probably will these days. These are the kind of reports that you may see from it, see a skipfish screenshot.

This all came from Google’s Online Security Blog. It is worth a reading, updates are not too much common nor too much big, so you can keep easily up to date with your favorite RSS reader.

I’m a secure little skunk.

Link del día: LINQ y JSON en JS

Aquellos que trabajamos en C# y hemos jugueteado un poco con el lenguaje de LINQ sabemos lo interesante que es poder fácilmente trabajar con datos. Hacer una búsqueda por algún campo de un array antes podía ser algo tan trabajoso como recorrer uno por uno, poner en una lista nueva los que cumplían con este criterio, luego utilizar la nueva lista y volver a filtrar si es que lo necesitamos. Ni hablar de trabajar con dos o más listas al mismo tiempo. Sin embargo, LINQ nos permitió realizar trabajos como ese en códigos de una línea como lista.Where(condicion).Select(campo).

LINQ se jacta de ser transparente y poder ser aplicado a cualquier fuente de datos, y acá hubo gente que quizo llevar eso más allá, para hacerlo trabajable de la misma forma en JavaScript, para la programación de cualquier aplicación web, y el manejo de datos listas de JSON. A esto llamaron jLinq, un trabajo de Hugo Bonacci, que en su web publica varios de sus proyectos e ideas.

La verdad es que no sé qué performance tendrá, pero si queremos simplificar un poco el procesamiento de datos de nuesto JavaScript (especialmente si mostramos cosas como tablas, listas o mucha información que se pueda ordenar, filtrar, etc.), esta parece ser una buena aproximación.

Soy un zorrinito mezcla-lenguajes.

A Tester’s Guide to .NET Programming

Terminé hace poco de leer el libro A Tester’s Guide to .NET Programming, de Randal Root y Mary Romero Sweeney, un interesante libro aproximativo a .NET de unas 630 páginas.

Mi prejuicio de este libro, basándome en su título, era que iba a encontrarme con un libro que mostrara características propias de Visual Studio y de la plataforma .NET para llevar a cabo tareas particulares de testing. Seguramente, pensaba yo, me encontraría con nuevos conceptos de testing y nuevas características o nuevos usos de características existentes que me permitirían mejorar las metodologías que conozco para testing.

Desafortunadamente, no ha sido el caso. El libro, debo decir aún así, es realmente bueno, muy detallado y explicativo en sus puntos. La suposición que el libro hace es: tomemos como tester a aquél que quiere realizar testeos (más que nada de aceptación de usuario) sobre un determinado software, y basado en eso, el programa enseña programación en VB.NET y C# orientado a la utilidad de ciertas cosas.

A pesar de no haber cumplido con lo que esperaba, este libro es una joyita. Al poner un concepto tan poco desarrollado sobre testing, nos permite utilizar este libro como un manual para aprender a programar en ambos lenguajes, planteando al testing como un ejercicio práctico que desarrollaremos a lo largo de toda la lectura. Y esto mismo es interesante, porque no solo tiene una aplicación práctica muy directa – cosa que muchos estudiantes de un lenguaje siempre quieren ver prontamente – sino que es fácil de entender y de sobrellevar.

El libro comienza con conceptos algo vagos de la programación, solo mostrando que con determinadas líneas en determinado lugar podremos lograr cierto resultado. Avanzando más, de a poco nos introducen a las clases, métodos, conceptos de orientación a objetos y reutilización de código. No llega a niveles más avanzados como la creación de frameworks para generar testware más complejo, pero se para a un paso de eso, y lo mantiene simple. Esto nos permite seguir el libro sin necesidad de demasiada atención, en donde sabemos que la información no está condensada sino explicada de formas que son fáciles de comprender, llenos de ejemplos, llenos de ejercicios prácticos y todo eso nos genera el impulso de seguir avanzando.

Como tal, resumo, este libro no tenía lo que yo esperaba encontrar, pero ha sido un muy buen hallazgo aún así.

Soy un zorrinito .NET.