Showing posts with label gabriel. Show all posts
Showing posts with label gabriel. Show all posts

Saturday, 7 March 2015

Azure AD Cloud silent Authentication with ADAL and TokenCache encryption

The ADAL library was launched a couple of years ago, luckily people from Microsoft like Vittorio Bertocci, have been working hard to have a library capable of doing authentication between native applications, web applications and web services (Web API). ADAL.js is in beta, it is a full working library but there are some issues need to be solved before we can have a robust library (This is when I am writing my article so March 2015) but Vittorio promised me we will have something working in this side by April 2015.
Unfortunately for me, and due to a project we are releasing before April, there is not choice, so I will have to write a wrapper around my Azure Web Api’s and skip Adal.js until we have v1.0. So you will be asking yourself “what is this guy talking about?”.
Ok, let’s go to start from a "simple" scenario, so you can have an  idea of what I am talking about. I have a Web Application (MVC 5) which is talking with few web services (Web API 2’s). All of them are register in Azure AD, so Azure AD acts as a Black Box, and manage the authentication. In this case Azure AD will take care of the MVC Web Application and the Web API’s Authentication.
So where is the problem? Well if I want to be sure that the Web Application and the Web APis share the same authentication, I will have to use refresh tokens, but because the hard work done by Vittorio’s team, now we can use Silent Tokens, so we don’t have to worry about building a whole system to refresh the tokens, but the only place where we need to worry it is about about where to store the Tokens.
It is quite common to store the tokens in Sessions, but there is a small problem with this, you lose the multi-farm factor plus WebApi’s don’t have sessions, so when you have to talk to them becomes an impossible job.
So what is the solution? store the tokens in a persistent area. If we have a native application we could store the token’s in the file system, but if we have a Web Application we could store them in Blob Storage or Databases.
Let’s go to a scenario, when someone, which is not really a user, and it is trying to get our token to access to our farm or MVC application, manage to access to the database or file system. Well, I think we will be in big trouble here. That person will have at least 20 minutes (Time when our token expires) to hack our system.
So… let’s go to make the things more difficult for this person, let’s go to inject encryption in the tokens, with double key encryption, String and Byte Array.
The following example it is a simple call from a MVC Application to a Web API 2 using silent Authentication.
...//## GETTING THE TOKEN TO BE AUTHORISE string userObjectID = ClaimsPrincipal.Current.FindFirst(userSchema).Value; AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new TokenEncryptedDatabaseCache(userObjectID, "Iamakey"); ClientCredential credential = new ClientCredential(clientId, appKey); AuthenticationResult result = authContext.AcquireTokenSilent(resourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));string authnHeader = "Authorization: Bearer " + result.AccessToken; ...


Now… check the parameter we are passing on  AcquireTokenSilent , it is a TokenCache.

This is the model we use to store the data in the database:


public class PerUserWebCache { [Key] public int EntryId { get; set; } public string WebUserUniqueId { get; set; } public byte[] CacheBits { get; set; } public DateTime LastWrite { get; set; } }
And finally this is the beauty! TokenEncryptedDatabaseCache, which encapsulates the encryption. You will need to create a dbcontext to store the model with the data…which I am going to add as well, so you can save some time.

using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Web;using System.Web.Caching;using Microsoft.IdentityModel.Clients.ActiveDirectory;using System.Security.Cryptography;using System.IO; public class TokenEncryptedDatabaseCache : TokenCache { private TokenCacheDataContext db = new TokenCacheDataContext(); string User; private PerUserWebCache Cache; private string Key; /// <summary> /// Contsructor for the TokenEncryptedDatabaseCache class /// </summary> /// <param name="user">Current User</param> /// <param name="key">Key for the encription</param> public TokenEncryptedDatabaseCache(string user, string key) { Key = key; User = user; this.AfterAccess = AfterAccessNotification; this.BeforeAccess = BeforeAccessNotification; this.BeforeWrite = BeforeWriteNotification; //## We check if the user is in our database Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); //## If that is the case we keep it in memory //## We decrypt the token this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> /// Method to clean the database /// </summary> public override void Clear() { base.Clear(); foreach (var cacheEntry in db.PerUserCacheList) db.PerUserCacheList.Remove(cacheEntry); db.SaveChanges(); } /// <summary> /// ADAL raise a notification before acces to the cache. /// Notification raised before ADAL accesses the cache. /// This is your chance to update the in-memory copy from the DB, /// if the in-memory version is stale. The token is decrypted. /// </summary> /// <param name="args"></param> void BeforeAccessNotification(TokenCacheNotificationArgs args) { if (Cache == null) { // first time access Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } else { // retrieve last write from the DB var status = from e in db.PerUserCacheList where (e.WebUserUniqueId == User) select new { LastWrite = e.LastWrite }; // if the in-memory copy is older than the persistent copy if (status.First().LastWrite > Cache.LastWrite) //// read from from storage, update in-memory copy { Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } } this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> ///Notification raised after ADAL accessed the cache. ///If the HasStateChanged flag is set, ADAL changed the content of the cache, ///At this time we encrypt the token and save it. /// </summary> /// <param name="args"></param> void AfterAccessNotification(TokenCacheNotificationArgs args) { // if state changed if (this.HasStateChanged) { Cache = new PerUserWebCache { WebUserUniqueId = User, CacheBits = Encrypt(this.Serialize()), LastWrite = DateTime.Now }; //// update the DB and the lastwrite db.Entry(Cache).State = Cache.EntryId == 0 ? EntityState.Added : EntityState.Modified; db.SaveChanges(); this.HasStateChanged = false; } } void BeforeWriteNotification(TokenCacheNotificationArgs args) { // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry } /// <summary> /// Encription of the token. /// </summary> /// <param name="DataToEncrypt">Data to be encrypted</param> /// <returns>Data encrypted.</returns> private byte[] Encrypt(byte[] DataToEncrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream,aes.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToEncrypt, 0, DataToEncrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } /// <summary> /// Decryption of the token. /// </summary> /// <param name="DataToDecrypt">Data to be decrypted</param> /// <returns>Data decrypted</returns> private byte[] Decrypt(byte[] DataToDecrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToDecrypt, 0, DataToDecrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } }

This is the class where we have our DBContext


using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Data.Entity;using System.Data.Entity.ModelConfiguration.Conventions;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheDataContext : DbContext { public TokenCacheDataContext() : base("TokenCacheDataContext") { } public DbSet<PerUserWebCache> PerUserCacheList { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }


Initializer…

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<TokenCacheDataContext> { }

Well, I hope you enjoy, any questions let me know.

Tuesday, 2 April 2013

List Fields not showing up in Edit, Display or New Forms when we create a new List Definition on Sharepoint 2010

Create List Definitions, suppose to be an easy task to do, but sometimes you could end wasting plenty of time. How do you avoid that?. Well one of the most common errors is that after creating the list you are going to add your new item, and by magic the fields are not populated in the entry form. You go to your view, and you see they are all there.

This happens because the content type created by Visual Studio keeps pointing to the default one, 0x01. How to sort it?, pretty easy:

Step 1
Go to Schema.XML and find the section content types, it should look something like this:

 <MetaData>
    <ContentTypes>
      <ContentTypeRef ID="0x01">
        <Folder TargetName="Item" />
      </ContentTypeRef>
      <ContentTypeRef ID="0x0120" />
    </ContentTypes>
    <Fields>


Step 2
Now, just remove that annoying default content type. The whole new thing should look like this:

  <MetaData>
    
    <Fields>


Step 3
If you want to be 100% sure that you want to display the form properly, just add the following attributes to the element field: 
- ShowInEditForm = “TRUE”
- ShowInDisplayForm = “TRUE”
- ShowInNewForm =”TRUE”


So you should have something like this:

<MetaData>    
    <Fields>
      <Field ID="{24B3F6C1-B75B-4821-A466-C1E979B87B81}" Name="Manufacturer" DisplayName="Manufacturer" Type="Text" Required="TRUE" ShowInEditForm="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="TRUE"/>
      <Field ID="{24B3F6C1-B75B-4821-A466-C1E979B87B82}" Name="Model" DisplayName="Model" Type="Text" Required="TRUE" ShowInEditForm="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="TRUE"/>
      <Field ID="{24B3F6C1-B75B-4821-A466-C1E979B87B83}" Name="CPUMake" DisplayName="CPU Make" Type="Choice" Required="TRUE" ShowInEditForm="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="TRUE" >

Conclusion: List definition, are cleaner that programmatically modes, but you could end having big headaches if they don’t work properly.

Friday, 15 March 2013

“external content types are not available. Contact your system administrator” in Business Connectivity Services on Sharepoint 2013.

I remember spending one or two days trying to sort out this issue. It was quite annoying, mainly because the hardest part was already developed in Visual Studio 2012, and I couldn’t see the content type from my BCS connexion.

Right, I think it will be useful to show what to do if you have this problem, the problem is basically that our External Content Type Picker is empty:
image

Option 1
Go to “Central Administration->Manage Service Applications->Business Data Connectivity Service”. Be sure you BDC Connexion is there. If it is, select it, and go to “Set Object Permissions” and set “All Authenticated Users” enable for Edit, Execute, Selectable In Clients and Set Permissions. Tick “Propagate permissions to all methods of this external content type. Doing so will overwrite existing permissions.”.
After doing this, everything should work an look like this:
image

Option 2
If you have removed the Method “ReadItem” it will be deployed but not showed it. Be sure your BDCModel has the two main methods “ReadList” and “ReadItem”:
image

 

 

 

 

 

 

 

 

Option 3
If you still having the same problem… be sure when you go to your “BDC Explorer” under Visual Studio 2012, select TypeDescriptor and the “Identifier” is select it. That is the main link between the class and the entity. For example here, I select BaseUri TypeDescriptor and I link it with BaseUri from the class.
image

Conclusion
There are more problems related. but I think this is a good start point.

Wednesday, 13 March 2013

“The password supplied with the username was not correct. Verify that it was entered correctly and try again” on Sharepoint 2013

At some point I had that annoying pop up box asking to change the Sharepoint admin password. Ok I changed the password, and I went to Central Administration to create a new Web Application…

I created and I have got this message, what I think it is exactly the same I used to get in Sharepoint 2007 and Sharepoint 2010.

This is normally because the farm credentials have not been updated. To do this I always use the old fashion STSADM command.

Go to Microsoft SharePoint 2013 Products->SharePoint 2013 Management Shell and type the following:

stsadm -o updatefarmcredentials -userlogin yourdomain/youruser -password yourpassword

Enjoy!

Monday, 2 August 2010

How to save an office 2010 document into Sharepoint 2010.

In the past, Sharepoint 2003/2007 was unable to save documents from your Microsoft Office to your Sharepoint system.
This was a problem for companies interested in using Sharepoint as a Document Management System. In the past some developers, like myself, decided to design Office addins in order to create a bridge between Microsoft Office and Sharepoint.
All od these issues are comming to and end with the Sharepoint 2010/Office 2010 couple marriage.
The only thing we need to do now it tell exactly where we need to save it.
(To do this, you will need at least a Sharepoint installation)



After this click on File->Save & Send -> Save to Sharepoint-> and type the name of the server where you want to keep the docs and job done.










Thursday, 19 March 2009

CAML (Collaborative Application Markup Language) PART I


CAML (Collaborative Application Markup Language) is an XML based markup language used with the family of Microsoft SharePoint technologies (Windows Sharepoint Services and Office SharePoint Server). Unlike plain XML, CAML contains specific groups of tags to both define and display (render) data.

This is base definition for CAML, but CAML allows you to query Sharepoint at any single level, we can say that CAML is the T-SQL of Sharepoint. In fact the notation is quite similar in some ways, or at least we can realize for the code that CAML is not a program language, it is a propel query language based in XML.

The best way of using CAML in your C#.NET/VB.NET code, or even in your Java code, is by calling the out-of-the-box web service that comes with Sharepoint. This web services can be called from anywhere but they have always to point your server, in few words, they can not be moved.

GetListItems(…) is the method you need in order to use CAML. Top do this the best way is do the following:

In your project Right click in “Web References” and “Add Web Reference”



If tou type the name of your server ie: called "mymoss", in order to use the query you will have: http://mymosss/_vti_bin/Lists.asmx?op=GetListItems



private WSSObject[] ListFilesFromHistory(string _sSiteUrl, string _sHistoryNameList, string _sRowLimit)
{
string _sows_Edited_x0020 ="";
int iCounter = 0;
WSSObject[] wFolders = null;
string siteUrl = _sSiteUrl;//this.UserWebSite;

MOSS2007.GetListCollection.Lists wsList = new MOSS2007.GetListCollection.Lists();
wsList.Credentials = System.Net.CredentialCache.DefaultCredentials;

wsList.Url = siteUrl + @"/_vti_bin/lists.asmx";
// get a list of all top level lists
XmlNode allLists = wsList.GetListCollection();

// load into an XML document so we can use XPath to query content
XmlDocument allListsDoc = new XmlDocument();

// Loading all the stuff in the XmlDocument class
allListsDoc.LoadXml(allLists.OuterXml);

XmlNamespaceManager ns = new XmlNamespaceManager(allListsDoc.NameTable);
ns.AddNamespace("d", allLists.NamespaceURI);

// now get the GUID of the document library we are looking for
//XmlNode dlNode = allListsDoc.SelectSingleNode("/d:Lists/d:List[@FeatureId='00bfea71-e717-4e80-aa17-d0c71b360101']", ns);

//It gets all the nodes related with that ID
//XmlNodeList dlNodeList =allListsDoc.SelectNodes("/d:Lists/d:List[@FeatureId='" + GetFeatureIDFromLibrary("Reporting Templates").ToString() + "']", ns);
XmlNodeList dlNodeList = null;

dlNodeList = allListsDoc.SelectNodes"/d:Lists/d:List[@Title='"+_sHistoryNameList+"']", ns);

//The WWSObject is initilized
wFolders = new WSSObject[dlNodeList.Count];

//The WWSObject is filled.
foreach (XmlNode dlNode in dlNodeList)
{
wFolders[iCounter] = new WSSObject (dlNode.Attributes["Title"].Value, dlNode.Attributes["Title"].Value);

// obtain the GUID for the document library and the webID
string documentLibraryGUID = dlNode.Attributes["ID"].Value;
string webId = dlNode.Attributes["WebId"].Value;

// Creating ViewFields CAML
XmlDocument viewFieldsDoc = new XmlDocument();
XmlNode ViewFields = AddXmlElement(viewFieldsDoc, "ViewFields", "");
AddFieldRef(ViewFields, "GUID");
AddFieldRef(ViewFields, "ContentType");
AddFieldRef(ViewFields, "BaseName");
AddFieldRef(ViewFields, "Modified");
AddFieldRef(ViewFields, "EncodedAbsUrl");
AddFieldRef(ViewFields, "CheckedOutDate");
AddFieldRef(ViewFields, "Subject");
AddFieldRef(ViewFields, "Title");
AddFieldRef(ViewFields, "Author");
AddFieldRef(ViewFields, "Date Modified");
AddFieldRef(ViewFields, "Format");
AddFieldRef(ViewFields, "Version");
AddFieldRef(ViewFields, "URL");
AddFieldRef(ViewFields, "Edited_x0020_by");

//######################################
//##ows_Edited_x0020_by="14;#Jon Taylor"
//######################################

// create QueryOptions CAML
XmlDocument queryOptionsDoc = new XmlDocument();
XmlNode QueryOptions = AddXmlElement(queryOptionsDoc, "QueryOptions", "");
//XmlNode Query = CreateNode("Query", @" " + _sows_Edited_x0020);
XmlNode Query = CreateNode("Query", _sows_Edited_x0020);

AddXmlElement(QueryOptions, "Folder", wFolders[iCounter].Name);
//AddXmlElement(QueryOptions, "IncludeMandatoryColumns", "FALSE");


//// this element is the key to getting the full recusive list
XmlNode node = AddXmlElement(QueryOptions, "ViewAttributes", "");
AddXmlAttribute(node, "Scope", "Recursive");

// obtain the list of items in the document library
//XmlNode listContent = wsList.GetListItems(documentLibraryGUID, null, null, ViewFields, null, QueryOptions, webId);
//XmlNode listContent = wsList.GetListItems(documentLibraryGUID, null, null, ViewFields, null, QueryOptions, webId);
XmlNode listContent = wsList.GetListItems(documentLibraryGUID, null, Query, ViewFields, _sRowLimit, QueryOptions, webId);

//The results are loaded
XmlDocument xmlResultsDoc = new XmlDocument();
xmlResultsDoc.LoadXml(listContent.OuterXml);


ns = new XmlNamespaceManager(xmlResultsDoc.NameTable);
ns.AddNamespace("z", "#RowsetSchema");

//I dunno what it does...
XmlNodeList rows = xmlResultsDoc.SelectNodes("//z:row", ns);

if (rows.Count > 0)
{
wFolders[iCounter].Objects = new WSSObject[rows.Count];
int iFileCounter = 0;
foreach (XmlNode row in rows)
{
string _sCheckBy = row.Attributes["ows_CheckoutUser"] != null ? row.Attributes["ows_CheckoutUser"].Value : "";
//##Every file is added
//##
//##If the file doesn't have extension we add a ""
//if (row.Attributes["ows_DocIcon"] == null) wFolders[iCounter].Objects[iFileCounter++] = new WSSObject(row.Attributes ["ows_FileLeafRef"].Value, row.Attributes["ows_Editor"].Value, row.Attributes["ows_EncodedAbsUrl"].Value, row.Attributes["ows_FileRef"].Value, row.Attributes["ows_Modified"].Value, row.Attributes["ows_ContentType"].Value, row.Attributes["ows_GUID"].Value, "", _sCheckBy);
//##Else we add the right extension
wFolders[iCounter].Objects[iFileCounter] = new WSSObject(row.Attributes ["ows_FileLeafRef"].Value, "", row.Attributes["ows_EncodedAbsUrl"].Value, row.Attributes["ows_FileRef"].Value, row.Attributes["ows_Modified"].Value, row.Attributes["ows_ContentType"].Value, row.Attributes["ows_GUID"].Value, "", _sCheckBy);
wFolders[iCounter].Objects [iFileCounter].HistoryAuthor = row.Attributes["ows_Author"].Value != null ? row.Attributes["ows_Author"].Value : "";
wFolders[iCounter].Objects [iFileCounter].HistoryFormat = row.Attributes["ows_FileLeafRef"].Value!=null?row.Attributes["ows_FileLeafRef"].Value:"";
wFolders[iCounter].Objects[iFileCounter].HistoryModified = row.Attributes["ows_Modified"].Value != null ? row.Attributes["ows_Modified"].Value : "";
wFolders[iCounter].Objects[iFileCounter].HistorySubject = row.Attributes["ows_Subject"].Value != null ? row.Attributes["ows_Subject"].Value : "";
wFolders[iCounter].Objects[iFileCounter].HistoryTitle = row.Attributes["ows_FileLeafRef"].Value != null ? row.Attributes["ows_FileLeafRef"].Value : "";
wFolders[iCounter].Objects[iFileCounter].HistoryURL = row.Attributes["ows_URL"].Value != null ? row.Attributes["ows_URL"].Value : "";
wFolders[iCounter].Objects[iFileCounter].HistoryVersion = row.Attributes["ows_FileLeafRef"].Value != null ? row.Attributes["ows_FileLeafRef"].Value : "";

iFileCounter++;
//##
//Console.WriteLine(row.Attributes["ows_ContentType"].Value + " " + row.Attributes ["ows_GUID"].Value + " :: " + row.Attributes["ows_BaseName"].Value);
}
}

iCounter++;
}

//CurrentFolderStructure = wFolders;

return wFolders;
}

Thursday, 13 November 2008

How to change the font forecolor, background font color and background color in a TabControl

Because the tab control does not provide any property to change font background, the font forecolor and the box background I have decided to override the DrawItem event so I can control this tab component when windows is painting it.

This event will help you to set the font background, the font forecolor and the box background of
the tab control.

To use it:
1- Just add this event into your code.
2- on Properties change DrawMode property to OwnerDraw so we can mess around with this event later.
3- Go to the events and in the DrawItem event just type the name of our event;TabAndBackgroundBoxColorHandler.

You can modify more parts of the tabcontrol. you just need to capture the coordinates



private void TabAndBackgroundBoxColorHandler(object sender, DrawItemEventArgs e)
{
//########################################################
//## TabAndBackgroundBoxColorHandler ##
//## ##
//## How to use it: -Just set the tab control from ##
//## DrawMode to OwnerDraw. ##
//## -Add this Event into DrawItem event.##
//## -Set the colors you want. ##
//########################################################

//## Getting The tab control to work with it
System.Windows.Forms.TabControl _tTabControl = ((System.Windows.Forms.TabControl)(sender));//tabControl1;

StringFormat _sfStringFormat = new StringFormat();
string _sTabName = null;
Rectangle _rTabCoordinates = e.Bounds;
//Rectangle _rBoxCoordinates = new Rectangle(e.Bounds.X, e.Bounds.Y, _tTabControl.Width, _tTabControl.Height);
Rectangle _rBoxCoordinates = new Rectangle(0, 0, _tTabControl.Width, _tTabControl.Height);
Font _fTabFont;
Brush _brBackBrush = new SolidBrush(Color.Black); //Set background font color
Brush _brForeBrush = new SolidBrush(Color.AliceBlue);//Set foreground font color
Brush _brBackBox = new SolidBrush(Color.Red);//Set background box color color

//## We check the index, so we can paint the selected tab
if (e.Index == _tTabControl.SelectedIndex)
_fTabFont = new Font(e.Font.FontFamily, e.Font.Size, FontStyle.Bold);
else
_fTabFont = e.Font;

//## Drawing the rectangle for the main box
if (!_tTabControl.TabPages[0].Capture)
{
_tTabControl.TabPages[0].Capture = true;//.BackColor = ((SolidBrush)_brBackBrush).Color;
e.Graphics.FillRectangle(_brBackBox, _rBoxCoordinates);
}
else if (_tTabControl.TabPages.Count==e.Index)
{
_tTabControl.TabPages[0].Capture = false;
}

//e.Graphics.FillRectangle(_brBackBox, _rBoxCoordinates);

//## Getting the tab name so we can work with it.
_sTabName = _tTabControl.TabPages[e.Index].Text;

//## Aligning the text...
_sfStringFormat.Alignment = StringAlignment.Center;

//## This will fill the rectangle where the tab is with the color
//## Set before...
e.Graphics.FillRectangle(_brBackBrush, e.Bounds);

//## Creating the new rectange where T is.
//## +-----+
//## | T |
//## +-----+---------+
//## | P |
//## +---------------+
_rTabCoordinates = new Rectangle(_rTabCoordinates.X, _rTabCoordinates.Y + 3, _rTabCoordinates.Width, _rTabCoordinates.Height - 3);

//## Drawing the rectangle in the TabControl + The aligment...
e.Graphics.DrawString(_sTabName, _fTabFont, _brForeBrush, _rTabCoordinates, _sfStringFormat);

//## Dispose objects.
_sfStringFormat.Dispose();

//## Disposing if we have selected something.
if (e.Index == _tTabControl.SelectedIndex)
{
_fTabFont.Dispose();
_brBackBrush.Dispose();
_brForeBrush.Dispose();
_brBackBox.Dispose();
}
else
{
_brBackBrush.Dispose();
_brForeBrush.Dispose();
_brBackBox.Dispose();
}
}