<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>André Santos, Author at Blog IT</title>
	<atom:link href="https://blogit.create.pt/author/andresantos/feed/" rel="self" type="application/rss+xml" />
	<link>https://blogit.create.pt/author/andresantos/</link>
	<description>Create IT blogger community</description>
	<lastBuildDate>Wed, 29 May 2019 11:26:36 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>
	<item>
		<title>Straight A&#8217;s in WebPagetest with Umbraco</title>
		<link>https://blogit.create.pt/andresantos/2018/11/27/straight-as-in-webpagetest-with-umbraco/</link>
					<comments>https://blogit.create.pt/andresantos/2018/11/27/straight-as-in-webpagetest-with-umbraco/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Tue, 27 Nov 2018 21:40:06 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Microsoft Azure]]></category>
		<category><![CDATA[azure cdn]]></category>
		<category><![CDATA[blobstorage]]></category>
		<category><![CDATA[cdn]]></category>
		<category><![CDATA[imageprocessor]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[UmbracoFileSystemProviders.Azure]]></category>
		<category><![CDATA[WebPagetest]]></category>
		<guid isPermaLink="false">https://blogit.create.pt/?p=7450</guid>

					<description><![CDATA[<p>Before launching a new website, there&#8217;s a checklist I go through, to make sure that everything is ready. One of the items in my checklist is to test the website against WebPagetest. WebPagetest is a tool that was originally developed by AOL for use internally and was open-sourced in 2008 under a BSD license. The [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2018/11/27/straight-as-in-webpagetest-with-umbraco/">Straight A&#8217;s in WebPagetest with Umbraco</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">B</span>efore launching a new website, there&#8217;s a checklist I go through, to make sure that everything is ready. One of the items in my checklist is to test the website against <strong>WebPagetest</strong>.</p>
<blockquote class="td_quote_box td_box_right"><p>WebPagetest is a tool that was originally developed by <a href="http://dev.aol.com/">AOL</a> for use internally and was open-sourced in 2008 under a BSD license. The online version at <a href="https://www.webpagetest.org/">www.webpagetest.org</a> is run for the benefit of the performance community with several companies and individuals providing the testing infrastructure around the globe.</p></blockquote>
<p>This tool tests any website against 6 major performance affecting factors, and provides a myriad of graphs and logs that make abundantly clear what might be slowing down your site.</p>
<p>In this post I&#8217;ll provide ways to make your site get straight A&#8217;s in WebPagetest.</p>
<p><span id="more-7450"></span></p>
<h1>Setup</h1>
<p>To start this off, let&#8217;s setup our environment. We&#8217;ll just need the following:</p>
<ul>
<li>Visual Studio 2017</li>
<li>Microsoft Azure account</li>
</ul>
<p>In Visual Studio, let&#8217;s create a new empty ASP.NET Web Application project. Then, we&#8217;ll need the latest and greatest Umbraco NuGet package (I used version 7.12.4). Once it finishes installing, just launch the website and install Umbraco with all defaults. This will bootstrap Umbraco with the Starter Website, which we&#8217;ll use as our &#8220;guinea pig&#8221; for WebPagetest.</p>
<p>Next: publish it! We can use the publish wizard to automatically create our new WebApp and SQL Database in Azure. Before installing Umbraco in Azure, we&#8217;ll need to change the Web.config so that the install wizard is run again (I use <a href="https://filezilla-project.org/">Filezilla</a> to change it in Azure):</p>
<p>Clear the Umbraco version number:</p>
<pre class="brush: xml; title: Web.config; notranslate">
&lt;add key=&quot;umbracoConfigurationStatus&quot; value=&quot;&quot;&gt;
</pre>
<p>Clear the Umbraco connection string:</p>
<pre class="brush: xml; title: Web.config; notranslate">
&lt;add name=&quot;umbracoDbDSN&quot; connectionstring=&quot;&quot; providername=&quot;System.Data.SqlClient&quot;&gt;
</pre>
<p>With these changes in place, we&#8217;re good to go. This time, we&#8217;ll not use the defaults in the Umbraco install wizard, since we&#8217;ll want to use the SQL Database we&#8217;ve just created in Azure.</p>
<h1>First test</h1>
<p>For our tests, we&#8217;ll use the people page of the Starter Website. This is the score I got with a standard (S0) database and a basic WebApp:</p>
<p><img decoding="async" class="size-medium wp-image-7454 aligncenter" src="https://blogit.create.pt////wp-content/uploads/2018/09/first-webpagetest-300x91.png" alt="" width="300" height="91" srcset="https://blogit.create.pt/wp-content/uploads/2018/09/first-webpagetest-300x91.png 300w, https://blogit.create.pt/wp-content/uploads/2018/09/first-webpagetest-768x233.png 768w, https://blogit.create.pt/wp-content/uploads/2018/09/first-webpagetest-696x211.png 696w, https://blogit.create.pt/wp-content/uploads/2018/09/first-webpagetest.png 841w" sizes="(max-width: 300px) 100vw, 300px" /></p>
<p>Since this is a very small site, only used for demonstration purposes, half of the metrics are already cleared! However, this is not usually the case for bigger websites. For this reason, I&#8217;ll still present some solutions to improve the grade for these metrics.</p>
<h1>First Byte Time</h1>
<p><em>These test measures the time it takes for the first byte to reach the client&#8217;s browser after the initial http request.</em></p>
<p>There are two main factors that influence this result:</p>
<ul>
<li>Server power</li>
<li>The webpage complexity (integrations with external services, complex logic involved, etc)</li>
</ul>
<h3>How to get an A</h3>
<p>The easiest way to mitigate this problem is by caching. You can see how to do output caching in Umbraco by reading this old post of mine: <a href="https://blogit.create.pt////andresantos/2016/06/30/umbraco-and-donut-output-cache/">https://blogit.create.pt////andresantos/2016/06/30/umbraco-and-donut-output-cache/</a>.</p>
<h1>Keep-alive Enabled</h1>
<p><em>Keep alive is a method to allow the same tcp connection for HTTP conversation instead of opening a new one with each new request.</em></p>
<h3>How to get an A</h3>
<p>This setting is active by default in IIS, so, it&#8217;s also active by default in Azure WebApps, so it&#8217;s easy to get an A!</p>
<h1>Compress Transfer</h1>
<p><em>Gzip compresses your webpages, style sheets and javascripts, before sending them over to the browser. This drastically reduces transfer time since the files are much smaller.</em></p>
<h3>How to get an A</h3>
<p>Just add this setting to your Web.config file:</p>
<pre class="brush: xml; title: Web.config; notranslate">
&lt;httpcompression dynamiccompressionenablecpuusage=&quot;0&quot; dynamiccompressiondisablecpuusage=&quot;90&quot; nocompressionforhttp10=&quot;false&quot; nocompressionforproxies=&quot;false&quot;&gt;
    &lt;statictypes&gt;
        &lt;add mimetype=&quot;text/*&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;message/*&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;application/javascript&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;application/font-woff&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;application/font-woff2&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;application/vnd.ms-fontobject&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;application/octet-stream&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;*/*&quot; enabled=&quot;false&quot;&gt;
    &lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/statictypes&gt;
    &lt;dynamictypes&gt;
        &lt;add mimetype=&quot;text/*&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;message/*&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;application/javascript&quot; enabled=&quot;true&quot;&gt;
        &lt;add mimetype=&quot;*/*&quot; enabled=&quot;false&quot;&gt;
    &lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/add&gt;&lt;/dynamictypes&gt;
&lt;/httpcompression&gt;
</pre>
<h1>Compress Images</h1>
<p><em><span class="ILfuVd">Image compression is minimizing the size in bytes of a graphics file without degrading the quality of the image to an unacceptable level.</span></em></p>
<h3>How to get an A</h3>
<p>In Umbraco, to get an A in this grade, you need to do two things:</p>
<ol>
<li>Crop every image and use srcsets where you can</li>
<li>Use the PostProcessor plugin for Image Processor</li>
</ol>
<p>Cropping an image is easy in Umbraco:</p>
<pre class="brush: xml; title: People.cshtml; notranslate">
&lt;div class=&quot;employee-grid__item__image&quot; style=&quot;background-image: url('@person.Photo.GetCropUrl(width: 323, height: 300, quality: 85)')&quot;&gt;&lt;/div&gt;
</pre>
<p>In order to use the PostProcessor plugin, you just need to install it via nuget: <a href="https://www.nuget.org/packages/ImageProcessor.Web.PostProcessor/1.3.1.25">https://www.nuget.org/packages/ImageProcessor.Web.PostProcessor/1.3.1.25</a>.</p>
<h1>Cache Static Content</h1>
<p><em>Static content is content that changes rarely. For this reason it can be cached in the user&#8217;s browser to avoid downloading the same file over and over again.</em></p>
<h3>How to get an A</h3>
<p>Just set the time it takes for the content to expire in the user&#8217;s browser and add extra mime types if you want:</p>
<pre class="brush: xml; title: Web.config; notranslate">
&lt;staticcontent&gt;
    &lt;clientcache cachecontrolmode=&quot;UseMaxAge&quot; cachecontrolmaxage=&quot;7.24:00:00&quot;&gt;
    &lt;remove fileextension=&quot;.air&quot;&gt;
    &lt;mimemap fileextension=&quot;.air&quot; mimetype=&quot;application/vnd.adobe.air-application-installer-package+zip&quot;&gt;
    &lt;remove fileextension=&quot;.svg&quot;&gt;
    &lt;mimemap fileextension=&quot;.svg&quot; mimetype=&quot;image/svg+xml&quot;&gt;
    &lt;remove fileextension=&quot;.woff&quot;&gt;
    &lt;mimemap fileextension=&quot;.woff&quot; mimetype=&quot;application/x-font-woff&quot;&gt;
    &lt;remove fileextension=&quot;.woff2&quot;&gt;
    &lt;mimemap fileextension=&quot;.woff2&quot; mimetype=&quot;application/x-font-woff2&quot;&gt;
    &lt;remove fileextension=&quot;.less&quot;&gt;
    &lt;mimemap fileextension=&quot;.less&quot; mimetype=&quot;text/css&quot;&gt;
    &lt;remove fileextension=&quot;.mp4&quot;&gt;
    &lt;mimemap fileextension=&quot;.mp4&quot; mimetype=&quot;video/mp4&quot;&gt;
    &lt;remove fileextension=&quot;.json&quot;&gt;
    &lt;mimemap fileextension=&quot;.json&quot; mimetype=&quot;application/json&quot;&gt;
&lt;/mimemap&gt;&lt;/remove&gt;&lt;/mimemap&gt;&lt;/remove&gt;&lt;/mimemap&gt;&lt;/remove&gt;&lt;/mimemap&gt;&lt;/remove&gt;&lt;/mimemap&gt;&lt;/remove&gt;&lt;/mimemap&gt;&lt;/remove&gt;&lt;/mimemap&gt;&lt;/remove&gt;&lt;/clientcache&gt;&lt;/staticcontent&gt;
</pre>
<h1>Effective use of CDN</h1>
<p><em> A content delivery network (CDN) refers to a geographically distributed group of servers which work together to provide fast delivery of Internet content. A CDN allows for the quick transfer of assets needed for loading Internet content including HTML pages, javascript files, stylesheets, images, and videos.</em></p>
<h3>How to get an A</h3>
<p>In Umbraco, you can achieve this last grade by doing two things:</p>
<ol>
<li>Use an Azure Blob Storage for media storage by installing this nuget package: <a href="https://github.com/JimBobSquarePants/UmbracoFileSystemProviders.Azure">https://github.com/JimBobSquarePants/UmbracoFileSystemProviders.Azure</a>.</li>
<li>Create an Azure CDN for serving these blobs through a content delivery network.</li>
</ol>
<p>After creating an Azure CDN service and waiting about an hour for it to be available, the following presents my configuration for the media assets to be provided by it:</p>
<pre class="brush: xml; title: config/imageprocessor/security.config; notranslate">
&lt;!--?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?--&gt;
&lt;security&gt;
  &lt;services&gt;
    &lt;service name=&quot;LocalFileImageService&quot; type=&quot;ImageProcessor.Web.Services.LocalFileImageService, ImageProcessor.Web&quot;&gt;
    &lt;!--Disable the LocalFileImageService and enable this one when using virtual paths. --&gt;
    &lt;service prefix=&quot;media/&quot; name=&quot;CloudImageService&quot; type=&quot;ImageProcessor.Web.Services.CloudImageService, ImageProcessor.Web&quot;&gt;
      &lt;settings&gt;
        &lt;setting key=&quot;Container&quot; value=&quot;media&quot;&gt;
        &lt;setting key=&quot;MaxBytes&quot; value=&quot;8194304&quot;&gt;
        &lt;setting key=&quot;Timeout&quot; value=&quot;30000&quot;&gt;
        &lt;setting key=&quot;Host&quot; value=&quot;https://&lt;umbracositename&gt;.blob.core.windows.net/media&quot;&gt;
      &lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/settings&gt;
    &lt;/service&gt;
  &lt;/service&gt;&lt;/services&gt;
&lt;/security&gt;
</pre>
<pre class="brush: xml; title: config/imageprocessor/security.config; notranslate">
&lt;!--?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?--&gt;
&lt;caching currentcache=&quot;AzureBlobCache&quot;&gt;
  &lt;caches&gt;
    &lt;cache name=&quot;AzureBlobCache&quot; type=&quot;ImageProcessor.Web.Plugins.AzureBlobCache.AzureBlobCache, ImageProcessor.Web.Plugins.AzureBlobCache&quot; maxdays=&quot;365&quot;&gt;
      &lt;settings&gt;
        &lt;setting key=&quot;CachedStorageAccount&quot; value=&quot;DefaultEndpointsProtocol=https;AccountName=&lt;accountname&gt;;AccountKey=&lt;accountkey&gt;;EndpointSuffix=core.windows.net&quot;&gt;
        &lt;setting key=&quot;CachedBlobContainer&quot; value=&quot;cache&quot;&gt;
        &lt;setting key=&quot;UseCachedContainerInUrl&quot; value=&quot;false&quot;&gt;
        &lt;setting key=&quot;SourceStorageAccount&quot; value=&quot;DefaultEndpointsProtocol=https;AccountName=&lt;accountname&gt;;AccountKey=&lt;accountkey&gt;;EndpointSuffix=core.windows.net&quot;&gt;
        &lt;setting key=&quot;SourceBlobContainer&quot; value=&quot;media&quot;&gt;
        &lt;setting key=&quot;StreamCachedImage&quot; value=&quot;false&quot;&gt;
        &lt;setting key=&quot;CachedCDNRoot&quot; value=&quot;https://&lt;cdnrootname&gt;.azureedge.net&quot;&gt;
        &lt;setting key=&quot;CachedCDNTimeout&quot; value=&quot;1000&quot;&gt;
      &lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/setting&gt;&lt;/settings&gt;
    &lt;/cache&gt;
  &lt;/caches&gt;
&lt;/caching&gt;
</pre>
<h1>Conclusion</h1>
<p>So, if you followed these tips correctly, you&#8217;ll be able to run WebPagetest and get the same result as I did:</p>
<p><img decoding="async" class="wp-image-7937 size-medium aligncenter" src="https://blogit.create.pt////wp-content/uploads/2018/11/straightAs-300x89.png" alt="straightAs" width="300" height="89" srcset="https://blogit.create.pt/wp-content/uploads/2018/11/straightAs-300x89.png 300w, https://blogit.create.pt/wp-content/uploads/2018/11/straightAs-768x228.png 768w, https://blogit.create.pt/wp-content/uploads/2018/11/straightAs-696x207.png 696w, https://blogit.create.pt/wp-content/uploads/2018/11/straightAs.png 837w" sizes="(max-width: 300px) 100vw, 300px" /></p>
<p>You can find the complete report here: <a href="https://www.webpagetest.org/result/181127_2A_bea6941dcd20d38ab54c29409fca9363/">https://www.webpagetest.org/result/181127_2A_bea6941dcd20d38ab54c29409fca9363/</a>.</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2018/11/27/straight-as-in-webpagetest-with-umbraco/">Straight A&#8217;s in WebPagetest with Umbraco</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2018/11/27/straight-as-in-webpagetest-with-umbraco/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Redirecting 2 domains from http to https and non-www to www</title>
		<link>https://blogit.create.pt/andresantos/2018/10/30/redirecting-2-domains-from-http-to-https-and-non-www-to-www/</link>
					<comments>https://blogit.create.pt/andresantos/2018/10/30/redirecting-2-domains-from-http-to-https-and-non-www-to-www/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Tue, 30 Oct 2018 12:25:59 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[configuration]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[naked domain]]></category>
		<category><![CDATA[non-www]]></category>
		<category><![CDATA[redirection]]></category>
		<category><![CDATA[web.config]]></category>
		<category><![CDATA[www]]></category>
		<guid isPermaLink="false">https://blogit.create.pt/?p=7670</guid>

					<description><![CDATA[<p>Recently I had to configure some redirections in a website that contains two different domains. Both use https and I want the main site, which uses the primary domain, to only be accessible through it&#8217;s www version. For the second site, since it&#8217;s a sub-domain, I only want it to only be available in https. [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2018/10/30/redirecting-2-domains-from-http-to-https-and-non-www-to-www/">Redirecting 2 domains from http to https and non-www to www</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Recently I had to configure some redirections in a website that contains two different domains.</p>
<p>Both use https and I want the main site, which uses the primary domain, to only be accessible through it&#8217;s www version. For the second site, since it&#8217;s a sub-domain, I only want it to only be available in https.</p>
<p><span id="more-7670"></span></p>
<p>Let&#8217;s say the domain is <strong>mydomain.com</strong> and the subdomain is <strong>mysub.mydomain.com</strong>. The only URLs I want to be available are:</p>
<ul>
<li>https://www.mydomain.com</li>
<li>https://mysub.mydomain.com</li>
</ul>
<p>Typically I would make the non-www to www redirection through the DNS provider, but for this website it was not possible.</p>
<p>The following is the configuration I used in my Web.config:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;rewrite&gt;
    &lt;rules&gt;
        &lt;rule name=&quot;Redirect from non www mydomain.com&quot; stopProcessing=&quot;true&quot;&gt;
            &lt;match url=&quot;.*&quot; /&gt;
            &lt;conditions&gt;
                &lt;add input=&quot;{HTTP_HOST}&quot; pattern=&quot;^mydomain.com$&quot; /&gt;
            &lt;/conditions&gt;
            &lt;action type=&quot;Redirect&quot; url=&quot;https://www.mydomain.com/{R:0}&quot; redirectType=&quot;Permanent&quot; /&gt;
        &lt;/rule&gt;
        &lt;rule name=&quot;Redirect from non https mydomain.com&quot; stopProcessing=&quot;true&quot;&gt;
            &lt;match url=&quot;.*&quot; /&gt;
            &lt;conditions&gt;
                &lt;add input=&quot;{HTTPS}&quot; pattern=&quot;^OFF$&quot; /&gt;
                &lt;add input=&quot;{HTTP_HOST}&quot; pattern=&quot;^www.mydomain.com$&quot; /&gt;
            &lt;/conditions&gt;
            &lt;action type=&quot;Redirect&quot; url=&quot;https://www.mydomain.com/{R:0}&quot; redirectType=&quot;Permanent&quot; /&gt;
        &lt;/rule&gt;
        &lt;rule name=&quot;Redirect from non https mysub.mydomain.com&quot; stopProcessing=&quot;true&quot;&gt;
            &lt;match url=&quot;.*&quot; /&gt;
            &lt;conditions&gt;
                &lt;add input=&quot;{HTTPS}&quot; pattern=&quot;^OFF$&quot; /&gt;
                &lt;add input=&quot;{HTTP_HOST}&quot; pattern=&quot;^mysub.mydomain.com$&quot; /&gt;
            &lt;/conditions&gt;
            &lt;action type=&quot;Redirect&quot; url=&quot;https://mysub.mydomain.com/{R:0}&quot; redirectType=&quot;Permanent&quot; /&gt;
        &lt;/rule&gt;
    &lt;/rules&gt;
&lt;/rewrite&gt;
</pre>
<p>Hope this helps!</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2018/10/30/redirecting-2-domains-from-http-to-https-and-non-www-to-www/">Redirecting 2 domains from http to https and non-www to www</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2018/10/30/redirecting-2-domains-from-http-to-https-and-non-www-to-www/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>KendoUI Grid, OData, WebAPI and Entity Framework</title>
		<link>https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/</link>
					<comments>https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Wed, 05 Jul 2017 10:42:53 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[entity framework]]></category>
		<category><![CDATA[grid]]></category>
		<category><![CDATA[kendoui]]></category>
		<category><![CDATA[odata]]></category>
		<category><![CDATA[sql database]]></category>
		<category><![CDATA[webapi]]></category>
		<category><![CDATA[webapp]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1764</guid>

					<description><![CDATA[<p>In the latest project I was involved with, there was a need to present database tables data directly in the Umbraco&#8217;s backoffice, where all CRUD operations must be supported. This tables could be something like 1000 to 1 million rows and both the website and database would be hosted in Azure, in a WebApp and [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/">KendoUI Grid, OData, WebAPI and Entity Framework</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">I</span>n the latest project I was involved with, there was a need to present database tables data directly in the Umbraco&#8217;s backoffice, where all CRUD operations must be supported. This tables could be something like 1000 to 1 million rows and both the website and database would be hosted in Azure, in a WebApp and SQL Database respectively. Users should be able to create complex filters and order the data as they wish as well as update the data individually and in batch.</p>
<p>From the get-go there were some challenges we needed to overcome in order to create an usable system, that was user-friendly, fast, reliable, and where no conflitcts would come up when data was being updated from multiple users.</p>
<p>The solution was a new Umbraco Package we called <em>Search Manager</em> (since the database tables ultimately feed an Azure Search index) that uses <a href="http://demos.telerik.com/kendo-ui/grid/index">Telerik&#8217;s Kendo UI Grid</a>, WebAPI and Entity Framework. The following presents the process it took to reach the end result.</p>
<p><span id="more-6483"></span></p>
<h2>Step 1</h2>
<p>We created a simple Umbraco Package that implemented a Kendo UI Grid which got its data from a WebAPI method that returned all rows from a table using Petapoco.</p>
<p>This allowed us to have the complete table data in memory using a Kendo UI Grid. It worked great but had major problems:</p>
<ul>
<li>A table with around 40k rows was actually a 40 MBs json download in order to populate the grid</li>
<li>Users could be working with outdated data, since it was loaded in memory and changes could have been made to the source</li>
</ul>
<h2>Step 2</h2>
<p>We started using the server paging feature of the Kendo Grid. This dropped the download size greatly, but we still had problems:</p>
<ul>
<li>Even though we are working with only a subset of results client-side, we were still getting the complete table data server-side, using petapoco.</li>
</ul>
<p>We needed an easy way to translate the client-side queries, such as filters, paging, order, etc to SQL queries.</p>
<h2>Step 3</h2>
<p>We changed the type of queries being generated by the Grid to OData. Using the <a href="https://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData">OData WebAPI nuget package</a> we are able to map the odata query  to a C# object and then directly apply the query to any IQueryable data.</p>
<p>Petapoco does not provide any IQueryable data support, so we changed our ORM to Entity Framework, which does, and <em>voilá</em>!</p>
<h2>Finally</h2>
<p>With everything in place, we are able to have a filtered subset of results in a Grid, that gets exactly what it needs to display from a dynamically generated SQL query.</p>
<ul>
<li>It is fast because we only get exactly what we need:</li>
</ul>
<p>An OData query generated by the Kendo UI Grid looks like this:</p>
<pre class="brush: plain; title: ; notranslate">
http://localhost:14231/umbraco/backoffice/SearchManager/PricesApi/Read?%24inlinecount=allpages&%24top=20&%24skip=40&%24orderby=ProviderName+desc&%24filter=(NatureProcCode+eq+%27exames%27+and+AvgAmtClaimed+gt+10)
</pre>
<p>Which is translated to the follwing SQL query:</p>
<pre class="brush: sql; title: ; notranslate">
exec sp_executesql N'SELECT 
    &#x5B;Project1].&#x5B;Approved] AS &#x5B;Approved], 
    &#x5B;Project1].&#x5B;DiffWeightedRate] AS &#x5B;DiffWeightedRate], 
    &#x5B;Project1].&#x5B;SubmitPrvId] AS &#x5B;SubmitPrvId], 
    &#x5B;Project1].&#x5B;PracticeSeq] AS &#x5B;PracticeSeq], 
    &#x5B;Project1].&#x5B;NatureProcCode] AS &#x5B;NatureProcCode], 
    &#x5B;Project1].&#x5B;GroupProcCode] AS &#x5B;GroupProcCode], 
    &#x5B;Project1].&#x5B;Network] AS &#x5B;Network], 
    &#x5B;Project1].&#x5B;AmtClaimedWeighted] AS &#x5B;AmtClaimedWeighted], 
    &#x5B;Project1].&#x5B;ManualPrice] AS &#x5B;ManualPrice], 
    &#x5B;Project1].&#x5B;IsImported] AS &#x5B;IsImported], 
    &#x5B;Project1].&#x5B;DiffCalculatedRate] AS &#x5B;DiffCalculatedRate], 
    &#x5B;Project1].&#x5B;AmtContract] AS &#x5B;AmtContract], 
    &#x5B;Project1].&#x5B;AvgAmtClaimed] AS &#x5B;AvgAmtClaimed], 
    &#x5B;Project1].&#x5B;ProviderName] AS &#x5B;ProviderName], 
    &#x5B;Project1].&#x5B;ClinicName] AS &#x5B;ClinicName]
    FROM ( SELECT 
        &#x5B;Extent1].&#x5B;Approved] AS &#x5B;Approved], 
        &#x5B;Extent1].&#x5B;DiffWeightedRate] AS &#x5B;DiffWeightedRate], 
        &#x5B;Extent1].&#x5B;SubmitPrvId] AS &#x5B;SubmitPrvId], 
        &#x5B;Extent1].&#x5B;PracticeSeq] AS &#x5B;PracticeSeq], 
        &#x5B;Extent1].&#x5B;NatureProcCode] AS &#x5B;NatureProcCode], 
        &#x5B;Extent1].&#x5B;GroupProcCode] AS &#x5B;GroupProcCode], 
        &#x5B;Extent1].&#x5B;Network] AS &#x5B;Network], 
        &#x5B;Extent1].&#x5B;AmtClaimedWeighted] AS &#x5B;AmtClaimedWeighted], 
        &#x5B;Extent1].&#x5B;ManualPrice] AS &#x5B;ManualPrice], 
        &#x5B;Extent1].&#x5B;IsImported] AS &#x5B;IsImported], 
        &#x5B;Extent1].&#x5B;DiffCalculatedRate] AS &#x5B;DiffCalculatedRate], 
        &#x5B;Extent1].&#x5B;AmtContract] AS &#x5B;AmtContract], 
        &#x5B;Extent1].&#x5B;AvgAmtClaimed] AS &#x5B;AvgAmtClaimed], 
        &#x5B;Extent1].&#x5B;ProviderName] AS &#x5B;ProviderName], 
        &#x5B;Extent1].&#x5B;ClinicName] AS &#x5B;ClinicName]
        FROM (SELECT 
    &#x5B;PriceGrid].&#x5B;Approved] AS &#x5B;Approved], 
    &#x5B;PriceGrid].&#x5B;DiffWeightedRate] AS &#x5B;DiffWeightedRate], 
    &#x5B;PriceGrid].&#x5B;SubmitPrvId] AS &#x5B;SubmitPrvId], 
    &#x5B;PriceGrid].&#x5B;PracticeSeq] AS &#x5B;PracticeSeq], 
    &#x5B;PriceGrid].&#x5B;NatureProcCode] AS &#x5B;NatureProcCode], 
    &#x5B;PriceGrid].&#x5B;GroupProcCode] AS &#x5B;GroupProcCode], 
    &#x5B;PriceGrid].&#x5B;Network] AS &#x5B;Network], 
    &#x5B;PriceGrid].&#x5B;AmtClaimedWeighted] AS &#x5B;AmtClaimedWeighted], 
    &#x5B;PriceGrid].&#x5B;ManualPrice] AS &#x5B;ManualPrice], 
    &#x5B;PriceGrid].&#x5B;IsImported] AS &#x5B;IsImported], 
    &#x5B;PriceGrid].&#x5B;DiffCalculatedRate] AS &#x5B;DiffCalculatedRate], 
    &#x5B;PriceGrid].&#x5B;AmtContract] AS &#x5B;AmtContract], 
    &#x5B;PriceGrid].&#x5B;AvgAmtClaimed] AS &#x5B;AvgAmtClaimed], 
    &#x5B;PriceGrid].&#x5B;ProviderName] AS &#x5B;ProviderName], 
    &#x5B;PriceGrid].&#x5B;ClinicName] AS &#x5B;ClinicName]
    FROM &#x5B;dbo].&#x5B;PriceGrid] AS &#x5B;PriceGrid]) AS &#x5B;Extent1]
        WHERE (&#x5B;Extent1].&#x5B;NatureProcCode] = @p__linq__0) AND (&#x5B;Extent1].&#x5B;AvgAmtClaimed] &gt; @p__linq__1)
    )  AS &#x5B;Project1]
    ORDER BY &#x5B;Project1].&#x5B;ProviderName] DESC, &#x5B;Project1].&#x5B;AmtClaimedWeighted] ASC, &#x5B;Project1].&#x5B;AmtContract] ASC, &#x5B;Project1].&#x5B;Approved] ASC, &#x5B;Project1].&#x5B;AvgAmtClaimed] ASC, &#x5B;Project1].&#x5B;ClinicName] ASC, &#x5B;Project1].&#x5B;DiffCalculatedRate] ASC, &#x5B;Project1].&#x5B;DiffWeightedRate] ASC, &#x5B;Project1].&#x5B;GroupProcCode] ASC, &#x5B;Project1].&#x5B;IsImported] ASC, &#x5B;Project1].&#x5B;ManualPrice] ASC, &#x5B;Project1].&#x5B;NatureProcCode] ASC, &#x5B;Project1].&#x5B;Network] ASC, &#x5B;Project1].&#x5B;PracticeSeq] ASC, &#x5B;Project1].&#x5B;SubmitPrvId] ASC
    OFFSET @p__linq__2 ROWS FETCH NEXT @p__linq__3 ROWS ONLY  option (Recompile)',N'@p__linq__0 varchar(8000),@p__linq__1 decimal(2,0),@p__linq__2 int,@p__linq__3 int',@p__linq__0='exames',@p__linq__1=10,@p__linq__2=40,@p__linq__3=20
</pre>
<ul>
<li>It&#8217;s user-friendly and reliable because users can filter the data as if they were using an excel file on steroids</li>
<li>It avoids conflicts because we only get a subset of the results each time and each change is commited in a single transaction</li>
</ul>
<h2>Code samples:</h2>
<p>&nbsp;</p>
<pre class="brush: jscript; title: grid setup; notranslate">
grid = $(&quot;#grid&quot;).kendoGrid({
                dataSource: new kendo.data.DataSource({
                    transport: transport,
                    schema: schema,
                    serverFiltering: true,
                    serverPaging: true,
                    serverSorting: true,
                    type: &quot;odata&quot;,
                    pageSize: 20
                }),
                dataBound: onDataBound,
                pageable: true,
                filterable: true,
                sortable: true,
                scrollable: false,
                navigatable: true,
                resizable: true,
                batch: true,
                columnMenu: true,
                editable: true,
                toolbar: &#x5B;
					{ template: '&lt;a class=&quot;k-button k-button-icontext k-grid-save-batch&quot; href=&quot;\\#&quot;&quot;&gt;&lt;span class=&quot;k-icon k-i-update&quot;&gt;&lt;/span&gt;Guardar Alterações&lt;/a&gt;' },
					{ name: &quot;cancel&quot;, text: &quot;Cancelar&quot; },
                ],
                columns: &#x5B;...]
            });
</pre>
<p>&nbsp;</p>
<pre class="brush: csharp; title: WebAPI; notranslate">
&#x5B;HttpGet]
        public IHttpActionResult Read(ODataQueryOptions&lt;PriceGrid&gt; opts)
        {
            Mapper.CreateMap&lt;PriceGrid, PriceGridBO&gt;();

            using (var context = new EntitiesContext())
            {
                using (var qh = new HintScope(context, QueryHintType.Recompile))
                {
                    List&lt;PriceGrid&gt; results = opts
                        .ApplyTo(context.PriceGrids)
                        .Cast&lt;PriceGrid&gt;()
                        .ToList();

                    List&lt;PriceGridBO&gt; boEntities = results.Select(x =&gt; Mapper.Map&lt;PriceGrid, PriceGridBO&gt;(x)).ToList();

                    PriceResponse response = new PriceResponse()
                    {
                        Entities = boEntities,
                        Total = Request.ODataProperties().TotalCount.HasValue ? Request.ODataProperties().TotalCount.Value : 0
                    };

                    return Ok(response);
                }
            }
        }
</pre>
<p>The post <a href="https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/">KendoUI Grid, OData, WebAPI and Entity Framework</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>First ever Lisbon Umbraco Meetup!</title>
		<link>https://blogit.create.pt/andresantos/2016/11/21/first-ever-lisbon-umbraco-meetup/</link>
					<comments>https://blogit.create.pt/andresantos/2016/11/21/first-ever-lisbon-umbraco-meetup/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 21 Nov 2016 10:26:33 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[Meetup]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1631</guid>

					<description><![CDATA[<p>The first ever Umbraco Meetup in Portugal is going to be held this Thursday at Create IT offices in Lisbon. The event filled up fast which leads me to believe that Umbraco is getting recognized as one of the best CMS&#8217;s to work with. We&#8217;ll try to record some of it, and share it on [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/11/21/first-ever-lisbon-umbraco-meetup/">First ever Lisbon Umbraco Meetup!</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The first ever Umbraco Meetup in Portugal is going to be held this Thursday at Create IT offices in Lisbon. The event filled up fast which leads me to believe that Umbraco is getting <em>recognized</em> as one of the best CMS&#8217;s to work with.</p>
<p>We&#8217;ll try to record some of it, and share it on youtube!</p>
<style type="text/css">#meetup_oembed .mu_clearfix:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; }* html #meetup_oembed .mu_clearfix, *:first-child+html #meetup_oembed .mu_clearfix { zoom: 1; }#meetup_oembed { background:#eee;border:1px solid #ccc;padding:10px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;margin:0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; }#meetup_oembed h3 { font-weight:normal; margin:0 0 10px; padding:0; line-height:26px; font-family:Georgia,Palatino,serif; font-size:24px }#meetup_oembed p { margin: 0 0 10px; padding:0; line-height:16px; }#meetup_oembed img { border:none; margin:0; padding:0; }#meetup_oembed a, #meetup_oembed a:visited, #meetup_oembed a:link { color: #1B76B3; text-decoration: none; cursor: hand; cursor: pointer; }#meetup_oembed a:hover { color: #1B76B3; text-decoration: underline; }#meetup_oembed a.mu_button { font-size:14px; -moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:2px solid #A7241D;color:white!important;text-decoration:none;background-color: #CA3E47; background-image: -moz-linear-gradient(top, #ca3e47, #a8252e); background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #a8252e), color-stop(1, #ca3e47));disvplay:inline-block;padding:5px 10px; }#meetup_oembed a.mu_button:hover { color: #fff!important; text-decoration: none; }#meetup_oembed .photo { width:50px; height:50px; overflow:hidden;background:#ccc;float:left;margin:0 5px 0 0;text-align:center;padding:1px; }#meetup_oembed .photo img { height:50px }#meetup_oembed .number { font-size:18px; }#meetup_oembed .thing { text-transform: uppercase; color: #555; }</style>
<div id="meetup_oembed" style="height:163px">
<div style="overflow:hidden;max-height:123px">
<h3>Lisbon Umbraco Meetup</h3>
<p style="margin:0 0 10px;font-size:12px;line-height:16px;">Lisbon, PT <br />          <span style="font-size:14px;font-weight:bold;">105</span> <em>Members</em></p>
<p style="line-height:16px">The Lisbon Umbraco Meetup is a networking group for anyone interested in Umbraco. We&#8217;re planning on having at least one formal presentation followed by some lively debates or &#8230;</p>
</p></div>
<p style="margin:10px 0 5px;"><a href="https://www.meetup.com/Lisbon-Umbraco-Meetup/" target="_blank" class="mu_button">Check out this Meetup Group &rarr;</a></p>
</div>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/11/21/first-ever-lisbon-umbraco-meetup/">First ever Lisbon Umbraco Meetup!</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2016/11/21/first-ever-lisbon-umbraco-meetup/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Umbraco and Donut Output Cache</title>
		<link>https://blogit.create.pt/andresantos/2016/06/30/umbraco-and-donut-output-cache/</link>
					<comments>https://blogit.create.pt/andresantos/2016/06/30/umbraco-and-donut-output-cache/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Thu, 30 Jun 2016 14:22:01 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[controller]]></category>
		<category><![CDATA[Devtrends]]></category>
		<category><![CDATA[Donut Output Cache]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[mvc 5]]></category>
		<category><![CDATA[MVCDonutCaching]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1571</guid>

					<description><![CDATA[<p>Donut Output Caching is a type of output caching where certain parts of a web page are not cached. It&#8217;s a simple way of boosting your site performance! ASP.NET doesn&#8217;t provide a native way of donut output caching, so we must resort to a great NuGet package called MVCDonutCaching. You can read all about it here: http://www.devtrends.co.uk/blog/donut-output-caching-in-asp.net-mvc-3. [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/06/30/umbraco-and-donut-output-cache/">Umbraco and Donut Output Cache</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Donut Output Caching is a type of output caching where certain parts of a web page are not cached. It&#8217;s a simple way of boosting your site performance!</p>
<p>ASP.NET doesn&#8217;t provide a native way of donut output caching, so we must resort to a great NuGet package called <a href="https://github.com/moonpyk/mvcdonutcaching">MVCDonutCaching</a>. You can read all about it here: <a href="http://www.devtrends.co.uk/blog/donut-output-caching-in-asp.net-mvc-3">http://www.devtrends.co.uk/blog/donut-output-caching-in-asp.net-mvc-3</a>.</p>
<p>Since Umbraco is built on top of ASP.NET MVC, we can easily use this package on our websites. There are, however, some caveats, because Umbraco doesn&#8217;t use the native MVC routing.</p>
<p>For Umbraco, every page that uses the same document type is processed through the same route, therefore, if we have 100 news articles that use the NewsItem document type, from the moment we open one news article page, every other news item page will display the same information, despite having a different URL!</p>
<p><span id="more-6482"></span></p>
<p>The way to bypass this issue is the following:</p>
<p>Create a Global.cs file that includes the logic to cache a page based on their url and not the document type</p>
<pre class="brush: csharp; title: Global.cs; notranslate">
namespace CreateIT.Web
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Umbraco.Web;

    public class Global : UmbracoApplication
    {
        public override string GetVaryByCustomString(HttpContext context, string custom)
        {
            if (custom.ToLower() == &quot;url&quot;)
            {
                return &quot;url=&quot; + context.Request.Url.AbsoluteUri;
            }

            return base.GetVaryByCustomString(context, custom);
        }
    }
}
</pre>
<p>Then, instruct Umbraco to use this class when launching the UmbracoApplication by changing the Global.asax file:</p>
<pre class="brush: plain; title: Global.asax; notranslate">
&amp;lt;%@ Application Codebehind=&quot;Global.cs&quot; Inherits=&quot;CreateIT.Web.Global&quot; Language=&quot;C#&quot; %&amp;gt;
</pre>
<p>Finally, setup an Output Cache profile in your Web.config:</p>
<pre class="brush: xml; title: Web.config; notranslate">
&amp;lt;system.web&amp;gt;
    (...)
    &amp;lt;caching&amp;gt;
      &amp;lt;outputCacheSettings&amp;gt;
        &amp;lt;outputCacheProfiles&amp;gt;
          &amp;lt;add enabled=&quot;true&quot; name=&quot;CreateIT.Actions&quot; duration=&quot;900&quot; location=&quot;Server&quot; varyByCustom=&quot;url&quot; varyByParam=&quot;*&quot; /&amp;gt;
        &amp;lt;/outputCacheProfiles&amp;gt;
      &amp;lt;/outputCacheSettings&amp;gt;
    &amp;lt;/caching&amp;gt;
&amp;lt;/system.web&amp;gt;
</pre>
<p>And decorate the chosen controllers (that you used to <a href="https://our.umbraco.org/documentation/reference/routing/custom-controllers">hijack the Umbraco routes</a>) with the Donut Output Cache attribute:</p>
<pre class="brush: csharp; title: HomepageController.cs; notranslate">
&#x5B;DonutOutputCache(CacheProfile = &quot;CreateIT.Actions&quot;)]
public class HomepageController : Umbraco.Web.Mvc.RenderMvcController
{
</pre>
<p>If you wish to exclude a certain part of your page from the cache, just create a Surface Controller action that returns a partial view and use the extension method provided by MVCDonutCaching for @Html.Action().</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/06/30/umbraco-and-donut-output-cache/">Umbraco and Donut Output Cache</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2016/06/30/umbraco-and-donut-output-cache/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Umbraco profiler</title>
		<link>https://blogit.create.pt/andresantos/2016/06/08/umbraco-profiler/</link>
					<comments>https://blogit.create.pt/andresantos/2016/06/08/umbraco-profiler/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Wed, 08 Jun 2016 11:16:45 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[profiler]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1481</guid>

					<description><![CDATA[<p>Quick tip: If you need to find which part of your Umbraco application is slowing the site down, follow these steps: Set the debug property of the compilation section of your Web.config to true: &#60;compilation defaultLanguage=&#8221;c#&#8221; debug=&#8221;true&#8221; batch=&#8221;false&#8221; targetFramework=&#8221;4.5&#8243;&#62; If you&#8217;re using an older version of Umbraco, you also probably need to set the umbracoDebugMode app setting to [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/06/08/umbraco-profiler/">Umbraco profiler</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Quick tip:</p>
<p>If you need to find which part of your Umbraco application is slowing the site down, follow these steps:</p>
<ol>
<li>Set the debug property of the compilation section of your Web.config to <strong>true</strong>: <em>&lt;compilation defaultLanguage=&#8221;c#&#8221; <strong>debug=&#8221;true&#8221;</strong> batch=&#8221;false&#8221; targetFramework=&#8221;4.5&#8243;&gt;</em>
<ul>
<li>If you&#8217;re using an older version of Umbraco, you also probably need to set the umbracoDebugMode app setting to true in the same config file: <em>&lt;add key=&#8221;umbracoDebugMode&#8221; value=&#8221;true&#8221; /&gt;</em></li>
</ul>
</li>
<li>Add the query string <strong>?umbdebug=true</strong> to your Umbraco page URL</li>
<li>Thats it!</li>
</ol>
<figure id="attachment_1491" aria-describedby="caption-attachment-1491" style="width: 543px" class="wp-caption alignnone"><a href="http://blogit-create.com/wp-content/uploads/2016/06/profiler.png"><img fetchpriority="high" decoding="async" class="size-full wp-image-1491" src="http://blogit-create.com/wp-content/uploads/2016/06/profiler.png" alt="umbraco profiler" width="543" height="472" srcset="https://blogit.create.pt/wp-content/uploads/2016/06/profiler.png 543w, https://blogit.create.pt/wp-content/uploads/2016/06/profiler-300x261.png 300w, https://blogit.create.pt/wp-content/uploads/2016/06/profiler-483x420.png 483w" sizes="(max-width: 543px) 100vw, 543px" /></a><figcaption id="caption-attachment-1491" class="wp-caption-text">umbraco profiler</figcaption></figure>
<p>It uses <a href="http://miniprofiler.com/">Mini Profiler</a>, so you can add your own profile stops, but out-of-the-box it already analyses the time it takes to render each view.</p>
<p>Happy profiling!</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/06/08/umbraco-profiler/">Umbraco profiler</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2016/06/08/umbraco-profiler/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Remote resource handling in a MVC website project</title>
		<link>https://blogit.create.pt/andresantos/2016/04/15/remote-resource-handling-in-a-mvc-website-project/</link>
					<comments>https://blogit.create.pt/andresantos/2016/04/15/remote-resource-handling-in-a-mvc-website-project/#comments</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Fri, 15 Apr 2016 18:01:43 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[action filter]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[mvc 5]]></category>
		<category><![CDATA[resources]]></category>
		<category><![CDATA[resx]]></category>
		<category><![CDATA[viewmodel]]></category>
		<category><![CDATA[visual studio]]></category>
		<category><![CDATA[webapi]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1381</guid>

					<description><![CDATA[<p>I don&#8217;t have very fond memories of using .Net resource files (.resx) to handle the translation of static web page elements such as form labels. The Visual Studio resx editor was slow and using it to change resources that were more than a single line of text was a pain. I still have nightmares where [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/04/15/remote-resource-handling-in-a-mvc-website-project/">Remote resource handling in a MVC website project</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I don&#8217;t have very fond memories of using .Net resource files (.resx) to handle the translation of static web page elements such as form labels.</p>
<p>The Visual Studio <em>resx</em> editor was slow and using it to change resources that were more than a single line of text was a pain. I still have nightmares where I&#8217;m trying to edit complete email templates on a single field of a <em>resx</em> file&#8230;</p>
<p>The biggest problem with resource files, however, is that they are included in the project. This creates a problem for both the developers and the content managers, since the content managers are not able to change resources themselves, and the developers need to be changing resources files when they could be doing something productive.</p>
<p>The following will show a way to handle resource items in a MVC website project, where the resources can be stored wherever they are more easily handled by content managers: a SharePoint list, a database table, an excel file, or something completely different.</p>
<p><span id="more-6480"></span></p>
<p>The process is really simple:</p>
<ol>
<li>Select every resource item key needed to render the current page</li>
<li>Fetch the resources associated with the keys provided at the end of the current action</li>
<li>Populate a ViewModel property with the resource items</li>
<li>Use the resource items in their respective places in the markup</li>
</ol>
<h3>1 &#8211; Compile a list of resource keys</h3>
<p>Typically in a website, there are certain areas of the layout that are common across all pages, the header and the footer being the most prominent candidates. There are, however, other areas that can be reused across a smaller subset of pages such as a common banner in the FAQ pages. Taking this into account, we split the resource keys by groups, where each group (a list of strings) contains every resource key needed for that area and load them on each action, like so:</p>
<pre class="brush: csharp; title: Area resource keys; notranslate">
model.ContentResourceKeys = new List&amp;lt;string&amp;gt;();
model.ContentResourceKeys.AddRange(ResourceHelper.SOCIALNETWORK_RESOURCE_KEYS);
model.ContentResourceKeys.AddRange(ResourceHelper.HOTEL_BANNER_RESOURCE_KEYS);
model.ContentResourceKeys.AddRange(ResourceHelper.DOCUMENTS_RESOURCE_KEYS);
model.ContentResourceKeys.AddRange(ResourceHelper.GENERAL_FOOTER_BANNERS_RESOURCE_KEYS);
model.ContentResourceKeys.AddRange(ResourceHelper.LANDING_PAGE_SEARCH_AREA_RESOURCE_KEYS);
model.ContentResourceKeys.AddRange(ResourceHelper.LANDING_PAGE_MODULES_RESOURCE_KEYS);
</pre>
<p>The ContentResourceKeys property is defined in a <em>MasterModel</em> that is extended by every View Model used across the site, as can be seen here: <a href="http://blogit.create.pt/andresantos/2014/11/07/pre-set-common-viewmodel-preperties-before-action-result/">http://blogit.create.pt/andresantos/2014/11/07/pre-set-common-viewmodel-preperties-before-action-result/</a>. That post also describes the Action Filter that is executed after every action on the site and it&#8217;s the place where we append the footer and header resources to the <strong>ContentResourceKeys</strong> list:</p>
<pre class="brush: csharp; title: Master resouce keys; notranslate">
List&amp;lt;string&amp;gt; resourceKeys = new List&amp;lt;string&amp;gt;();
resourceKeys.AddRange(ResourceHelper.HEADER_RESOURCE_KEYS.ToList());
resourceKeys.AddRange(ResourceHelper.LOGIN_RESOURCE_KEYS.ToList());
resourceKeys.AddRange(ResourceHelper.FOOTER_RESOURCE_KEYS.ToList());
resourceKeys.AddRange(ResourceHelper.LOADING_RESOURCE_KEYS.ToList());
resourceKeys.AddRange(ResourceHelper.HEADER_COOKIES_RESOURCE_KEYS.ToList());

if (model.ContentResourceKeys != null &amp;amp;&amp;amp; model.ContentResourceKeys.Count &amp;gt; 0)
{
    resourceKeys.AddRange(model.ContentResourceKeys);
}
</pre>
<h3>2 &#8211; Fetch the resources</h3>
<p>After compiling the resource keys needed, we go on and fetch the associated resource items. The project where this was implemented used a WebAPI to serve all the page content and the resource items were no exception. So, we send a POST request to the resource WebAPI method and fetch the needed resources, for the user language, from the available backoffice (we used a SharePoint list).</p>
<pre class="brush: csharp; title: Fetch the keys; notranslate">
model.Resources.AddRange(ResourceFacade.ListResources(model.CurrentLanguageCode, resourceKeys));
</pre>
<h3>3 &#8211; Add them to the View Model</h3>
<p>The <em>Resources</em> property available in the View Model where we added the result of the call to the resources WebAPI method is a string Dictionary. Since C# Dictionaries don&#8217;t provide an <em>AddRange</em> method, we create an extension method that enables us to append resources to a dictionary that&#8217;s not empty. Moreover, with this extension method we are able to scrap duplicate keys:</p>
<pre class="brush: csharp; title: Dictionary AddRange extension method; notranslate">
public static class IDictionaryExtensions
{
    public static void AddRange&amp;lt;T, S&amp;gt;(this IDictionary&amp;lt;T, S&amp;gt; source, IDictionary&amp;lt;T, S&amp;gt; collection)
    {
        if (collection == null)
        {
            throw new ArgumentNullException(&quot;Collection is null&quot;);
        }

        foreach (var item in collection)
        {
            if (!source.ContainsKey(item.Key))
            {
                source.Add(item.Key, item.Value);
            }
        }
    }
}
</pre>
<h3>4 &#8211; Use the resources in the views</h3>
<p>Since every View Model extends the MasterModel, we are able to use the same property everywhere:</p>
<pre class="brush: xml; title: Usage in the view; notranslate">
&amp;lt;footer id=&quot;footer&quot;&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;div class=&quot;newsletter&quot;&amp;gt;
      &amp;lt;h2&amp;gt;@Model.Resources&#x5B;&quot;Footer.Newsletter.Title&quot;]&amp;lt;/h2&amp;gt;
</pre>
<p>This way, we are able to manage the resource keys in a SharePoint list, or any other backoffice solution.</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/04/15/remote-resource-handling-in-a-mvc-website-project/">Remote resource handling in a MVC website project</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2016/04/15/remote-resource-handling-in-a-mvc-website-project/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Automatic generation of Umbraco packages with Grunt</title>
		<link>https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/</link>
					<comments>https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/#comments</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 07 Mar 2016 16:03:50 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[automatization]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[grunt]]></category>
		<category><![CDATA[npm]]></category>
		<category><![CDATA[NPM Scripts Task Runner]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Section]]></category>
		<category><![CDATA[visual studio]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1161</guid>

					<description><![CDATA[<p>Manually creating Umbraco packages can be tiresome. If you&#8217;re continuously building upon the same package, doing it manually is wasting time that can be more useful developing new features. This problem presented itself to me when improving Approve It. In order to create the Umbraco package I need several things: The main assembly The new dashboard [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/">Automatic generation of Umbraco packages with Grunt</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Manually creating Umbraco packages can be tiresome.</p>
<p>If you&#8217;re continuously building upon the same package, doing it manually is wasting time that can be more useful developing new features.</p>
<p>This problem presented itself to me when improving <a href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">Approve It</a>. In order to create the Umbraco package I need several things:</p>
<ul>
<li>The main assembly</li>
<li>The new dashboard section html file</li>
<li>The App_Plugins folder which contains every angular controller and views, javascript frameworks and custom css</li>
<li>The <a href="http://packageactioncontrib.codeplex.com/" target="_blank" rel="noopener">Package Actions Contrib</a> assembly and its respective actions that enable me to bundle some translations in the package</li>
</ul>
<p>I decided to go with <a href="http://gruntjs.com/getting-started" target="_blank" rel="noopener">Grunt</a> and the <a href="https://www.npmjs.com/package/grunt-umbraco-package" target="_blank" rel="noopener">Grunt Umbraco Package</a> task. A grunt file is a script that typically automates the process of executing some tasks. These scripts can be executed on top of <a href="https://www.npmjs.com/" target="_blank" rel="noopener">NPM</a>, a package manager for javscript that provides a command line tool to run its packages. To streamline even more the package creation, I installed a Visual Studio extension called <a href="https://visualstudiogallery.msdn.microsoft.com/8f2f2cbc-4da5-43ba-9de2-c9d08ade4941" target="_blank" rel="noopener">NPM Scripts Task Runner</a>, that detects the grunt file and provides a simples UI to handle its tasks.</p>
<p><span id="more-6479"></span></p>
<h3>Step 1 &#8211; Install NPM</h3>
<p>With NPM installed we are able to execute a Grunt file.</p>
<h3>Step 2 &#8211; Install NPM Scripts Task Runner</h3>
<p>The Visual Studio extension:</p>
<figure id="attachment_1241" aria-describedby="caption-attachment-1241" style="width: 471px" class="wp-caption alignnone"><a href="http://blogit-create.com/wp-content/uploads/2016/03/npmscriptsvs.png" rel="attachment wp-att-1241"><img decoding="async" class="wp-image-1241 size-full" src="http://blogit-create.com/wp-content/uploads/2016/03/npmscriptsvs.png" alt="NPM Scripts Task Runner Visual Studio extension" width="471" height="235" /></a><figcaption id="caption-attachment-1241" class="wp-caption-text">NPM Scripts Task Runner Visual Studio extension</figcaption></figure>
<h3>Step 3 &#8211; Create a Grunt file</h3>
<p>And place it in your project:</p>
<pre class="brush: jscript; title: Gruntfile.js; notranslate">
module.exports = function (grunt) {

    // Setup
    var pkg = grunt.file.readJSON('package.json');
    var projectRoot = 'C:/path/to/project/ApproveIt/';
    var packageNamespace = &quot;Create.Plugin&quot;;

    // Grunt Configuration
    grunt.initConfig({
        pkg: pkg,
        clean: {
            files: &#x5B;
                'bld/App_Plugins',
                'bld/bin',
                'bld/Umbraco'
            ]
        },
        copy: {
            release: {
                files: &#x5B;
                    {
                        expand: true,
                        cwd: projectRoot + 'bin/',
                        src: &#x5B;
                            packageNamespace + '.' + pkg.name + '.dll',
                            'PackageActionsContrib.dll'
                        ],
                        dest: 'bld/bin/'
                    },
                    {
                        expand: true,
                        cwd: projectRoot + 'App_Plugins/',
                        src: &#x5B;'**'],
                        dest: 'bld/App_Plugins/'
                    },
                    {
                        expand: true,
                        cwd: projectRoot + 'Dashboard/Views/dashboard/approveIt/',
                        src: &#x5B;'approveItdashboardintro.html'],
                        dest: &quot;bld/Umbraco/Views/dashboard/approveIt/&quot;
                    }
                ]
            }
        },
        umbracoPackage: {
            release: {
                src: 'bld/',
                dest: 'bin/umbraco',
                options: {
                    name: pkg.name,
                    version: pkg.version,
                    url: pkg.url,
                    license: pkg.license.name,
                    licenseUrl: pkg.license.url,
                    author: pkg.author.name,
                    authorUrl: pkg.author.url,
                    readme: pkg.readme,
                    outputName: pkg.name + '.v' + pkg.version + '.zip',
                    manifest: 'package.xml'
                }
            }
        }
    });

    // Loading of Grunt Tasks
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-umbraco-package');
};
</pre>
<p>This file has three sections, which I&#8217;ll explain in a different order:</p>
<ol>
<li>Setup</li>
<li>Grunt configuration</li>
<li>Loading of grunt tasks</li>
</ol>
<h4>Loading of Grunt Tasks</h4>
<p>Most actions we need a grunt file to execute are simplified by the use of existing Grunt tasks. In my case, I only need 3:</p>
<ol>
<li><em>grunt-contrib-clean</em>: deletes files and folders</li>
<li><em>grunt-contrib-copy</em>: copies files and folders</li>
<li><em>grunt-umbraco-package</em>: generates an umbraco package</li>
</ol>
<h4>Grunt File Setup</h4>
<p>Here I define a set of variables and load an external configuration file that contains project properties I use later on:</p>
<pre class="brush: jscript; title: package.json; notranslate">
{
  &quot;author&quot;: {
    &quot;name&quot;: &quot;André Santos&quot;,
    &quot;url&quot;: &quot;http://www.create.pt&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;grunt&quot;: &quot;~0.4.5&quot;,
    &quot;grunt-contrib-clean&quot;: &quot;^0.6.0&quot;,
    &quot;grunt-contrib-copy&quot;: &quot;~0.4.1&quot;,
    &quot;grunt-umbraco-package&quot;: &quot;1.0.0&quot;
  },
  &quot;license&quot;: {
    &quot;name&quot;: &quot;MIT&quot;,
    &quot;url&quot;: &quot;http://opensource.org/licenses/MIT&quot;
  },
  &quot;name&quot;: &quot;ApproveIt&quot;,
  &quot;readme&quot;: &quot;Umbraco Plugin that creates a section that shows all the content that is waiting approval for publishing.&quot;,
  &quot;url&quot;: &quot;https://our.umbraco.org/projects/backoffice-extensions/approve-it/&quot;,
  &quot;version&quot;: &quot;1.0.1&quot;
}
</pre>
<h4>Grunt Configuration</h4>
<p>This section is where everything comes together. The <em>package.json</em> configuration file is loaded and the three aforementioned grunt tasks are implemented:</p>
<h5>Clean</h5>
<p>In here, the directories used as the source for the umbraco package creation, are configured so that when we can empty them at will</p>
<h5>Copy</h5>
<p>This task copies every file needed by the plugin to a centralized location, creating a snapshot of our plugin. We copy full directories and hand picked files as needed.</p>
<h5>Umbraco Package</h5>
<p>Finally, this task picks up every file we have copied and creates the final Umbraco package. It uses the configuration properties set in <em>package.json</em> and uses a custom <em>package.xml</em> file so that I can include my custom post umbraco package install actions (using Package Actions Contrib):</p>
<pre class="brush: xml; title: package.xml; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;
&lt;umbPackage&gt;
  &lt;info&gt;
    &lt;package&gt;
      &lt;name&gt;&lt;%= name %&gt;&lt;/name&gt;
      &lt;version&gt;&lt;%= version %&gt;&lt;/version&gt;
      &lt;license url=&quot;&lt;%= licenseUrl %&gt;&quot;&gt;&lt;%= license %&gt;&lt;/license&gt;
      &lt;url&gt;&lt;%= url %&gt;&lt;/url&gt;
      &lt;requirements&gt;
        &lt;major&gt;3&lt;/major&gt;
        &lt;minor&gt;0&lt;/minor&gt;
        &lt;patch&gt;0&lt;/patch&gt;
      &lt;/requirements&gt;
    &lt;/package&gt;
    &lt;author&gt;
      &lt;name&gt;&lt;%= author %&gt;&lt;/name&gt;
      &lt;website&gt;&lt;%= authorUrl %&gt;&lt;/website&gt;
    &lt;/author&gt;
    &lt;readme&gt;&lt;!&#x5B;CDATA&#x5B;&lt;%= readme %&gt;]]&gt;&lt;/readme&gt;
  &lt;/info&gt;
  &lt;DocumentTypes /&gt;
  &lt;Templates /&gt;
  &lt;Stylesheets /&gt;
  &lt;Macros /&gt;
  &lt;DictionaryItems /&gt;
  &lt;Languages /&gt;
  &lt;DataTypes /&gt;
  &lt;control /&gt;
  &lt;Actions&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;addDashboardSection&quot; dashboardAlias=&quot;StartupApproveItDashboardSection&quot;&gt;
      &lt;section&gt;
        &lt;areas&gt;
          &lt;area&gt;approveIt&lt;/area&gt;
        &lt;/areas&gt;
        &lt;tab caption=&quot;Get Started&quot;&gt;
          &lt;control showOnce=&quot;true&quot; addPanel=&quot;true&quot; panelCaption=&quot;&quot;&gt;
            views/dashboard/approveIt/approveItdashboardintro.html
          &lt;/control&gt;
        &lt;/tab&gt;
      &lt;/section&gt;
    &lt;/Action&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pt&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;cs&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;da&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en_us&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;es&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;fr&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;he&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;it&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ja&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ko&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;nl&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;no&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pl&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ru&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;sv&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;zh&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pt&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Editador por&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;cs&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;da&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en_us&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;es&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;fr&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;he&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;it&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ja&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ko&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;nl&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;no&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pl&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ru&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;sv&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;zh&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
  &lt;/Actions&gt;
  &lt;files&gt;
  	&lt;% files.forEach(function(file) { %&gt;
  	&lt;file&gt;
      &lt;guid&gt;&lt;%= file.guid %&gt;&lt;/guid&gt;
      &lt;orgPath&gt;&lt;%= file.dir %&gt;&lt;/orgPath&gt;
      &lt;orgName&gt;&lt;%= file.name %&gt;&lt;/orgName&gt;
    &lt;/file&gt;
	&lt;% }); %&gt;
  &lt;/files&gt;
&lt;/umbPackage&gt;
</pre>
<p>This file uses the Grunt templating engine so that, not only can I include the Package Action Contrib custom actions, but also some custom variables, such as:</p>
<ul>
<li>The mandatory list of files that is automatically updated when a new file is added to the project output</li>
<li>The <em>package.json</em> variables</li>
</ul>
<p>That&#8217;s it! We can now create new versions of Approve It without having to go into the Umbraco backoffice and doing it manually every time we need a new package version.</p>
<p><em>You can find the complete source code in my GitHub: <a href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>. You can download the Approve It package here: <a href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/">Automatic generation of Umbraco packages with Grunt</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Building an Umbraco 7 backoffice extension – Part III</title>
		<link>https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/</link>
					<comments>https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/#comments</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 30 Nov 2015 15:02:06 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AngularJS]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Section]]></category>
		<category><![CDATA[WebMatrix]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=841</guid>

					<description><![CDATA[<p>The previous post (Part II) showed how to populate the content tree of a custom Umbraco backoffice section. This one presents a way for content managers to quickly handle each post pending approval, which means we&#8217;re going to create our own AngularJS controller and view. AngularJS is a MVC javascript framework mainly mantained by Google which [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/">Building an Umbraco 7 backoffice extension – Part III</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">T</span>he previous post (<a title="Building an Umbraco 7 backoffice extension - Part II" href="http://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii" target="_blank" rel="noopener">Part II</a>) showed how to populate the content tree of a custom Umbraco backoffice section. This one presents a way for content managers to quickly handle each post pending approval, which means we&#8217;re going to create our own AngularJS controller and view.</p>
<p><a title="Angular.JS" href="https://angularjs.org/" target="_blank" rel="noopener">AngularJS</a> is a MVC javascript framework mainly mantained by Google which primary goal is to aid in the development of single-page applications. It&#8217;s use on the Umbraco backoffice was the most notable change when Umbraco 7 was first released.</p>
<p>When the content manager selects one node pending approval we want him to be able to take three quick actions:</p>
<ul>
<li>get some information about that node;</li>
<li>quickly access it in the content section;</li>
<li>quickly publish it.</li>
</ul>
<p><span id="more-6478"></span></p>
<p>New Angular controllers and views must be placed in a specific Umbraco directory, following a specific directory structure:</p>
<ul>
<li>the main directory must reside at ~\App_Plugins\ApproveIt &#8211; the plugin name</li>
<li>the views and controllers must reside at App_Plugins\ApproveIt\backoffice\approvalTree\ &#8211; the tree controller name</li>
</ul>
<p>First things first:</p>
<p>Create a package manifest so that Umbraco knows there are new javascript files available, and place it in the root of App_Plugins\ApproveIt directory.</p>
<pre class="brush: plain; title: package.manifest; notranslate">

{
	javascript: &#x5B;
		'~/App_Plugins/ApproveIt/backoffice/approvalTree/edit.controller.js',
		'~/App_Plugins/ApproveIt/approval.resource.js'
	]
}

</pre>
<p>Then we must create the javascript files referenced by the manifest and the respective angular view.</p>
<pre class="brush: jscript; title: approval.resource.js; notranslate">

angular.module(&quot;umbraco.resources&quot;)
	.factory(&quot;approvalResource&quot;, function ($http) {
	    return {
	        getById: function (id) {
	            return $http.get(&quot;backoffice/ApproveIt/ApprovalApi/GetById?id=&quot; + id);
	        },
	        publish: function (id) {
	            return $http.post(&quot;backoffice/ApproveIt/ApprovalApi/PostPublish?id=&quot; + id);
	        }
	    };
	});
</pre>
<p>This javascript file provides a new <a title="Angular Service" href="https://docs.angularjs.org/guide/services">Angular Service</a> that can then be used by Angular controllers. This service is no more than a facade for the ApprovalApiController created on <a title="Building an Umbraco 7 backoffice extension - Part II" href="http://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii" target="_blank" rel="noopener">Part II</a>. The methods it connects to are created further on.</p>
<pre class="brush: jscript; title: edit.controller.js; notranslate">

angular.module(&quot;umbraco&quot;).controller(&quot;Approval.ApprovalEditController&quot;,
	function ($scope, $routeParams, approvalResource, notificationsService, navigationService) {

	    $scope.loaded = false;

	    if ($routeParams.id == -1) {
	        $scope.node = {};
	        $scope.loaded = true;
	    }
	    else {
	        approvalResource.getById($routeParams.id).then(function (response) {
	            $scope.node = response.data;
	            $scope.loaded = true;
	        });
	    }

	    $scope.publish = function (node) {
	        approvalResource.publish(node.Id).then(function (response) {
	            $scope.node = response.data;
	            $scope.contentForm.$dirty = false;
	            navigationService.syncTree({ tree: 'approvalTree', path: &#x5B;-1, -1], forceReload: true });
	            notificationsService.success(&quot;Success&quot;, node.Name + &quot; has been published&quot;);
	        });
	    };

	});

</pre>
<p>This Angular controller is called when the Approve It section is being used:</p>
<ul>
<li>If no node is selected (for instance, when we access this section for the first time) we do nothing;</li>
<li>If a node is selected (<em>$routeParams.id != -1</em>) we request the node details through our approval.resource service facade</li>
<li>Finally, the quick publish method is also implemented here. This methods calls the ApprovalResource service which then invokes the publish method of the ApprovalApiController.</li>
</ul>
<pre class="brush: xml; title: edit.html; notranslate">

&lt;form name=&quot;contentForm&quot;
      ng-controller=&quot;Approval.ApprovalEditController&quot;
      ng-show=&quot;loaded&quot;
      ng-submit=&quot;publish(node)&quot;
      val-form-manager&gt;
    &lt;umb-panel&gt;
        &lt;umb-header&gt;
            &lt;div class=&quot;span7&quot;&gt;
                &lt;umb-content-name placeholder=&quot;@placeholders_entername&quot;
                                  ng-model=&quot;node.Name&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;span5&quot;&gt;
                &lt;div class=&quot;btn-toolbar pull-right umb-btn-toolbar&quot;&gt;
                    &lt;umb-options-menu ng-show=&quot;currentNode&quot;
                                      current-node=&quot;currentNode&quot;
                                      current-section=&quot;{{currentSection}}&quot;&gt;
                    &lt;/umb-options-menu&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/umb-header&gt;
        &lt;div class=&quot;umb-panel-body umb-scrollable row-fluid&quot;&gt;
            &lt;div class=&quot;tab-content form-horizontal&quot; style=&quot;padding-bottom: 90px&quot;&gt;
                &lt;div class=&quot;umb-pane&quot;&gt;

                    &lt;umb-control-group label=&quot;Name&quot; description=&quot;Content's name&quot;&gt;
                        &lt;input type=&quot;text&quot; class=&quot;umb-editor umb-textstring&quot; ng-model=&quot;node.Name&quot; required /&gt;
                    &lt;/umb-control-group&gt;

                    &lt;div class=&quot;umb-tab-buttons&quot; detect-fold&gt;
                        &lt;div class=&quot;btn-group&quot;&gt;
                            &lt;a class=&quot;btn&quot; ng-href=&quot;#/content/content/edit/{{node.Id}}&quot;&gt;
                                Show
                            &lt;/a&gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;btn-group&quot;&gt;
                            &lt;button type=&quot;submit&quot; data-hotkey=&quot;ctrl+s&quot; class=&quot;btn btn-success&quot;&gt;
                                Publish
                            &lt;/button&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/umb-panel&gt;
&lt;/form&gt;
</pre>
<p>The node view presents the waiting approval page name and two buttons: one to quickly navigate to it&#8217;s details and another to publish it.</p>
<p>Finally, we need to update the ApprovalApiController.cs to include two new methods:</p>
<pre class="brush: csharp; title: ; notranslate">

public IContent GetById(int id)
{
    IContent content = ApplicationContext.Services.ContentService.GetById(id);
    return content;
}

public IContent PostPublish(int id)
{
    IContent node = ApplicationContext.Services.ContentService.GetById(id);
    ApplicationContext.Services.ContentService.Publish(node);

    return node;
}
</pre>
<ul>
<li>GetById: returns the node details to the angular view</li>
<li>PostPublish: publishes the node being viewed</li>
</ul>
<p>By following these steps we are able to create a complete new Umbraco backoffice section that provides us with a way to manage every site content waiting approval for publishing:</p>
<p><a href="http://blogit-create.com/wp-content/uploads/2015/11/approveit1.0.png"><img decoding="async" class="aligncenter wp-image-1031" src="http://blogit-create.com/wp-content/uploads/2015/11/approveit1.0.png" alt="approveit1.0" width="600" height="410" /></a></p>
<p><em>If you want to see how this plugin as been evolving, you can check its source code in github: <a title="https://github.com/ViGiLnT/ApproveIt" href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>. You can also download the plugin ready to be installed at Umbraco&#8217;s extensions repository: <a title="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>. Other Umbraco 7 tutorials and information can be found here: <a title="http://umbraco.github.io/Belle" href="http://umbraco.github.io/Belle" target="_blank" rel="noopener">http://umbraco.github.io/Belle</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/">Building an Umbraco 7 backoffice extension – Part III</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title>Building an Umbraco 7 backoffice extension – Part II</title>
		<link>https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/</link>
					<comments>https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 23 Nov 2015 10:00:56 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AngularJS]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Section]]></category>
		<category><![CDATA[WebMatrix]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=721</guid>

					<description><![CDATA[<p>The previous post (Building an Umbraco 7 backoffice extension – Part I), demonstrated how easy it is to create a new section in Umbraco&#8217;s backoffice. This post will show how we can populate this new section with meaningful content coming directly from the backoffice. To begin with, we must reference a few more assemblies (also [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/">Building an Umbraco 7 backoffice extension – Part II</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">T</span>he previous post (<a title="Building an Umbraco 7 backoffice extension – Part I" href="http://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/" target="_blank" rel="noopener">Building an Umbraco 7 backoffice extension – Part I</a>), demonstrated how easy it is to create a new section in Umbraco&#8217;s backoffice. This post will show how we can populate this new section with meaningful content coming directly from the backoffice.</p>
<p>To begin with, we must reference a few more assemblies (also available at the Umbraco&#8217;s installation <em>bin</em> directory):</p>
<ul>
<li>umbraco.dll</li>
<li>cms.dll</li>
<li>Umbraco.Core.dll</li>
<li>System.Web.Http</li>
<li>System.Net.Http.Formatting</li>
</ul>
<p>Then we can populate our section with a tree containing content awaiting approval. To accomplish this we must extend an Umbraco class named TreeController and decorate it with information about our <strong>Approve It</strong> plugin:</p>
<p><span id="more-6471"></span></p>
<pre class="brush: csharp; title: ; notranslate">

&#x5B;Tree(&quot;approveIt&quot;, &quot;approvalTree&quot;, &quot;Content for Approval&quot;)]
&#x5B;PluginController(&quot;ApproveIt&quot;)]
public class ApprovalTreeController : TreeController
{
   protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
   {
       var ctrl = new ApprovalApiController();
       var nodes = new TreeNodeCollection();

       IUser user = UmbracoContext.Security.CurrentUser;

       //check if we're rendering the root node's children
       if (id == Constants.System.Root.ToInvariantString())
       {
           foreach (IContent content in ctrl.GetAll(user))
           {
                var node = CreateTreeNode(
                    content.Id.ToString(),
                    &quot;-1&quot;,
                    queryStrings,
                    content.Name,
                    &quot;icon-document&quot;,
                    false);

               nodes.Add(node);
            }
       }

        return nodes;
    }

    protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
    {
        var menu = new MenuItemCollection();

        if (id == Constants.System.Root.ToInvariantString())
        {
            // root actions
            menu.Items.Add&lt;RefreshNode, ActionRefresh&gt;(
                ApplicationContext.Services.TextService.Localize(ActionRefresh.Instance.Alias));
        }

        return menu;
    }
}
</pre>
<p>To create a tree we must implement two methods:</p>
<ol>
<li>GetTreeNodes: in here we call the GetAll method of the ApprovalApiController that we&#8217;ll create in the following. This GetAll method returns a list of content waiting approval and we just iterate through it all and create tree nodes;</li>
<li>GetMenuForNode: presents a dropdown menu when we right click the root node to refresh the list.</li>
</ol>
<p>Next we create our custom WebAPI and name it ApprovalApiController. For it to be available in our site, we need only extend the UmbracoAuthorizedJsonController.</p>
<pre class="brush: csharp; title: ; notranslate">

&#x5B;PluginController(&quot;ApproveIt&quot;)]
public class ApprovalApiController : UmbracoAuthorizedJsonController
{
    public IList&lt;IContent&gt; UnpublishedContent { get; set; }

    public IEnumerable&lt;IContent&gt; GetAll(IUser user)
    {
        UnpublishedContent = new List&lt;IContent&gt;();

        IList&lt;IContent&gt; root = ApplicationContext.Services.ContentService.GetRootContent().ToList();

        foreach (IContent content in root)
        {
            GetNode(content);
        }

        return UnpublishedContent;
    }

    private void GetNode(IContent node)
    {
        if (!node.Published)
        {
            UnpublishedContent.Add(node);
        }

        foreach (IContent child in ApplicationContext.Services.ContentService.GetChildren(node.Id))
        {
            GetNode(child);
        }
    }
}

</pre>
<p>The GetAll method, iterates through all the nodes in the content section and finds the ones that are not published, that is, that are waiting approval.</p>
<p>With these two classes we are able to populate our section with a tree containing the content nodes that are waiting approval:</p>
<p><a href="http://blogit-create.com/wp-content/uploads/2015/11/approveit2.png"><img decoding="async" class="alignnone size-medium wp-image-791" src="http://blogit-create.com/wp-content/uploads/2015/11/approveit2-167x300.png" alt="approveit2" width="167" height="300" /></a></p>
<p>The next post in the series will show how to create the Angular view and controller that will enable content managers to actually manage the content waiting approval.</p>
<p><em>The Approve It extension can be found here: <a title="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>. The complete source code can be found here: <a title="https://github.com/ViGiLnT/ApproveIt" href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/">Building an Umbraco 7 backoffice extension – Part II</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
