Showing posts with label Web Controls. Show all posts
Showing posts with label Web Controls. Show all posts

Tuesday, November 02, 2010

How to add detail textbox opon the check of a check box

Demo:
Example:
Markup:
<%@ Page Language="C#" %>
<html>   
<head id="Head1" runat="server">  
</head>
<body>
    <form id="form1" runat="server">
        <asp:FreeCheckBox runat="server" 
            ID="freeCheckBox" 
            DetailPlaceHolderId="phDetails" 
            Text="Are you disabled?" 
            DetailLabel="Please please provide details" />
        <asp:PlaceHolder runat="server" ID="phDetails" />
        <hr />
        <asp:Button runat="server" ID="btnSave" Text="Save" />
    </form>
</body>
</html>
Control:
public class FreeCheckBox : CheckBox
{
    private TextBox txtDetail;
    private Panel pnlDetail;
    private string script = @"
    function ShowHideDetailPanel(detailPanel, source) {            
        var panel = document.getElementById(detailPanel);
        if (panel == undefined) return;
        if (source.checked == true)
            panel.style.display = ""block"";
        else
            panel.style.display = ""none"";
    }
";
    public string DetailLabel { getset; }
    public string Details { getset; }
    public string DetailPlaceHolderId { getset; }
    public bool IsMultiline { getset; }
    public FreeCheckBox()
    {
        this.DetailPlaceHolderId = string.Empty;
        this.IsMultiline = false;
    }
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        this.CreateControlHeirarchy();
    }
    public void CreateControlHeirarchy()
    {
        this.pnlDetail = new Panel() { ID = string.Concat(this.ID, "_DetailPanel"), CssClass = "DetailPanel" };
        this.txtDetail = new TextBox() { ID = string.Concat(this.ID, "_TextBox"), 
                                            CssClass = "DetailTextBox", TextMode = TextBoxMode.MultiLine };
        if (this.IsMultiline) this.txtDetail.TextMode = TextBoxMode.MultiLine;
        this.pnlDetail.Controls.Add(new Label() { Text = this.DetailLabel, CssClass = "DetailLabel" });
        this.pnlDetail.Controls.Add(this.txtDetail);
        PlaceHolder phDetail = this.Page.FindControl(this.DetailPlaceHolderId) as PlaceHolder;
        Control parent = this.Parent;
        while (phDetail == null)
        {
            phDetail = parent.FindControl(this.DetailPlaceHolderId) as PlaceHolder;
            if (phDetail == null) parent = parent.Parent;
            if (parent == nullbreak;
        }
        if (phDetail != null) phDetail.Controls.Add(pnlDetail);
        this.Attributes.Add("onclick", 
                string.Format("javascript:ShowHideDetailPanel('{0}', this)", pnlDetail.ClientID));
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if (this.Checked)
            this.pnlDetail.Attributes.Add("style""display:block;");
        else
            this.pnlDetail.Attributes.Add("style""display:none;");
    }
    protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        bool value = base.LoadPostData(postDataKey, postCollection);
        if (this.Checked)
            this.Details = this.txtDetail.Text;
        return value;
    }
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        this.Page.ClientScript.RegisterClientScriptBlock(
                this.GetType(), this.GetType().Name, this.script, true);
    }
}

Sunday, October 17, 2010

Page.RegisterRequiresRaiseEvent

When you implement IPostBackEventHanlder RaisePostBackEvent method get automatically get called only if you postback the form with _EVENTTARGET equal to UniqueID of the web control [__doPostBack(‘ctrl001$mybutton1’,’’)].  But if you need to develop complex postback enable control which will invoke the RaisePostBackEvent as a response to custom __EVENTTRAGETs [__doPostBack(‘custom_target’)] not the UniqueID of the control, then you can register the complex postback enable control with Page.RegisterRequiresRaiseEvent

Example 1: This control postback with the __EVENTTARGET, which is equal UniqueID of the control. This picks up the RaisePostBackEvent method automatically. 
public class CustomButton : WebControlIPostBackEventHandler
{
    public event EventHandler Click;
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write(string.Format(
            @"<input type=""button"" name=""{0}"" id=""{1}"" 
                value=""Click"" onclick=""javascript:__doPostBack('{0}','')"" />",
            this.UniqueID, this.ClientID));
    }
    public void RaisePostBackEvent(string eventArgument)
    {
        if (this.Click != null)
            this.Click(thisnew EventArgs());
    }
}
Example 2: This control postback with the __EVENTTARGET which is NOT equal to UniqueID of the control. So RaisePostBackEvent will NOT get raised automatically. Thus we need to call Page.RegisterRequiresRaiseEvent explicitly. However this approach does not pick up the eventArgument from the postback call.
public class ComplexButton : WebControlIPostBackEventHandler
{
    public event EventHandler Click;
    public void RaisePostBackEvent(string eventArgument)
    {
        if (this.Click != null)
            this.Click(thisnew EventArgs());
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        this.Page.RegisterRequiresRaiseEvent(this);
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write("<div>");
        writer.Write(string.Format(
            @"<input type=""button"" name=""{0}"" id=""{1}"" 
                value=""Save"" onclick=""javascript:__doPostBack('{0}','')"" />",
            this.UniqueID + "$Name"this.ClientID + "_Name"));
        writer.Write(string.Format(
            @"<input type=""button"" name=""{0}"" id=""{1}"" 
                value=""Edit"" onclick=""javascript:__doPostBack('{0}','')"" />",
            this.UniqueID + "$Age"this.ClientID + "_Age"));
        writer.Write("</div>");
    }
}

Example 3: If you need to map different buttons inside the same web control to different event, it is better to use same name as the custom controls UniqueID for the __EVENTTARGET and use eventArgument to distinguish which button get clicked.
public class ComplexButton : WebControlIPostBackEventHandler
{
    public event EventHandler Save;
    public event EventHandler Edit;
    public void RaisePostBackEvent(string eventArgument)
    {
        if (string.IsNullOrEmpty(eventArgument)) return;
        if (eventArgument.Equals("Save"))
        {
            if (this.Save != null)
                this.Save(thisnew EventArgs());
        }
        else if (eventArgument.Equals("Edit"))
        {
            if (this.Edit != null)
                this.Edit(thisnew EventArgs());
        }
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write("<div>");
        writer.Write(string.Format(
            @"<input type=""button"" name=""{0}"" id=""{1}"" 
                    value=""Save"" onclick=""javascript:__doPostBack('{0}','Save')"" />",
            this.UniqueID, this.ClientID + "_Name"));
        writer.Write(string.Format(
            @"<input type=""button"" name=""{0}"" id=""{1}"" 
                    value=""Edit"" onclick=""javascript:__doPostBack('{0}','Edit')"" />",
            this.UniqueID, this.ClientID + "_Age"));
        writer.Write("</div>");
    }
}

Page RegisterRequiresPostBack method.

When you implement a custom control, only if you are implementing IPostBackDataHandler, the LoadPostData method get automatically get called only if you add an input control with the same name as the custom control’s UniqueID. If you don’t want to add a input control with a name equal to custom controls UniqueID property, then you can explicitly register custom control to invoke LoadPostData method by registering with RegisterRequiresPostBack(...) method.
Example 1: This control renders an input control with a name equal to custom controls UniqueID property.
public class CustomTextBox : WebControlIPostBackDataHandler
{
    public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        ///
        /// automatically invoked as we have a 
        /// html input control with a name equal to
        /// web controls UniqueID
        ///
        string text = postCollection[postDataKey];
        return true;
    }
    public void RaisePostDataChangedEvent()
    {
            
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write(string.Format("<input type=\"input\" name=\"{0}\" id=\"{1}\" />"this.UniqueID, this.ClientID));
    }
}
Example 2: This control does not render an input control with a name equal to custom controls UniqueID property. Thus we need RegisterRequiresPostBack call in the load method with the parameter of self.
public class ComplexControl : WebControlIPostBackDataHandler
{
    public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        ///
        /// will NOT get automatically invoked 
        /// because we dont have input control with name
        /// equal to web controls UniqueID
        /// THUS we need to RegisterRequiresPostBack call
        /// 
        string name = postCollection[this.UniqueID + "$Name"];
        string age = postCollection[this.UniqueID + "$Age"];
        return true;
    }
    public void RaisePostDataChangedEvent()
    {
 
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        this.Page.RegisterRequiresPostBack(this);
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write("<div>");
        writer.Write(string.Format("Name: <input type=\"input\" name=\"{0}\" id=\"{1}\" />",
                                        this.UniqueID + "$Name"this.ClientID + "_Name"));
        writer.Write(string.Format("Age: <input type=\"input\" name=\"{0}\" id=\"{1}\" />",
                                        this.UniqueID + "$Age"this.ClientID + "_Age"));
        writer.Write("</div>");
    }
}

Thursday, September 30, 2010

How to build an addable DropDownList (can add items using JavaScript )


Demo:

Sometimes asp.net developers find when they change the items collection in a DropDownList in the client side using JavaScripts in the very next postback they run in to Event Validation error. Yes, this is true, because any attacker can inject malicious items to the item list rather than the proper rendered list, they can break down your site. But the problem is there are some legitimate instances where we intentionally add items the DropDownList in the client side using JavaScripts.

This article provides a solution to add items at the client side without Event Validation errors. Idea is the handle item list in hidden field and mark the hidden field as the actual control. So in a postback, actually item list will not get validated but the hidden field get validated.
 No problem so far, but how we can merge newly added items with excising items? For this, by the time I render the control, I add comma separated list of items as the value of hidden field. Then on the event of adding new items to the list in the client side, inside the JavaScript it appends the newly added items to the hidden field value. So job is almost done, then what we all need to do is, get the hidden field value in the LoadPostBackData event and repopulate the item list. Vola job done.

Control itself it renders the item adding JavaScript event (ready made) so all you need to do is call the JavaScript function with three parameters 

Param1 - value of new item
Param2 - text of new item
Param3 - id of the DropDownList - ClientID
AddListItem(value, text, targetListId);

Markup:
<%@ Register Assembly="ActiveTest" Namespace="ActiveTest" TagPrefix="asp" %>
<%@ Page Language="C#" %>
<html>
<head runat="server">
    <script language="javascript" type="text/javascript">
        function AddItem() {
 
            var value = document.getElementById('<%=txtValue.ClientID %>').value;
            var text = document.getElementById('<%=txtText.ClientID %>').value;
            var targetListId = '<%=addlFreeDownDownList.ClientID %>';
 
            AddListItem(value, text, targetListId);
            return false;
        } 
       
    </script>
</head>
<body>
    <form id="form1" runat="server">
        Add Item - 
        Text: <asp:TextBox runat="server" ID="txtText" /> 
        Value: <asp:TextBox runat="server" ID="txtValue" />
        <asp:Button runat="server" ID="btnAdd" Text="Add" OnClientClick="javascript:return AddItem()" />
        <hr />
        <asp:AddableDropDownList runat="server" ID="addlFreeDownDownList">
            <asp:ListItem>Orange</asp:ListItem>
            <asp:ListItem>Blue</asp:ListItem>
            <asp:ListItem>Red</asp:ListItem>
            <asp:ListItem>Yellow</asp:ListItem>
            <asp:ListItem>Black</asp:ListItem>
        </asp:AddableDropDownList>
        <hr />
        <asp:Button runat="server" ID="btnSave" Text="Save" />
    </form>
</body>
</html>

Control:
public class AddableDropDownList : DropDownList
{
    private string script = @"
    function AddListItem(value, text, target) {
        var list = target + ""List"";
        var option = document.createElement(""option"");
        document.getElementById(list).options.add(option);
        option.text = text;
        option.value = value;
        var target = document.getElementById(target);
        if (target.value == """") target.value = value + ""=="" + text;
        else target.value = target.value + "",:,"" + value + ""=="" + text;
        return false;
    } 
";
    public string ListID
    {
        get
        {
            return this.ClientID + "List";
        }
    }
    protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        string items = postCollection[postDataKey];
        this.Items.Clear();
        if (string.IsNullOrEmpty(items)) return true;
        foreach (string item in Regex.Split(items, ",:,"))
        {
            string[] s = Regex.Split(item, "==");
            if (s.Length != 2)
                throw
                    new Exception("Invalid item the list, list item cannot have == or ,:, combinations");
            this.Items.Add(new ListItem(s[1], s[0]));
        }
        this.SelectedValue = postCollection[postDataKey + "List"];
        return true;
    }
    protected override void Render(HtmlTextWriter writer)
    {
        StringBuilder content = new StringBuilder();
        if (this.AutoPostBack)
        {
            StringBuilder script = new StringBuilder(this.Attributes["onchange"]);
            if (script.Length == 0) script.AppendFormat("javascript:{0}",
                            string.Format("__doPostBack('{0}','');"this.ClientID));
            else
            {
                if (!script.ToString().EndsWith(";")) script.Append(";");
                script.AppendFormat("__doPostBack('{0}','');"this.ClientID);
            }
            this.Attributes.Add("onchange", script.ToString());
        }
        StringBuilder b = new StringBuilder();
        HtmlTextWriter h = new HtmlTextWriter(new StringWriter(b));
        this.Attributes.Render(h);
        content.AppendFormat("<select id=\"{0}List\" name=\"{1}List\"{2}{3}>",
                this.ClientID, this.UniqueID,
                b.Length == 0 ? string.Empty : string.Format(" {0}", b.ToString()),
                !string.IsNullOrEmpty(this.CssClass) ?
                    string.Format(" class=\"{0}\""this.CssClass) : string.Empty);
        foreach (ListItem item in this.Items)
            content.AppendFormat("<option value=\"{0}\"{1}>{2}</option>",
                item.Value, item.Selected ? " selected=\"selected\"" : string.Empty, item.Text);
        content.Append("</select>");
        content.AppendFormat("<input type=\"hidden\" id=\"{0}\" name=\"{1}\"  value=\"{2}\" />",
                this.ClientID, this.UniqueID, this.GetValue());
        writer.Write(content.ToString());
    }
    private string GetValue()
    {
        StringBuilder value = new StringBuilder();
        foreach (ListItem item in this.Items)
        {
            if (value.Length != 0) value.Append(",:,");
            value.AppendFormat("{0}=={1}", item.Value, item.Text);
        }
        return value.ToString();
    }
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), this.GetType().Name, this.script, true);
    }
}

How to upload multiple files?

  1. Using FlistList control example
  2. Using third party jQuery + flash plugin example

How to add controls on a button click?

  1. Basic example
  2. Simple example - (Row of controls)
  3. Using DropDownList value example
  4. Using TextBox value example
  5. Full example - (Row of controls)

How to build a custom control?

  1. How to build a custom TextBox?
  2. How to build a templated control?
  3. How to implement a collection property?
  4. How to build a custom checkbox control?
  5. How to clone a control?
  6. How to build a resettable panel?
  7. How to build a directory browser? 

Tuesday, September 07, 2010

LoadViewState(...) method does not fire in a postback

Unless you have some values in the ViewState the LoadViewState(...) method will not get fired in a postback.
Example:
<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <script runat="server">
        public string Value
        {
            get { return (string)ViewState["Value"]; }
            set { ViewState["Value"] = value; }
        }
        protected override void LoadViewState(object savedState)
        {
            Response.Write("Load View State <br />");
            base.LoadViewState(savedState);
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Response.Write("Page Load <br />");
        }
        protected void Save(object sender, EventArgs e)
        {
            Response.Write("My Save Method - I didnt add anyting to View state <br />");
        }
        protected override object SaveViewState()
        {
            Response.Write("Save View State <br />");
            return base.SaveViewState();
        }
        protected void Add(object sender, EventArgs e)
        {
            Response.Write(string.Format("My Add Method - I add some value to view state <strong>{0}</strong> <br />",
                string.IsNullOrEmpty(this.Value) ? "Next time page will load view sate" : string.Empty));
            this.Value = DateTime.Now.ToShortTimeString();
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <div id="ProductList">
            <asp:Button runat="server" ID="btnSave" OnClick="Save" Text="Save" />
            <asp:Button runat="server" ID="btnAdd" OnClick="Add" Text="Add" />
        </div>
    </div>
    </form>
</body>
</html>

Friday, August 20, 2010

How to encode html in client side and benefit from request validation in asp.net in a case of html text input

It is common issue to have request validation failure while having HTML input in form inputs (TextBoxes). So easiest and widely used approach is to turn off request validation in the page to accept html input and encode only the expected control text. By ignoring other input controls we leave considerable space for attackers to inject some nasty html in to input controls.
So this approach is to have an input control which accepts html text in the page and also have request validation enabled. For this we can encode html in the client side and send only the encoded html to the server to comply with request validation in asp.net


Demo:
Tip: .net framwork validate inputs of asp.net controls only
<input type='text' runat='server id="txtName" /> - get validated
<asp:TextBox runat="server" Id="txtName" /> - get validated
It ignores regular input elements in a the page.
<input type='text '>  - not get validated
I have two side by side input controls one is text input and other is hiden field. From those two only the hidden field represents a .net control. You actually type on non .net control (html input). In the same time when I type it encodes html into hidden field (asp.net control) So even though I post two input controls when I post data one has row html(input element) and one has encoded html (asp.net control) .net framwork identify no risk as .net control (hidden filed) has encorded html. It just ignore the regular text input control with row html.
For a confirmation please see view source after row html postback of the demo page.
Example of use:
<%@ Register Assembly="ActiveTest" Namespace="ActiveTest" TagPrefix="asp" %> 
<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head2" runat="server">
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
    <script runat="server">
        protected void Save(object sender, EventArgs e)
        {
            string encodedHtml = this.htxtDescrition.Text;
        }
    </script>
</head>
<body>
    <form id="form2" runat="server">
        Description <asp:HtmlTextBox runat="server" ID="htxtDescrition" IsMultiLine="true"  />
        <hr />
        <asp:Button runat="server" ID="Button1" Text="Save" OnClick="Save" />
    </form>
</body>
</html>
Control Implementaion
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class HtmlTextBox : WebControlIPostBackDataHandlerINamingContainer
{
    public string Text
    {
        get { return (string)(ViewState["Text"] ?? (ViewState["Text"] = string.Empty)); }
        set { ViewState["Text"] = value; }
    }
    public int Rows
    {
        get { return (int)(ViewState["Rows"] ?? (ViewState["Rows"] = 10)); }
        set { ViewState["Rows"] = value; }
    }
    public int Colums
    {
        get { return (int)(ViewState["Colums"] ?? (ViewState["Colums"] = 40)); }
        set { ViewState["Colums"] = value; }
    }
    private string controlScript = @"
        function EncodeHtml(soruce) {
            var text = $(""#"" + soruce).val();
            var encodedHtml = $(""<div/>"").text(text).html();
            $(""#"" + soruce + ""Value"").val(encodedHtml);
        }
    ";
    private string instanceScript = @"
        $(document).ready(function () {{
            var controlId = ""#"" + ""{0}"" + ""Value"";
            var text = $(controlId).val();
            var encodedHtml = $(""<div/>"").text(text).html();
            $(controlId).val(encodedHtml);
        }});
    ";
    public bool IsMultiLine { getset; }
    public bool AutoPostBack { getset; }
    public event EventHandler TextChanged;
    public virtual bool LoadPostData(string postDataKey,
                                    NameValueCollection postCollection)
    {
        string postedValue = postCollection[postDataKey];
        this.Text = postedValue;
        return false;
    }
    public virtual void RaisePostDataChangedEvent()
    {
        OnCheckChanged(EventArgs.Empty);
    }
    protected virtual void OnCheckChanged(EventArgs e)
    {
        if (TextChanged != null)
            TextChanged(this, e);
    }
    protected override void Render(HtmlTextWriter output)
    {
        string colsAndRows = this.IsMultiLine ? string.Format(" rows=\"{0}\" cols=\"{1}\"",
            this.Rows, this.Colums) : string.Empty;
        output.Write(string.Format(@"
            <{0} type=""text"" id=""{1}"" {2}{3} 
            onchange=""javascript:EncodeHtml('{1}')""{4}>{5}</{0}>",
            this.IsMultiLine ? "textarea" : "input",
            this.ClientID,
            this.IsMultiLine ? string.Empty : string.Format(" value=\"{0}\""this.Text),
            this.AutoPostBack ?
                string.Format(" onclick=\"javascript:__doPostBack('{0}','');\""this.UniqueID) : string.Empty,
            colsAndRows,
            this.IsMultiLine ? this.Text : string.Empty));
        output.Write(string.Format("<input type='hidden' value='{0}' id='{1}Value' name='{2}' />",
            this.Text,
            this.ClientID,
            this.UniqueID));
    }
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        if (this.Page != null)
        {
            this.Page.ClientScript.RegisterStartupScript(this.GetType(), this.ClientID,
                string.Format(this.instanceScript, this.ClientID), true);
            this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
                this.GetType().Name, this.controlScript, true);
        }
    }
}

Thursday, August 19, 2010

How to upload multiple files using custom web control like asp.net DropDownList

‘How to handle multi file upload?’ is a frequently asked a question through the web developing community. There are numerous solutions being published. In this blog I have posted another post to demonstrate how to upload multiple files at the same time using third party plug-in called file uploadify. However this post aims to answer the same question in different approach. Idea is to have a file list control like asp.net DropDownList control. This is a very basic implementation but this code has holes for template.  This example uses JQuery.

Demo:


Page:

Markup:
Mark-up very simple, first we have to register the asp.net control with tag prefix. In this case, this control is hosted in the name space called ActiveTest and in an assembly ActiveTest.dll. Then we can add FileList control as same as we add a DropDownList to the page.
<%@ Page Language="C#" %>
<%@ Register Assembly="ActiveTest" Namespace="ActiveTest" TagPrefix="asp" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head2" runat="server">
    <script type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>
</head>
<body>
    <form id="form2" runat="server">
        <asp:FileList runat="server" ID="flFileList" DataNameField="Name" DataIdField="Id" />
        <asp:Button runat="server" ID="Button1" Text="Save" />
    </form>
</body>
</html>
Code
At the end of the section, you will be able to find a class called Document, which I use to DataBind the FileList control. I hold list of Documents in the ViewState for the purpose of demonstration. You might get this from database.  In the page load method, if page is not a postback, I do create an initial list of fake documents and bind it to the file list. Then again in real world this should come from the database or relevant source.
Then the Save event, probably the most important bit that we may consider. Save event happens as a consequence of save button click, please see the mark-up to locate save button.  In the save button I repopulate the document list that I have created in the initial page load. Then I loop through all the items in the FileList. FileList Item (definition can be found at the end of this post) has four major properties.
  1. IsNew - A new file
  2. IsEmpty - User has clicked add more but has not selected a file
  3. IsEdited - User has edited the existing file
  4. IsExisting  - Not edited existing file
Then I check the state of the item. If file is new or edited then we have to save the file.  Then again before I save the file for the safety, I check if the item has a file or not.  At the same time I populate new Document object and add to my document list. If the file is existing file, we don’t have to save the file but of course we have to add the document object created based on existing file to my document list in-order to bind the FileList.
public partial class Test : Page
{
    private List<Document> documents
    {
        get { return ViewState["Documents"as List<Document>; }
        set { ViewState["Documents"] = value; }
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if (!this.IsPostBack)
        {
            documents = new List<Document>();
            for (int i = 0; i < 10; i++)
                documents.Add(new Document() { Name = "Document Title " + i, FileName = "Doc.doc", Id = "Id" + i });
            this.flFileList.DataSoruce = documents;
            this.flFileList.DataBind();
        }
    }
    protected void Save(object sender, EventArgs e)
    {
        this.documents.Clear();
        foreach (FileListItem item in this.flFileList.Items)
        {
            ///
            /// Save files 
            /// Re-Populate items list
            ///
            if (item.IsEdited || item.IsNew)
            {
                Document d = new Document();
                if (item.HasFile)
                {
                    d.Name = item.File.FileName;
                    d.FileName = item.File.PostedFile.FileName;
                    d.Id = item.IsNew ? "Id" + this.flFileList.Items.IndexOf(item) : item.Id;
                    ///
                    /// item.File.PostedFile.SaveAs("pathToSave");
                    ///                            
                }
                this.documents.Add(d);
            }
            else if (item.IsExisting)
                this.documents.Add(new Document() { Name = item.Name, Id = item.Id });
        }
        ///
        /// Databind
        ///
        this.flFileList.DataSoruce = documents;
        this.flFileList.DataBind();
    }
}
 
[Serializable]
public class Document
{
    public string Id { getset; }
    public string Name { getset; }
    public string FileName { getset; }
}

Control Implementation:
FileList control is probably the most important bit of the post. If you in a hurry and need to enjoy the rest of the day, you may use the control implementation as it is, and get the example running. But for the people who are curious it works, I will explain how it works.
  1. Control has a property called MaxFiles. When it does not have initial list of files to display (i.e. fresh file list), control shows only a single file upload and add more button right next to file upload control.
  2. By clicking add new button you will be able to add another file upload input without a postback, so it is quick and will NOT destroy your excising selected files.
  3. You will be able to add more file uploads up to the number you specify in the MaxFiles property. By default MaxFiles is 5.
  4. When you save selected files, and then in the save event if you rebind the list with saved document list, control will show file names in read-only text boxes and edit button next to each text box to edit them.  By clicking edit button textbox and edit button get converted in to file upload control where you may chose a different file as an edit.
  5. Next to existing files there is a empty input field and add more button which behaves exactly same as we discussed in the point 1 and 2.
Private script variable:
Control uses java scripts to show additional file input fields without using postbacks. Script variable holds the script for this. In the runtime we inject couple of values to the script like currentRow which depends on bounded data source and maxRowCount which corresponds to MaxFile.  Then in the control’s pre-render method, we register the this script with the type of FileList control and ClientID as the key so that each FileList control in the page will have separate script.
Private style variable:
File list control specific styles which will get added to the page header. Those who want to control the disabled colour, textbox disabled colour get injected to styles at the runtime and get registered with the page in the pre-render method.
CreateControlHeirarchy() Method:
The CreateControlHeirarchy() method is the master beast which get called in the page init method. In this method execution loops through the existing items and add necessary controls to the control. Please note the ItemTemplate property. Please add more logic here to get this control customized with template. Each row of control will have a designated index and control classes will get appended with index to provide unique css class name so that jQuery can pick them up.
DataBind() Method:
This uses IEnumarable DataSource property. In the DataBind() method, execution loops through each item in the IEnumarable DataSource and it uses reflection to grill through the object and find the values. This reflection uses DataNameField property and DataIdField property to grab values to FileListItem. Then after populating all the file list items corresponds to data source then it call CreateControlHeirarchy() method to regenerate control collection.

[ParseChildren(true)]
[DefaultProperty("ItemCount")]
public class FileList : WebControlINamingContainerIPostBackDataHandler
{
    #region Attributes
 
    private List<FileListItem> items;
    private List<FileListRow> rows = new List<FileListRow>();
    private string script = @"
        var currentRow = {0};
        var maxRowCount = {1};
        function ShowEditFile(index) {{
            $("".FileName"" + index).hide();
            $("".EditButton"" + index).hide();
            $("".FileUpload"" + index).show();
            return false;
        }}
        function ShowAddFile() {{
            $("".FileListRow"" + currentRow).show();
            currentRow++;
            if (currentRow == maxRowCount)
                $("".AddMore"").attr(""disabled"", ""disabled"");            
            return false;            
        }}
    ";
    private string styles = @"
        <style type=""text/css"">
            .FileList .Hide {{ display:none; }}
            .FileList .FileName {{ background-color: {0}; }}        
        </style>
    ";
 
    #endregion
 
    #region Properties
 
    public List<FileListItem> Items { get { return this.items; } }
    public List<FileListRow> Rows { get { return this.rows; } }
    public int MaxFiles { getset; }
    public IEnumerable DataSoruce { getset; }
    public string DataNameField { getset; }
    public string DataIdField { getset; }
    public int ItemCount { getset; }
    public string ReadOnlyColor { getset; }
 
    [PersistenceMode(PersistenceMode.InnerProperty)]
    [TemplateContainer(typeof(FileListRow))]
    public ITemplate ItemTemplate { getset; }
 
    #endregion
 
    #region Constructors
 
    public FileList()
        : base(HtmlTextWriterTag.Div)
    {
        this.Initialize();
    }
    public void Initialize()
    {
        this.MaxFiles = 5;
        this.ReadOnlyColor = "#ccc";
    }
 
    #endregion
 
    #region Methods
 
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        this.CreateControlHeirarchy();
    }
    public void CreateControlHeirarchy()
    {
        this.Controls.Clear();
        this.TrackViewState();
        this.CssClass = string.Format("{0} {1}"this.CssClass, this.GetType().Name).Trim();
        if (this.items != null)
        {
            this.ItemCount = this.items.Count;
            foreach (FileListItem item in this.items)
                this.AddItem(this.items.IndexOf(item), item);
        }
        else
        {
            int itemCount = 0;
            if (int.TryParse(HttpContext.Current.Request.Form[this.UniqueID], out itemCount))
                this.ItemCount = itemCount;
            for (int i = 0; i < this.ItemCount; i++)
                this.AddItem(i, null);
        }
        for (int i = this.ItemCount; i < this.MaxFiles + this.ItemCount; i++)
        {
            FileListRow row = new FileListRow()
            {
                CssClass = string.Format("{0} {0}{1} {2}",
                    typeof(FileListRow).Name,
                    i, i == this.ItemCount ? string.Empty : "Hide").Trim()
            };
            this.Controls.Add(row);
            this.rows.Add(row);
            if (this.ItemTemplate != null)
                this.ItemTemplate.InstantiateIn(row);
            row.Controls.Add(new FileUpload()
            {
                ID = string.Format("FileUpload{0}", i),
                CssClass = string.Format("FileUpload{0}", i)
            });
            row.Controls.Add(new HiddenField()
            {
                ID = string.Format("FileId{0}", i)
            });
        }
        this.Controls.Add(new Button()
        {
            Text = "Add More",
            OnClientClick = "javascript:return ShowAddFile()",
            CssClass = "AddMore"
        });
    }
    private void AddItem(int index, FileListItem item)
    {
        FileListRow row = new FileListRow() { CssClass = typeof(FileListRow).Name };
        this.Controls.Add(row);
        this.rows.Add(row);
        if (this.ItemTemplate != null)
            this.ItemTemplate.InstantiateIn(row);
        TextBox txtFile = new TextBox()
        {
            ID = string.Format("FileName{0}", index),
            CssClass = string.Format("FileName{0} FileName", index)
        };
        if (item != null) txtFile.Text = item.Name;
        txtFile.Attributes.Add("readonly""readonly");
        row.Controls.Add(txtFile);
        row.Controls.Add(new Button()
        {
            Text = "Edit",
            ID = string.Format("EditButton{0}", index),
            CssClass = string.Format("EditButton{0} EditButton", index),
            OnClientClick = string.Format("javascript:return ShowEditFile({0})", index)
        });
        row.Controls.Add(new FileUpload()
        {
            ID = string.Format("FileUpload{0}", index),
            CssClass = string.Format("FileUpload{0} Hide FileUpload", index)
        });
        HiddenField fileId = new HiddenField() { ID = string.Format("FileId{0}", index) };
        if (item != null) fileId.Value = item.Id;
        row.Controls.Add(fileId);
    }
    public override void DataBind()
    {
        base.DataBind();
        this.items = new List<FileListItem>();
        if (this.DataSoruce != null)
        {
            foreach (object item in this.DataSoruce)
            {
                if (!string.IsNullOrEmpty(this.DataNameField))
                {
                    FileListItem i = new FileListItem();
                    string fn = string.Empty;
                    string fi = string.Empty;
                    foreach (PropertyInfo p in item.GetType().GetProperties())
                    {
                        if (p.Name.Equals(this.DataNameField))
                        {
                            fn = (p.GetValue(item, null) ?? string.Empty).ToString();
                            i.Id = i.Name = fn;
                        }
                        if (!string.IsNullOrEmpty(this.DataIdField) && p.Name.Equals(this.DataIdField))
                        {
                            fi = (p.GetValue(item, null) ?? string.Empty).ToString();
                            i.Id = string.IsNullOrEmpty(fi) ? fn : fi;
                        }
                    }
                    this.items.Add(i);
                }
                else
                {
                    string value = (item ?? string.Empty).ToString();
                    this.items.Add(new FileListItem() { Name = value, Id = value });
                }
            }
        }
        this.CreateControlHeirarchy();
    }
    protected override void RenderContents(HtmlTextWriter writer)
    {
        base.RenderContents(writer);
        writer.Write("<input type=\'Hidden\' value=\'{0}\' id=\'{1}\' name=\'{2}\' />"this.ItemCount, this.ClientID, this.UniqueID);
    }
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        if (this.Page != null)
        {
            this.Page.ClientScript.RegisterClientScriptBlock(
                this.GetType(),
                this.ClientID,
                string.Format(this.script, this.ItemCount + 1, this.ItemCount + this.MaxFiles),
                true);
            Literal styles = new Literal() { Text = string.Format(this.styles, this.ReadOnlyColor) };
            if (this.Page.Header != null)
                this.Page.Header.Controls.Add(styles);
        }
    }
 
    #endregion
 
    #region IPostBackDataHandler Members
 
    public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        this.ItemCount = int.Parse(postCollection[postDataKey]);
        this.items = new List<FileListItem>();
        foreach (FileListRow row in this.rows)
        {
            int index = this.rows.IndexOf(row);
            TextBox txtFile = row.FindControl(string.Format("FileName{0}", index)) as TextBox;
            HiddenField hdnId = row.FindControl(string.Format("FileId{0}", index)) as HiddenField;
            FileUpload fuFile = row.FindControl(string.Format("FileUpload{0}", index)) as FileUpload;
            FileListItem item = new FileListItem();
            if (txtFile != null) item.Name = txtFile.Text;
            if (hdnId != null) item.Id = hdnId.Value;
            if (fuFile != null) item.File = fuFile;
            this.items.Add(item);
        }
        return true;
    }
    public void RaisePostDataChangedEvent()
    {
 
    }
 
    #endregion
}


Support Classes:
FileListRow and FileListItem are the two support classes for the FileList control. There are no descriptive logic but solely there are there to provide data structures to the FileList control.
[Serializable]
public class FileListRow : WebControlINamingContainer
{
    public FileListRow()
        : base(HtmlTextWriterTag.Div)
    {
 
    }
}
[Serializable]
public class FileListItem
{
    #region Attributes
 
    private bool hasIdAndName
    {
        get { return !string.IsNullOrEmpty(this.Name) && !string.IsNullOrEmpty(this.Id); }
    }
 
    #endregion
 
    #region Properties
 
    public string Name { getset; }
    public string Id { getset; }
    public FileUpload File { getset; }
    public bool HasFile
    {
        get
        {
            return this.File != null && this.File.HasFile;
        }
    }
    public bool IsEdited
    {
        get { return this.hasIdAndName && this.HasFile; }
    }
    public bool IsNew
    {
        get { return !this.hasIdAndName && this.HasFile; }
    }
    public bool IsEmpty
    {
        get { return !this.hasIdAndName && !this.HasFile; }
    }
    public bool IsExisting
    {
        get { return this.hasIdAndName; }
    }
 
    #endregion
}

Azure Storage Account Types

Defferent Types of Blobs Block blobs store text and binary data. Block blobs are made up of blocks of data that can be managed individually...