<?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>.NET Archives - Blog IT</title>
	<atom:link href="https://blogit.create.pt/tag/net/feed/" rel="self" type="application/rss+xml" />
	<link>https://blogit.create.pt/tag/net/</link>
	<description>Create IT blogger community</description>
	<lastBuildDate>Fri, 28 Jun 2024 13:30:49 +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>Configuring Azure Application Insights on a .NET 6 API</title>
		<link>https://blogit.create.pt/andrepires/2024/05/27/configuring-azure-application-insights-on-a-net-6-api/</link>
					<comments>https://blogit.create.pt/andrepires/2024/05/27/configuring-azure-application-insights-on-a-net-6-api/#respond</comments>
		
		<dc:creator><![CDATA[André Pires]]></dc:creator>
		<pubDate>Mon, 27 May 2024 08:21:04 +0000</pubDate>
				<category><![CDATA[Misc]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://blogit.create.pt/?p=13492</guid>

					<description><![CDATA[<p>In this blog post we are going to be showing you can configure Azure Application Insights to send your logs, exceptions and performance metrics from a .NET 6 API. InstallationStart by installing the Microsoft.ApplicationInsights.AspNetCore nugget package. Now in your startup class you will need to configure some options for the telemetry collection.You can use the [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andrepires/2024/05/27/configuring-azure-application-insights-on-a-net-6-api/">Configuring Azure Application Insights on a .NET 6 API</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p></p>



<p>In this blog post we are going to be showing you can configure Azure Application Insights to send your logs, exceptions and performance metrics from a .NET 6 API.<br><br><strong>Installation</strong><br>Start by installing the <em>Microsoft.ApplicationInsights.AspNetCore</em> nugget package.<br><br>Now in your startup class you will need to configure some options for the telemetry collection.<br>You can use the <em>AddApplicationInsightsTelemetry </em>extension method on the <em>IServiceCollection </em>and pass your options as so:<br></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class Startup
{
    public virtual void ConfigureServices(IServiceCollection services)
    {
        services.AddApplicationInsightsTelemetry(GetApplicationInsightsServiceOptions());    
    }

    private static ApplicationInsightsServiceOptions GetApplicationInsightsServiceOptions()
    {
        return new ApplicationInsightsServiceOptions
        {
            AddAutoCollectedMetricExtractor = false,
            EnableEventCounterCollectionModule = false,
            EnableDiagnosticsTelemetryModule = false,
            EnablePerformanceCounterCollectionModule = true,
            EnableDependencyTrackingTelemetryModule = true,
            EnableRequestTrackingTelemetryModule = false,
            ConnectionString = Configuration&#x5B;ConfigurationConstants.ApplicationInsightsConnectionString],
        };
    }
}
</pre></div>


<p>This is where we define the types of telemetry we want to collect.<br>The documention of the options are here: <a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#use-applicationinsightsserviceoptions">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#use-applicationinsightsserviceoptions</a><br>We are also getting the connection string of the application insights resource from our appsettings.</p>



<p><strong>Telemetry modules</strong><br>Now if we want to go further, we can even specify what metrics to collect for each module.<br>For instance, let&#8217;s say we want to indicate which performance metrics to collect. Then you can do the following:<br></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
        services.ConfigureTelemetryModule&lt;PerformanceCollectorModule&gt;((module, applicationInsightsServiceOptions) =&gt;
        {
            module.DefaultCounters.Add(new PerformanceCounterCollectionRequest(@&quot;\Process(??APP_WIN32_PROC??)\Private Bytes&quot;, @&quot;\Process(??APP_WIN32_PROC??)\Private Bytes&quot;));
            module.DefaultCounters.Add(new PerformanceCounterCollectionRequest(@&quot;\Process(??APP_WIN32_PROC??)\% Processor Time&quot;, @&quot;\Process(??APP_WIN32_PROC??)\% Processor Time&quot;));
        });
</pre></div>


<p>You can view the documentation on configuring telemetry modules here: <a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#configure-or-remove-default-telemetrymodules">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#configure-or-remove-default-telemetrymodules</a><br><br><strong>Extensibility</strong><br>To add or remove properties from collected telemetry (before it gets sent to Azure) you can create classes that implement the <em>ITelemetryInitializer </em>class from the Azure Application Insights SDK (<a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#add-telemetryinitializers">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#add-telemetryinitializers</a>).<br><br>A classic example for a telemetry initializer is a correlation id initializer. You would get a correlation id from your http request and pass it to all telemetry so that you can have a nice trace of everything that went through your system:<br></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class CorrelationIdInitializer : ITelemetryInitializer
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private const string CorrelationIdProperty = &quot;CorrelationId&quot;;

    public CorrelationIdInitializer(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        ISupportProperties telemetryProperties = (ISupportProperties)telemetry;

        // This is just an extension method where you would extract the correlation id from the request headers according to your header name.
        string correlationId = _httpContextAccessor.HttpContext.GetCorrelationId();

        if (!string.IsNullOrWhiteSpace(correlationId) &amp;&amp; !telemetryProperties.Properties.ContainsKey(CorrelationIdProperty))
        {
            telemetryProperties.Properties.Add(CorrelationIdProperty, correlationId);
        }
    }
</pre></div>


<p>To register the telemetry initializer, simply register the class and its respective interface (always ITelemetryInitializer) on your service collection: <em>services.AddSingleton&lt;ITelemetryInitializer, CorrelationIdInitializer&gt;();</em></p>



<p><strong>Filtering</strong><br>Additionally, you also have the ability to filter out telemetry from being collected. This is a great feature that allows you to focus only storing what you really care about and will save you a LOT of costs.<br>Do keep in mind that filtering out telemetry does mean that you won&#8217;t be able to query it and therefore can make your tracing a bit difficult.<br>This is all related to telemetry processors (<a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#add-telemetry-processors">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#add-telemetry-processors</a>).<br><br>Imagine you have thousands of fast dependencies (see the list of automatically tracked dependencies <a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-dependencies#automatically-tracked-dependencies">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-dependencies#automatically-tracked-dependencies</a>) that are being stored by your system but you come to a conclusion that they only make it hard to monitor your system and you don&#8217;t really need them.<br>Filtering them out is the way to go OR you could consider sampling instead <a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#sampling">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#sampling</a>.<br>Lets create a processor that filters out successful dependencies that had a duration under 500 milliseconds:<br></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class FastDependencyProcessor : ITelemetryProcessor
{
    private ITelemetryProcessor Next { get; set; }

    public FastDependencyProcessor(ITelemetryProcessor next)
    {
        Next = next;
    }

    public void Process(ITelemetry item)
    {
        if (!ShouldFilterTelemetry(item))
        {
            Next.Process(item);
        }
    }

    public bool ShouldFilterTelemetry(ITelemetry item)
    {
        bool shouldFilterTelemetry = false;

        DependencyTelemetry dependency = item as DependencyTelemetry;

        if (dependency != null
            &amp;&amp; dependency.Duration.TotalMilliseconds &lt; 500
            &amp;&amp; dependency.Success.HasValue &amp;&amp; dependency.Success.Value)
        {
            shouldFilterTelemetry = true;
        }

        return shouldFilterTelemetry;
    }
}
</pre></div>


<p>After creating the class, you have to register the telemetry processor as part of your service collection like so:<br><em>services.AddApplicationInsightsTelemetryProcessor&lt;FastDependencyProcessor</em>&gt;<em>();</em><br><br>Keep in mind that if your telemetry processors will be executed in the order in which you&#8217;ve called them in your register process.<br><br><strong>Final notes</strong><br><br>&#8211; Azure Application Insights is a great tool that gives you visibility on how your application is doing.<br>You can use the SDK to collect most telemetry automatically or you could even instrument specific scenarios manually by making use of the <em>TelemetryClient</em> instance (<a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#how-can-i-track-telemetry-thats-not-automatically-collected">https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew#how-can-i-track-telemetry-thats-not-automatically-collected</a>).<br>&#8211; It is important to monitor your costs as it is easy to end up storing everything and not only what you need. So start by defining what you need to collect and what metrics will be relevant for you.<br>Then you can filter out the irrelevant data and from there on you could even create alerts from the data you collect, using Azure Monitor.<br>If you are trying out Application Insights on a test environment, you can even set a daily cap of X GB so that you control your spending. Simply navigate to the Azure Application Insights resource through the Azure Portal and click &#8220;Usage and estimated Costs&#8221; on the sidebar:<br></p>



<figure class="wp-block-image size-full is-resized"><img fetchpriority="high" decoding="async" width="248" height="229" src="https://blogit.create.pt/wp-content/uploads/2024/05/image.png" alt="" class="wp-image-13493" style="width:238px;height:auto" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="638" height="40" src="https://blogit.create.pt/wp-content/uploads/2024/05/image-1.png" alt="" class="wp-image-13494" srcset="https://blogit.create.pt/wp-content/uploads/2024/05/image-1.png 638w, https://blogit.create.pt/wp-content/uploads/2024/05/image-1-300x19.png 300w" sizes="(max-width: 638px) 100vw, 638px" /></figure>



<p><br>&#8211; From what I&#8217;ve seen so far, the collection of dependencies/exceptions can represent the majority of costs as you may have a lot of dependencies flowing through your system and because exceptions are a type of telemetry that occupies a lot of space. Filtering out irrelevant dependencies will definitely help.<br>As for exceptions, you may consider using the Result pattern instead of using exceptions for the normal control flow of your code. This also has the advantage of decreasing the impact on performance lead by exceptions to your application since you reduce the amount of exceptions thrown.</p>
<p>The post <a href="https://blogit.create.pt/andrepires/2024/05/27/configuring-azure-application-insights-on-a-net-6-api/">Configuring Azure Application Insights on a .NET 6 API</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andrepires/2024/05/27/configuring-azure-application-insights-on-a-net-6-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Integration Tests in .NET WebApi</title>
		<link>https://blogit.create.pt/andresantos/2024/04/01/integration-tests-in-net-webapi/</link>
					<comments>https://blogit.create.pt/andresantos/2024/04/01/integration-tests-in-net-webapi/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 01 Apr 2024 12:47:41 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Automatic Testing]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[inmemorydatabase]]></category>
		<category><![CDATA[integrated]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://blogit.create.pt/?p=13472</guid>

					<description><![CDATA[<p>In software development, testing is an essential aspect that ensures the stability and reliability of applications. Three primary types of tests are commonly used: unit tests, integration tests, and end-to-end tests. In this blog post, we will discuss these testing types in the context of a .NET WebAPI project and provide an example implementation of integration testing using an in-memory database.</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2024/04/01/integration-tests-in-net-webapi/">Integration Tests in .NET WebApi</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In software development, testing is an essential aspect that ensures the stability and reliability of applications. Three primary types of tests are commonly used: unit tests, integration tests, and end-to-end tests. In this blog post, we will discuss these testing types in the context of a .NET WebAPI project and provide an example implementation of integration testing using an in-memory database.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Unit Tests, Integration Tests, and End-to-End Tests: What&#8217;s the Difference?</h2>



<ul class="wp-block-list">
<li><strong>Unit tests</strong> focus on testing individual units or components of your application in isolation. This type of test verifies whether the unit/component works correctly and adheres to its business logic.</li>



<li><strong>Integration tests</strong> aim to test multiple units or components together, ensuring that they interact properly. Integration tests can uncover issues related to data flow, communication between components, and external dependencies like databases.</li>



<li><strong>End-to-end (E2E)</strong> tests simulate a complete user scenario by testing the entire application from start to finish. E2E tests can help identify issues related to multiple components, external APIs, and user interfaces.</li>
</ul>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<h2 class="wp-block-heading">Example Implementation in .NET</h2>



<p>To perform integration tests, we will use an in-memory database and the <em>WebApplicationFactory </em>feature of ASP.NET Core. This approach allows us to test our WebAPI application with a real web server in memory, simulating how the components interact with each other when making requests.</p>



<p>First, let&#8217;s create an InMemoryDbAppFactory that sets up our in-memory web server and database:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class InMemoryDbAppFactory : WebApplicationFactory&lt;Program&gt;
{
    private readonly string _environment;

    public InMemoryDbAppFactory()
    {
        _environment = &quot;IntegrationTests&quot;;
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        Environment.SetEnvironmentVariable(&quot;ASPNETCORE_ENVIRONMENT&quot;, _environment);
        builder.UseEnvironment(_environment);
        builder.UseSetting(&quot;https_port&quot;, &quot;8080&quot;);

        builder.ConfigureTestServices(services =&gt;
        {
            services.RemoveAll&lt;ITestRepository&gt;();
            services.TryAddTransient&lt;ITestRepository, TestRepositoryTest&gt;();

            // Anonymous authentication
            services
                .AddAuthentication(&quot;Test&quot;)
                .AddScheme&lt;AuthenticationSchemeOptions, TestAuthenticationHandler&gt;(&quot;Test&quot;, options =&gt; { });
        });
    }
}
</pre></div>


<p>In order to use an in-memory database, we will override the connection string injection in our <em>ServiceCollectionExtensions</em>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public static partial class ServiceCollectionExtensions
{
    public static IServiceCollection AddDbContexts(
        this IServiceCollection services,
        IConfiguration configuration,
        IWebHostEnvironment environment
    )
    {
        if (environment.IsEnvironment(&quot;IntegrationTests&quot;))
        {
            var connDb = new SqliteConnection(&quot;DataSource=db;mode=memory;cache=shared&quot;);
            connDb.Open();
            services.AddDbContext&lt;dbContext&gt;(options =&gt; options.UseSqlite(connDb));
        }
        else
        {
            services.AddDbContext&lt;dbContext&gt;(
                options =&gt; options.UseSqlServer(configuration.GetConnectionString(&quot;ConnectionString&quot;)).UseExceptionProcessor(),
                ServiceLifetime.Scoped
            );
        }

        return services;
    }
}
</pre></div>


<p>Finally, this is how a test looks like:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;Collection(&quot;MemoryDbIntegrationTests&quot;)]
public class ApiTests : IClassFixture&lt;InMemoryDbAppFactory&gt;
{
    private readonly InMemoryDbAppFactory _factory;
    private readonly HttpClient _client;

    public ApiTests(InMemoryDbAppFactory factory)
    {
        _factory = factory;
        _client = factory.CreateClient();

        // Ensures a clean database before each test
        factory.ResetDb();
    }

    &#x5B;Fact]
    public async Task GetList_Should_Return_List()
    {
        // Arrange

        // Act
        var response = await _client.GetAsync(&quot;/api/list&quot;);
        var content = await response.Content.ReadFromJsonAsync&lt;IEnumerable&lt;Dto&gt;&gt;();

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        content.Should().NotBeNull();
        content.Should().HaveCount(2);
    }
}

</pre></div></div>
</div>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Using an in-memory database for integration tests has both advantages and disadvantages:</h2>



<h3 class="wp-block-heading">Advantages:</h3>



<ul class="wp-block-list">
<li><strong>Faster test execution</strong>: In-memory databases provide quicker test execution times since the data is stored in memory instead of on disk. This can significantly improve your overall testing performance and help you find issues faster.</li>



<li><strong>Easy to set up and tear down</strong>: In-memory databases are easy to create, modify, and delete without the need for external dependencies or configuration changes. This makes it easier to write, run, and maintain your tests.</li>



<li><strong>Consistent test data</strong>: In-memory databases allow you to create a known set of data for your tests, ensuring that each test starts with the same initial conditions. This can help reduce the likelihood of inconsistent test results and make it easier to identify issues related to data flow or dependencies between tests.</li>
</ul>



<h3 class="wp-block-heading">Disadvantages:</h3>



<ul class="wp-block-list">
<li><strong>Limited scalability</strong>: In-memory databases have limited capacity and may not be suitable for testing large datasets or complex scenarios that require high levels of concurrency.</li>



<li><strong>Lack of realism</strong>: In-memory databases may not accurately represent the behavior or performance of a production database, especially when dealing with complex queries or data modification operations. This can make it difficult to identify issues related to database schema, indexing, and query optimization.</li>



<li><strong>Limited support for advanced features</strong>: using SQLite as our in memory database we don&#8217;t have support for multiple schemas and some advanced query features.</li>



<li><strong>Seed data</strong>: the need to create seed data can be tedious and time consuming.</li>
</ul>



<p>When deciding whether to use an in-memory database, mock data, or a real web server in memory for your integration tests, consider the specific requirements of your project and weigh the advantages and disadvantages of each approach. In many cases, using a combination of testing types and approaches can help you ensure the stability, reliability, and performance of your .NET WebAPI application.</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2024/04/01/integration-tests-in-net-webapi/">Integration Tests in .NET WebApi</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2024/04/01/integration-tests-in-net-webapi/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>TWIL: The weird way .NET&#8217;s config system parses JSONs</title>
		<link>https://blogit.create.pt/hugobarroca/2022/02/07/twil-the-weird-way-nets-config-system-parses-jsons/</link>
					<comments>https://blogit.create.pt/hugobarroca/2022/02/07/twil-the-weird-way-nets-config-system-parses-jsons/#respond</comments>
		
		<dc:creator><![CDATA[Hugo Barroca]]></dc:creator>
		<pubDate>Mon, 07 Feb 2022 13:01:05 +0000</pubDate>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[appsettings]]></category>
		<guid isPermaLink="false">https://blogit.create.pt/?p=12619</guid>

					<description><![CDATA[<p>The day is a Friday. We were calmly deploying an app we tested locally to the test environment. The error we had was pointing towards the config files. Off we go to check the appsettings.json file… Every value seems correct, but the error is definitely pointing us here. Here&#8217;s a simplified version of the offending [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/hugobarroca/2022/02/07/twil-the-weird-way-nets-config-system-parses-jsons/">TWIL: The weird way .NET&#8217;s config system parses JSONs</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The day is a Friday. We were calmly deploying an app we tested locally to the test environment. The error we had was pointing towards the config files. Off we go to check the appsettings.json file…</p>



<p>Every value seems correct, but the error is definitely pointing us here. Here&#8217;s a simplified version of the offending line:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
Console.Writeline(AConfig.ASubConfig);
</pre></div>


<p>And the corresponding appsettings:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
    &quot;AConfig&quot; : {
        &quot;ASubConfig&quot; : &#x5B;]
    }
}
</pre></div>


<p>&#8220;But an empty array is not the same as a null value! And even if it was, it&#8217;s a value inside the &#8220;ASubConfig&#8221; property! There&#8217;s no way for this to be null!&#8221; was what I thought. And I thought wrong.</p>



<h2 class="wp-block-heading" id="how-the-file-is-actually-parsed">How the file is actually parsed</h2>



<p>Actually, there&#8217;s a way for it to be null. Or at least, the JSON Provider which is gonna look at ours JSONs thinks so. Since .NET&#8217;s config system is ready to take in different providers, it was built with the common points of said providers in mind… And hierarchies aren&#8217;t one of those points.</p>



<p>What happens is that the JSON Provider&#8217;s parser is going to look at our JSON file and it&#8217;s going to map all those informations to a dictionary, in which every value is mapped with a key which corresponds to the path to that value in the file.</p>



<p>The &#8220;gotcha&#8221; here is in the way the arrays are processed: Each index of each array is processed as a part of the path. Meaning, that the following appsettings:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
    &quot;AnArray&quot; : &#x5B;&quot;AThing&quot;, &quot;AnotherThing&quot;, &quot;AThirdThing&quot;]
}
</pre></div>


<p>Will originate a dictionary with the following entries:</p>



<ul class="wp-block-list"><li>AnArray:0 = &#8220;AThing&#8221;</li><li>AnArray:1 = &#8220;AnotherThing&#8221;</li><li>AnArray:2 = &#8220;AThirdThing&#8221;</li></ul>



<h2 class="wp-block-heading" id="why-do-empty-arrays-cause-an-issue">Why do empty arrays cause an issue?</h2>



<p>What happens if the array is empty? Well, by convention, an empty array wont originate any entries in our dictionary… So, to the .NET binder which is gonna look at our dictionary, that array doesn&#8217;t exist at all.</p>



<p>Even moreso, as there is no value inside that array which we can map to some path, every object on top of it in the hierarchy won&#8217;t have any value we can map either, meaning those objects won&#8217;t exist themselves (assuming they don&#8217;t have any other properties with not null values).</p>



<p>In practice, our dictionary will be empty, our .NET binder won&#8217;t be able to map anything, and the number of &#8220;WTH&#8221;s per second will spike before lunch time.</p>



<p>TLDR: .NET&#8217;s config system treats any object in appsettings.json that does not have any value associated with it&#8217;s properties as if it didn&#8217;t exist, and the same happens if that value is an empty array.</p>



<p>If anyone wants to read more on this, here&#8217;s a link for the github issue where I found most of this information:</p>



<p><a href="https://github.com/dotnet/extensions/issues/1341">https://github.com/dotnet/extensions/issues/1341</a></p>



<p>Happy coding everyone!</p>
<p>The post <a href="https://blogit.create.pt/hugobarroca/2022/02/07/twil-the-weird-way-nets-config-system-parses-jsons/">TWIL: The weird way .NET&#8217;s config system parses JSONs</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/hugobarroca/2022/02/07/twil-the-weird-way-nets-config-system-parses-jsons/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>.NET EF Core 6 support for groupby top(n) queries</title>
		<link>https://blogit.create.pt/telmorodrigues/2022/02/04/net-ef-core-6-support-for-groupby-topn-queries/</link>
					<comments>https://blogit.create.pt/telmorodrigues/2022/02/04/net-ef-core-6-support-for-groupby-topn-queries/#respond</comments>
		
		<dc:creator><![CDATA[Telmo Rodrigues]]></dc:creator>
		<pubDate>Fri, 04 Feb 2022 16:14:46 +0000</pubDate>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Sql]]></category>
		<category><![CDATA[entity framework]]></category>
		<category><![CDATA[SQL]]></category>
		<guid isPermaLink="false">https://blogit.create.pt/?p=12605</guid>

					<description><![CDATA[<p>EF Core 6 comes with some GroupBy queries improvements. In this post I wanna talk about the improvements related to &#8220;group by top(n)&#8221; queries. Let&#8217;s say we have the following table Documents: and that we want to get the two most recent documents for each user. For instance, if we have the following records: the [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/telmorodrigues/2022/02/04/net-ef-core-6-support-for-groupby-topn-queries/">.NET EF Core 6 support for groupby top(n) queries</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>EF Core 6 comes with some GroupBy queries improvements. In this post I wanna talk about the improvements related to &#8220;group by top(n)&#8221; queries.</p>



<p></p>



<p></p>



<p>Let&#8217;s say we have the following table Documents:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
CREATE TABLE &#x5B;dbo].&#x5B;Documents](
    &#x5B;Id] &#x5B;integer] PRIMARY KEY,
    &#x5B;UserId] &#x5B;integer] NOT NULL,
    &#x5B;Title] &#x5B;nvarchar](50) NOT NULL,
    &#x5B;Body] &#x5B;nvarchar](250) NOT NULL,
    &#x5B;CreatedOn] &#x5B;datetime] NOT NULL
)
</pre></div>


<p>and that we want to get the two most recent documents for each user. For instance, if we have the following records:</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" src="https://blogit.create.pt/wp-content/uploads/2022/02/image-6.png" alt="" class="wp-image-12606" width="523" height="183" srcset="https://blogit.create.pt/wp-content/uploads/2022/02/image-6.png 523w, https://blogit.create.pt/wp-content/uploads/2022/02/image-6-300x105.png 300w" sizes="(max-width: 523px) 100vw, 523px" /></figure>



<p>the query should return</p>



<figure class="wp-block-image size-full"><img decoding="async" width="520" height="146" src="https://blogit.create.pt/wp-content/uploads/2022/02/image-7.png" alt="" class="wp-image-12607" srcset="https://blogit.create.pt/wp-content/uploads/2022/02/image-7.png 520w, https://blogit.create.pt/wp-content/uploads/2022/02/image-7-300x84.png 300w" sizes="(max-width: 520px) 100vw, 520px" /></figure>



<p>To do this query using EF Core and LINQ we can try to group the Documents by UserId and then sort each group by the CreatedOn column to pick the first two documents for each user.</p>



<p>We can start by trying to group all documents for each user</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
 var usersDocs = await ctx
        .Documents
        .GroupBy(doc =&gt; doc.UserId)
</pre></div>


<p>and then for each group we try to sort its elements by the CreatedOn column</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
    var usersDocs = await ctx
        .Documents
        .GroupBy(doc =&gt; doc.UserId)
        .SelectMany(userDocs =&gt; userDocs.OrderByDescending(doc =&gt; doc.CreatedOn).Take(2))
        .ToArrayAsync();
</pre></div>


<p>If we try to execute this query using a previous version of EF Core 6 we get the following error:</p>



<p></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
    .OrderByDescending(doc =&amp;gt; doc.CreatedOn)&#039; could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to &#039;AsEnumerable&#039;, &#039;AsAsyncEnumerable&#039;, &#039;ToList&#039;, or &#039;ToListAsync&#039;. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.&#039;
</pre></div>


<p></p>



<p>This is because the previous versions of EF don&#8217;t know how to translate the GroupBy inner expressions to sql. After following their suggestion I ended up with this query</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
    var usersDocs = await ctx
        .Documents
        .Select(doc =&gt; doc.UserId)
        .Distinct()
        .SelectMany(userId =&gt; 
            ctx
            .Documents
            .Where(doc =&gt; doc.UserId == userId)
            .OrderByDescending(doc =&gt; doc.CreatedOn)
            .Take(2)
        )
        .ToArrayAsync();
</pre></div>


<p>However this query has two main issues. It does a distinct over the UserId column, loads all the user ids to application memory and then it does a query for each user resulting in a n+1 query problem.</p>



<p>One way to solve these issues is to forget LINQ and rewrite the query using raw sql with a partition by UserId and the ROW_NUMBER() window funtion, doing a CTE .</p>



<p>Now, with the EF Core 6 we can use the first version of the query, since the EF Core team has added the support for translating some GroupBy inner expressions and it can translate this LINQ query to a single sql query.</p>



<p></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
    var usersDocs = await ctx
        .Documents
        .GroupBy(doc =&gt; doc.UserId)
        .SelectMany(userDocs =&gt; userDocs.OrderByDescending(doc =&gt; doc.CreatedOn).Take(2))
        .ToArrayAsync();
</pre></div>


<p>You can read more about these features and other improvements added to GroupBy queries&nbsp;<a href="https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/whatsnew#improved-groupby-support">here</a>&nbsp;and check the related github issues&nbsp;<a href="https://github.com/dotnet/efcore/issues/12088">12088</a>&nbsp;<a href="https://github.com/dotnet/efcore/issues/13805">13805</a>&nbsp;</p>



<p>Happy coding!</p>
<p>The post <a href="https://blogit.create.pt/telmorodrigues/2022/02/04/net-ef-core-6-support-for-groupby-topn-queries/">.NET EF Core 6 support for groupby top(n) queries</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/telmorodrigues/2022/02/04/net-ef-core-6-support-for-groupby-topn-queries/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>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>
<p><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 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>
<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>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>
	</channel>
</rss>
