Thursday, October 17, 2013

Dynamic Table Partition Switching (MSSQL)

Problem

When you have a lot of data need to be inserted into the database, or so called data import, table locking is inevitable due to the database is ensuring data integrity with the read committed isolation level. When one transaction is required to perform a lot of data insertion or update, it is going to lock the table and therefore could cause more wait time for any other transaction which are currently queuing up for table access. It is recommended to split the huge data insertion or update into multiple transaction which can be completed quickly by writing the query carefully.

Today topic is about making use of table partition switching to reduce number of table lock and you still be able to insert huge amount of records. The idea is coming from Serena Yeoh, I am here to share the concept and how it was implemented.

Concept

Imagine that you need to insert huge amount of records into a table. You worry that your action will cause table locking and others cannot access the table, eventually they get query timeout error or deadlock error from their application. The concept is to have a staging (temporary) table which is created base on the main table which you are going to insert huge amount of records into it. You can just insert all the records into the staging table first temporarily without affecting others who are using the main table. When your data is ready in the staging table, at the main table, split the last partition into a new partition with the size same as the staging table size, then switch the staging table to the main table newly split partition, then merge the split partition back with the last partition.


Challenges

The are a few challenges to implement the above concept. One of them is the identity that you are going to insert into the staging table may already exists in the main table, since it could be used by a different session which is doing record insertion. The identity seed of the main table is incrementing from time to time. Therefore, the main table primary key constraint check will fail and it prevent you from switching the staging table in to the split partition.

Another challenge is when a session is inserting multiple records, at the same time, you have split the last partition to a new partition. The other session which is inserting records, the records will be leak into the new partition. When you have data in the new partition, you cannot switch the staging table to the new partition.

When you have above challenges solved, you will face another problem with partition fragmentation. Imagine that there are multiple session are doing the same table partition splitting and merging, if one fail, it will leave the split partition there with empty data. Although you can protect it with transaction, the split partition can be rollback, but the table identity cannot be rollback because there are other sessions still doing the record insertion, and table identity keep incrementing, rolling back the identity could cause key duplication error later. Anyway, it is very rare that staging table cannot be switch in, unless it contain dirty data. However, it is still manageable because we can fix the data, switch and merge the partition manually later.

The following are the implementation of the concept and the solution for the mentioned challenges:

Implementation

First, we have to make sure the main table must be a partitioned table. All the indexes of the main table also must be partitioned. For me, I have a simple table, and only have one partition. I created a RANGE LEFT partition function, and a simple partition scheme which have all partition store in PRIMARY file group.

CREATE PARTITION FUNCTION [ContactListPartitionFunction] (BIGINT)
    AS RANGE LEFT
    FOR VALUES (0);

CREATE PARTITION SCHEME [ContactListPartitionScheme]
    AS PARTITION [ContactListPartitionFunction]

    ALL TO ([PRIMARY]);

Create a partitioned table.

CREATE TABLE [dbo].[ContactList] (
    [ContactId]     BIGINT        IDENTITY (1, 1) NOT NULL,
    [FullName]      VARCHAR (100) NOT NULL,
    [ContactNumber] VARCHAR (50)  NOT NULL,
    [Address]       VARCHAR (500) NOT NULL,
    [CreationDate]  DATETIME      NOT NULL,
    CONSTRAINT [PK_ContactList] PRIMARY KEY CLUSTERED ([ContactId] ASC) ON [ContactListPartitionScheme] ([ContactId])

);

Create partitioned indexes for my table.

CREATE NONCLUSTERED INDEX [IX_CreationDate_Sort]
    ON [dbo].[ContactList]([CreationDate] ASC)

    ON [ContactListPartitionScheme] ([ContactId]);

CREATE NONCLUSTERED INDEX [IX_Contact_Search]
    ON [dbo].[ContactList]([FullName] ASC, [ContactNumber] ASC)
    INCLUDE([Address])

    ON [ContactListPartitionScheme] ([ContactId]);

Next, we prepare a temp table (staging table) for the main table. You can do it by using SQL Server Management Studio or writing T-SQL scripts manually.


Right click the main table, then look for Storage menu, click the Manage Partition menu.


Select "Create a staging table for partition switching".



Enter the staging table name, this is the temp table which you are going to insert record into it. Set any new boundary value, this is the value that we need to change it dynamically later in programming way by depending on the number of record in this table.



Run the script immediately.



Now, delete the constraint of the temp table, because we are going to add it later.


Then, proceed to insert multiple records into the temp table. Take note that the identity of the record must be in numeric data type. The reason is because we need to make use of the identity to solve the potential duplicate identity constraint check challenge while switching table partition. The inserted first row of record must always start with identity value 1 and is incremental for the rest of the rows.


Once the data in the temp table is created properly, we can now proceed to split the partition.
The following are the information that we need before performing the data transfer:

  1. Temp table row count
  2. Main table last identity
  3. Partition number
  4. Previous partition boundary (left)
  5. Split partition boundary (right)
The following script is the step by step execution for the concept. See the comment for more detail.

DECLARE @rowCount BIGINT
DECLARE @currentIdentity BIGINT
DECLARE @newIdentity BIGINT
DECLARE @partitionNumber INT
DECLARE @leftBoundary BIGINT
DECLARE @rightBoundary BIGINT
DECLARE @sql NVARCHAR(1000)

-- Use transaction to lock the table for table switching
-- Try to minimize code and reduce as much wait time as possible
BEGIN TRY
BEGIN TRANSACTION

-- Step 1 : Get the row count of the temp table
SELECT @rowCount = COUNT(0) FROM [dbo].[Staging_ContactList]

-- Step 2 : Get the last identity of the main table
SET @currentIdentity = CONVERT(BIGINT, IDENT_CURRENT('[dbo].[ContactList]'))

-- Step 3 : Get the boundary base on number of record to be inserted
-- This identity = 1 check is required when the table is empty, left boundary must be 0
IF @currentIdentity = 1
       SET @leftBoundary = @currentIdentity - 1
ELSE
       SET @leftBoundary = @currentIdentity
SET @rightBoundary = @leftBoundary + @rowCount

-- Step 4 : Reseed the table with the new identity for other session to insert new record with latest identity and avoid duplicate key constraint failure while table switching
-- Also to reserve identity for the temp table
DBCC CHECKIDENT('ContactList', RESEED, @rightBoundary)

PRINT 'Row Count : ' + CONVERT(VARCHAR, @rowCount)
PRINT 'Current Identity : ' + CONVERT(VARCHAR, @currentIdentity)
PRINT 'Left Boundary : ' + CONVERT(VARCHAR, @leftBoundary)
PRINT 'Right Boundary : ' + CONVERT(VARCHAR, @rightBoundary)

-- Step 5 : Update temp table identity base on the main table current identity to solve the identity constraint check challenge
-- When you have the identity value in proper order start from 1, 2, 3, 4... in your temp table
-- And, the last identity before the partition to be switched is 8592
-- Update all the identity in the temp table to 8592+1, 8592+2, 8592+3, 8592+4...
-- End result you will not have duplicate key error occur when switching table
UPDATE [dbo].[Staging_ContactList]
SET ContactId = ContactId + @leftBoundary

-- Step 6 : Get the partition number base on the new boundary value
SELECT @partitionNumber = $PARTITION.ContactListPartitionFunction(@rightBoundary)
PRINT 'Partition Number : ' + CONVERT(VARCHAR, @partitionNumber)

-- Step 7 : Add check constraint to temp table to fulfill the criteria for table partition switching
SET @sql = '
ALTER TABLE [dbo].[Staging_ContactList] WITH CHECK ADD CONSTRAINT [chk_Staging_ContactList_partition_' + CONVERT(VARCHAR, @partitionNumber) + '] CHECK ([contactID]>N''' + CONVERT(VARCHAR, @leftBoundary) + ''' AND [contactID]<=N''' + CONVERT(VARCHAR, @rightBoundary) + ''')
ALTER TABLE [dbo].[Staging_ContactList] CHECK CONSTRAINT [chk_Staging_ContactList_partition_' + CONVERT(VARCHAR, @partitionNumber) + ']
'
PRINT @sql
EXEC sp_executesql @sql

-- Step 8 : Switch the partition
ALTER TABLE [dbo].[Staging_ContactList]
SWITCH TO [dbo].[ContactList]
PARTITION @partitionNumber;

-- Step 9 : Merge previous partition with the partition of previous partition
ALTER PARTITION FUNCTION ContactListPartitionFunction ()
MERGE RANGE (@rightBoundary);

ALTER PARTITION FUNCTION ContactListPartitionFunction ()
MERGE RANGE (@leftBoundary);

-- Optional 1 : Drop the temp table
--DROP TABLE [dbo].[Staging_ContactList]

-- Optional 2 : Truncate table then drop the check constraint
SET @sql = '
ALTER TABLE [dbo].[Staging_ContactList] DROP CONSTRAINT [chk_Staging_ContactList_partition_' + CONVERT(VARCHAR, @partitionNumber) + ']
'
PRINT @sql
EXEC sp_executesql @sql

COMMIT TRANSACTION
END TRY
BEGIN CATCH
       SELECT
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;
       ROLLBACK TRANSACTION
END CATCH

Testing

When you are playing around with the script above, it is useful to query the partition from time to time to visualize what is happening with the table partition by executing the following query:

SELECT p.partition_number AS [Partition No] , g.name AS [FileGroup], p.[rows] AS Rows, r.[value] AS [Boundary] FROM sys.partitions p
INNER JOIN sys.indexes i ON p.object_id = i.object_id AND p.index_id = i.index_id
INNER JOIN sys.partition_schemes s ON s.data_space_id = i.data_space_id
INNER JOIN sys.destination_data_spaces d ON d.partition_scheme_id = s.data_space_id AND d.destination_id = p.partition_number
INNER JOIN sys.filegroups g ON g.data_space_id = d.data_space_id
LEFT JOIN sys.partition_range_values r ON r.function_id = s.function_id AND r.boundary_id = p.partition_number
WHERE p.[object_id] = OBJECT_ID('ContactList') AND i.index_id = 1

Now, test the concept whether it works or not. I have created a simple T-SQL which keep inserting records to my main table, at the same time, I ready my temp table data, then execute the above script. I see my records in the staging table successfully switch in to the main table.

Performance

The table partition split is going to take some time to complete the process, and during the time it will cause table locking. When the partition size is big, it is going to take even lot more time to split the partition. It is advisable to have multiple small - medium size partitions in one table instead of just one big partition. It is always faster to split a smaller partition.

Security

This concept require you to create and alter table and deal with partitions. If you wonder what security rights is required if you use this implementation, the answer is db_ddladmin or db_owner.


If you like this concept or think this is a crazy idea, feel free to drop me a comment. Thanks.


Sunday, September 1, 2013

Workflow Management Service (WMS) - High Memory Usage

The WorkflowManagementService.exe process is using a lot of memory until you get the OutOfMemoryException error when you are hosting your application. Looking at this situation, you may be suspecting the process is having memory leak problem. Restarting the AppFabric Workflow Management Service (WMS) windows service can temporarily free up the memory, however, the WMS process memory usage will still keep growing gradually until your server is out of memory again.

How to troubleshoot?

Open up Event Viewer, go to the Applications and Services Logs, expand the Microsoft folder, then expand Application Server-System Services folder, and look into the Admin log. You should see a lot of error which are related to Workflow Management Service. Let’s take a look at the logs with the source of Application Server-System Services Workflow Management Service only.



Following are the errors which you would see from the log.

Sample Error 1:
Failed to invoke service management endpoint at 'net.pipe://<server name>//ServiceManagement.svc' to activate service '/<service name>.svc'.\rException: 'The message with To 'net.pipe://<server name>//ServiceManagement.svc' cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher.  Check that the sender and receiver's EndpointAddresses agree.'

Sample Error 2:
Could not find net.pipe base address for site name 'Default Web Site' application name '/<your application name>. Check that the site and application have net.pipe enabled, and that the application still exists.

Sample Error 3:
Throwing an exception. Exception Could not locate binding information for the site Default Web Site and application /<your application name>.

Steps to rectify the problem:

WMS is trying to invoke the ServiceManagement service endpoint. Check whether the WMS is invoking a valid service endpoint. Open up IIS, go to the Website level, and check whether you have net.pipe binding information created?

If yes, go to the Application level, check whether you have net.pipe protocol enabled.

If problem persist, filter the event log, look for the error similar to Sample Error 3. The website name and application name mentioned in the log most likely does not exist in IIS. When WMS failed to activate the service due to the missing application in IIS, it keeps retrying and logging the error to the event log. And, because of this never ending retrying and logging, it flooded the event logs every few seconds, and took away some processing power and memory.


Base on the AppFabric system architecture, WMS actually loads the service configuration base on the data from the AppFabric Persistence Store database. More Info



When you have a new service deployed to the IIS, WMS will register your service into the AppFabric persistence store, in the System.Activities.DurableInstancing.ServiceDeploymentsTable. Querying this table will show you all the services which are under WMS monitoring. Identify and confirm the services in this table are all exist and hosted in your IIS.

By right, you should have one application server, one AppFabric service and one instance store. If you come to this post, that's mean you had setup the AppFabric architecture wrongly like me. I suppose you have multiple application servers, multiple AppFabric services, and they are sharing one instance store. If you are referring to the architecture from the MSDN blog, and questioning why this architecture setup is wrong, may be this diagram below confuse us?



I suppose the diagram is meant for load balanced environment, one same application in multiple servers and one database and one persistence store.

Anyway, back to the problem. In order to fix this up, we have to have one application server, one AppFabric service and one persistence store. And, following is the steps of how to clean up the data in the persistence store.

Identify all the unwanted services from the ServiceDeploymentTable, mark down the services ID first. We have to clean up all the instances related to the service before deleting the service. Perform the following query to check your instances.

SELECT *
FROM [System.Activities.DurableInstancing].[InstancesTable] t1
LEFT JOIN [System.Activities.DurableInstancing].[ServiceDeploymentsTable] t2
ON t1.ServiceDeploymentId = t2.Id
WHERE t2.Id IN (<your sIds>)


Then, delete all the instances.

DELETE
FROM [System.Activities.DurableInstancing].[InstancesTable]
WHERE ServiceDeploymentId IN (<your sIds>)


Then, delete the services.

DELETE
FROM [System.Activities.DurableInstancing].[ServiceDeploymentsTable]
WHERE Id IN (<your sIds>)


Finally, restart the WMS windows service. Then, monitor your Event Viewer to see any error still occur.

Credits:

Friday, June 7, 2013

Windows Store App - Calendar Control (XAML)

Recently I have been researching and playing around with the Windows Store App development for the Windows 8 and RT. I am used to develop enterprise solution or business application and I thought of may be creating something useful that can be used with a tablet PC. When I was playing with the Windows Store App UI and the available controls, I realize that the most important or commonly used controls such as Calendar, DateTime Picker, GridView, etc are not available in Windows Store App. :(

Therefore, I have no choice but to create my own Calendar control. And, yeah, I know there are 3rd party Calendar controls available such as Telerik but I just want to try to create one by my own. I made it and today I want to share about the making of custom Calendar control in XAML for Windows Store App. This is how my Calendar control look like:



Concept

The logic of the calendar construction is to construct the previous month date boxes first. If the last day of the previous month is on Saturday (last day of the week), then skip making date boxes, otherwise create number of boxes until before the first day of current month.

Then, start appending the current month date boxes and if the row contain 7 boxes, then create a new row and then append more boxes until the last day of the month.

Finally, append the remaining boxes start with the first day of next month until the last day of the week.


Implementation

So, how to implement the above concept?
First, create a UserControl for the Calendar control and design the grid in such way:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
   
    <!-- Header -->
    <Rectangle Grid.Row="0" Grid.ColumnSpan="3"
                Style="{StaticResource CalendarHeaderBox}" />
    <TextBlock Grid.Column="1" Name="CurrentDateText"
                Style="{StaticResource CalendarHeader}" />
   
    <!-- Navigation Button -->
    <Button Name="PreviousButton" Grid.Column="0" Content="&lt;"
            HorizontalAlignment="Left" Margin="20"
            Tapped="PreviousButton_Tapped" />
    <Button Name="NextButton" Grid.Column="2" Content="&gt;"
            HorizontalAlignment="Right" Margin="20"
            Tapped="NextButton_Tapped" />
   
    <!-- Calendar Grid -->
    <Grid Grid.Row="1" Grid.ColumnSpan="3" Name="CalendarGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
    </Grid>

</Grid>


And then, create another UserControl for the individual box of the day.

<Grid Name="ItemBox" Style="{StaticResource CalendarItemBox}">
    <Rectangle Stroke="Gainsboro" StrokeThickness="1" ></Rectangle>
    <TextBlock Name="ItemValue" Text="1" Style="{StaticResource CalendarItem}" />

</Grid>


This box user control contain very simple logic which is to display the box background color and text after the user control is loaded.

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    ItemValue.Text = this.Text;
    ItemBox.Style = this.GridStyle;

    if (this.Value.CompareTo(DateTime.Today) == 0)
        ItemBox.Background = new SolidColorBrush(Colors.Orange);

}


Now back to the Calendar user control, create a method to display the day of the week (the blue color header boxes as you see from above screenshot). I will just skip the detail, and below is the code snippet of my logic to form the calendar with the boxes (user controls). If you want to see the full detail, scroll down to the end of this post and download my source code.

The calendar formation is divided into 3 parts: Previous Month + Current Month + Next Month.

Before that, I need to create a new event handler to handle the value change after the user click at the the box. Also, I need to create the properties to store the current calendar viewing month value and the selected date value.

public DateTime CurrentDate { get; set; }
public DateTime SelectedDate { get; set; }

private event EventHandler<TappedRoutedEventArgs> _selectionChange;
public event EventHandler<TappedRoutedEventArgs> SelectionChange
{
    add
    {
        _selectionChange += value;
    }
    remove
    {
        _selectionChange -= value;
    }
}

public void OnSelectionChange(object sender, TappedRoutedEventArgs e)
{
    if (_selectionChange != null)
        _selectionChange(sender, e);

}

Next, create the methods to form the Calendar.

This method is to construct the previous month calendar.
private void InitializePreviousMonthBoxes()
{
    DateTime previousMonthDate = this.CurrentDate.AddMonths(-1);
    DateTime previousMonthDateIteration = new DateTime(previousMonthDate.Year, previousMonthDate.Month, DateTime.DaysInMonth(previousMonthDate.Year, previousMonthDate.Month));
    CalendarGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });

    for (int dayOfWeek = (int)previousMonthDateIteration.DayOfWeek; dayOfWeek >= 0; dayOfWeek--)
    {

        CalendarItem item = new CalendarItem(previousMonthDateIteration, previousMonthDateIteration.Day.ToString(), Application.Current.Resources["CalendarOtherMonthItemBox"] as Style);
        item.PointerEntered += (sender, args) =>
        {
            ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarMouseOverItemBox"] as Style;
        };

        item.PointerExited += (sender, args) =>
        {
            if (((CalendarItem)sender).Value == this.SelectedDate)
                ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarSelectedItemBox"] as Style;
            else
                ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarOtherMonthItemBox"] as Style;
        };

        //delegate the tapped event to selection change event
        item.Tapped += (sender, args) =>
        {
            //update the selected date value
            this.SelectedDate = ((CalendarItem)sender).Value;
            this.CurrentDate = this.CurrentDate.AddMonths(-1);
            InitializeCalendar();

            OnSelectionChange(sender, args);
        };

        item.SetValue(Grid.RowProperty, 1);
        item.SetValue(Grid.ColumnProperty, dayOfWeek);

        CalendarGrid.Children.Add(item);

        previousMonthDateIteration = previousMonthDateIteration.AddDays(-1);
    }

}


This method is to construct current month calendar.

private void InitializeCurrentMonthBoxes()
{
    int row = 1;
    int maxDay = DateTime.DaysInMonth(this.CurrentDate.Year, this.CurrentDate.Month);
    for (int day = 1; day <= maxDay; day++)
    {
        DateTime dateIteration = new DateTime(this.CurrentDate.Year, this.CurrentDate.Month, day);
        int dayOfWeek = (int)dateIteration.DayOfWeek;

        CalendarItem item = new CalendarItem(dateIteration, day.ToString(), Application.Current.Resources["CalendarItemBox"] as Style);
        item.PointerEntered += (sender, args) =>
        {
            ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarMouseOverItemBox"] as Style;
        };

        item.PointerExited += (sender, args) =>
        {
            if (((CalendarItem)sender).Value == this.SelectedDate)
                ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarSelectedItemBox"] as Style;
            else
                ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarItemBox"] as Style;
        };

        //delegate the tapped event to selection change event
        item.Tapped += (sender, args) =>
        {
            //get the box day value
            ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarSelectedItemBox"] as Style;

            //get the box before selected value and reset the color
            var selectedItem = (CalendarItem)CalendarGrid.Children.Single(x =>
                                    x.GetType() == typeof(CalendarItem) &&
                                    ((CalendarItem)x).Value == this.SelectedDate);

            selectedItem.GridStyle = Application.Current.Resources["CalendarItemBox"] as Style;

            //update the selected date value
            this.SelectedDate = ((CalendarItem)sender).Value;

            OnSelectionChange(sender, args);
        };

        //highlight selected date
        if (this.SelectedDate.CompareTo(dateIteration) == 0)
            item.GridStyle = Application.Current.Resources["CalendarSelectedItemBox"] as Style;

        item.SetValue(Grid.RowProperty, row);
        item.SetValue(Grid.ColumnProperty, dayOfWeek);

        CalendarGrid.Children.Add(item);

        if (dayOfWeek == 6 && day != maxDay)
        {
            row++;
            CalendarGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
        }
    }

}

This method is to construct next month calendar.

private void InitializeNextMonthBoxes()
{
    DateTime nextMonthDate = this.CurrentDate.AddMonths(1);
    DateTime nextMonthDateIteration = new DateTime(nextMonthDate.Year, nextMonthDate.Month, 1);

    int lastRow = CalendarGrid.RowDefinitions.Count - 1;

    if (nextMonthDateIteration.DayOfWeek != DayOfWeek.Sunday)
        for (int dayOfWeek = (int)nextMonthDateIteration.DayOfWeek; dayOfWeek < 7; dayOfWeek++)
        {
            CalendarItem item = new CalendarItem(nextMonthDateIteration, nextMonthDateIteration.Day.ToString(), Application.Current.Resources["CalendarOtherMonthItemBox"] as Style);
            item.PointerEntered += (sender, args) =>
            {
                ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarMouseOverItemBox"] as Style;
            };

            item.PointerExited += (sender, args) =>
            {
                if (((CalendarItem)sender).Value == this.SelectedDate)
                    ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarSelectedItemBox"] as Style;
                else
                    ((CalendarItem)sender).GridStyle = Application.Current.Resources["CalendarOtherMonthItemBox"] as Style;
            };

            //delegate the tapped event to selection change event
            item.Tapped += (sender, args) =>
            {
                //update the selected date value
                this.SelectedDate = ((CalendarItem)sender).Value;
                this.CurrentDate = this.CurrentDate.AddMonths(1);
                InitializeCalendar();

                OnSelectionChange(sender, args);
            };

            item.SetValue(Grid.RowProperty, lastRow);
            item.SetValue(Grid.ColumnProperty, dayOfWeek);

            CalendarGrid.Children.Add(item);

            nextMonthDateIteration = nextMonthDateIteration.AddDays(1);
        }

}

In the end, you need to execute these above 3 methods to form a complete calendar of the month. Finally, use the above custom control in the page like this with correct namespace:

<Page
    x:Class="CalendarControl.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CalendarControl"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:custom="using:CalendarControl.CustomControl"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Name="SelectedDateText"
                 Width="1024" Margin="10" />
        <custom:Calendar Grid.Row="1" x:Name="MyCalendar"
                            Width="1024"
                            Height="768"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Margin="10" SelectionChange="Calendar_SelectionChange" />
    </Grid>

</Page>


Then, how to get the selected date value? Just use the following code.

private void Calendar_SelectionChange(object sender, TappedRoutedEventArgs e)
{
    //if you get the out of context error, make sure to use the namespace x:
    //in your XAML - x:Name
    SelectedDateText.Text = MyCalendar.SelectedDate.ToString("yyyy-MM-dd");

}


If you are interested with my source code, feel free to download from HERE.
Customize the Calendar for your own purpose and please let me know or drop me a comment if you detect any bug with the Calendar control.

My next post is going to be DateTime Picker for Windows Store App (XAML). Stay tuned.




Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...