SQL Server Database Index optimization – maintenance plan

Properly optimized table indexes are crucial in database performance tuning. This is especially true when the database is growing overtime causing indexes to be fragmented. The more an index is fragmented, the longer time is needed for database reads.

When should the indexes be rebuilt? Microsoft recommends to reorganize indexes when fragmentation level is between 5% and 30%. When fragmentation is above 30% then the index should be rebuilt.

The best way of ensuring low fragmentation levels is to setup maintenance plan for the database that will be triggered periodically (preferably at night). The script below can be used in scheduled job. You can use @PrintInformationOnly param to print the index information first, for all database tables. When you set it to 0, the actual rebuild or reorganize process will start. I have also added @ExcludeTableList to exclude some tables that should be skipped (for example are too big or not used).

DECLARE	@return_value int

EXEC	@return_value = [dbo].[IndexOptimize]
		@databaseName = N'myDatabase',
		@IndexFragmentationMin = 5,
		@IndexFragmentationRebuildMin = 30,
		@PrintInformationOnly = 1

SELECT	'Return Value' = @return_value

The actual script starts here. You may want to adjust FILLFACTOR params separately for each tables as this should be configured depending on data update frequency. By setting MAXDOP= 1 param, we disable parallel threads during rebuild process, so the server CPU will not go to high.

I have successfully tested this script on database of size about 150 GB. Be aware that initial index rebuild may take a few hours, depending on fragmentation levels.

 
CREATE PROCEDURE [dbo].[IndexOptimize] 
@databaseName varchar(200) = 'MyTable',
@IndexFragmentationMin int = 5,
@IndexFragmentationRebuildMin int = 30,
@skipLargeTables bit = 0,
@ExcludeTableList nvarchar(max) = 'MyBigTable1,MyBigTable2',
@PrintInformationOnly bit = 0
AS
BEGIN

BEGIN TRY
	declare @dbid int = 0;
	set @dbid = (select [dbid] from [Master].dbo.[sysdatabases] where name = @databaseName);
    ------------------
	DECLARE @Schema VARCHAR(200);  
	DECLARE @Table NVARCHAR(200);  
	DECLARE @Index VARCHAR(200);  
	DECLARE @avg_fragmentation_in_percent int; 
	DECLARE @page_count int; 

	DECLARE db_cursor CURSOR FOR  
		SELECT 
		dbschemas.[name] as 'Schema', 
		dbtables.[name] as 'Table', 
		dbindexes.[name] as 'Index',
		indexstats.avg_fragmentation_in_percent,
		indexstats.page_count
		FROM sys.dm_db_index_physical_stats (@dbid, NULL, NULL, NULL, NULL) AS indexstats
		INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id]
		INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id]
		INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id]
		AND indexstats.index_id = dbindexes.index_id
		WHERE dbindexes.[name] is not null and indexstats.database_id = @dbid  
		ORDER BY indexstats.avg_fragmentation_in_percent desc

	OPEN db_cursor   
	FETCH NEXT FROM db_cursor INTO @Schema, @Table, @Index, @avg_fragmentation_in_percent, @page_count 

	WHILE @@FETCH_STATUS = 0   
	BEGIN  
	   if(@skipLargeTables = 1 and CHARINDEX(@Table, @ExcludeTableList) > 0)
	   begin
		 print 'skipped index (' + CONVERT(varchar(10),@avg_fragmentation_in_percent) + '%) ' + @Index + ' for table ' + @Table + ', page count ' + CONVERT(varchar(10),@page_count)
	   end
	   else
	   begin
			if(@avg_fragmentation_in_percent <= @IndexFragmentationRebuildMin and @avg_fragmentation_in_percent > @IndexFragmentationMin and LEN(@Index) > 3)
			begin
			   print 'reorganizing index (' + CONVERT(varchar(10),@avg_fragmentation_in_percent) + '%) ' + @Index + ' for table ' + @Table + ', page count ' + CONVERT(varchar(10),@page_count)
			  
			   if(@PrintInformationOnly = 0)
			   begin
			      exec('ALTER INDEX ' + @Index + ' ON dbo.' + @Table + ' REORGANIZE;');
			   end
			end
		    
			if(@avg_fragmentation_in_percent > @IndexFragmentationRebuildMin and LEN(@Index) > 3)
			begin
			   print 'rebuilding index (' + CONVERT(varchar(10),@avg_fragmentation_in_percent) + '%) ' + @Index + ' for table ' + @Table + ', page count ' + CONVERT(varchar(10),@page_count)
			  
			   if(@PrintInformationOnly = 0)
			   begin
			      exec('ALTER INDEX ' + @Index + ' ON dbo.' + @Table + ' REBUILD WITH (MAXDOP=1,FILLFACTOR = 90)');
			   end
			end
		end
			
		FETCH NEXT FROM db_cursor INTO @Schema, @Table, @Index, @avg_fragmentation_in_percent, @page_count  
	END   

	CLOSE db_cursor   
	DEALLOCATE db_cursor
	
	END TRY 
BEGIN CATCH	
	DECLARE @ErrorMessage NVARCHAR(4000);
	DECLARE @ErrorSeverity INT;
	DECLARE @ErrorState INT;
	SELECT @ErrorMessage = 'Message: '+ ERROR_MESSAGE() + CHAR(13) + CHAR(10) + 
						   'Procedure: ' + CAST(ERROR_PROCEDURE() AS VARCHAR(MAX)) + CHAR(13) + CHAR(10) +  
						   'Line: ' + CAST(ERROR_LINE() AS VARCHAR(MAX)) + CHAR(13) + CHAR(10) +  
						   'Number: ' + CAST(ERROR_NUMBER() AS VARCHAR(MAX)),  @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();	

	RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
	RETURN 0
END CATCH

END

Microsoft-SQL-Server

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 4.00 out of 5)
Loading...Loading...

Deleting old files by using schedule task and PowerShell

If you are administrator or programmer, you probably were in situation that you wanted to free up some server space by deleting old unused files. Following script may be quite useful if you want to get rid of old files of the certain type – also if they are packed into .zip packages.

Script checks for all predefined file types with the creation date older than 6 months (can be configured). If an old file is detected then it is being deleted and that information is being stored to the local log.txt file. The old .zip files will also be deleted (containing only the files we want to delete).

The recommended way of using it is to create windows schedule task that runs periodically, so the server space usage is always optimized.

delete_old_files

 param (
[int]$TestMode = 1,
[int]$DeleteOlderThanMonths = 6,
[string]$DeleteFileExtension = ".jpg",
[string]$Root_Folder = "C:\",
[string]$Logfile = "Log.txt"
)

#writes log messages
Function LogWrite
{
   Param ([string]$logstring)
   $currentdate = get-date -format "M-d-yyyy HH:MM"
   Add-content $Logfile -value "$currentdate | $logstring"
}

#checks if zip file contains only DeleteFileExtension file type
Function CanZIPbeDeleted($zipFilePath)
{
  [int]$canBeDeleted = 1;

  try { 
      $zipArchive = Get-Item -Path $zipFilePath | ForEach-Object { 
         (New-Object -ComObject Shell.Application).NameSpace($_.FullName)
      }
  
      foreach($item in $zipArchive.items())
      {
        if(!$item.Path.EndsWith($DeleteFileExtension))
        {
           $canBeDeleted = 0
           $msg = "$zipFilePath cannot be deleted as it contains non $DeleteFileExtension file"
           LogWrite $msg
           Write-Host $msg -foreground Green 
           return 0
        }
     }   
   } 
   catch { 
       $msg = "Unexpected Error. Error details: $_.Exception.Message"
       LogWrite $msg
       Write-Host $msg -foreground Red 
       return 0
   } 
 
    return $canBeDeleted;
}

#deletes the file
Function DeleteFile($filePathToDelete)
{
  try
  { 
     if($TestMode -eq 0)
     {
       Remove-Item $filePathToDelete
     }
     
     $size = (Get-Item -Path $filePathToDelete | Select-Object Name, @{Name="Size";Expression={[string]::Format("{0:0.00} MB", $_.Length / 1MB)}}).Size
     $msg = "File deleted ($size): $filePathToDelete"
     LogWrite $msg 
     Write-Host $msg -foreground "magenta"  
  } 
  catch { 
     $msg = "Unexpected Error. Error details: $_.Exception.Message"
     LogWrite $msg 
     Write-Host $msg -foreground Red 
   }    
}

 
 get-childitem -Path $Root_Folder –recurse |  
 where-object { 
    $_.CreationTime -lt (get-date).addMonths(-$DeleteOlderThanMonths) -and ($_.FullName.EndsWith($DeleteFileExtension) -or $_.FullName.EndsWith(".zip"))
  } | 
 Foreach-Object {
 
    if ($_.FullName.EndsWith(".zip"))
    {
       $canDeleteZip = CanZIPbeDeleted $_.FullName
       if($canDeleteZip -eq 1)
       {
          DeleteFile $_.FullName
       }
    }
    else
    {
       DeleteFile $_.FullName
    }
 } 

Enjoy!
delete_old_files

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 4.00 out of 5)
Loading...Loading...

SVN log parsing using PowerShell

This script can be useful when gathering SVN release changes that occurred since the last release. It connects to SVN server, gets logs from within date range (last build and date now). Logs are then filtered, cleaned (removed if no comments were added). Next, email message is sent based on the retrieved data. To be able to run the script, you need to configure it by setting-up your own svn server url, credentials, also please make sure you have enabled running ps scripts on your server.

Enjoy!

svn_log_parser

 param (
[string]$Version = "2.12.0.2",
[string]$SendEmailsTo = "test@test.com",
[string]$SVNUser = "user",
[string]$SVNPass = "pass",
[string]$SVNbranchRoot = "https://svn.test.com/www/Branches/MyApp/2.12.0"
)

#get svn logs for the current release
 Function GetSVNLogs($lastBuildDate_)
 {
    [string]$fromDate = $lastBuildDate_.ToString("yyyy-MM-dd");
    [string]$toDate = (get-date).ToString("yyyy-MM-dd");
    [string]$RevisionDates = "{$fromDate}:{$toDate}"; 

    #add -v param for verbose
    1$log = 1(&$svn log $SVNbranchRoot -r $RevisionDates --limit 500 --xml --username $SVNUser --password $SVNPass);

    $logObjects = $log.log.logentry | Foreach-Object {
            $logEntry = $_

            $logEntry | Select-Object `
                @{ Name = "Revision"; Expression = { [int]$logEntry.revision } },
                @{ Name = "Author"; Expression = { $logEntry.author } },
                @{ Name = "Date"; 
                   Expression = {
                       if ( $NoFormat )
                       {
                           [datetime]$logEntry.date
                       }
                       else
                       {
                           "{0:dd/MM/yyyy hh:mm:ss}" -f [datetime]$logEntry.date
                       }
                   } },
                @{ Name = "Message"; Expression = { $logEntry.msg } } | 

            Foreach-Object {        
                $_ | where-object { ![string]::IsNullOrEmpty($logEntry.msg) }  | Select-Object  Author, Date, Message              
            }
      }

    return $logObjects
 }

#send email function
 Function SendEmail($SendEmailsTo_, $EmailSubject_, $changes_)
 {
   $emailFrom = "automation@test.com" 
   $smtpserver="smtp.test.com" 
   $smtp=new-object Net.Mail.SmtpClient($smtpServer) 

   foreach ($email in $SendEmailsTo_.split(';'))
   {   
      $smtp.Send($emailFrom, $email, $EmailSubject_, $changes_)
   }
 }

#get svn logs since the last release
$SVNChanges = GetSVNLogs $lastBuildDate

$changes += "`r`n`r`n";
$changes += "SVN changes sice last release`r`n";
$changes += $SVNChanges | Format-Table -AutoSize | Out-String;
$changes += "`r`n-------------------------------------------------------------------`r`n";
$changes += "This is automated email, please do reply directly!";

#send email 
$EmailSubject = "Release $Version changes";

SendEmail $SendEmailsTo $EmailSubject $changes;

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Displaying dynamic javascript charts using MVC

When creating business solutions there is often a need to display dynamic JavaScript charts using specific entity data that can be easily extended throughout the system. Properly designed system should always provide clear separation between business entities and UI layer. When using JavaScript this separation can be easily neglected.
In this article I will show you how to use entity oriented approach when mixing C# and JavaScript code.

In order to do that we will create JavaScript api that will be used to call web controllers such as clientController, productController, userController etc. Each of these controllers will provide us entity related data that can be displayed on the web pages throughout the system. The data will be sent using json protocol along with the target chart configuration we want.

jschart_line

In order to display above charts within our application, we need to simply create div tag that will serve as a placeholder for our chart, we also need to assign attributes and “chartButton” class name to the element that will trigger chart display. The attributes will contain entity name e.g. data-entity=”client”, chart placeholder e.g. targetdiv=”chart1Div” and optionally data parameters e.g. params=”clientId=?&fullInfo=?”

  <input type="button" value="show client chart" class="chartButton" data-entity="client" data-params="clientId=@ViewBag.ClientId&fullInfo=true" data-targetdiv="chart1Div" />
  <input type="button" value="show product chart" class="chartButton" data-entity="product" data-targetdiv="chart1Div" />
  <input type="button" value="show user chart" class="chartButton" data-entity="user" data-targetdiv="chart1Div" />
  <div id="chart1Div"></div>

We also need to add following script references to our _Layout.cshtml file. In our case we will use HighCharts, hence reference to highcharts.js script (we are not limited only to this control, you can convert it to use other JavaScript chart controls as well if needed). The file chartAPI.js will contain our main logic that connects Web Api controllers and JavaScript code.

   <script src="https://code.highcharts.com/highcharts.js" type="text/javascript"></script>
   <script src="@Url.Content("~/Scripts/chartAPI.js")" type="text/javascript"></script>

Let’s start implementation from creating Web Api entity controllers and chart data dto object that will transfer the chart data and it’s configuration.

The ChartData class will contain chart series and chartConfig object – it can be extended at any time depending on our requirements.

   public class ChartData
   {
        public ChartConfig ChartConfig { get; set; }
        public List<Series> Series { get; set; }
    }

    public class Series
    {
        public string Name { get; set; }
        public string Color { get; set; }
        public string DashStyle { get; set; }      
        public List<PointData> Points { get; set; }
    }

    public class PointData
    {
        public DateTime Date { get; set; }
        public double Value { get; set; }
    }

    public class ChartConfig
    {
        public ChartConfig()
        {
            tooltipPointFormat = "";
        }

        public string chartTitle { get; set; }
        public string defaultSeriesType { get; set; }
        public int width { get; set; }
        public int height { get; set; }
        public string tooltipPointFormat { get; set; }
        public bool pie_allowPointSelect { get; set; }
        public bool pie_dataLabelsEnabled { get; set; }
        public bool pie_showInLegend { get; set; }   
    }

Having above classes created, we can now use it to fill it with dummy data inside our entity controller. We can add as many series as we like, we may also configure desired chart’s width, height, series colors etc. Please note that controller’s method will be mapped based on defined html params (see above html configuration).

 public string Get(int clientId, bool fullInfo = true)
 { 
    var data = new ChartData();

    //configure chart
    data.ChartConfig = new ChartConfig()
    {
        chartTitle = "Clients",
        defaultSeriesType = "line",
        width = 800,
        height = 400,
    };

    #region add series
    data.Series = new List<Series>();

    data.Series.Add(new Series()
    {
        Name = "Series1",
        Color = ColorTranslator.ToHtml(Color.Navy),
        DashStyle = "ShortDash",
        Points = new List<PointData>()
    });

    data.Series.Add(new Series()
    {
        Name = "Series2",
        Color = ColorTranslator.ToHtml(Color.Red),
        Points = new List<PointData>()
    });

    data.Series.Add(new Series()
    {
        Name = "Series3",
        Points = new List<PointData>()
    });

    #endregion

    #region add dummy data
    var random = new Random();
    foreach (var serie in data.Series)
    {
        for (var i = 0; i < 50; i++)
        {
            serie.Points.Add(new PointData()
            {
                Date = DateTime.Now.AddDays(-i),
                Value = random.Next(10, 100)
            });
        }
    }
    #endregion

    var json = JsonHelper.ToJSON<ChartData>(data);

    return json;
}

Our chartAPI.js file will contain main JavaScript logic encapsulating data retrieval and constructing the chart object. The function getEntityData simply calls appropriate entity controller using $.getJSON function. When data is retrieved, the renderChart function is used to create chart object, fill it with data and configure according to obtained configuration.

 //this function is used by UI to trigger chart display
$(document).ready(function () {
    //process data on button click
    $(".chartButton").click(function (sender) {

        //read button attributes
        var entity = $(this).attr("data-entity");
        var targetDiv = $(this).attr("data-targetDiv");
        var params = $(this).attr("data-params");

        //call encapsulated function
        getEntityData(entity, params, function (data, status, response) {
            if (status === "success") {
                renderChart(data, targetDiv);
            }
            else if (status === "error") {
                //process the error here if needed

                alert('An error occurred: ' + response);
            }
            else if (status === "complete") {
                //attach your load completed events here if needed

                //alert('Data loaded!');
            }
        });
    });
});

//Gets data for the entity
//This function is encapsulated 
function getEntityData(entityName, params, callbackFn) {
    if (typeof callbackFn != 'function') {
        alert('Incorrect callback function attached!'); return;
    }

    //make sure correct path is set
    var root = location.protocol + "//" + location.host;

    $.getJSON(root + '/API/' + entityName + "?" + params, null)
    .success(function (data, status, response) {
        //create object from json
        var resultObject = JSON.parse(data);
        callbackFn(resultObject, status, response);
    })
    .error(function (e, status, response) {
        callbackFn(e, status, response);
    })
    .complete(function (e, status, response) {
        callbackFn(e, "complete", response);
    });
}

//Renders chart
function renderChart(data, renderTo) {
    var chart1 = new Highcharts.Chart({
        chart: {
            renderTo: renderTo,
            defaultSeriesType: data.ChartConfig.defaultSeriesType,
            width: data.ChartConfig.width,
            height: data.ChartConfig.height,
        },
        title: {
            text: data.ChartConfig.chartTitle,
        },
        tooltip: {
         pointFormat: data.ChartConfig.tooltipPointFormat,
        },
        plotOptions: {
                pie: {
                    allowPointSelect: data.ChartConfig.pie_allowPointSelect,
                    cursor: 'pointer',
                    dataLabels: {
                        enabled: data.ChartConfig.pie_dataLabelsEnabled,
                    },
                    showInLegend: data.ChartConfig.pie_showInLegend,
                },
            },
        xAxis: {
            title: {
                text: 'date'
            },
            type: 'datetime',
        },
        yAxis: {
            title: {
                text: 'value'
            }
        },
    });

    $.each(data.Series, function (index, item) {
        //add series
        chart1.addSeries({
            name: item.name,
            dashStyle: item.DashStyle,
            color: item.Color,
            data: []
        }, false);

        //add data points
        $.each(item.Points, function (index, pointData) {
            chart1.series[chart1.series.length - 1].addPoint([
                Date.parse(new Date(parseInt(pointData.Date.substr(6)))),//format date
                pointData.Value,
            ], false);
        });
    });

    chart1.redraw();
}

It is more than certain that you will have to adjust above solution to your specific requirements. At the same time it should give you a good starting point to implement your own solution based on this example.

I have attached project files below for your convenience.

jsChartMVC

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 4.00 out of 5)
Loading...Loading...

Entity validation engine using c# expression rules

Validating entity data is the common task when building business applications. Very often happens that we want to separate the rules from the app code to be able to quickly change it without affecting the application. In this article we will build simple validation engine that will be evaluating rules stored as linq expressions.

In our example we will hardcode the rules in separate .cs files (based on entity type). You may also want to store the linq expressions as a string in database and also compile the code dynamically from the string and then execute the rules based on the output dll file.

validation-engine

In our case we will hard code the rule in .css files. The following example shows the rule definitions for Client entity.

  public class ClientValidationRules : BaseValidationRuleSet<Client>
   {
        public ClientValidationRules(Client arg)
        {
            RuleList.Add(new ValidationRule<Client>(
                //set the rule
                a => a.Name.Trim().Any() && !a.Name.Any(b => char.IsLower(b))
                ////
                , arg)
                {
                    //set the result
                    Message = "Name cannot be all in uppercase!",
                    ResultTypeIfFailed = ValidationResultType.ERROR,
                    SuggestionString = arg.Name.ToLower()
                });

            RuleList.Add(new ValidationRule<Client>(
                //set the rule
               a => a.DateOfBirth.AddYears(18) > DateTime.Now
                ////
               , arg)
            {
                //set the result
                Message = "You must be at lest 18 years old to register!",
                ResultTypeIfFailed = ValidationResultType.ERROR,
                SuggestionString = string.Format("Wait {0} days", DateTime.Now.Subtract(arg.DateOfBirth).TotalDays.ToString("0"))
            });
        }
    }
}

You can see that this way allows us to easily define rule’s logic based on the entity data. We can also define suggestion message for each rule separately. By setting ValidationResultType as a rule result we can for example allow to save data if this is only a warning or prevent saving the data by the user if this is an error.

The implementation is as follows. Let’s start with generic ValidationRule class containing the rule data and delegate to run.

 namespace RuleEngine
 {
    public class ValidationRule<T> where T : class
    {
        public string Message { get; set; }
        public string SuggestionString { get; set; }
        
        public ValidationResultType ResultTypeIfFailed { get; set; }

        internal Func<T, bool> RuleDelegate { get; set; }
        internal T ObjectTovalidate { get; set; }

        public ValidationRule(Func<T, bool> rule, T arg)
        {
            RuleDelegate = rule;
            ObjectTovalidate = arg;
        }

        public bool RunRuleDelegate()
        {
            return RuleDelegate(ObjectTovalidate);
        }
    }
 }

It’s base class will have the “Run” method implementation.

 namespace RuleEngine
 {
    public abstract class BaseValidationRuleSet<T> where T : class
    {
        public List<ValidationRule<T>> RuleList { get; internal set; }

        /// <summary>
        /// Initiates rule list
        /// </summary>
        public BaseValidationRuleSet()
        {
            RuleList = new List<ValidationRule<T>>();
        }

        /// <summary>
        /// Run all added rules
        /// </summary>
        /// <returns></returns>
        public ValidationResult Run()
        {
            foreach (var rule in this.RuleList)
            {
                var result = rule.RunRuleDelegate();

                if (result)
                {
                    return new ValidationResult() { IsValid = false, ResultType = rule.ResultTypeIfFailed, Message = rule.Message, SuggestionString = rule.SuggestionString };
                }
            }

            return new ValidationResult() { IsValid = true, ResultType = ValidationResultType.OK };
        }
    }
}

Let’s define ValidationResult class that will be returned after running the rule delegate.

 namespace RuleEngine
 {
    public class ValidationResult
    {
        public bool IsValid { get; set; }
        public string SuggestionString { get; set; }
        public string Message { get; set; }
        public ValidationResultType ResultType { get; set; }
    }
}

The ValidationEngine class will have the entry point for different entity validation methods. You may also want to create generic function to pass the validating object to. You also can preload all rules at runtime and evaluate dynamically using AppDomain.CurrentDomain.GetAssemblies() method.

  public class ValidationEngine: ValidationEngineBase
  {
      public ValidationResult RunProjectRules(Project project)
      {
          return new ProjectValidationRules(project).Run();
      }
      public ValidationResult RunClientRules(Client client)
      {
          return new ClientValidationRules(client).Run();
      }
   }

In order to test our solution we can use console app. We also need to load some fake data into our entities.

     static void Main(string[] args)
        {
            var validationEngine = new ValidationEngine();

            #region load entities
            var client = new Client()
                  {
                      Name = "CLIENT NUMBER1a",
                      DateOfBirth = DateTime.Now.AddYears(-10)
                  };

            var project = new Project()
            {
                Name = "PROJECT NUMBER1",
                StartDate = DateTime.Now.AddDays(1),
                EndDate = DateTime.Now.AddDays(1)
            }; 
            #endregion


            #region validate client
            Console.WriteLine("Starting client validation");

            var result = validationEngine.RunClientRules(client);

            if (!result.IsValid)
            {
                Console.WriteLine(result.Message);
                Console.WriteLine(string.Format("Suggestion: {0}", result.SuggestionString));
            }
            else
            {
                Console.WriteLine("OK");
            }
            #endregion

            Console.WriteLine("--------------------");

            #region validate project
            Console.WriteLine("Starting project validation");

            result = validationEngine.RunProjectRules(project);

            if (!result.IsValid)
            {
                Console.WriteLine(result.Message);
                Console.WriteLine(string.Format("Suggestion: {0}", result.SuggestionString));
            }
            else
            {
                Console.WriteLine("OK");
            }
            #endregion

            Console.ReadLine();
        }

I have included project files bellow for your tests.


#Article update#

We can also create simple business rule engine in very similar way, here is the implementation:

 namespace RuleEngine
 {
    /// <summary>
    /// To be extended if needed
    /// </summary>
    public abstract class BusinessRuleEngineBase
    {
        
    }

    /// <summary>
    /// Runs set of rules for different entities
    /// </summary>
    public class BusinessRuleEngine : BusinessRuleEngineBase
    {
        /// <summary>
        /// Run rules for ScheduledFlight 
        /// </summary>
        /// <param name="scheduledFlight"></param>
        /// <returns></returns>
        public bool RunBusinessRulesFor(ScheduledFlight scheduledFlight)
        {
            return new FlightBusinessRules(scheduledFlight).Run();
        }
    }
 }

Business rule implementation containing evaluating and executing delegate

/// <summary>
/// Runs delegate on the specified business rule set
/// </summary>
/// <typeparam name="T"></typeparam>
public class BusinessRule<T> where T : class
{
    public string RuleName { get; set; }
    internal Func<T, bool> RuleDelegate { get; set; }
    internal Func<T, bool> ExecuteRuleDelegate { get; set; }
    internal T ObjectToValidate { get; set; }
    internal T ObjectToApply { get; set; }

    public BusinessRule(Func<T, bool> rule, Func<T, bool> ruleApply, T arg)
    {
        RuleDelegate = rule;
        ExecuteRuleDelegate = ruleApply;
        ObjectToValidate = arg;
        ObjectToApply = arg;
    }

    /// <summary>
    /// Determines if business rule should be applied
    /// </summary>
    /// <returns></returns>
    public bool ApplyRuleDelegate()
    {
        return RuleDelegate(ObjectToValidate);
    }

    /// <summary>
    /// Applies business rule on the current entity
    /// </summary>
    /// <returns></returns>
    public bool ApplyBusinessDelegate()
    {
        return ExecuteRuleDelegate(ObjectToApply);
    }
    
}

BaseBusinessRuleSet base class

/// <summary>
/// Base class for business rule sets
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BaseBusinessRuleSet<T> where T : class
{
    public List<BusinessRule<T>> RuleList { get; internal set; }

    /// <summary>
    /// Initiates business rule list
    /// </summary>
    public BaseBusinessRuleSet()
    {
        RuleList = new List<BusinessRule<T>>();
    }

    /// <summary>
    /// Run all added business rules
    /// </summary>
    /// <returns></returns>
    public bool Run()
    {
        foreach (var rule in this.RuleList)
        {
            var result = rule.ApplyRuleDelegate();

            //if rule is true, run business logic
            if (result)
            {
                rule.ApplyBusinessDelegate();

                return true;
            }
        }

        return false;
    }
}

And finally the business rule set-up class.

public class FlightBusinessRules : BaseBusinessRuleSet<ScheduledFlight>
{
    /// <summary>
    /// Setup business rules for ScheduledFlight
    /// </summary>
    /// <param name="arg"></param>
    public FlightBusinessRules(ScheduledFlight arg)
    {

        RuleList.Add(new BusinessRule<ScheduledFlight>(
            //set the rule
                a => (double)arg.Passengers.Count(p => p.Type == PassengerType.AirlineEmployee) / arg.Passengers.Count > arg.FlightRoute.MinimumTakeOffPercentage
            ,
            //execute rule
                 e =>
                 {
                     //lower the base price till 100
                     if (arg.FlightRoute.BasePrice >= 100)
                     {
                         arg.FlightRoute.BasePrice -= 10;
                     }

                     //add more logic here...

                     return true;
                 },
                arg) { RuleName = "ApplyDiscountFor_AirlineEmployee" });


        RuleList.Add(new BusinessRule<ScheduledFlight>(
            //set the rule
              a => a.FlightRoute.BasePrice == 0
              ,
            //execute rule
               e =>
               {
                   //enforce minimal base price
                   arg.FlightRoute.BasePrice = 100;

                   return true;
               },
              arg) { RuleName = "EnsureMinimalBasePrice" });

    }
}

RuleEngine

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Using Power-Shell in automated tasks (batch script alternative)

Power Shell it’s the Microsoft’s Automation framework sitting on top of .Net (System.Management.Automation) that lets us perform advanced automated tasks. It can help not only the administrators but also the programmers to save a lot of time by automating repeatable processes. The script’s syntax is quite intuitive and easy to learn so it allows users already writing e.g. batch scripts to grasp it very fast.

If we have for example an old batch file that executes sql scripts stored in the folder as follows:

 @ECHO OFF
 echo script folder: %1
 echo server: %2
 echo database: %3
 echo database user: %4
 echo ----------------

setlocal enabledelayedexpansion
cd /d %1

for %%d in (*.sql) do (
   set filecontent=
   for /f "delims=" %%a in (%%d) do (set "filecontent=!filecontent!%%a")
   set "temp=!filecontent!"
   set "filecontent=!temp:#PARAM#=%4!"
   echo executing %%d
   echo ----------
   sqlcmd -S%2 -E -Q"!filecontent!" -d%3 
)
pause

then the above script can be easily replaced with Power-Shell. Using PS gives us the same flexibility of scripting languages and at the same time allows us to interact with .Net framework when required.

Before we can start executing PS scripts on our computer, we need to enable execution which is disabled by default. We can do it using following cmdlets:

 //enable executing 
 Set-ExecutionPolicy RemoteSigned

 //get back to default settings
 Set-ExecutionPolicy Default

After we’ve created test.ps1 file, we can start implementation. In order to make testing easier we can define default params inside the script using param(…) constructor. Please note that by using [string] keyword we actually creating strongly typed objects. Defined parameters will be overwritten by the user’s passed-in params.

ps-script

The first command (dir $ScriptFolder) just lists all files within the directory, then ForEach-Object function iterates through all files getting their content into $query variable. Next the $query text is being replaced with our parameter if we have any. The final task is just to execute SQLCMD command passing in query and database connection settings.

param (
[string]$ScriptFolder = "Database\Procedures",
[string]$Server = ".\SQL2008R2",
[string]$Database = "MyDb",
[string]$DatabaseUser = "user"
)


(dir $ScriptFolder) | 
  ForEach-Object { 
    $query = get-content $_.FullName -Raw
    $query = $query.Replace("#PARAM#","user");
    SQLCMD -S $Server -E -Q $query -d $Database
   }

In order to run our Power-Shell script we need just execute following line:

 //command line
 powershell -noexit C:\test.ps1 -ScriptFolder "C:\Database\Procedures" -Server ".\SQL2008R2" -Database "MyDB" -DatabaseUser "user"

Good luck with your Power-Shell scripting 🙂

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Displaying interactive MS Chart objects in ASP.NET MVC

Displaying charts using MS Chart is usually simple in asp.net webform. The problem exists if we want to use it in Asp.net MVC application without putting it on .aspx page. In this article I will show you how to display interactive chart using purely MVC view.

mschart_mvc

Lets start with creating ChartHelper class that will contain basic logic for creating chart objects. In the function CreateDummyChart() we will just create standard MS Chart object with some dummy data.

 public static Chart CreateDummyChart()
 {
    var chart = new Chart() { Width = 600, Height = 400 };
    chart.Palette = ChartColorPalette.Excel;
    chart.Legends.Add(new Legend("legend1") { Docking = Docking.Bottom });

    var title = new Title("Test chart", Docking.Top, new Font("Arial", 15, FontStyle.Bold), Color.Brown);
    chart.Titles.Add(title);
    chart.ChartAreas.Add("Area 1");

    chart.Series.Add("Series 1");
    chart.Series.Add("Series 2");

    chart.BackColor = Color.Azure;
    var random = new Random();
    
    //add random data: series 1
    foreach (int value in new List<int>() { random.Next(100), random.Next(100), random.Next(100), random.Next(100) })
    {
        chart.Series["Series 1"].Points.AddY(value);

        //attach JavaScript events - it can also be ajax call
        chart.Series["Series 1"].Points.Last().MapAreaAttributes = "onclick=\"alert('value: #VAL, series: #SER');\"";
    }

    //add random data: series 2
    foreach (int value in new List<int>() { random.Next(100), random.Next(100), random.Next(100), random.Next(100) })
    {
        chart.Series["Series 2"].Points.AddY(value);

        //attach JavaScript events - it can also be ajax call
        chart.Series["Series 2"].Points.Last().MapAreaAttributes = "onclick=\"alert('value: #VAL, series: #SER');\"";
    }

    return chart;
 }

Next, we need to create function that takes our newly created chart object and saves it to memory stream. After that we only need to convert the stream to byte array and to base64 string afterwards.

Having image string created, we simply construct html “img” tag to be rendered by our view. Please note that “data:image/png;base64” attributes are necessary to tell the browser to render it as image.

 public static string GetChartImageHtml(Chart chart)
 {
    using (var stream = new MemoryStream())
    {
        var img = "<img src='data:image/png;base64,{0}' alt='' usemap='#" + ImageMap + "'>";

        chart.SaveImage(stream, ChartImageFormat.Png);

        var encoded = Convert.ToBase64String(stream.ToArray());

        return string.Format(img, encoded);
    }
 }

The final thing is to create DisplayChart() method in the view to be used by Html.RenderAction. Apart from chart’s image string, we also need to display chart’s image map in order to enable chart JavaScript events when needed.

public ActionResult DisplayChart()
{  
    var chart = ChartHelper.CreateDummyChart();
   
    var result = new StringBuilder();
    result.Append(ChartHelper.GetChartImageHtml(chart));
    result.Append(chart.GetHtmlImageMap(ChartHelper.ImageMap));

    return Content(result.ToString());
}

And finally this is our view

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>

@{ Html.RenderAction("DisplayChart"); }

I have attached project files to save your time 🙂

mschart-mvc

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Using WCF REST service in server and client side calls

When using WCF services sometimes there is a need to use REST architecture, especially for web applications. Using REST, brings a lot of benefits such as: broad caching possibilities, interoperability, simplicity etc. Also using REST with JSON instead of soap/xml based protocol decreases network bandwidth and makes it easier to utilize by JavaScript Ajax calls.

In this article I will show you how to retrieve and update data from WCF restful service using JSON protocol. In order to do that we will build request helper to wrap-up our requests sent from MVC application. We will also use JSONP data format to make cross-domain ajax calls to WCF service.

wcf_rest_json_calls

Let’s create MVC application and IIS hosted WCF service. In order to apply custom routing to our service, we will have to change following settings:
– set aspNetCompatibilityEnabled=”true” in WCF web.config file,
– set [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] – to our service class,

We also need to set crossDomainScriptAccessEnabled=”true” in WCF web.config for our ajax calls from different domain (different localhost port).

Please see WCF web.config settings:

 <?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <authentication mode="None"/>
  </system.web>
  <system.serviceModel>
     <bindings>
        <webHttpBinding>
          <binding name="webHttpBindingWithJsonP" crossDomainScriptAccessEnabled="true" />
        </webHttpBinding>
      </bindings>
    <services>
      <service name="WCFRestfulService.MyService" behaviorConfiguration="RESTBehavior">
        <endpoint address=""  binding="webHttpBinding" contract="WCFRestfulService.IRestService"
                  behaviorConfiguration="MyEndpointBehavior"  bindingConfiguration="webHttpBindingWithJsonP">
        </endpoint>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="RESTBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="MyEndpointBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
       multipleSiteBindingsEnabled="true">
    </serviceHostingEnvironment>
  </system.serviceModel>
</configuration>

Next let’s create our ServiceContract and DataContract. Please note that we are defining UriTemplate to specified url contract routing, we will also use WebMessageFormat.Json attribute to declare the request format.

 [ServiceContract(Namespace = "https://mysite.com/2013/06/08", Name = "WCFRestService")]
 public interface IRestService
 {
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json,
    UriTemplate = "Products?skip={skip}&top={top}")]
    [Description("Returns a list of all products")]
    List<Product> GetAllProducts(int skip, int top);

    [OperationContract]
    [WebInvoke(Method = "DELETE", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json,
    UriTemplate = "DeleteProduct?productID={productID}")]
    [Description("Deletes a product")]
    string DeleteProduct(string productID);

    [OperationContract]
    [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json,
    UriTemplate = "/UpdateProduct")]
    [Description("Updates the product")]
    string UpdateProduct(Product productDto);
 }

 [DataContract]
 public class Product
 {
    [DataMember]
    public string ProductID { get; set; }

    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public string Category { get; set; }

    [DataMember]
    public double Price { get; set; }
 }

Also, in the service global file we need to define following routing rule.

 protected void Application_Start(object sender, EventArgs e)
  {
      RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(MyService)));
  }

Our .svc file should contain following (Factory=”System.ServiceModel.Activation.WebScriptServiceHostFactory” added)

 <%@ ServiceHost Language="C#" Debug="true" Service="WCFRestfulService.MyService" CodeBehind="MyService.svc.cs" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

Inside our service class we will add following test methods:

 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
 public class MyService : IRestService
 {
    List<Product> IRestService.GetAllProducts(int skip, int top)
    {
        var productList = new List<Product>();
        var random = new Random();

        #region simulate database
        for (var i = 0; i < 100; i++)
        {
            productList.Add(new Product()
            {
                ProductID = i.ToString(),
                Category = "Category " + i % 2,
                Name = "Product " + i.ToString(),
                Price = random.Next(10, 100)
            });
        } 
        #endregion

        //////////////////////

        var productSet = (from product in productList
                          orderby product.Name
                          select product).Skip(skip).Take(top).ToList();

        return productSet;
    }

    string IRestService.DeleteProduct(string productID)
    {
        //delete from database here


        return "OK";//return status or error
    }

    string IRestService.UpdateProduct(Product productDto)
    {
        //update the product here


        return "OK";//return status or error
    }

}

Next, in MVC application let’s create request helper that we will use to wrap-up our requests executed from C# controllers. Helper functions simply convert our data to JSON format when sending REST requests. We will also de-serialize objects from JSON to C# when retrieving the data.

 public class RequestHelper
 {
    /// <summary>
    /// Get object using GET
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="url"></param>
    /// <returns></returns>
    public static T GetObjectRest<T>(string url)
    {
        var request = HttpWebRequest.Create(url);
        request.Method = "GET";

        var response = request.GetResponse() as HttpWebResponse;
        using (var resStream = response.GetResponseStream())
        {
            using (var reader = new StreamReader(resStream))
            {
                var jsonResponse = reader.ReadToEnd();

                return FromJSON<T>(jsonResponse);
            }
        }
    }

    /// <summary>
    /// Updates object
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    public static string UpdateObjectRest<T>(string url, T objectDto)
    {
        var request = (HttpWebRequest)WebRequest.Create(url); 
        request.Method = "POST";
        request.ContentType = "application/json";

        //send the data.
        using (var requestWriter = new StreamWriter(request.GetRequestStream()))
        {
            requestWriter.Write(ToJSON<T>(objectDto));
        }

        //read response
        var response = (HttpWebResponse)request.GetResponse();
        using (var resStream = response.GetResponseStream())
        {
            using (StreamReader reader = new StreamReader(resStream))
            {
                return reader.ReadToEnd();
            }
        }
    }

    /// <summary>
    /// Send DELETE request
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    public static string DeleteObjectRest(string url)
    {
        var request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "DELETE";

        //read response
        var response = (HttpWebResponse)request.GetResponse();
        using (var resStream = response.GetResponseStream())
        {
            using (StreamReader reader = new StreamReader(resStream))
            {
                return reader.ReadToEnd();
            }
        }
    }

    /// <summary>
    /// Deserialize json to custom object
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="json"></param>
    /// <returns></returns>
    public static T FromJSON<T>(string json)
    {
        var jss = new JavaScriptSerializer();

        return jss.Deserialize<T>(json);
    }

    /// <summary>
    /// Serializes object to json
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="objectDto"></param>
    /// <returns></returns>
    public static string ToJSON<T>(T objectDto)
    {
        var jss = new JavaScriptSerializer();

        return jss.Serialize(objectDto);
    }
}

Calling Rest service will look as follows:

  //get list of products
  var productList = RequestHelper.GetObjectRest<List<Product>>("https://localhost:9582/Products?skip=0&top=10");

Retrieving data using client side Ajax request is quite difficult because of the cross-domain policy (CORS) disabling web script access by default. The way around that is to use JSONP data format so the server is treating our request as a script call allowing us to return data back inside the call-back function. The limitation of that is that we can only use GET method to retrieve the data. If you need full access to the WCF restful service, it is recommended to make your Ajax requests from the same domain.

 <script type="text/javascript">
    function GetProducts() {
        $.ajax({
            type: "GET", //only 'get' is supported with 'jasonp' datatype
            url: "https://localhost:9582/Products?skip=0&top=10",
            dataType: "jsonp",
            data: '',
            success: function (result) {
                alert('Received: ' + result.length + ' products');
            },
            error: function (xhr, ajaxOptions, thrownError) {
                // alert(xhr.status);
                // alert(thrownError);
            }
        });
    }
</script>

Please find the project files included below.

WCFRestfulService

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Custom RetryPolicy class for Windows Azure transient error handling

According to definition, the Cloud it’s “A set of interconnected servers located in one or more data centres”. As per definition this kind of environment is naturally prone to network latency and other related environmental issues. This is especially true if we are communicating with on premises applications to synchronize and load the data over public network.

In order to ensure that our systems are reliable and functioning correctly within such a environment, we should use RetryPolicy to retry error prone operation when an transient error occurs.

In this article I will show you how to use default RetryPolicy with configuration stored in web.config file. You will also learn how to create custom retry policy class to be used throughout your application for the specific error types you define.

Using retry policy objects simply allows us to retry an operation multiple times at the intervals we configure. Image below shows debugging information when retrying operations that cause the errors:

custom_retry_policy

In our example we will user Microsoft Enterprise Library 5.0 (updated version). To create instances we will use RetryPolicyFactory that can create retry policy objects of following types: AzureCachingRetryPolicy, AzureServiceBusRetryPolicy, AzureStorageRetryPolicy, SqlCommandRetryPolicy, SqlConnectionRetryPolicy. Each type handles specific error types.

The configuration also includes ErrorDetectionStrategy object that is being configured in web.config file:

   <configSections>
    <section name="RetryPolicyConfiguration" type="Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling.Configuration.RetryPolicyConfigurationSettings, Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling" requirePermission="true"/>
    <section name="typeRegistrationProvidersConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.TypeRegistrationProvidersConfigurationSection, Microsoft.Practices.EnterpriseLibrary.Common"/>
  </configSections>
  <RetryPolicyConfiguration defaultRetryStrategy="Fixed Interval Retry Strategy" defaultSqlConnectionRetryStrategy="Incremental Retry Strategy">
    <incremental name="Incremental Retry Strategy" retryIncrement="00:00:01" initialInterval="00:00:01" maxRetryCount="10"/>
    <fixedInterval name="Fixed Interval Retry Strategy" retryInterval="00:00:01" maxRetryCount="10"/>
    <exponentialBackoff name="Backoff Retry Strategy" minBackoff="00:00:01" maxBackoff="00:00:30" deltaBackoff="00:00:10" maxRetryCount="10" firstFastRetry="false"/>
  </RetryPolicyConfiguration>
  <typeRegistrationProvidersConfiguration>
    <clear/>
    <add name="Caching" sectionName="cachingConfiguration"/>
    <add name="Cryptography" sectionName="securityCryptographyConfiguration"/>
    <add name="Exception Handling" sectionName="exceptionHandling"/>
    <add name="Instrumentation" sectionName="instrumentationConfiguration"/>
    <add name="Logging" sectionName="loggingConfiguration"/>
    <add name="Policy Injection" sectionName="policyInjection"/>
    <add name="Security" sectionName="securityConfiguration"/>
    <add name="Data Access" providerType="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSyntheticConfigSettings, Microsoft.Practices.EnterpriseLibrary.Data"/>
    <add name="Validation" providerType="Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.ValidationTypeRegistrationProvider, Microsoft.Practices.EnterpriseLibrary.Validation"/>
    <add sectionName="RetryPolicyConfiguration" name="RetryPolicyConfiguration"/>
  </typeRegistrationProvidersConfiguration>

Let’s create helper class to handle creation of retry policy objects. The first method retrieves the default policy with the configuration stored in web.config file.
We will also attach an Retrying event to log the retry operations.

 public static RetryPolicy GetDefaultPolicy(string name)
 {
    RetryPolicy retryPolicy;

    try
    {
        retryPolicy = RetryPolicyFactory.GetRetryPolicy<StorageTransientErrorDetectionStrategy>(name);
        retryPolicy.Retrying += retryPolicy_Retrying;
    }
    catch (NullReferenceException)
    {
        throw new Exception("Unable to read transient fault handling behaviour from web.config file - section for TransientFaultHandling could be missing.");
    }

    return retryPolicy;
}

 static void retryPolicy_Retrying(object sender, RetryingEventArgs e)
 {
    var message = string.Format(
        "Retry - Count: {0}, Delay: {1}, Exception: {2}",
         e.CurrentRetryCount,
         e.Delay,
         e.LastException.Message);

    Trace.TraceEvent(TraceEventType.Information, 0, message);// write to log
  }

Our next helper function will create custom retry policy. In the constructor we will pass in error types we want to be included in retry operations. Ideally this could be stored in config file.

 public static RetryPolicy GetCustomRetryPolicy()
 {
    var retryPolicy = new RetryPolicy(
        new CustomTransientErrorDetectionStrategy(
            new List<Type>() {
                typeof(DivideByZeroException),
                typeof(IndexOutOfRangeException),
            }),
        new CustomRetryStrategy());

    retryPolicy.Retrying += retryPolicy_Retrying;

    return retryPolicy;
}

Now let’s create custom retry policy class. The most important part of it is ShouldRetry method which simply returns the delegate allowing to evaluate whether to continue retrying operation or not. You can also apply your own logic in this place based on your requirements.

 public class CustomRetryStrategy : RetryStrategy
 {
  private readonly int retryCount = 3;
  private readonly TimeSpan retryInterval = TimeSpan.FromMilliseconds(1000);

  public CustomRetryStrategy()
    : base("customRetryStrategy", true)
  {
    //default values
  }

  public CustomRetryStrategy(int retryCount, TimeSpan retryInterval)
    : base("customRetryStrategy", true)
  {
    this.retryCount = retryCount;
    this.retryInterval = retryInterval;
  }

  public override ShouldRetry GetShouldRetry()
  {
    if (this.retryCount == 0)
    {
        return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval)
        {
            interval = TimeSpan.Zero;

            return false;
        };
    }

    return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval)
    {
        if (currentRetryCount < this.retryCount)
        {
            var random = new Random();
            //set random interval within the threshold
            interval = TimeSpan.FromMilliseconds(random.Next((int)(this.retryInterval.TotalMilliseconds * 0.8), (int)(this.retryInterval.TotalMilliseconds * 1.2)));


			//use your logic here
			//....

            return true;
        }

        interval = TimeSpan.Zero;

        return false;
     };
   }
 }

When creating custom retry policy we also need to create CustomTransientErrorDetectionStrategy class inheriting from ITransientErrorDetectionStrategy interface. In this class we simply evaluate the error type that is currently occurring and need to decide whether retry policy object will attempt to handle it. In order to do that we will pass in our error types to the class constructor. Next, we will check the error type within the IsTransient method to return true if error must cause the retry operation or false otherwise.

 public class CustomTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
 {
    List<Type> exceptionTypesToRetry;

    public CustomTransientErrorDetectionStrategy(List<Type> exceptionType)
    {
        exceptionTypesToRetry = exceptionType;
    }

    public bool IsTransient(Exception ex)
    {
        if (exceptionTypesToRetry.Contains(ex.GetType()))
        {
            return true;
        }
        return false;
    }
 }

Finally in our controller we can test it as follows:

 RetryPolicyHelper.GetDefaultPolicy("Incremental Retry Strategy").ExecuteAction(() =>
 {
     //index out of range exception - it won't be retried
     //as it defaults to sql connection errors
     var array = new int[] { 1, 2, 3 };
     var a = array[5];
 });

 RetryPolicyHelper.GetCustomRetryPolicy().ExecuteAction(() =>
 {
     //divide by zero exception - will be retried
     //as we defined exception types to:
     //DivideByZeroException, IndexOutOfRangeException
     var a = 0;
     var b = 10 / a;
 });

I have included project files below for your tests 🙂

Azure_RetryPolicy

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Deploying website files to multiple folders with exclusions

When automating deployments it is sometimes needed to create copy of your website/folder in different locations with some excluded files e.g. configuration files or temp folders.

The script below will help you to achieve that. It can be triggered automatically on your post-deployment action (possibly in your CI server) or executed manually. Script just copies all files from source folder into multiple locations, except for the files and sub-folders you specify. Files with the same names will be overridden in target locations.

Copy_websites

param (
##main paths
[string]$Source_wwwroot = "c:\inetpub\wwwroot\sourceApp",
[string]$Copy1_wwwroot = "c:\inetpub\wwwroot\copy1App",
[string]$Copy2_wwwroot = "c:\inetpub\wwwroot\copy2App",
##copy exceptions
$exclude = @("Web.config","Web.Debug.config","Web.Release.config"),
$excludeMatch = @("TempFiles","TempImages","obj")
)

if(-not(Test-Path $Source_wwwroot)) { "Source folder not found!"; return;}
if(-not(Test-Path $Copy1_wwwroot)) { "Copy1 folder not found!"; return;}
if(-not(Test-Path $Copy2_wwwroot)) { "Copy2 folder not found!"; return;}

If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{ 
  "You need to run this script as admin!";
  sleep 3000
  return;
}

Write-Host "`n#######################`nThis script will copy files from: `n$Source_wwwroot `nto: `n$Copy1_wwwroot and`n$Copy2_wwwroot `n";

Write-Host "except files: $exclude";
Write-Host "except folders: $excludeMatch";

Read-host -prompt "`n#######################`nPress enter to start copying the files"

function CopyFiles($from, $to){
    [regex] $excludeMatchRegEx = ‘(?i)‘ + (($excludeMatch |foreach {[regex]::escape($_)}) –join “|”) + ‘’
    Get-ChildItem -Path $from -Recurse -Exclude $exclude | 
     where { $excludeMatch -eq $null -or $_.FullName.Replace($from, "") -notmatch $excludeMatchRegEx} |
     Copy-Item -Destination {
      if ($_.PSIsContainer) {
       Join-Path $to $_.Parent.FullName.Substring($from.length)
      } else {
       Join-Path $to $_.FullName.Substring($from.length)
      }
     } -Force -Exclude $exclude
 }

 "copying files for copy1..."
 CopyFiles $Source_wwwroot $Copy1_wwwroot

 "copying files for copy2..."
 CopyFiles $Source_wwwroot $Copy2_wwwroot

"Done!";
sleep 10000

Enjoy!
Copy_websites

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...