Revisiting FormsAuthentication.

FormsAuthentication module exists as an in-built HttpModule. For every incoming request, if it finds a valid, non-expired ‘FormsAuthenticationTicket’, it will try to authenticate the user and create a GeneralPrincipal object. The ‘GeneralPrincipal’ object represents the current user. Below are some examples, highlighting how to work with, additional info attached to the AuthenticationTicket.

Scenario 1:  A simple Forms Authentication example
web.config

<authentication mode="Forms">
            <forms loginUrl="SignOn.aspx" name="AuthCookie" timeout="60" path="/">
            </forms>
        </authentication>
        <authorization>
            <deny users="?"/>
            <allow users="*"/>
        </authorization>

SignOn.aspx.cs

protected void btnLogin_Click(object sender, EventArgs e)
{
bool isAuthenticated = IsAuthenticated(txtUserName.Text, txtPassword.Text);
if (isAuthenticated == true)
{
    // generate authentication ticket
    FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,   // version
                            txtUserName.Text,           // username
                            DateTime.Now,               // creation
                            DateTime.Now.AddMinutes(60),// Expiration
                            false,   // Persistent
                            ""   //No additional data supplied
                        );                     
    // Encrypt the ticket.
    string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
    // Create a cookie and add the encrypted ticket to the cookie
    HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName,
                                encryptedTicket); 
    Response.Cookies.Add(authCookie);
    // Redirect the user to the originally requested page
    Response.Redirect(FormsAuthentication.GetRedirectUrl(
                                                    txtUserName.Text,
                                                    false));
}
}

private bool IsAuthenticated(string username, string password)
{
    //The user can be validated/authenticated against custom SQl Server database, 
    //or membership or ActiveDirectory users.
    // Need to implement your own authentication logic. 
    return true;
}

Finally my landing page ‘SecurePage0.aspx ‘.

Few things to notice here. After user is successfully authenticated, the in-built FormsAuthentication module attaches the GenericPrincipal object to the Context.User. I don’t have to do that.The GenericPrincipal object contains an Identity object and roles info. This object is now available throughout the current request, something like a request basis cache. We’ll look into that in more details, a little later.

using System.Security.Principal;

public partial class SecurePage0 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        GenericPrincipal cp1 = (GenericPrincipal)HttpContext.Current.User;
        bool _isAuthenticated = Context.User.Identity.IsAuthenticated;
        string _userName = Context.User.Identity.Name;
        bool _role = Context.User.IsInRole("Manager");
    }
}

Here the GenericPrincipal as well as Context.User contains logged-in user’s username,  whether authenticated or not and absolutely NO role information. Remember, while creating the AuthenticationTicket, I passed an empty string for user data argument.(hence IsInRole function always returns false). Exact same information is available in Context.User too. So in this default mode , I have 2 pieces of user-information available – IsAuthenticated and UserName – in the context object as well as in my GenericPrincipal which anyway represents the current logged-in user.The in-built FormsAuthentication module did all the work.

img1

Scenario 2: Forms Authentication with Roles

Now let’s say, I want to add some additional information about roles, the current user belongs in. The GenericPrincipal object contains roles information fields.Hence when I create the FormsAuthentication ticket manually, I can attach the roles for the given user, as a string object to the ticket Here’s the FormsAuthenticationTicket creation code,which adds role info as the last parameter.

protected void btnLogin_Click(object sender, EventArgs e)
{
bool isAuthenticated = IsAuthenticated(txtUserName.Text, txtPassword.Text);
if (isAuthenticated == true)
{
    // generate authentication ticket
    FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,   // version
                            txtUserName.Text,           // username
                            DateTime.Now,               // creation
                            DateTime.Now.AddMinutes(60),// Expiration
                            false,   // Persistent
                            GetRoles(txtUserName.Text)   //Additional role info
                        );                     
    // Encrypt the ticket.
    string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
    // Create a cookie and add the encrypted ticket to the cookie
    HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName,
                                encryptedTicket); 
    Response.Cookies.Add(authCookie);
    // Redirect the user to the originally requested page
    Response.Redirect(FormsAuthentication.GetRedirectUrl(
                                                    txtUserName.Text,
                                                    false));
}
}

private bool IsAuthenticated(string username, string password)
{
    //The user can be validated/authenticated against custom SQl Server database, 
    //or membership or ActiveDirectory users.
    // Need to implement your own authentication logic. 
    return true;
}
private string GetRoles(string username)
{
    //return dummy role string. In real scenario, get roles from 
    //appropriate source like DB.
    return "Senior Manager|Manager|Employee";
}

The default FormsAuthentication module won’t attach role info to the GenericPrincipal object. I need to write code for that. Global.asax —> Application_PostAuthenticateRequest event handler is the spot, wherein I can attach roles to the Principal object. So here’s my global.asax

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

if (null == authCookie)
{
    // No authentication cookie.
    return;
}

//4.Add the following code to extract and decrypt the authentication 
//ticket from the forms authentication cookie. 
FormsAuthenticationTicket authTicket = null;
try
{
    authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
    // Log exception details 
    return;
}

if (null == authTicket)
{
    // Cookie failed to decrypt.
    return;
}
string[] roles = authTicket.UserData.Split('|');
FormsIdentity id = new FormsIdentity(authTicket);
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
//There are 2 security contexts - Thread.CurrentPrincipal and Context.User
//need to keep thread.CurrentPrincipal in sync with Context.User
//Thread.Currentprincipal is used in declarative role checks
//using PrincipalPermissionAttributes
System.Threading.Thread.CurrentPrincipal = HttpContext.Current.User;
}

Now in one of my authenticated page I can check roles, something like this

using System.Security.Principal;

public partial class SecurePage02 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
    GenericPrincipal cp = (GenericPrincipal)HttpContext.Current.User;
    bool _isAuthenticated = Context.User.Identity.IsAuthenticated;
    string _userName = Context.User.Identity.Name;
    bool _role = Context.User.IsInRole("Manager");
        
    Response.Write("Authenticated Identity is: " + cp.Identity.Name);
    Response.Write("<p>");

    if (cp.IsInRole("Senior Manager"))
    {
        Response.Write(cp.Identity.Name + " is in the " + "Senior Manager Role");
        Response.Write("<p>");
    }
}
}

Since my Thread.CurrentPrincipal is updated, I can even write role compliant class or function.

[PrincipalPermission(SecurityAction.Demand, Role="Manager")]
    public class Class1
    {
        public string str1 = "xxx";
    }

    [PrincipalPermission(SecurityAction.Demand, Role="Manager")]
    public void abc1()
    {
        //do something
    }

If any user , who does not belong to the role ‘Manager’, tries to access the class, a SecurityException is raised.

Scenario 3: CustomPrincipal.  The GenericPrincipal class has only one Role related member function “IsInRole”. What if I want to check, if user belongs to a set of roles or things like IsUserPresentInAnyRole? basically extending Role functionality, — then I need a CustomPrincipal class which implements IPrincipal interface.Here’s an example.

//CustomPrincipal class

public class GCustomPrincipal : IPrincipal
{
    private IIdentity _identity;
    private string[] _roles;

    // IPrincipal Implementation
    public bool IsInRole(string role)
    {
        return Array.BinarySearch(_roles, role) >= 0 ? true : false;
    }
    public IIdentity Identity
    {
        get
        {
            return _identity;
        }
    }

    public GCustomPrincipal(IIdentity identity, string [] roles)
    {
      _identity = identity;
      _roles = new string[roles.Length];
      roles.CopyTo(_roles, 0);
      Array.Sort(_roles);
    }

    //Add the following two public methods, which provide extended role-based checking functionality.
    // Checks whether a principal is in all of the specified set of roles
    public bool IsInAllRoles( params string [] roles1 )
    {
      foreach (string searchrole in _roles )
      {
        if (Array.BinarySearch(_roles, searchrole) < 0 )
          return false;
      }
      return true;
    }

    // Checks whether a principal is in any of the specified set of roles
    public bool IsInAnyRoles( params string [] roles1 )
    {
      foreach (string searchrole in _roles )
      {
        if (Array.BinarySearch(roles1, searchrole ) > 0 )
          return true;
      }
      return false;
    }
}

Global.asax

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
    {
        string cookieName = FormsAuthentication.FormsCookieName;
        HttpCookie authCookie = Context.Request.Cookies[cookieName];

        if (null == authCookie)
        {
            // There is no authentication cookie.
            return;
        }

        //Add the following code to extract and decrypt the 
        //authentication ticket from the forms authentication cookie. 
        FormsAuthenticationTicket authTicket = null;
        try
        {
            authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        }
        catch (Exception ex)
        {
            return;
        }

        if (null == authTicket)
        {
            // Cookie failed to decrypt.
            return;
        }

        //Add the following code to parse out the pipe separate list of role names attached to the 
        //ticket when the user was originally authenticated. 
        // When the ticket was created, the UserData property was assigned a
        // pipe delimited string of role names.
        string[] roles = authTicket.UserData.Split('|');

        //Add the following code to create a FormsIdentity object 
        //with the user name obtained from the ticket name and 
        //a CustomPrincipal object that contains this identity together with the user's role list. 
        FormsIdentity id = new FormsIdentity(authTicket);

        // This principal will flow throughout the request.
        GCustomPrincipal principal = new GCustomPrincipal(id, roles);
        // Attach the new principal object to the current HttpContext object
        Context.User = principal;
        //need to keep thread.CurrentPrincipal in sync with Context.User
        //Thread.Currentprincipal is used in declarative role checks
        //using PrincipalPermissionAttributes
        System.Threading.Thread.CurrentPrincipal = HttpContext.Current.User;
    }

Authenticated page

protected void Page_Load(object sender, EventArgs e)
{
    GCustomPrincipal cp = HttpContext.Current.User as GCustomPrincipal;
    Response.Write("Authenticated Identity is: " + cp.Identity.Name);
    Response.Write("<p>");

    if ( cp.IsInRole("Senior Manager") )
    {
        Response.Write( cp.Identity.Name + " is in the " + "Senior Manager Role" );
        Response.Write( "<p>" );            
    }

    if ( cp.IsInAnyRoles("Senior Manager", "Manager", "Employee", "Sales") )
    {
        Response.Write( cp.Identity.Name + " is in one of the specified roles");
        Response.Write( "<p>" );
    }
}

Scenario 4: CustomPrincipal and CustomIdentity

Sometimes I may want to save more user info, say things like — UserID, userCreationDate, primary email etc, in the FormsAuthenticationTicket. The ticket is safe, since it’s encrypted , signed by ASP.net. Here I need a CustomIdentity class implementing IIdentity interface. Below is my FormsAuthenticationTicket creation code. SignOn.aspx (Notice the emailid in the GetRoles function)

protected void btnLogin_Click(object sender, EventArgs e)
{
bool isAuthenticated = IsAuthenticated(txtUserName.Text, txtPassword.Text);
if (isAuthenticated == true)
{
    // generate authentication ticket
    FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,   // version
                            txtUserName.Text,           // username
                            DateTime.Now,               // creation
                            DateTime.Now.AddMinutes(60),// Expiration
                            false,   // Persistent
                            GetRoles(txtUserName.Text)   //Additional role info
                        );                     
    // Encrypt the ticket.
    string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
    // Create a cookie and add the encrypted ticket to the cookie
    HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName,
                                encryptedTicket); 
    Response.Cookies.Add(authCookie);
    // Redirect the user to the originally requested page
    Response.Redirect(FormsAuthentication.GetRedirectUrl(
                                                    txtUserName.Text,
                                                    false));
}
}

private bool IsAuthenticated(string username, string password)
{
    //The user can be validated/authenticated against custom SQl Server database, 
    //or membership or ActiveDirectory users.
    // Need to implement your own authentication logic. 
    return true;
}
private string GetRoles(string username)
{
    //return dummy role string. In real scenario, get roles from 
    //appropriate source like DB. Additional user info like emailid
    string _strEmailRoles = "a@a.com|Senior Manager,Manager,Employee";
    return _strEmailRoles;
}

CustomIdentity class

public class GCustomIdentity : IIdentity
{
    private System.Web.Security.FormsAuthenticationTicket ticket;

    public GCustomIdentity(System.Web.Security.FormsAuthenticationTicket ticket)
    {
        this.ticket = ticket;
    }

    public string AuthenticationType
    {
        get { return "FormsAuth With Custom Identity"; }
    }

    public bool IsAuthenticated
    {
        get { return true; }
    }

    public string Name
    {
        get { return ticket.Name; }
    }

    public string Email
    {
        get
        {
        string[] data = ticket.UserData.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
        if (data.Length > 0)
            return data[0];
        else
            return "";
        }
    }

    public string[] Roles
    {
        get
        {
            string[] data = ticket.UserData.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            if (data.Length > 1)
            {
                string[] roles = data[1].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                return roles;
            }
            else
                return new string[] { "" };
        }
    }

}

CustomPrincipal class, having CustomIdentity

public class GCustomPrincipal2 : IPrincipal
{
    private GCustomIdentity _identity;
    private string[] _roles;
    public bool IsInRole(string role)
    {
        return Array.BinarySearch(_roles, role) >= 0 ? true : false;
    }
    public IIdentity Identity
    {
        get
        {
            return this._identity;
        }
        set
        {
            this._identity = (GCustomIdentity)value;
        }
    }

    public GCustomPrincipal2(IIdentity identity, string[] roles)
    {
        this._identity = (GCustomIdentity)identity;
        _roles = new string[roles.Length];
        roles.CopyTo(_roles, 0);
        Array.Sort(_roles);
    }

    // Checks whether a principal is in any of the specified set of roles
    public bool IsInAnyRoles(params string[] roles1)
    {
        foreach (string searchrole in _roles)
        {
            if (Array.BinarySearch(roles1, searchrole) > 0)
                return true;
        }
        return false;
    }
}

Global.asax –> PostAuthenticateRequest

GCustomIdentity id = new GCustomIdentity(authTicket);
string[] roles = id.Roles;
GCustomPrincipal2 principal = new GCustomPrincipal2(id, roles);
Context.User = principal;
System.Threading.Thread.CurrentPrincipal = HttpContext.Current.User;

Few other useful facts.

1) After encryption the maximum FormsAuthentication cookie size is 4096 bytes. If the size is exceeded, the cookie won’t be send to browser.

2) FormsAuthenticationTicket info can’t be updated. For cases, when certain user profile data has changed or role info has changed just re-create the ticket and add it as a value to the same Authentication cookie.

3) The FormsAuthenticationTicket is encrypted and digitally signed before it is added to the cookie and sent to browser. On the server,just the ticket is verified and not the cookie.

4) With non-persistent cookie, if the ticket has expired and not the cookie, FormsAuthentication will still redirect user to login page. The user needs to login/authenticate again.

5) A user is authenticated only once per request. Hence during login,after successful authentication, Response.Redirect makes a brand new request and the request comes in as authenticated.

That’s all for now. Thanks for reading.

Advertisements
Posted in General ASP.Net C# | Leave a comment

Working extensively with Roles in Webforms.

The example below have different scenarios with roles, at various core levels, like –
Role based functionalities in a given page
Role based pages
Role based folders
Role based UserControls

To begin with, I created a simple forms-based website which uses the in-built membership api to work with roles. Using the WSAT (Website Administration Tool), I created the following roles —  Admin, Manager, Owner, Support, user.  My test website accepts authenticated users, belonging in one of the above groups. An example, as how the user “Tom” belongs to 2 roles — Manager as well as Owner.

tom
web.config

<authentication mode="Forms">
    <forms cookieless="UseCookies" defaultUrl="~/HomePage.aspx" loginUrl="LoginStuff/login.aspx" enableCrossAppRedirects="false" protection="All" timeout="30">
    </forms>
</authentication>
<membership defaultProvider="GMemberShipProvider" >
    <providers>
        <add name="GMemberShipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Gconnectionstring" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="3" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression=""/>
    </providers>
</membership>
<roleManager enabled="true" cacheRolesInCookie="true" cookieName="TROLES" defaultProvider="GRoleProvider">
    <providers>
        <add connectionStringName="Gconnectionstring" applicationName="/" name="Demo_RoleProvider" type="System.Web.Security.SqlRoleProvider, System.Web,  Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
    </providers>
</roleManager>

<authorization>
  <allow roles="Admin,Manager,Owner,user,Support"/>
  <deny users = "*"/>
</authorization>

If you see the “Solution Explorer”, I have created various folders like —  AdminStuff, PublicStuff, RoleSensitiveStuff, RoleSensitiveUserControls.

img1
1) AdminStuff –>  Only users under “Admin” role can access any page under it. Since I know this upfront, I just added a web.config in that folder, which makes sure that only admin roles can access it. Hence on a folder level, applying roles is quite easy.
<?xml version=”1.0″?>
<configuration>
    <system.web>
      <authorization>
        <allow roles=”Admin”/>
        <deny users=”*” />
      </authorization>
    </system.web>
</configuration>

2) Very similar, I have another folder called “PublicStuff”. Any page/contents under that folder does not need any kind of authentication. You type-in that aspx page name in browser and that page shows up. Again, since this is known during design phase, a simple web.config in that folder will take care of it.
<?xml version=”1.0″?>
<configuration>
    <system.web>
      <authorization>
        <allow users=”*”/>
      </authorization>
    </system.web>
</configuration>

3) Now consider the folder “RoleSensitiveStuff”.  Here we would protect 2 kind of things.
3.1) -> Any aspx page which we know, can be accessed only by certain roles.
3.2) -> Inside a given aspx page, certain functionalities/actions which are role-based and NOT the complete page. 

3.1) First let’s see — how to work with an aspx page which is totally role dependent. Here’s my ManagerOnlyPage.aspx. Only authenticated users belonging to “manager” role can access it. If any other user tries to load that page, they get redirected to “UnAuthorized.aspx”. I used a base page class to help me out here. My ManagerOnlyPage.aspx is derived from a BasePage class. In the basePage class, I check, if the incoming authenticated used belongs to a certain role. If yes, render that page which is asked for — else redirect him to “UnAuthorized.aspx”.The advantage of using a base page class here is, if in future, I need to add more such role dependent pages, I would add/extend all the role related logic at one central place.
here’s my base page class

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if (this.Page.ToString().ToLower().Contains("manageronlypage_aspx"))
    {
        //Easy way to detect and redirect
        if (!Roles.IsUserInRole(RoleName.Manager.ToString()))
            Context.Response.Redirect("~/UnAuthorized.aspx");
           
        //Another more generic way of doing the same thing
        if (!IsAuthorizedToView())
            Context.Response.Redirect("~/UnAuthorized.aspx");

        return;
    }        
}

private bool IsAuthorizedToView()
{
    DataTable dt = DataLayer.DL_GetRolesByAction("ManagerOnlyPage");
    foreach (DataRow dr in dt.Rows)
    {
        if (Roles.IsUserInRole(dr["RoleName"].ToString())) return true;
    }
    return false;
}

In the base page class, there are 2 ways to redirect the user. In the first method, I just check if the user is in “Manager” role and then redirect appropriately. The second method is little different. Currently my page “ManagerOnlyPage.aspx” should be accessible by managers only. Say in future I may want to make it accessible for “Admin” role too. In order to do that, without modifying any code, I needed to create a mapping table in Membership database, which maps roles with do-able functionalities/actions. Then it gets dynamic, so in future, if I want to add “Admin” role , which can also see that page, all I need to do is add another record in DB and the logic will take care of it.

mapping

public static DataTable DL_GetRolesByAction(string _ActionName)
{
SqlDataReader reader = null;
DataTable dt = new DataTable();
using (SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["Gconnectionstring"].ConnectionString))
{
    cn.Open();
    SqlCommand cmd = new SqlCommand();
    cmd.CommandText = "GetRolesByAction";
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.AddWithValue("@ActionName", _ActionName);
    cmd.Connection = cn;
    reader = cmd.ExecuteReader();
    dt.Load(reader);
}
return dt;
}

3.2) In RoleSensitiveFunctionality.aspx page , certain functionalities/actions are role dependent and not the complete page. I added the role dependant functionalities under different panels, and then based on my mapping table in membership database, I show/hide appropriate functionality/Action.

<asp:panel ID="Panel1" runat="server">
    Action A1: <asp:Button ID="btnActionA1" runat="server" Text="Owner Only" /><br /><br />
</asp:panel>
<asp:panel ID="Panel2" runat="server">
    Action A2:<asp:button ID="btnActionA2" runat="server" text="Manager Only" /><br />
</asp:panel>
<asp:panel ID="Panel3" runat="server">
    Action A3:<asp:button ID="btnActionA3" runat="server" text="Manager Or Admin" /><br />
</asp:panel>
<asp:panel ID="Panel4" runat="server">
        Action A4:<asp:button ID="btnActionA4" runat="server" text="Either User Or Owner" /><br />
</asp:panel> 
public partial class RoleSensitiveStuff_RoleSensitiveFunctionality : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        ShowHideUI();
    }
}

public void ShowHideUI()
{
    Panel1.Visible = false;
    DataTable dt = DataLayer.DL_GetRolesByAction("A1");
    foreach (DataRow dr in dt.Rows)
    {
        Panel1.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel1.Visible) break;
    }

    Panel2.Visible = false;
    dt = DataLayer.DL_GetRolesByAction("A2");
    foreach (DataRow dr in dt.Rows)
    {
        Panel2.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel2.Visible) break;
    }

    Panel3.Visible = false;
    dt = DataLayer.DL_GetRolesByAction("A3");
    foreach (DataRow dr in dt.Rows)
    {
        Panel3.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel3.Visible) break;
    }

    Panel4.Visible = false;
    dt = DataLayer.DL_GetRolesByAction("A4");
    foreach (DataRow dr in dt.Rows)
    {
        Panel4.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel4.Visible) break;
    }
         
}
}

4) Lastly the folder “RoleSensitiveUserControls”.  If certain chunk of role-dependent functionality (say any menu) is repeating across many pages, it’s best to put that in a user control and add all role based logic in that UserControl. This way, the UserControl itself takes care of roles in one central place and the parent page don’t have to worry on that during re-use.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SubMenu1.ascx.cs" Inherits="RoleSensitiveUserControls_SubMenu1" %>
<ul>
<li>L1:<asp:LinkButton ID="LinkButton1" runat="server">Manager Only</asp:LinkButton></li>
<li>L2:<asp:LinkButton ID="LinkButton2" runat="server">LinkButton</asp:LinkButton></li>
<li>L3:<asp:LinkButton ID="LinkButton3" runat="server">LinkButton</asp:LinkButton></li>
<li>L4:<asp:LinkButton ID="LinkButton4" runat="server">LinkButton</asp:LinkButton></li>
</ul>

public partial class RoleSensitiveUserControls_SubMenu1 : System.Web.UI.UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            ShowHideUI();
        }
    }
    public void ShowHideUI()
    {
        LinkButton1.Visible = false;
        DataTable dt = DataLayer.DL_GetRolesByAction("L1");
        foreach (DataRow dr in dt.Rows)
        {
            LinkButton1.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
            if (LinkButton1.Visible) break;
        }
    }
}

Thanks for reading.

Posted in General ASP.Net C# | Tagged , | Leave a comment

Few random development Tips-N-Tricks-7

Some scenarios with Container.DataItem.
Container.DataItem represents each row of the underlying Datasource of any Databound Control. Below are few examples, as how this helps.

1) Using Container.DataItem to set css of any row in any Databound control.
Below here it sets a different class for the last row in a Repeater.

<asp:Repeater ID="rpt1" runat="server">
<ItemTemplate>
  ccc <%# ((DataRowView)(Container.DataItem)).DataView.Count %>
  <br />
  dddd <%# Container.ItemIndex%>
  <br />
  <li class='<%#(Container.ItemIndex == (((DataRowView)(Container.DataItem)).DataView.Count - 1)) ? "last" : "" %>' > 
             <%# ((DataRowView)Container.DataItem)["product_name"]%> 
  </li>
</ItemTemplate>
</asp:Repeater>

2) Using Container.DataItem to set alternate row css.

<ItemTemplate>
<DIV id="r11" runat="server" class='<%#(Container.DataItemIndex % 2 == 0) ? "class1" : "class2" %>'>
</ItemTemplate>

3) Using Container.DataItem to bind any Databound control to an array, say ArrayList.

<asp:Repeater ID="rpt1" runat="server">
<ItemTemplate>
    <%# Container.DataItem %>
    <br />
</ItemTemplate>
</asp:Repeater>

protected void Page_Load(object sender, EventArgs e)
{
    ArrayList al = new ArrayList();
    al.Add("Data1");
    al.Add("Data2");
    al.Add("Data3");
    rpt1.DataSource = al;
    rpt1.DataBind();
}

4) Using Container.DataItem to bind any nested control — say Repeater inside a ListView

<asp:ListView runat="server" ID="lv1" >
<LayoutTemplate>
<table>
    <tr id="itemPlaceholder" runat="server"></tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<table>
<tr class='<%# Convert.ToBoolean(Container.DataItemIndex % 2) ? "RowOdd" : "RowEven" %>' >
    <td ><%#Eval("CategoryID", "{0:d}")%></td>
    <td ><%# Eval("CategoryName")%></td>
    <td ><%# Eval("Description")%></td>
</tr>
</table>
<asp:Repeater ID="rptProducts" runat="server" DataSource='<%# ((DataRowView)Container.DataItem).Row.GetChildRows("myrelation") %>'>
    <ItemTemplate>
    <tr >
    <td><%# DataBinder.Eval(Container.DataItem, "[\"ProductID\"]")%></td>
    <td><%# DataBinder.Eval(Container.DataItem, "[\"ProductName\"]")%></td>
    <td><%# DataBinder.Eval(Container.DataItem, "[\"QuantityPerUnit\"]")%></td>
    <td><%# DataBinder.Eval(Container.DataItem, "[\"UnitPrice\"]")%></td>
    </tr>
    </ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:ListView>

Thanks for reading.

Posted in Tips-n-Tricks | 1 Comment

Crystal Reports: Dynamic data – Static schema

With crystal reports, the design of the report is strongly tied to underlying table’s schema. The schema can not be attached to the report or altered during runtime. The crystal report needs to know the table structure , field names to display, statically. Then during runtime , those fields can be populated with data from any source — say a  table (having same schema) residing in database, or objects like DataSet/DataTable (having same structure/schema) or from any .xsd file, or whatever available options are. The point is, the data can be fetched from any source, — only the structure/names of table/fields (ie schema) needs to be exact-match with the report’s static schema, finalized during report’s design phase. Hence things like, binding a dynamically generated dataset (having dynamic field-names), to a blank crystal report is not possible. Below are 2 examples, one with DataTable and another with a regular table present in SQL Server database. In both the cases, the report schema being same, when I switch the environment from ‘dev’ to ‘test’ and vice-versa. the report fetches data from the corresponding database/source.

With DataTable

protected void btnTest_Click(object sender, EventArgs e)
{
    getReport(false);
}
protected void btnDev_Click(object sender, EventArgs e)
{
    getReport(true);
}
public void getReport(bool nDevEnv)
{
    ReportDocument cryRpt = new ReportDocument();
    DataTable dt = getAllOrders(nDevEnv);
    cryRpt.Load(Server.MapPath("~/MyReports/CrystalReportDevTest.rpt"));
    cryRpt.SetDataSource(dt);
    CrystalReportViewer1.ReportSource = cryRpt;
    CrystalReportViewer1.RefreshReport();
}
public DataTable getAllOrders(bool nDevEnv)
{
 string sqlCon = "";
 sqlCon = nDevEnv ? "DEV CONNECTION STRING" :
                    "TEST CONNECTION STRING";
 using (SqlConnection c = new SqlConnection(sqlCon))
 {
    c.Open();
    using (SqlDataAdapter a = new SqlDataAdapter("SELECT Product_name,Product_price FROM Product", c))
    {
        DataTable t = new DataTable();
        a.Fill(t);
        return t;
    }
 }
}

With Database

protected void btnTest_Click(object sender, EventArgs e)
{
    GenerateReport(false);
}
protected void btnDev_Click(object sender, EventArgs e)
{
    GenerateReport(true);
}
private void GenerateReport(bool IsDev)
{
    ReportDocument cryRpt = new ReportDocument();
    cryRpt.Load(Server.MapPath("~/MyReports/CrystalReportDevTestDB.rpt"));
    ConnectToEnv(cryRpt, IsDev);
    CrystalReportViewer1.ReportSource = cryRpt;
    CrystalReportViewer1.RefreshReport();
}
private ReportDocument ConnectToEnv(ReportDocument cryRpt, bool IsDev)
{
    TableLogOnInfos crtableLogoninfos = new TableLogOnInfos();
    TableLogOnInfo crtableLogoninfo = new TableLogOnInfo();
    ConnectionInfo crConnectionInfo = new ConnectionInfo();
    Tables CrTables;

    if (IsDev)
    {
        crConnectionInfo.ServerName = "DEV Server name";
        crConnectionInfo.DatabaseName = "DEV DB Name";
        crConnectionInfo.UserID = "gTest1";
        crConnectionInfo.Password = "pwd1";
    }
    else
    {
        crConnectionInfo.ServerName = "TEST Server name";
        crConnectionInfo.DatabaseName = "TEST DB Name";
        crConnectionInfo.UserID = "gTest2";
        crConnectionInfo.Password = "pwd1";
    }
    CrTables = cryRpt.Database.Tables;

    foreach (CrystalDecisions.CrystalReports.Engine.Table CrTable in CrTables)
    {
        crtableLogoninfo = CrTable.LogOnInfo;
        crtableLogoninfo.ConnectionInfo = crConnectionInfo;
        CrTable.ApplyLogOnInfo(crtableLogoninfo);
    }
    return cryRpt;
}

Thanks for reading.

Posted in Crystal Reports | Leave a comment

ListView with multiple Checkboxes/RadioButtons.

I had a little struggle working with multiple checkboxes/radiobuttons inside a ListView. For Checkboxes inside the ListView, I needed to associate multiple column values from the underlying DataTable, as part of the value attribute. These values again needed to be available at client end as well as on server. For the server part, I obtained these values from DataKeyNames property of the container ListView. I had to pre-populate the checkbox, alter (check/uncheck the checkboxes) and then do a postback. The example below uses HtmlInputCheckBox in ItemTemplate of the ListView. Everything’s same for radiobuttons too, except, I had to handle the grouping issue with radio buttons. Namely, when multiple radio buttons are a part of any databound control, since the name attribute is rendered unique — mutually-exclusive property won’t work.There are many existing approaches with JQuery/custom RadioButton, but I personally found dynamically generating the radio buttons from code-behind, is the simplest and solid. For all the radiobuttons which have same groupname, are kept in the same ItemTemplate, to take care of the grouping bug.

Here’s the datatable, for the ListView binding.

img1
First — CheckBox & ListView. Here’s the output page screen-shot.

CheckBoxOutput

//markup

<head runat="server">
<title></title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.js" type="text/javascript"></script>
</head>
<script type="text/javascript">
$(document).ready(function () {
    $('input[type=checkbox]').click(function () {
        if ($(this)[0].checked == true) {
            var _temp =  $(this)[0].value;
        }
    });
});
</script>
<asp:ListView ID="lv1" GroupItemCount="3" runat="server" DataKeyNames="id1,id2" onitemdatabound="lv1_ItemDataBound" >
<LayoutTemplate>
<fieldset>
    <legend>MyGroups</legend>
    &nbsp;
    <asp:PlaceHolder runat="server"  ID="groupPlaceholder"></asp:PlaceHolder>
</fieldset>
</LayoutTemplate>
<GroupTemplate>
<fieldset>
    <legend>Individual Group</legend>
    <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
</fieldset>    
</GroupTemplate>
<GroupSeparatorTemplate>
&nbsp;
</GroupSeparatorTemplate>
<EmptyDataTemplate>No records present.</EmptyDataTemplate>
<ItemTemplate>
&nbsp;&nbsp;
<input id="c1"  runat="server" value='<%# Eval("id1")+","+Eval("id2") %>' type="checkbox" /><%# Eval("Text1") %>
</ItemTemplate>  
</asp:ListView>
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
        BindListView();
}

private void BindListView()
{
    lv1.DataSource = GetData();
    lv1.DataBind();
}

private DataTable GetData()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("id1", System.Type.GetType("System.Int32"));
    dt.Columns.Add("id2", System.Type.GetType("System.Int32"));
    dt.Columns.Add("Text1", System.Type.GetType("System.String"));
    dt.Columns.Add("Text2", System.Type.GetType("System.String"));
    int k = 0;
    string strTemp = "grp1";
    for (int i = 0; i < 12; i++)
    {
        DataRow dr = dt.NewRow();
        dr["id1"] = i + 13;
        dr["id2"] = i + 14;
        dr["Text1"] = "SomeText1-" + i.ToString();
        dr["Text2"] = strTemp;
        k++;
        if (k == 3)
        {
            strTemp = "grp1" + i.ToString();
            k = 0;
        }
        dt.Rows.Add(dr);
    }
    return dt;
}

private void FetchListViewRecords()
{
    foreach(ListViewItem _item in lv1.Items)
    {
        HtmlInputCheckBox c1 = (HtmlInputCheckBox)_item.FindControl("c1");
        if (c1.Checked)
        {
            //Get the individual values from DataKeyNames array
            //instead of checkbox value.
            string val1 = lv1.DataKeys[_item.DataItemIndex].Values[0].ToString();
            string val2 = lv1.DataKeys[_item.DataItemIndex].Values[1].ToString();
        }
    }
}

protected void lv1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    if (e.Item.ItemType == ListViewItemType.DataItem)
    {
        ListViewDataItem dataItem = (ListViewDataItem)e.Item;
        DataRowView drv = (DataRowView)dataItem.DataItem;
        HtmlInputCheckBox c1 = (HtmlInputCheckBox)e.Item.FindControl("c1");
        if (drv["id2"].ToString() == "16")
        {
            c1.Checked = true;
        }
    }
}

protected void Button1_Click(object sender, EventArgs e)
{
    FetchListViewRecords();
}

Next — RadioButtons & ListView.

RadioOutput

<script type="text/javascript">
$(document).ready(function () {
    $('input[type=radio]').click(function () {
        if ($(this)[0].checked == true) {
            var _temp = $(this)[0].value;
        }
    });
});
</script>
<asp:ListView ID="lv1" GroupItemCount="3" runat="server" DataKeyNames="id1,id2" onitemdatabound="lv1_ItemDataBound" >
<LayoutTemplate>
<fieldset>
    <legend>MyGroups</legend>
    &nbsp;
    <asp:PlaceHolder runat="server"  ID="groupPlaceholder"></asp:PlaceHolder>
</fieldset>
</LayoutTemplate>
<GroupTemplate>
<fieldset>
    <legend>Mutually Exclusive individual Group</legend>
    <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
</fieldset>    
</GroupTemplate>
<GroupSeparatorTemplate>
&nbsp;
</GroupSeparatorTemplate>
<EmptyDataTemplate>No records present.</EmptyDataTemplate>
<ItemTemplate>
&nbsp;&nbsp;
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</ItemTemplate>  
</asp:ListView>
private string CurrGrpName = String.Empty;
DataTable dt3 = new DataTable();

protected void Page_Load(object sender, EventArgs e)
{
    BindListView();
}

private void BindListView()
{
    dt3 = GetData();
    lv1.DataSource = dt3;
    lv1.DataBind();
}
private DataTable GetData()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("id1", System.Type.GetType("System.Int32"));
    dt.Columns.Add("id2", System.Type.GetType("System.Int32"));
    dt.Columns.Add("Text1", System.Type.GetType("System.String"));
    dt.Columns.Add("GrpInfo", System.Type.GetType("System.String"));
    int k = 0;
    string strTemp = "grp1";
    for (int i = 0; i < 12; i++)
    {
        DataRow dr = dt.NewRow();
        dr["id1"] = i + 13;
        dr["id2"] = i + 14;
        dr["Text1"] = "SomeText1-" + i.ToString();
        dr["GrpInfo"] = strTemp;
        k++;
        if (k == 3)
        {
            strTemp = "grp1" + i.ToString();
            k = 0;
        }
        dt.Rows.Add(dr);
    }
    return dt;
}
private void FetchListViewRecords()
{
    foreach (ListViewItem _item in lv1.Items)
    {
        PlaceHolder c1 = (PlaceHolder)_item.FindControl("PlaceHolder1");
        foreach (Control c11 in c1.Controls)
        {
            //Interested only in RadioButton
            if (c11.GetType().Name == "RadioButton")
            {
                RadioButton rb = (RadioButton)c11;
                if (rb.Checked)
                {
                    string val0 = rb.Text;
                    //Get the individual values from DataKeyNames array
                    string val1 = lv1.DataKeys[_item.DataItemIndex].Values[0].ToString();
                    string val2 = lv1.DataKeys[_item.DataItemIndex].Values[1].ToString();
                }
            }
        }
    }
}
protected void lv1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    if (e.Item.ItemType == ListViewItemType.DataItem)
    {
        ListViewDataItem dataItem = (ListViewDataItem)e.Item;
        DataRowView drv = (DataRowView)dataItem.DataItem;
        if (CurrGrpName != drv["GrpInfo"].ToString())
        {
            PlaceHolder PlaceHolder1 = (PlaceHolder)e.Item.FindControl("PlaceHolder1");
            //For all records which have same grpInfo and hence making
            //those radio-buttons to have same groupname
            //are placed in the same ItemTemplate.
            DataTable dt2 = dt3.Select("GrpInfo = '" + drv["GrpInfo"].ToString() + "'").CopyToDataTable();
            foreach (DataRow dr in dt2.Rows)
            {
                RadioButton rb = new RadioButton();
                rb.ID = "i" + dr["id1"].ToString();
                rb.GroupName = dr["GrpInfo"].ToString();
                rb.Text = dr["Text1"].ToString();
                rb.Attributes.Add("value", dr["id1"].ToString() + "," + dr["id2"].ToString());
                PlaceHolder1.Controls.Add(rb);
                PlaceHolder1.Controls.Add(new LiteralControl("&nbsp;"));
            }
            CurrGrpName = drv["GrpInfo"].ToString();
        }
    }
}
protected void Button1_Click(object sender, EventArgs e)
{
    FetchListViewRecords();
}

That’s all about it. Thanks for reading.

Posted in General ASP.Net C# | Tagged , , | Leave a comment

iTextSharp — few C# examples.

iTextSharp is open source PDF solution. In most of the examples below, I tried to alter,copy a template PDF and then save it into a brand new output PDF file. It’s easy to work with PDFs , when we have a basic template (created externally using Adobe/OpenOffice) in place, —  instead of generating a new PDF from scratch. For any data which needs to be modified, I do it in the web page and then save the modified data/results to a pdf file. This prevents the need, for user to modify the pdf form fields.
http://www.itextpdf.com/book/chapter.php?id=1
This web link was the basic source, for all my scenarios. (Note: The examples in iTextSharp site are in java)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using System.IO;
using iTextSharp.text.pdf;
using System.Data;
using System.Text;
using iTextSharp.text.pdf.parser;
using System.util.collections;
using iTextSharp.text;
using System.Net.Mail;

public partial class PDFScenarios : System.Web.UI.Page
{
public string P_InputStream = "~/MyPDFTemplates/ex1.pdf";
public string P_InputStream2 = "~/MyPDFTemplates/ContactInfo.pdf";
public string P_InputStream3 = "~/MyPDFTemplates/MulPages.pdf";
public string P_InputStream4 = "~/MyPDFTemplates/CompanyLetterHead.pdf";
public string P_OutputStream = "~/MyPDFOutputs/ex1_1.pdf";

 

//Read all 'Form values/keys' from an existing multi-page PDF document
public void ReadPDFformDataPageWise()
{
PdfReader reader = new PdfReader(Server.MapPath(P_InputStream3));
AcroFields form = reader.AcroFields;
try
{
for (int page = 1; page <= reader.NumberOfPages; page++)
{
    foreach (KeyValuePair<string, AcroFields.Item> kvp in form.Fields)
    {
        switch (form.GetFieldType(kvp.Key))
        {
            case AcroFields.FIELD_TYPE_CHECKBOX:
            case AcroFields.FIELD_TYPE_COMBO:
            case AcroFields.FIELD_TYPE_LIST:
            case AcroFields.FIELD_TYPE_RADIOBUTTON:
            case AcroFields.FIELD_TYPE_NONE:
            case AcroFields.FIELD_TYPE_PUSHBUTTON:
            case AcroFields.FIELD_TYPE_SIGNATURE:
            case AcroFields.FIELD_TYPE_TEXT:
                int fileType = form.GetFieldType(kvp.Key);
                string fieldValue = form.GetField(kvp.Key);
                string translatedFileName = form.GetTranslatedFieldName(kvp.Key);
                break;
        }
    }
}
}
catch
{
}
finally
{
    reader.Close();
}
}
//Read and alter form values for only second and 
//third page of an existing multi page PDF doc.
//Save the changes in a brand new pdf file.
public void ReadAlterPDFformDataInSelectedPages()
{
PdfReader reader = new PdfReader(Server.MapPath(P_InputStream3));
reader.SelectPages("1-2"); //Work with only page# 1 & 2
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(Server.MapPath(P_OutputStream), FileMode.Create)))
{
    AcroFields form = stamper.AcroFields;
    var fieldKeys = form.Fields.Keys;
    foreach (string fieldKey in fieldKeys)
    {
        //Replace Address Form field with my custom data
        if (fieldKey.Contains("Address"))
        {
            form.SetField(fieldKey, "MyCustomAddress");
        }
    }
    //The below will make sure the fields are not editable in
    //the output PDF.
    stamper.FormFlattening = true;
}
}
//Extract text from an existing PDF's second page.
private string ExtractText()
{
    PdfReader reader = new PdfReader(Server.MapPath(P_InputStream3));
    string txt =  PdfTextExtractor.GetTextFromPage(reader, 2, new LocationTextExtractionStrategy());
    return txt;
}
//Create a brand new PDF from scratch and without a template
private void CreatePDFNoTemplate()
{
    Document pdfDoc = new Document();
    PdfWriter writer = PdfWriter.GetInstance(pdfDoc, new FileStream(Server.MapPath(P_OutputStream), FileMode.OpenOrCreate));

    pdfDoc.Open();
    pdfDoc.Add(new Paragraph("Some data"));
    PdfContentByte cb = writer.DirectContent;
    cb.MoveTo(pdfDoc.PageSize.Width / 2, pdfDoc.PageSize.Height / 2);
    cb.LineTo(pdfDoc.PageSize.Width / 2, pdfDoc.PageSize.Height);
    cb.Stroke();

    pdfDoc.Close(); 
}
private void fillPDFForm()
{
    string formFile = Server.MapPath(P_InputStream);
    string newFile = Server.MapPath(P_OutputStream);
    PdfReader reader = new PdfReader(formFile);
    using (PdfStamper stamper = new PdfStamper(reader, new FileStream(newFile, FileMode.Create)))
    {
        AcroFields fields = stamper.AcroFields;

        // set form fields
        fields.SetField("name", "John Doe");
        fields.SetField("address", "xxxxx, yyyy");
        fields.SetField("postal_code", "12345");
        fields.SetField("email", "johndoe@xxx.com");

        // flatten form fields and close document
        stamper.FormFlattening = true;
        stamper.Close();
    }
}
//Helper functions
private void SendEmail(MemoryStream ms)
{
    MailAddress _From = new MailAddress("XXX@domain.com");
    MailAddress _To = new MailAddress("YYY@a.com"); 
    MailMessage email = new MailMessage(_From, _To); 
    Attachment attach = new Attachment(ms,  new System.Net.Mime.ContentType("application/pdf")); 
    email.Attachments.Add(attach);  
    SmtpClient mailSender = new SmtpClient("Gmail-Server"); 
    mailSender.Send(email);  
}

private void DownloadAsPDF(MemoryStream ms)
{
    Response.Clear();
    Response.ClearContent();
    Response.ClearHeaders();
    Response.ContentType = "application/pdf";
    Response.AppendHeader("Content-Disposition", "attachment;filename=abc.pdf");
    Response.OutputStream.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
    Response.OutputStream.Flush();
    Response.OutputStream.Close();
    Response.End();
    ms.Close();
}
//Working with Memory Stream and PDF
public void CreatePDFFromMemoryStream()
{
    //(1)using PDFWriter
    Document doc = new Document();
    MemoryStream memoryStream = new MemoryStream();
    PdfWriter writer = PdfWriter.GetInstance(doc, memoryStream);
    doc.Open();
    doc.Add(new Paragraph("Some Text"));
    writer.CloseStream = false;
    doc.Close();
    //Get the pointer to the beginning of the stream. 
    memoryStream.Position = 0;
    //You may use this PDF in memorystream to send as an attachment in an email
    //OR download as a PDF
    SendEmail(memoryStream);
    DownloadAsPDF(memoryStream);

    //(2)Another way using PdfStamper
    PdfReader reader = new PdfReader(Server.MapPath(P_InputStream2));
    using (MemoryStream ms = new MemoryStream())
    {
        PdfStamper stamper = new PdfStamper(reader, ms);
        AcroFields fields = stamper.AcroFields;
        fields.SetField("SomeField", "MyValueFromDB");
        stamper.FormFlattening = true;
        stamper.Close();
        SendEmail(ms);
    }
}
//Burst-- Make each page of an existing multi-page PDF document 
//as another brand new PDF document
private void PDFBurst()
{
    string pdfTemplatePath = Server.MapPath(P_InputStream3);
    PdfReader reader = new PdfReader(pdfTemplatePath);
    //PdfCopy copy;
    PdfSmartCopy copy;
    for (int i = 1; i < reader.NumberOfPages; i++)
    {
        Document d1 = new Document();
        copy = new PdfSmartCopy(d1, new FileStream(Server.MapPath(P_OutputStream).Replace(".pdf", i.ToString() + ".pdf"), FileMode.Create));
        d1.Open();
        copy.AddPage(copy.GetImportedPage(reader, i));
        d1.Close();
    }
}

 

//Copy a set of form fields from an existing PDF template/doc
//and keep appending to a brand new PDF file.
//The copied set of fields will have different values.
private void AppendSetOfFormFields()
{
    PdfCopyFields _copy = new PdfCopyFields(new FileStream(Server.MapPath(P_OutputStream), FileMode.Create));
    _copy.AddDocument(new PdfReader(a1("1")));
    _copy.AddDocument(new PdfReader(a1("2")));
    _copy.AddDocument(new PdfReader(new FileStream(Server.MapPath("~/MyPDFTemplates/Myaspx.pdf"), FileMode.Open)));
    _copy.Close();
}
//ConcatenateForms
private byte[] a1(string _ToAppend)
{
    using (var existingFileStream = new FileStream(Server.MapPath(P_InputStream), FileMode.Open))
    using (MemoryStream stream = new MemoryStream())
    {
        // Open existing PDF
        var pdfReader = new PdfReader(existingFileStream);
        var stamper = new PdfStamper(pdfReader, stream);
        var form = stamper.AcroFields;
        var fieldKeys = form.Fields.Keys;

        foreach (string fieldKey in fieldKeys)
        {
            form.RenameField(fieldKey, fieldKey + _ToAppend);
        }
        // "Flatten" the form so it wont be editable/usable anymore
        stamper.FormFlattening = true;
        stamper.Close();
        pdfReader.Close();
        return stream.ToArray();
    }
}
//Working with Image
private void AddAnImage()
{
    using (var inputPdfStream = new FileStream(@"C:\MyInput.pdf", FileMode.Open))
    using (var inputImageStream = new FileStream(@"C:\img1.jpg", FileMode.Open))
    using (var outputPdfStream = new FileStream(@"C:\MyOutput.pdf", FileMode.Create))
    {
        PdfReader reader = new PdfReader(inputPdfStream);
        PdfStamper stamper = new PdfStamper(reader, outputPdfStream);
        PdfContentByte pdfContentByte = stamper.GetOverContent(1);
        var image = iTextSharp.text.Image.GetInstance(inputImageStream);
        image.SetAbsolutePosition(1, 1);
        pdfContentByte.AddImage(image);
        stamper.Close();
    }

}
//Add Company Letter-Head/Stationary to an existing pdf
private void AddCompanyStationary()
{
    PdfReader reader = new PdfReader(Server.MapPath(P_InputStream2));
    PdfReader s_reader = new PdfReader(Server.MapPath(P_InputStream4));

    using (PdfStamper stamper = new PdfStamper(reader, new FileStream(Server.MapPath(P_OutputStream), FileMode.Create)))
    {
        PdfImportedPage page = stamper.GetImportedPage(s_reader, 1);
        int n = reader.NumberOfPages;
        PdfContentByte background;
        for (int i = 1; i <= n; i++)
        {
            background = stamper.GetUnderContent(i);
            background.AddTemplate(page, 0, 0);
        }
        stamper.Close();
    }
}
//Create a new PDF document by copying only 2nd page from an existing PDF document.
//Also add a date on the top-left corner.
private void AddText()
{
    //Method 1:
    PdfReader reader = new PdfReader(Server.MapPath(P_InputStream3));
    using (Document document = new Document())
    {
        using (PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(Server.MapPath(P_OutputStream), FileMode.Create)))
        {
            document.Open();
            PdfContentByte cb = writer.DirectContent;
            PdfImportedPage page = writer.GetImportedPage(reader, 2);

            document.NewPage();
            cb.AddTemplate(page, 0, 0);
            document.Add(new Paragraph(DateTime.Now.ToShortDateString()));
            document.Close();
        }
    }
    //Method 2:
    PdfReader reader2 = new PdfReader(Server.MapPath(P_InputStream3));
    using (PdfStamper stamper = new PdfStamper(reader, new FileStream(Server.MapPath(P_OutputStream), FileMode.Create)))
    {
        stamper.RotateContents = false;
        PdfContentByte canvas = stamper.GetOverContent(2);
        ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(DateTime.Now.ToShortDateString()), 0, 0, 0);
        stamper.Close();
    }
}
//DataSheets: Copy 2 pages from one PDF to another brand new PDF.
//Also alter some form data
private void CreatePDFByCopy()
{
    using (Document document = new Document())
    {
        using (PdfSmartCopy copy = new PdfSmartCopy(document, new FileStream(Server.MapPath(P_OutputStream), FileMode.Create)))
        {
            document.Open();
            for (int i = 0; i < 2; ++i)
            {
                PdfReader reader = new PdfReader(AddDataSheets("Some Text" + i.ToString()));
                copy.AddPage(copy.GetImportedPage(reader, 1));
            }
        }
    }
}
public byte[] AddDataSheets(string _data)
{
    string pdfTemplatePath = Server.MapPath(P_InputStream2);
    PdfReader reader = new PdfReader(pdfTemplatePath);
    using (MemoryStream ms = new MemoryStream())
    {
        using (PdfStamper stamper = new PdfStamper(reader, ms))
        {
            AcroFields form = stamper.AcroFields;
            var fieldKeys = form.Fields.Keys;
            foreach (string fieldKey in fieldKeys)
            {
                //Change some data
                if (fieldKey.Contains("Address"))
                {
                    form.SetField(fieldKey, _data);
                }
            }        
            stamper.FormFlattening = true;
        }
        return ms.ToArray();
    }
}

iTextSharp is open source, however,– you may want to read this link below.
http://itextpdf.com/terms-of-use/
Thanks for reading.
[UPDATE] You may want to check this related post too.itextSharp Form Fields


Posted in External Open Source APIs | 12 Comments

Few random development Tips-N-Tricks-6

Usually, to bind a dropdownlist, when we need multiple values, as a part of it’s DataValueField, either we run a foreach loop on ‘Dropdownlist.Items.Add’ OR in the SQL level change the SELECT statement to concatenate multiple fields. This example below, uses DataTable->Expression, to bind multiple values to Dropdownlist.

private void BindMultipleColumns()
{
 DataTable dt = GetSelectOutputORspOutput();
 dt.Columns.Add("NewIdColumn", System.Type.GetType("System.String"));
 dt.Columns["NewIdColumn"].Expression = "ColName1+','+ColName2";
 dt.AcceptChanges();
 ddl1.DataSource = dt;
 ddl1.DataTextField = "ColName4";
 ddl1.DataValueField = "NewIdColumn";
 ddl1.DataBind();
}

Thanks for reading.

Posted in Tips-n-Tricks | Leave a comment