Thursday, November 30, 2017

AnonymousPermMask64 for different anonymous settings for Sharepoint lists and document libraries

In addition to changing anonymous settings for the Sharepoint sites (see AllowAnonymousAccess, AnonymousState and AnonymousPermMask64 properties for Sharepoint sites with different anonymous configurations) it may be needed to enable anonymous access for particular lists and doclibs. In this case you need to set Anonymous users can access: Lists and libraries for the parent web (see above link) and enable anonymous access for the list/doclib. It is done from List settings > Permissions for this document library > Anonymous Access. Here you may check only View items permissions for anonymous users on regular web sites with read anonymous access. Let’s see how SPList.AnonymousPermMask64 property will be changed.

1. No anonymous access

In this case SPList.AnonymousPermMask64 = EmptyMask

2. View Items

Now SPList.AnonymousPermMask64 = ViewListItems, OpenItems, ViewVersions, ViewFormPages, Open, UseClientIntegration.

AllowAnonymousAccess, AnonymousState and AnonymousPermMask64 properties for Sharepoint sites with different anonymous configurations

In Sharepoint it is possible to use several anonymous configurations for the sites. They may be changed from Site settings > Site permissions > Anonymous access:

  • Entire Web site
  • Lists and libraries
  • Nothing

Here what description says about them:

Specify what parts of your Web site (if any) anonymous users can access. If you select Entire Web site, anonymous users will be able to view all pages in your Web site and view all lists and items which inherit permissions from the Web site. If you select Lists and libraries, anonymous users will be able to view and change items only for those lists and libraries that have enabled permissions for anonymous users.

And Nothing means that site doesn’t have anonymous access. Depending on used setting Sharepoint changes 3 properties of corresponding SPWeb object: AllowAnonymousAccess, AnonymousState and AnonymousPermMask64. Let’s see how they are changed depending on selection:

1. Anonymous users can access: Entire Web site

Property Value
AllowAnonymousAccess True
AnonymousState On
AnonymousPermMask64 ViewListItems, ViewVersions, ViewFormPages, Open, ViewPages, UseClientIntegration

2. Anonymous users can access: Lists and libraries

Property Value
AllowAnonymousAccess False
AnonymousState Enabled
AnonymousPermMask64 Open

2. Anonymous users can access: Nothing

Property Value
AllowAnonymousAccess False
AnonymousState Disabled
AnonymousPermMask64 EmptyMask

Monday, November 27, 2017

Problem with slow setting of composed look for Sharepoint site via OfficeDevPnP SetComposedLookByUrl method

In order to set composed look for Sharepoint site we may use SetComposedLookByUrl extension method from OfficeDevPnP library. According to documentation it does the following:

Retrieves the named composed look, overrides with specified palette, font, background and master page, and then recursively sets the specified values.

Here is the code of this method:

   1: public static void SetComposedLookByUrl(this Web web, string lookName,
   2:     string paletteServerRelativeUrl = null, string fontServerRelativeUrl = null,
   3:     string backgroundServerRelativeUrl = null, string masterServerRelativeUrl = null,
   4:     bool resetSubsitesToInherit = false, bool updateRootOnly = true)
   5: {
   6:     var paletteUrl = default(string);
   7:     var fontUrl = default(string);
   8:     var backgroundUrl = default(string);
   9:     var masterUrl = default(string);
  10:  
  11:     if (!string.IsNullOrWhiteSpace(lookName))
  12:     {
  13:         var composedLooksList = web.GetCatalog((int)ListTemplateType.DesignCatalog);
  14:  
  15:         // Check for existing, by name
  16:         CamlQuery query = new CamlQuery();
  17:         query.ViewXml = string.Format(CAML_QUERY_FIND_BY_FILENAME, lookName);
  18:         var existingCollection = composedLooksList.GetItems(query);
  19:         web.Context.Load(existingCollection);
  20:         web.Context.ExecuteQueryRetry();
  21:         var item = existingCollection.FirstOrDefault();
  22:  
  23:         if (item != null)
  24:         {
  25:             var lookPaletteUrl = item["ThemeUrl"] as FieldUrlValue;
  26:             if (lookPaletteUrl != null)
  27:             {
  28:                 paletteUrl = new Uri(lookPaletteUrl.Url).AbsolutePath;
  29:             }
  30:             var lookFontUrl = item["FontSchemeUrl"] as FieldUrlValue;
  31:             if (lookFontUrl != null)
  32:             {
  33:                 fontUrl = new Uri(lookFontUrl.Url).AbsolutePath;
  34:             }
  35:             var lookBackgroundUrl = item["ImageUrl"] as FieldUrlValue;
  36:             if (lookBackgroundUrl != null)
  37:             {
  38:                 backgroundUrl = new Uri(lookBackgroundUrl.Url).AbsolutePath;
  39:             }
  40:             var lookMasterUrl = item["MasterPageUrl"] as FieldUrlValue;
  41:             if (lookMasterUrl != null)
  42:             {
  43:                 masterUrl = new Uri(lookMasterUrl.Url).AbsolutePath;
  44:             }
  45:         }
  46:         else
  47:         {
  48:             Log.Error(Constants.LOGGING_SOURCE,
  49: CoreResources.BrandingExtension_ComposedLookMissing, lookName);
  50:             throw new Exception($"Composed look '{lookName}' can not be found; pass " +
  51: "null or empty to set look directly (not based on an existing entry)");
  52:         }
  53:     }
  54:  
  55:     if (!string.IsNullOrEmpty(paletteServerRelativeUrl))
  56:     {
  57:         paletteUrl = paletteServerRelativeUrl;
  58:     }
  59:     if (!string.IsNullOrEmpty(fontServerRelativeUrl))
  60:     {
  61:         fontUrl = fontServerRelativeUrl;
  62:     }
  63:     if (!string.IsNullOrEmpty(backgroundServerRelativeUrl))
  64:     {
  65:         backgroundUrl = backgroundServerRelativeUrl;
  66:     }
  67:     if (!string.IsNullOrEmpty(masterServerRelativeUrl))
  68:     {
  69:         masterUrl = masterServerRelativeUrl;
  70:     }
  71:  
  72:     //URL decode retrieved url's
  73:     paletteUrl = System.Net.WebUtility.UrlDecode(paletteUrl);
  74:     fontUrl = System.Net.WebUtility.UrlDecode(fontUrl);
  75:     backgroundUrl = System.Net.WebUtility.UrlDecode(backgroundUrl);
  76:     masterUrl = System.Net.WebUtility.UrlDecode(masterUrl);
  77:  
  78:     web.SetMasterPageByUrl(masterUrl, resetSubsitesToInherit, updateRootOnly);
  79:     web.SetCustomMasterPageByUrl(masterUrl, resetSubsitesToInherit, updateRootOnly);
  80:     web.SetThemeByUrl(paletteUrl, fontUrl, backgroundUrl, resetSubsitesToInherit,
  81:         updateRootOnly);
  82:  
  83:     // Update/create the "Current" reference in the composed looks gallery
  84:     string currentLookName = GetLocalizedCurrentValue(web);
  85:     web.CreateComposedLookByUrl(currentLookName, paletteUrl, fontUrl, backgroundUrl,
  86:         masterUrl, displayOrder: 0);
  87: }

The problem is that on the real production environments this method may work very slow (we needed to set composed look on all sub sites, i.e. pass resetSubsitesToInherit = true and updateRootOnly = false). When apply it on the site with many sub sites (about 4000 sub site) it worked almost 15 hours and then was interrupted with network connectivity exception.

Another problematic moment is that method doesn’t show any progress indicator, i.e. you just have to wait watching black screen all that time. At the moment I see only 1 workaround which at least will allow to see progress of the work: iterate through sub sites in own external loop, print url of currently updated web and call SetComposedLookByUrl for each web site separately with resetSubsitesToInherit = false and updateRootOnly = true. If you know other solutions please share them in comments.

Sunday, November 26, 2017

Real time currency conversion using web service for .Net

Sometimes you need to automatically convert currencies in the app. In order to do that you need to get current conversion ratios for appropriate currencies. There are many web services which provide this data: free and chargeable. In this post I will use free web service provided by Central bank of Russia, in your code you may use other services, e.g. service provided by Central European bank. There is convenient web service which returns currencies conversion ratios for the current day:

http://www.cbr.ru/scripts/XML_daily.asp?date_req=dd/MM/yyyy

E.g. if we want to get it for 26 Nov 2017 we need to use the following url:

http://www.cbr.ru/scripts/XML_daily.asp?date_req=26/11/2017

Here is result:

   1: <?xml version="1.0" encoding="windows-1251"?>
   2: <ValCurs Date="25.11.2017" name="Foreign Currency Market">
   3: <Valute ID="R01010">
   4:     <NumCode>036</NumCode>
   5:     <CharCode>AUD</CharCode>
   6:     <Nominal>1</Nominal>
   7:     <Name>Австралийский доллар</Name>
   8:     <Value>44,5778</Value>
   9: </Valute>
  10: <Valute ID="R01020A">
  11:     <NumCode>944</NumCode>
  12:     <CharCode>AZN</CharCode>
  13:     <Nominal>1</Nominal>
  14:     <Name>Азербайджанский манат</Name>
  15:     <Value>34,4609</Value>
  16: </Valute>
  17: <Valute ID="R01035">
  18:     <NumCode>826</NumCode>
  19:     <CharCode>GBP</CharCode>
  20:     <Nominal>1</Nominal>
  21:     <Name>Фунт стерлингов Соединенного королевства</Name>
  22:     <Value>77,9644</Value>
  23: </Valute>
  24: <Valute ID="R01060">
  25:     <NumCode>051</NumCode>
  26:     <CharCode>AMD</CharCode>
  27:     <Nominal>100</Nominal>
  28:     <Name>Армянских драмов</Name>
  29:     <Value>12,0933</Value>
  30: </Valute>
  31: <Valute ID="R01090B">
  32:     <NumCode>933</NumCode>
  33:     <CharCode>BYN</CharCode>
  34:     <Nominal>1</Nominal>
  35:     <Name>Белорусский рубль</Name>
  36:     <Value>29,2835</Value>
  37: </Valute>
  38: <Valute ID="R01100">
  39:     <NumCode>975</NumCode>
  40:     <CharCode>BGN</CharCode>
  41:     <Nominal>1</Nominal>
  42:     <Name>Болгарский лев</Name>
  43:     <Value>35,4717</Value>
  44: </Valute>
  45: <Valute ID="R01115">
  46:     <NumCode>986</NumCode>
  47:     <CharCode>BRL</CharCode>
  48:     <Nominal>1</Nominal>
  49:     <Name>Бразильский реал</Name>
  50:     <Value>18,1669</Value>
  51: </Valute>
  52: <Valute ID="R01135">
  53:     <NumCode>348</NumCode>
  54:     <CharCode>HUF</CharCode>
  55:     <Nominal>100</Nominal>
  56:     <Name>Венгерских форинтов</Name>
  57:     <Value>22,2428</Value>
  58: </Valute>
  59: <Valute ID="R01200">
  60:     <NumCode>344</NumCode>
  61:     <CharCode>HKD</CharCode>
  62:     <Nominal>10</Nominal>
  63:     <Name>Гонконгских долларов</Name>
  64:     <Value>74,9648</Value>
  65: </Valute>
  66: <Valute ID="R01215">
  67:     <NumCode>208</NumCode>
  68:     <CharCode>DKK</CharCode>
  69:     <Nominal>10</Nominal>
  70:     <Name>Датских крон</Name>
  71:     <Value>93,2302</Value>
  72: </Valute>
  73: <Valute ID="R01235">
  74:     <NumCode>840</NumCode>
  75:     <CharCode>USD</CharCode>
  76:     <Nominal>1</Nominal>
  77:     <Name>Доллар США</Name>
  78:     <Value>58,5318</Value>
  79: </Valute>
  80: <Valute ID="R01239">
  81:     <NumCode>978</NumCode>
  82:     <CharCode>EUR</CharCode>
  83:     <Nominal>1</Nominal>
  84:     <Name>Евро</Name>
  85:     <Value>69,3309</Value>
  86: </Valute>
  87: <Valute ID="R01270">
  88:     <NumCode>356</NumCode>
  89:     <CharCode>INR</CharCode>
  90:     <Nominal>100</Nominal>
  91:     <Name>Индийских рупий</Name>
  92:     <Value>90,5539</Value>
  93: </Valute>
  94: <Valute ID="R01335">
  95:     <NumCode>398</NumCode>
  96:     <CharCode>KZT</CharCode>
  97:     <Nominal>100</Nominal>
  98:     <Name>Казахстанских тенге</Name>
  99:     <Value>17,7114</Value>
 100: </Valute>
 101: <Valute ID="R01350">
 102:     <NumCode>124</NumCode>
 103:     <CharCode>CAD</CharCode>
 104:     <Nominal>1</Nominal>
 105:     <Name>Канадский доллар</Name>
 106:     <Value>45,9975</Value>
 107: </Valute>
 108: <Valute ID="R01370">
 109:     <NumCode>417</NumCode>
 110:     <CharCode>KGS</CharCode>
 111:     <Nominal>100</Nominal>
 112:     <Name>Киргизских сомов</Name>
 113:     <Value>83,9194</Value>
 114: </Valute>
 115: <Valute ID="R01375">
 116:     <NumCode>156</NumCode>
 117:     <CharCode>CNY</CharCode>
 118:     <Nominal>10</Nominal>
 119:     <Name>Китайских юаней</Name>
 120:     <Value>88,6349</Value>
 121: </Valute>
 122: <Valute ID="R01500">
 123:     <NumCode>498</NumCode>
 124:     <CharCode>MDL</CharCode>
 125:     <Nominal>10</Nominal>
 126:     <Name>Молдавских леев</Name>
 127:     <Value>33,7359</Value>
 128: </Valute>
 129: <Valute ID="R01535">
 130:     <NumCode>578</NumCode>
 131:     <CharCode>NOK</CharCode>
 132:     <Nominal>10</Nominal>
 133:     <Name>Норвежских крон</Name>
 134:     <Value>71,9099</Value>
 135: </Valute>
 136: <Valute ID="R01565">
 137:     <NumCode>985</NumCode>
 138:     <CharCode>PLN</CharCode>
 139:     <Nominal>1</Nominal>
 140:     <Name>Польский злотый</Name>
 141:     <Value>16,4902</Value>
 142: </Valute>
 143: <Valute ID="R01585F">
 144:     <NumCode>946</NumCode>
 145:     <CharCode>RON</CharCode>
 146:     <Nominal>1</Nominal>
 147:     <Name>Румынский лей</Name>
 148:     <Value>14,9107</Value>
 149: </Valute>
 150: <Valute ID="R01589">
 151:     <NumCode>960</NumCode>
 152:     <CharCode>XDR</CharCode>
 153:     <Nominal>1</Nominal>
 154:     <Name>СДР (специальные права заимствования)</Name>
 155:     <Value>82,5796</Value>
 156: </Valute>
 157: <Valute ID="R01625">
 158:     <NumCode>702</NumCode>
 159:     <CharCode>SGD</CharCode>
 160:     <Nominal>1</Nominal>
 161:     <Name>Сингапурский доллар</Name>
 162:     <Value>43,4502</Value>
 163: </Valute>
 164: <Valute ID="R01670">
 165:     <NumCode>972</NumCode>
 166:     <CharCode>TJS</CharCode>
 167:     <Nominal>10</Nominal>
 168:     <Name>Таджикских сомони</Name>
 169:     <Value>66,3656</Value>
 170: </Valute>
 171: <Valute ID="R01700J">
 172:     <NumCode>949</NumCode>
 173:     <CharCode>TRY</CharCode>
 174:     <Nominal>1</Nominal>
 175:     <Name>Турецкая лира</Name>
 176:     <Value>14,8728</Value>
 177: </Valute>
 178: <Valute ID="R01710A">
 179:     <NumCode>934</NumCode>
 180:     <CharCode>TMT</CharCode>
 181:     <Nominal>1</Nominal>
 182:     <Name>Новый туркменский манат</Name>
 183:     <Value>16,7243</Value>
 184: </Valute>
 185: <Valute ID="R01717">
 186:     <NumCode>860</NumCode>
 187:     <CharCode>UZS</CharCode>
 188:     <Nominal>10000</Nominal>
 189:     <Name>Узбекских сумов</Name>
 190:     <Value>72,3946</Value>
 191: </Valute>
 192: <Valute ID="R01720">
 193:     <NumCode>980</NumCode>
 194:     <CharCode>UAH</CharCode>
 195:     <Nominal>10</Nominal>
 196:     <Name>Украинских гривен</Name>
 197:     <Value>21,7429</Value>
 198: </Valute>
 199: <Valute ID="R01760">
 200:     <NumCode>203</NumCode>
 201:     <CharCode>CZK</CharCode>
 202:     <Nominal>10</Nominal>
 203:     <Name>Чешских крон</Name>
 204:     <Value>27,2844</Value>
 205: </Valute>
 206: <Valute ID="R01770">
 207:     <NumCode>752</NumCode>
 208:     <CharCode>SEK</CharCode>
 209:     <Nominal>10</Nominal>
 210:     <Name>Шведских крон</Name>
 211:     <Value>70,4829</Value>
 212: </Valute>
 213: <Valute ID="R01775">
 214:     <NumCode>756</NumCode>
 215:     <CharCode>CHF</CharCode>
 216:     <Nominal>1</Nominal>
 217:     <Name>Швейцарский франк</Name>
 218:     <Value>59,6350</Value>
 219: </Valute>
 220: <Valute ID="R01810">
 221:     <NumCode>710</NumCode>
 222:     <CharCode>ZAR</CharCode>
 223:     <Nominal>10</Nominal>
 224:     <Name>Южноафриканских рэндов</Name>
 225:     <Value>41,9715</Value>
 226: </Valute>
 227: <Valute ID="R01815">
 228:     <NumCode>410</NumCode>
 229:     <CharCode>KRW</CharCode>
 230:     <Nominal>1000</Nominal>
 231:     <Name>Вон Республики Корея</Name>
 232:     <Value>53,9061</Value>
 233: </Valute>
 234: <Valute ID="R01820">
 235:     <NumCode>392</NumCode>
 236:     <CharCode>JPY</CharCode>
 237:     <Nominal>100</Nominal>
 238:     <Name>Японских иен</Name>
 239:     <Value>52,5538</Value>
 240: </Valute>
 241: </ValCurs>

It shows conversion ratios related with Russian Ruble, e.g. if we check EUR:

   1: <Valute ID="R01239">
   2:     <NumCode>978</NumCode>
   3:     <CharCode>EUR</CharCode>
   4:     <Nominal>1</Nominal>
   5:     <Name>Евро</Name>
   6:     <Value>69,3309</Value>
   7: </Valute>

it shows that 1 (Nominal) Euro is equal to 69,3309 (Value) Rub. Having conversion ratios for all currencies and Ruble it is possible to get conversion ratios between any 2 currencies using simple proportions math. The following code shows how to convert value between RUR, EUR and USD in any order and direction:

   1:  
   2: public decimal? Convert(decimal sourceValue, Currency sourceCurrency, Currency targetCurrency)
   3: {
   4:     try
   5:     {
   6:         if (sourceCurrency == targetCurrency)
   7:         {
   8:             return sourceValue;
   9:         }
  10:  
  11:         var now = DateTime.Now;
  12:         string url = string.Format("http://www.cbr.ru/scripts/XML_daily.asp?date_req={0}",
  13:             now.ToString("dd/MM/yyyy"));
  14:         string str = this.webClient.Download(url, Encoding.GetEncoding("windows-1251"));
  15:         if (string.IsNullOrEmpty(str))
  16:         {
  17:             return null;
  18:         }
  19:         var doc = XDocument.Parse(str);
  20:         var elEUR = doc.Root.Elements("Valute").FirstOrDefault(e =>
  21:             e.Elements("CharCode").ElementAt(0).Value == "EUR");
  22:         decimal? nominalEUR =
  23:             DecimalHelper.ParseWithDotSeparator(elEUR.Element("Nominal").Value);
  24:         decimal? valueEUR =
  25:             DecimalHelper.ParseWithDotSeparator(elEUR.Element("Value").Value);
  26:  
  27:         var elUSD = doc.Root.Elements("Valute").FirstOrDefault(e =>
  28:             e.Elements("CharCode").ElementAt(0).Value == "USD");
  29:         decimal? nominalUSD =
  30:             DecimalHelper.ParseWithDotSeparator(elUSD.Element("Nominal").Value);
  31:         decimal? valueUSD =
  32:             DecimalHelper.ParseWithDotSeparator(elUSD.Element("Value").Value);
  33:  
  34:         double koefFromEURtoRUB = (double)valueEUR.Value / (double)nominalEUR.Value;
  35:         double koefFromUSDtoRUB = (double)valueUSD.Value / (double)nominalUSD.Value;
  36:  
  37:         double koefFromRUBtoEUR = 1 / koefFromEURtoRUB;
  38:         double koefFromUSDtoEUR = koefFromUSDtoRUB / koefFromEURtoRUB;
  39:  
  40:         double koefFromRUBtoUSD = 1 / koefFromUSDtoRUB;
  41:         double koefFromEURtoUSD = koefFromEURtoRUB / koefFromUSDtoRUB;
  42:  
  43:         double? koeff = null;
  44:         if (sourceCurrency == Currency.Rub)
  45:         {
  46:             if (targetCurrency == Currency.Eur)
  47:             {
  48:                 koeff = koefFromRUBtoEUR;
  49:             }
  50:             else if (targetCurrency == Currency.Usd)
  51:             {
  52:                 koeff = koefFromRUBtoUSD;
  53:             }
  54:         }
  55:         else if (sourceCurrency == Currency.Eur)
  56:         {
  57:             if (targetCurrency == Currency.Rub)
  58:             {
  59:                 koeff = koefFromEURtoRUB;
  60:             }
  61:             else if (targetCurrency == Currency.Usd)
  62:             {
  63:                 koeff = koefFromEURtoUSD;
  64:             }
  65:         }
  66:         else if (sourceCurrency == Currency.Usd)
  67:         {
  68:             if (targetCurrency == Currency.Rub)
  69:             {
  70:                 koeff = koefFromUSDtoRUB;
  71:             }
  72:             else if (targetCurrency == Currency.Eur)
  73:             {
  74:                 koeff = koefFromUSDtoEUR;
  75:             }
  76:         }
  77:         if (koeff == null)
  78:         {
  79:             return null;
  80:         }
  81:  
  82:         var targetValue = (decimal)Math.Round(((double)sourceValue) * koeff.Value, 2);
  83:         return targetValue;
  84:     }
  85:     catch (Exception x)
  86:     {
  87:         return null;
  88:     }
  89: }

Helper classes WebClient and DecimalHelper look like this:

   1: public class WebClient
   2: {
   3:     public string Download(string url, Encoding encoding)
   4:     {
   5:         try
   6:         {
   7:             if (string.IsNullOrEmpty(url))
   8:             {
   9:                 return string.Empty;
  10:             }
  11:             byte[] data;
  12:             using (var webClient = new System.Net.WebClient())
  13:             {
  14:                 data = webClient.DownloadData(url);
  15:             }
  16:             if (data == null)
  17:             {
  18:                 return string.Empty;
  19:             }
  20:             return encoding.GetString(data);
  21:         }
  22:         catch (Exception x)
  23:         {
  24:             return string.Empty;
  25:         }
  26:     }
  27: }

and

   1: public class DecimalHelper
   2: {
   3:     public static decimal? ParseWithDotSeparator(string val)
   4:     {
   5:         if (string.IsNullOrEmpty(val))
   6:         {
   7:             return null;
   8:         }
   9:         return decimal.Parse(val.Replace(",", "."), CultureInfo.InvariantCulture);
  10:     }
  11: }

Convert method receives 3 parameters:

  1. source value
  2. source currency
  3. target currency

At first we download current ratios from web service (lines 10-17). Then we calculate ratios for all required conversions EUR to RUR, USD to RUR, RUR to EUR, USD to EUR, RUR to USD, EUR to USD (lines 35-42) and choice correct ratio depending on arguments. If you will compare result with one get from Google (try to google e.g. eur to usd – Google will show conversion calculator), you will see that it is almost the same. Of course if conversion accuracy is crucial for your app then you need to use another web service which is updated more frequently.