Selecting ListView Items with CheckBoxes

by ashic 14. August 2010 04:57

Often we need to enable users to select multiple items from a ListView control and do a batch operation on them. While the ListView control does enable selection, it only supports selecting one row (item) at a time. This article shows a nice, easy and reusable way to enable multiple selection with checkboxes.

The Project

I've created a very simple "Empty ASP.NET WebForms Application" and added a standard Default.aspx page. I've created an App_Data folder and added a database called PeopleDB.mdf into it. The database consists of one simple table holding the Id, Name and AwesomePoints of a number of people* and also if they are attending some event. We want to list them in a simple ListView with pagination, and be able to select some of them and set all of the selected people's AwesomePoints in one go. We also want to be able to select some other people and set the boolean stating whether or not they're attending.

*these people are purely fictional and any resemblance to real people are entirely coincidental ;)

The Page Layout

The page layout is fairly simple. It's ugly and OMG I'm using tables for layout – help – the Earth is gonna explode…yeah right. At least it's apparent what needs doing. We show a list of people and provide checkboxes for each row to select the person for either batch update of their AwesomePoints or batch update of their IsAttending value. At the end of the page, we provide controls to actually carry out the batch updates. Here's the page body's markup so far:


<body>
    <form id="form1" runat="server">
    <div>
        <asp:ListView runat="server" DataKeyNames='Id' ID='lvPeople' ItemPlaceholderID='ph1'>
            <LayoutTemplate>
                <table>
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Awesome Points</th>
                            <th>Is Attending</th>
                            <th>Select for Updating Awesome Points</th>
                            <th>Select for Updating Attendee Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        <asp:PlaceHolder runat="server" ID='ph1' />
                    </tbody>
                </table>
            </LayoutTemplate>
            <ItemTemplate>
                <tr>
                    <td><%# Eval("Name") %></td>
                    <td><%# Eval("AwesomePoints") %></td>
                    <td><%# Eval("IsAttending") %></td>
                    <td><asp:CheckBox runat="server" ID='chkAwesomePoints' /></td>
                    <td><asp:CheckBox runat="server" ID='chkAttending' /></td>
                </tr>
            </ItemTemplate>
        </asp:ListView
        &lt;div>
            <asp:DataPager ID="DataPager1" runat="server" PagedControlID='lvPeople' PageSize='5'>
                <Fields>
                    <asp:NumericPagerField />
                </Fields>
            </asp:DataPager>
        </div>
        <br />
        <table border='none'>
            <tbody>
                <tr>
                    <td>Set Awesome Points to: <asp:TextBox runat="server" ID='txtAwesomePoints' /></td>
                    <td><asp:Button Text="Update Awesome Points" runat="server" /></td>
                </tr>
                <tr>
                    <td>Set Attendee Status to: <asp:CheckBox runat="server" ID='chkAttending' /></td>
                    <td><asp:Button Text="Update Attending Status" runat="server" /></td>
                </tr>
            </tbody>
        </table>
    </div>
    </form>
</body>

Notice that I've set the DataKeyNames fo the ListView to Id. I'll explain this part later.

The DataSource

We have the ListView, we now need to add a datasource. I could easily use a SqlDataSource or whatever else I want. But since I'll be updating the database as well, I'll just add an Entity Framework model to the project (and use an EntityDataSource control to populate the ListView). Right click the project in solution explorer and select "Add New Item". I chose ADO.NET Entity Data Model from the popup and name it PeopleModel.edmx.

1_adding_EF

From the next window, I select "Generate From Database". I get an option to select the connection to be used. Since I don't have one to the mdf file in App_Data, I select "New Connection". I ensure that the "Microsft SQL Serv Database File (SqlClient)" is selected and I "Browse" to my .mdf file in the App_Code folder. You could obviously use other databases and you'd configure the connection here. Finishing this dialog takes you back to the previous one, where you can click "Next".

2_EF_connection

The next window gives you options of selecting a subset or all of the tables, sprocs and views in the target database. We're only interested in the People table, so select it and click "Finish".

3_select_table

And that's it – our EF4 model is done. Let's now add an EntityDataSource to the page.To do this, add the following markup right after the ListView:


<asp:EntityDataSource runat="server" ID='edsPeople'
    ConnectionString="name=PeopleDBEntities"
    DefaultContainerName="PeopleDBEntities" EnableFlattening="False"
    EntitySetName="People"></asp:EntityDataSource>

(If you're using the webforms designer to hook up the datasource, be sure to compile the project before attempting to do so. Without a recompile, the required metadata would not be available to the user.)

Next, set the DataSourceId of the ListView to match the Id of the EntityDataSource:


<asp:ListView runat="server" ID='lvPeople' DataKeyNames='Id' ItemPlaceholderID='ph1' DataSourceID='edsPeople'>

At this point, we can run the page to see this:

4_initial_results

 

The ListView Extension

Add a folder called Extensions and in it, create a class called ListViewExtensions with the following code:



public static class ListViewExtensions
{
    public static List<DataKey> GetSelectedDataKeys(this ListView control, string checkBoxId)
    {
        return control.Items.Where(x => IsChecked(x, checkBoxId))
           .Select(x => control.DataKeys[x.DisplayIndex])
           .ToList();
    }

    private static bool IsChecked(ListViewDataItem item, string checkBoxId)
    {
        var control = item.FindControl(checkBoxId) as CheckBox;
        if (control == null)
        {
            return false;
        }

        return control.Checked;
    }
}

This class is the heart of what we're trying to do. It basically finds the checkbox with the Id passed in and returns the DataKey of every item in the ListView that has said checkbox checked. Remember the DataKeyNames property of the ListView that we set earlier? This is where that comes in. You can pass the names of the properties of each data item that you wish to have access to later on to the DataKeyNames. In our case, we only need the Id property, as such we passed in "Id". If for some reason, we wanted the AwesomePoints property too, we'd pass in "Id, AwesomePoints". Notice that our extension method returns a list of DataKeys. What is a DataKey? It's basically an object that holds the values of the properties mentioned in DataKeyNames. It has two properties – Value and Values. Value hold the value of the first data key property. So, if we had passed in "Id, AwesomePoints", Value would hold the value of the Id property for the associated item. Values on the other hand has an ordered dictionary. You would then find the value of the Id property in Values[0] and the value of the AwesomePoints property in Values[1] and so on.

Putting it to Work

We'll now add two handlers for the two button click events:



protected void UpdateAwesomePointsButton_Click(object sender, EventArgs e)
{
    var selectedKeys = lvPeople.GetSelectedDataKeys("chkAwesomePoints");
    var selectedIds = selectedKeys.Select(x => new Guid(x.Value.ToString()));

    if (selectedIds.Count() > 0)
    {
        var targetPoints = int.Parse(txtAwesomePoints.Text);
        var db = new PeopleDBEntities();

        db.People.Where(x => selectedIds.Contains(x.Id))
            .ToList()
            .ForEach(x => x.AwesomePoints = targetPoints);

        db.SaveChanges();
        lvPeople.DataBind();
    }

}

protected void UpdateAttendeeStatusButton_Click(object sender, EventArgs e)
{
    var selectedKeys = lvPeople.GetSelectedDataKeys("chkAttending");
    var selectedIds = selectedKeys.Select(x => new Guid(x.Value.ToString()));

    if (selectedIds.Count() > 0)
    {
        var targetValue = chkAttending.Checked;
        var db = new PeopleDBEntities();

        db.People.Where(x => selectedIds.Contains(x.Id))
            .ToList()
            .ForEach(x => x.IsAttending = targetValue);

        db.SaveChanges();
        lvPeople.DataBind();
    }
}

All we've done here is used our extension method to get the datakeys of the selected items in the ListView and have updated those items accordingly. Granted there's no validation or anything fancy going on – hey, this is a demo – it shows how we can know which items of a ListView have been selected.

Source

You can download the source by clicking here.

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET

ASP.NET Routing with IIS 7 – Remember Your Modules

by ashic 26. July 2010 13:04

ASP.NET 4.0 introduces routing and it works very well. With IIS 7, there are some interesting configuration options that can help improve your site's performance. When done wrong, they can have unexpected side effects. And we're going to look at one of them today.

Extensionless Urls and Wildcard Mappings

Before IIS 7, the easy way to get extensionless urls when working with routing would be to have a wildcard mapping (yes, I know there are other ways with ISAPI handlers and stuff, but that's not too simple). A wildcard mapping would simply put every request for every file through the asp.net pipeline running all the managed modules and stuff. This does carry with it a certain performance hit – whether that hit applies to you or not, you'd have had to test for yourself. There's a good chance that you'd need admin privileges to do this, which is another problem if you're on shared hosting. Even without routing, there are a ton of different things that need to be set in IIS, with no simple way of overriding in your individual application (without IIS access).

Enter IIS 7

IIS 7 reads in a section of the web.config file called <system.webserver>. The <system.webserver> node goes directly inside the <configuration> node ([i.e. it's parallel <system.web>). This section is totally ignored by previous versions and can enable you to set settings that previously required IIS access or some not so nice workarounds to do (if at all possible). In this section, you can tell ASP.NET to run certain modules, not run others – or even to run all managed modules for every request. The latter feature is also the easiest to set up, and simply goes like this:


<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
   </modules>
</system.webServer>

That would do exactly what you'd expect – it'll run all managed modules for all requests. That's kind of like the wildcard mappings. Not the question is, do we need to run every single managed module for every request? Probably not. So, this can be a chance for improvement. We only want routing to run for all requests – and that should give us extensionless urls. So let's do that now:

An Improvement

We'll now tell IIS 7 to not run all managed modules for all requests.


<system.webServer>
  <modules>
   </modules>
</system.webServer>

That was simple, but that now means that the routing module isn't running. Let's make that run for all requests. We can do so by doing this:


<system.webServer>
  <modules>
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
  </modules>
</system.webServer>

With this, our routing should work as expected, while not loading all the other managed modules for every request. So alls well, right?

Hang on a Mo'

While what we just did is better in the sense that it will only do routing for all requests (which we need for extensionless urls), we have potentially just introduced a bug. We're only running routing for every request, and all the other managed modules will only be run for known ASP.NET resources (for example, .aspx files). Read that again – other than routing, all the other managed modules are only be run for requests to known ASP.NET resources like .aspx files. Say, we have a page at http://mysite.com/folder/index.aspx. If we target that url, all shall be fine – IIS will see that the requested file has an aspx extension and it will run the required managed modules. Now, let's say we use routing to route requests for http://mysite.com/foo to that same page. If we request that url, IIS will look at it and will think it's not for ASP.NET. Since we configured the routing module to run, that will run and the request will get processed correctly by /folder/index.aspx. Keep in mind though, that since we aren't running all managed modules for all requests and don't have a wildcard mapping, IIS will not cause the other managed modules to fire. What does this mean? It means that commonly used functionality might suddenly disappear. If our /folder/index.aspx page uses Session, then we'll get an exception. (The error message for that particular one will keep you guessing – it tells you that you need to set enableSession to true!) In other words, if the page uses Session and if it's requested via it's actual path (i.e. ~/folder/index.aspx with the .aspx extension), then it'll run fine – but if it's requested with ~/foo (i.e. the extensionless route), then trying to access Session will fail.

So How do We Fix it?

One obvious way would be to enable all managed modules for all requests. But if we wish to follow our previous approach of only loading the required modules and not any more, then we can do this:


<system.webServer>
  <modules >
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
    <remove name="Session"/>
    <add name="Session" type="System.Web.SessionState.SessionStateModule" preCondition=""/>
  </modules>
</system.webServer>

In other words, we're first removing the Session module and then adding it in again with a blank precondition. This will ensure that the Session module also gets run for all requests and not just for known ASP.NET resources. As such, Session will work regardless of whether you're using a real path or a routed path. Keep in mind though, that if the concerned module is loaded in IIS, then we must ensure we use the exact same name in the <remove> tag(s). There is a bit of magic stringy nature to this, but luckily, you can lookup the module name easily in IIS. In IIS 7 manager, just select the server from the left and double click on "Modules" from the centre pane. You should see a list of all loaded modules with the first column showing the names. That name is what you should give the <remove> tag in the web.config file. Remember, you'll need to do this for all the modules you intend to have running. These include - but are not limited to – Session, Forms Authentication, Windows Authentication etc.

So next time you're doing "optimized" routing and your routed pages don't seem to be working right, check to see if the modules are in fact getting loaded.

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | IIS 7

Anonymous Types are Internal, C# 4.0 Dynamic Beware!

by ashic 26. May 2010 14:10

C# 4.0 has introduced the dynamic keyword. You can declare a variable as dynamic and regardless of what can be inferred at compile time, you can access any properties and call any methods and your code will still compile. Resolving of those properties and methods will be done at runtime. If at runtime, they aren't found, you'd get a runtime exception. If they are found, your code will run fine.

An Example

Consider a class called Person that has a property called Name. Consider the following piece of code:



Person p = new Person { Name = "John" };
object o = p;
dynamic d = o;
Console.WriteLine(d.Name);

On line 2, we're assigning p to an object reference o. Before C# 4.0, if you wanted to get the value of o's Name (and it does have a Name, since we know we assigned a Person object to o) you would have had to resort to reflection. With C# 4.0 however, we can use a dynamic variable like we did on line 3, and simply access the Name property on the dynamic variable. This program would compile fine, but it would require that the object assigned to d must (at runtime) have a property called Name. If it doesn't, it'll throw an exception.

So basically, we can assign a dynamic variable with any object and call properties and methods we expect it to have and it should run fine. Right? Not quite.

The Problem

The dynamic approach has one limitation – it must know the type of object it's dealing with. This isn't a problem for most cases, but it can be an issue when dealing with anonymous types. Let's get started building the project so you can see what I mean.

The Project

Create a basic .Net 4.0 console application called DynamicTest. Add a class library called ClassLibrary1 to the solution. Make class1.cs look like this:



namespace ClassLibrary1
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    } 

    public class Repository
    {
        Person _person = new Person { Name = "Adam", Age = 21 };
       
        public object GetPerson()
        {
            return _person;
        }

        public object GetPersonWrappedInAnonymousType()
        {
            return new { Person = _person };
        }
    }
}

We have a repository that has two methods. One simply returns a person as an object while the other wraps it in an anonymous type and returns an instance of that anonymous type. Going back to the main project's Program.cs, add the following code:



class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }    

    private void Run()
    {
        var rep = new Repository();

        dynamic data = rep.GetPerson();
        Console.WriteLine(data.Name); 

        dynamic data2 = rep.GetPersonWrappedInAnonymousType();
        Console.WriteLine(data2.Person.Name);
    }
}



That looks simple enough. data and data2 are both dynamic variables. As such, their Name and Person.Name properties will be resolved at runtime. The program compiles fine, but when we run it, we get this:

Adam

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'obj
ect' does not contain a definition for 'Person'
   at CallSite.Target(Closure , CallSite , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T
0 arg0)
   at DynamicTest.Program.Run() in F:\MyTests\C#\DynamicTest\DynamicTest\Program
.cs:line 24
   at DynamicTest.Program.Main(String[] args) in F:\MyTests\C#\DynamicTest\Dynam
icTest\Program.cs:line 13
Press any key to continue . . .

How come? The call to data.Name resolved fine, but the call to data2.Person failed. The error message states that the Person property could not be found at runtime. We can obviously see by looking at the code that data2 most definitely has a Person property and that in turn has a Name property. [And if you don't trust your eyes, you can run in debug mode, set a breakpoint just after data2 is initialized and if you use the watch window to view the properties of data2, you'll see that data2 does indeed have a data2.Person property but if you type in "data2.Person" in the watch window, you'll get the same exception…magic!]. So what gives?

Explanation

The reason the call to data2.Person fails is that the type information of data2 is not available at runtime. The reason it's not available is because anonymous types are not public. When the method is returning an instance of that anonymous type, it's returning a System.Object which references an instance of an anonymous type -  a type who's info isn't available to the main program. The dynamic runtime tries to find a property called Person on the object, but can't resolve it from the type information it has. As such, it throws an exception. The call to data.Name works fine since Person is a public class, that information is available and can be easily resolved.

This can affect you in any of the following cases (if not more):

1. You're returning a non-public, non-internal type using System.Object.
2. You're returning a non-public, non-internal derived type via a public Base type and accessing a property in the derived type that's not in the base type.
3. You're returning anything wrapped inside an anonymous type from a different assembly.

i.e. you need to be able to access the type info for the object at the point where its property / method is being resolved at runtime.

Solution

The solution is actually quite simple. All we have to do is open up AssemplyInfo.cs of the ClassLibrary1 project and add the following line to it:


[assembly:InternalsVisibleTo("DynamicTest")]


What this does is allow the main project to "look into" the internal types of ClassLibrary1. This means the dynamic runtime can find the type information of the anonymous type being returned and data2.Person.Name resolves perfectly. Anonymous types are internal. Adding that piece of code makes the type information "visible" to our main project.

Drawback

As you can see, the solution means you actually have to have control over the class library's source code for this to work. If you don't, then I guess you'd need to resort to good old fashioned reflection.

Application

This approach can come in handy for unit testing. You would expose the internal types of the assembly under test to the tests, and as such should be able to take advantage of the dynamic functionality of C# 4.0 when evaluating results from methods that return anonymous types.

Source

download

Shout it kick it on DotNetKicks.com

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Tags: , ,
Categories: C# | .NET

Fetching a Property Value via Reflection

by ashic 10. May 2010 21:31

Reflection is a method by which we can use metadata at runtime to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. It has many uses, ranging from allowing usage of attributes to creating instances of dynamically generated classes and using them. You can even use reflection to read and modify private member variables of an object. Today, we're going to look at a very simple example which will allow us to read the value of a public property given an instance.

The Class

Let's take a very simple class:



namespace MyNamespace
{
    public class MyClass
    {
        public int MyProperty { get; set; }
    }
}

Note, that I've put it in a separate namespace to show that it's easy to deal with this when using reflection.

The Runner

We'll run everything in a very simple console application:



public class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }

    private void Run()
    {
        MyClass o = new MyClass { MyProperty = 25 };
        object value = GetPropertyValue(o, "MyProperty");
        Console.WriteLine(value);
    }
}

We're basically creating an instance of MyClass and passing the instance and the string "MyProperty" to a method called GetProperty. The method is returning an object (which we could have cast to anything we needed). We're passing the returned object to Console.WriteLine.

The Method

This is where we use reflection to find the value of the "MyProperty" property given an instance. Add the following method to the class:



private object GetPropertyValue(object o, string propertyName)
{
    Type type = o.GetType();
    PropertyInfo info = type.GetProperty(propertyName);
    object value = info.GetValue(o, null);
    return value;
}

We're getting the type information by calling GetType() on the object. Next, we call GetProperty() on the type information. Note, this only gives us information about the property and not the property itself. We then have to call GetValue() on the property information passing in the instance from which to get the actual value.

And that's all it takes :)

PS: You'll need a reference to System.Reflection to use Reflection.

PPS: Yes, this is a very very simple example of using reflection, but I found quite a few questions over at the forums today that would be answered by this. We all have to start somewhere, right?

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | C#

Encrypted Hidden Redux : Let's Get Salty

by ashic 13. March 2010 09:37

This article builds on the ideas presented in my earlier article http://www.heartysoft.com/post/2010/02/25/Encrypted-Hidden-Inputs-in-ASPNET-MVC.aspx

If you haven't read that yet, I'd recommend doing so before proceeding.

Problems with the Previous Approach

The approach outlined in the previous article is pretty secure and easy to use. However, there are a few issues that can be improved upon:

  1. Security: The approach is using a symmetric encryption process with a fixed private key. Asymmetric encryption would no doubt provide more security, but the performance costs and hassle is probably not worth it. The problem with symmetric encryption is that brute force attacks can potentially break it. As such, using only a fixed encryption key could be a security issue. Given enough time and test pages, the encrypted values could lead to the leaking of the private key. This would result in the whole process being compromised. A couple of people provided feedback on this and were concerned. Still, such brute force attacks could take hundreds of years as the attacker would be required to test at least 2^56 keys. That's not an easy task, but we can use some salting techniques to make it even harder (a lot harder) to break. This would still be within the realms of symmetric encryption but would provide (a LOT of) added security. I'll explain the salting technique later.
  2. CSRFs: With the previous approach, an attacker could take the encrypted value and submit it as part of another request from his browser. The system would decrypt and use the value as normal. The previous process expected that the developer would use ASP.NET MVC's AntiForgeryToken to prevent CSRFs. In other words, the process did not prevent CSRFs on its own, but expected the developer to handle them by other means. With the new process outlined in this article, this will no longer be necessary.
  3. Encryption and Hashing method: The previous process used Triple DES for encryption and MD5 for hashing. The new process uses Rijndael encryption and SHA256 hashing. Rijndael is approved by the US government and is natively supported by the .Net framework. If the encryption and decryption are both going to be done on Windows boxes, then Rijndael is definitely the encryption algo to use over TDES. Similarly. SHA256 has a managed implementation and is superior to MD5.
  4. Code Smell: The previous implementation kind of had some code smells. Stuff was there where it shouldn't have been and it just didn't feel right. I reworked the design and although all smells aren't gone (hey, it's a demo!), a lot of the stench has been removed.

So What's Our Salt?

A salt is basically some random bytes added into our actual data during the encryption process. Having access to the key and the salt enables complete decryption. Having access to one but not the other doesn't. The salt needs to be random enough so that different users have different ones. We still need the salt to facilitate decryption on the server. A common technique is to store part of the salt at the server and part of it in the html sent to the browser. Other techniques generate the salts using some logic and sends some or none of the salt to the client. The bottom line is that the salt needs to be random so that an attacker can't simply take some values from a legitimate page output and then post it himself. The salt still needs to be available on the server when a legitimate user submits the page. ASP.NET MVC actually has such a per user per session value baked right in. It's the AntiForgeryToken. However there are a couple of issues that prevent us from using it directly:

1. The AntiForgeryToken helper uses some internal classes to generate the token data. As it's internal to MVC, we can't directly access the token data. We need to generate the AntiForgeryToken input tag and parse its value.

2. The AntiForgeryToken value is per user per session. If the developer puts an AntiForgeryToken on the page, then that value will be visible to the attacker. That essentially hands the attacker the salt. This is not acceptable. Luckily, the AntiForgeryToken helper can accept any string as its salt (yes, its own salt – not our final salt). We can pass a fixed string as the salt and that would ensure that using AntiForgeryTokens on the page doesn't interfere with or expose our salt.

As I said, the AntiForgeryToken's value is constant for the same user in the same browser session and the same "salt" parameter passed to the helper. But if the user opens another browser window or another user uses the system, then the value would be completely different. With this approach, we don't need to send any of the salt to the browser as all of it can be generated on the server on page submission. Only the encrypted values are ever sent to the client.

The Project

Since quite a bit of things have changed since the previous approach, I'll start afresh. First thing to do is create an MVC 2 project called "HiddenEncryptDemo".

I installed MVC 2 RC 2 before installing VS 2010 RC. If you have some other combination, then slight changes might be necessary, although the core ideas will still work (even with MVC 1.0).

Create a Models folder and add in a class called Computer:


public class Computer
{
    public string Setting { get; set; }
}

The reason I'm adding this class is to show that our approach will work not just on simple action parameters, but also on more complex types without a need for a separate model binder.

Next, add this to the Index.aspx view (in the /Views/Home folder):


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
    <%= Html.Hidden("computer.Setting", "hello world!") %>
    <input type="submit" value='submit' />
<% Html.EndForm(); %>

After that, add the following method to the Home controller:


[HttpPost]
public ActionResult Something(Computer computer)
{
    ViewData["Message"] = computer.Setting;
    return View("Index");
}

Nothing fancy, we're just setting the ViewData["Message"] to the computer.Setting value. Notice that in the code we added to Index.aspx, the form submitted to "Something" (the name of our action) and the name of the hidden input was "computer.Setting". The default model binder will find a value for "computer.Something" in the request parameters and upon seeing that it can set the Setting property of the computer parameter, it's going to set computer.Setting to the value it found (in our case, "hello world!"). If you run the project, you should see the index page with a submit button. Clicking the button will result in a post to the server where the Something action will get called. The Something action will then set the ViewData["Message"] and return the Index view. As such, you will see the words "hello world!" displayed on the page:

hello

On the initial Index page (or the page got after clicking the button), if you right click anywhere and select view source, you should see this (among other things):

hidden

Notice how the helper added a hidden input control, set its id to "computer_Setting" and name to "computer.Setting". The name is used as the key of the data in the post variables. Also notice how the value is "hello world!". The value is clearly visible to anyone looking at the source. Our goal is to come up with a way to hide that value from clear sight while at the same time ensuring everything works as smoothly and easily as it just did. A bonus would be if no change was required in the controller's code (i.e. no specific attributes needed on the consuming action or controller, no special model binding needed for the parameter etc.) – it would be unobtrusive. These are the exact requirements of the previous approach, but today, we want to add one more – the encryption should use a different salt for each user's session so that brute force approaches of uncovering the encryption key will fail and CSRFs will be prevented by default.

The Settings Provider

In the previous article, we handled settings from within the encryption provider. This seemed kind of messy and I decided to use a separate settings provider. Add a "Helpers" folder to the project and add the interface ISettingsProvider to it:


public interface ISettingsProvider
{
    byte[] EncryptionKey { get; }
    string EncryptionPrefix { get; }
    string SaltGeneratorKey { get; }
}

Next, add an implementation for the interface. The SettingsProvider class looks like this:


namespace HiddenEncryptDemo.Helpers
{
    using System.Configuration;
    using System.Security.Cryptography;
    using System.Text;
   
    public class SettingsProvider : ISettingsProvider
    {
        private static readonly byte[] _encryptionKey;
        private static readonly string _encryptionPrefix;
        private static readonly string _saltGeneratorKey;

        static SettingsProvider()
        {
            //read settings from configuration
            var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"];
            bool useHashing = true;
            if (string.Compare(useHashingString, "false", true) == 0)
            {
                useHashing = false;
            }

            _encryptionPrefix = ConfigurationManager.AppSettings["EncryptionPrefix"];
            if (string.IsNullOrWhiteSpace(_encryptionPrefix))
            {
                _encryptionPrefix = "encryptedHidden_";
            }

            _saltGeneratorKey = ConfigurationManager.AppSettings["EncryptionSaltGeneratorKey"];
            if (string.IsNullOrWhiteSpace(_saltGeneratorKey))
            {
                _saltGeneratorKey = "encryptionSaltKey";
            }

            var key = ConfigurationManager.AppSettings["EncryptionKey"];

            if (useHashing)
            {
                var hash = new SHA256Managed();
                _encryptionKey = hash.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
                hash.Clear();
                hash.Dispose();
            }
            else
            {
                _encryptionKey = UTF8Encoding.UTF8.GetBytes(key);
            }
        }

        #region ISettingsProvider Members

        public byte[] EncryptionKey
        {
            get { return _encryptionKey; }
        }

        public string EncryptionPrefix
        {
            get { return _encryptionPrefix; }
        }

        public string SaltGeneratorKey
        {
            get { return _saltGeneratorKey; }
        }

        #endregion
    }
}

Basically, we're just reading in some settings from the web.config file in the static constructor and returning the static values from the instance based getters. Notice that if UseHashingForEncryption setting is set, we first hash the encryption key in web.config using SHA256Managed before using it. The EncryptionPrefix is a prefix used to identify hidden inputs which were previously encrypted. The SaltGeneratorKey is a key used in getting the AntiForgeryToken.

The Encryption Provider

Add an interface called IEncryptString:


public interface IEncryptString : IDisposable
{
    string Encrypt(string value);
    string Decrypt(string value);
}

And an implementation called RijndaelStringEncrypter:


namespace HiddenEncryptDemo.Helpers
{
    using System;
    using System.Security.Cryptography;
    using System.Text;

    public class RijndaelStringEncrypter : IEncryptString
    {
        private RijndaelManaged _encryptionProvider;
        private ICryptoTransform _encrypter;
        private ICryptoTransform _decrypter;
        private byte[] _key;
        private byte[] _iv;

        public RijndaelStringEncrypter(ISettingsProvider settings, string salt)
        {
            _encryptionProvider = new RijndaelManaged();
            var saltBytes = UTF8Encoding.UTF8.GetBytes(salt);
            var derivedbytes = new Rfc2898DeriveBytes(settings.EncryptionKey, saltBytes, 3);
            _key = derivedbytes.GetBytes(_encryptionProvider.KeySize / 8);
            _iv = derivedbytes.GetBytes(_encryptionProvider.BlockSize / 8);
        }

        #region IEncryptString Members

        public string Encrypt(string value)
        {
            var valueBytes = UTF8Encoding.UTF8.GetBytes(value);

            if (_encrypter == null)
            {
                _encrypter = _encryptionProvider.CreateEncryptor(_key, _iv);
            }

            var encryptedBytes = _encrypter.TransformFinalBlock(valueBytes, 0, valueBytes.Length);
            var encrypted = Convert.ToBase64String(encryptedBytes);

            return encrypted;
        }

        public string Decrypt(string value)
        {
            var valueBytes = Convert.FromBase64String(value);

            if (_decrypter == null)
            {
                _decrypter = _encryptionProvider.CreateDecryptor(_key, _iv);
            }

            var decryptedBytes = _decrypter.TransformFinalBlock(valueBytes, 0, valueBytes.Length);
            var decrypted = UTF8Encoding.UTF8.GetString(decryptedBytes);

            return decrypted;
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_encrypter != null)
            {
                _encrypter.Dispose();
                _encrypter = null;
            }

            if (_decrypter != null)
            {
                _decrypter.Dispose();
                _decrypter = null;
            }

            if (_encryptionProvider != null)
            {
                _encryptionProvider.Clear();
                _encryptionProvider.Dispose();
                _encryptionProvider = null;
            }
        }

        #endregion
    }
}

This is a sginificant change from our previous encryption method. Previously, we created a single pair of ICryptoTransforms for encryption and decryption based on the encryption key. Since we need to provide different salts for encryption for different users and different sessions, that approach will no longer work. In the new approach, each instance of the RijndaelStringEncrypter has its own salt value. As a consequence, each instance needs its own pair of ICryptoTransforms. Looking at the constructor code:


public RijndaelStringEncrypter(ISettingsProvider settings, string salt)
{
    _encryptionProvider = new RijndaelManaged();
    var saltBytes = UTF8Encoding.UTF8.GetBytes(salt);
    var derivedbytes = new Rfc2898DeriveBytes(settings.EncryptionKey, saltBytes, 3);
    _key = derivedbytes.GetBytes(_encryptionProvider.KeySize / 8);
    _iv = derivedbytes.GetBytes(_encryptionProvider.BlockSize / 8);
}

we see that we're storing the key and iv bytes as instance variables. Please note that simply getting the bytes for the salt string using UTF8Encoding.UTF8.GetBytes(salt) is not good enough. The values generated by AntiForgeryToken - while different for each user and session – can be very similar. As such, if you're encrypting a small word (for example "hello") and use the encoding derived bytes, the first few bytes may be so similar that the encrypted output of "hello" might appear the same in the html output for different users / sessions. As such, we're using Rfc2898DerivedBytes to ensure that only the exact salt and encryption key produce the same iv bytes. Another thing to notice is that we're using the encryption key itself as the password passed to the Rfc2898DerivedBytes constructor. This essentially means that the encryption key and the passed in salt string is used to derive the actual iv (salt) used for encryption.

The Html Helper

Add a file called InputExtensions.cs and add the following code to it:


namespace HiddenEncryptDemo.Helpers
{
    using System.Web.Mvc;
    using System.Web.Mvc.Html;
    using System.Text.RegularExpressions;

    public static class InputExtensions
    {
        public static HeartysoftHtmlHelper Heartysoft(this HtmlHelper helper)
        {
            return new HeartysoftHtmlHelper(helper);
        }
    }

    public partial class HeartysoftHtmlHelper
    {
        private readonly HtmlHelper _helper;
        private readonly ISettingsProvider _settings;

        private static Regex _valueExtractorRegex = new Regex(".*value=\"(.+)\"/*", RegexOptions.Compiled);

        public HeartysoftHtmlHelper(HtmlHelper helper)
            : this(helper, new SettingsProvider())
        {
        }

        public HeartysoftHtmlHelper(HtmlHelper helper, ISettingsProvider settings)
        {
            _helper = helper;
            _settings = settings;
        }

        public string GetAntiForgeryToken(string salt)
        {
            var input = _helper.AntiForgeryToken(salt).ToString();
            var match = _valueExtractorRegex.Match(input);
            return match.Groups[1].Value;
        }

        public MvcHtmlString EncryptedHidden(string name, object value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            var strValue = value.ToString();
            string salt = GetAntiForgeryToken(_settings.SaltGeneratorKey);

            var encrypter = new RijndaelStringEncrypter(_settings, salt);
            var encryptedValue = encrypter.Encrypt(strValue);
            encrypter.Dispose();

            var encodedValue = _helper.Encode(encryptedValue);
            var newName = string.Concat(_settings.EncryptionPrefix, name);

            return _helper.Hidden(newName, encodedValue);
        }
    }
}

Let's dissect the helper a bit. There are basically two important methods. The first is GetAntiForgeryToken:


public string GetAntiForgeryToken(string salt)
{
    var input = _helper.AntiForgeryToken(salt).ToString();
    var match = _valueExtractorRegex.Match(input);
    return match.Groups[1].Value;
}

This get's the anti forgery token string given a specific salt. As I mentioned before, the AntiForgeryToken helper gets its value from a class internal to ASP.NET MVC and thus we can't get the value directly. To work around this, I'm using the helper to het the html of the anti forgery token hidden input and using a Regex to parse its value. The other interesting method is the EncryptedHidden function:


public MvcHtmlString EncryptedHidden(string name, object value)
{
    if (value == null)
    {
        value = string.Empty;
    }

    var strValue = value.ToString();
    string salt = GetAntiForgeryToken(_settings.SaltGeneratorKey);

    var encrypter = new RijndaelStringEncrypter(_settings, salt);
    var encryptedValue = encrypter.Encrypt(strValue);
    encrypter.Dispose();

    var encodedValue = _helper.Encode(encryptedValue);
    var newName = string.Concat(_settings.EncryptionPrefix, name);

    return _helper.Hidden(newName, encodedValue);
}

This method uses the previous one to get the salt, creates the encrypter and encrypts the input. It then uses the regular Hidden() helper to return the <input> tag with the value set to the encrypted data.

Configuration

Go to web.config and ensure that under <configuration>, the following entries are present:


<appSettings>
    <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/>
</appSettings>

The View

At this point, go to Index.aspx and change the html of the form to:


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
    <%= Html.Heartysoft().EncryptedHidden("computer.Setting", "hello world!") %>
    <%--<%= Html.Hidden("computer.Setting", "hello world!") %>--%>
    <input type="submit" value='submit' />
<% Html.EndForm(); %>

I've just commented out the old helper and used our new helper. If you run the page and view the source, you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="GlafeVH/jrfim7gXfqhSHg==" />

If you refresh the page and view source, you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="GlafeVH/jrfim7gXfqhSHg==" />

i.e. the value of the hidden input has not changed. If you open the same page in a different browser or close and open the browser or open the same page in a new window (not tab as sessions are shared across tabs), then you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="IM94hlaZP4HmqOhxw7Ewyg==" />

Notice how the value is different from the previous value. If you kept the previous browser open, then refreshing the page and viewing source, you should see this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="GlafeVH/jrfim7gXfqhSHg==" />

In other words, each session is getting a unique value that's different from the value got in a different session. As such, we can say that the encryption is using a per session salt which makes brute force attacks that much harder.

Please note that since the salt is per session, the exact values of the hidden inputs will vary greatly from the ones shown here. What's important is that in the same session, the value is the same, but the value differs from session to session.

The Controller Factory

We now need to actually decrypt values when the page is posted back. We do this via a custom ControllerFactory. Our controller factory will look for any encrypted input parameters, decrypt them and add them to RouteData using their original names so that they're available to the model binder. Add a class called DecryptingControllerFactory:


namespace HiddenEncryptDemo.Helpers
{
    using System.Linq;
    using System.Web.Mvc;
    using System.IO; 
   
    public class DecryptingControllerFactory : DefaultControllerFactory
    {
        private ISettingsProvider _settings = new SettingsProvider();

        public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            var parameters = requestContext.HttpContext.Request.Params;
            var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_settings.EncryptionPrefix)).ToList(); 

            IEncryptString decrypter = null;

            foreach (var key in encryptedParamKeys)
            {
                if (decrypter == null)
                {
                    decrypter = GetDecrypter(requestContext);
                }

                var oldKey = key.Replace(_settings.EncryptionPrefix, string.Empty);
                var oldValue = decrypter.Decrypt(parameters[key]);
                requestContext.RouteData.Values[oldKey] =  oldValue;
            }

            if (decrypter != null)
            {
                decrypter.Dispose();
            }

            return base.CreateController(requestContext, controllerName);
        }

        private IEncryptString GetDecrypter(System.Web.Routing.RequestContext requestContext)
        {
            var salt = GetCurrentSalt(requestContext);
            var decrypter = new RijndaelStringEncrypter(_settings, salt);
            return decrypter;
        }

        private string GetCurrentSalt(System.Web.Routing.RequestContext requestContext)
        {
            var controllerContext = new ControllerContext();
            controllerContext.RequestContext = requestContext;
            var viewContext = new ViewContext(controllerContext, new WebFormView("dummy"),
                new ViewDataDictionary(), new TempDataDictionary(), TextWriter.Null);

            var helper = new HtmlHelper(viewContext, new ViewPage());
            var afToken = helper.Heartysoft().GetAntiForgeryToken(_settings.SaltGeneratorKey);
            return afToken;
        }
    }
}

The controller factory is quite simple. It first checks the request parameters to see if there are any that have a key with a prefix equal to the EncryptionPrefix provided by the settings provider. If there are, we cycle through each and add an entry into RouteData with the key set to the parameter's key minus the prefix and the value set to the decrypted value. We create the decrypter only if needed so that if there aren't any encrypted hidden inputs, we don't waste any resources. Furthermore, if we do need a decrypter, we create an instance and reuse it for all the encrypted parameters for the request. Since the salt is per session, reusing the decrypter for all the encrypted parameters in the request is safe and reduces resource usage. To create the decrypter, we need the salt value that has to be equal to the salt used for encrypting. This is where the very interesting GetCurrentSalt method comes in:


private string GetCurrentSalt(System.Web.Routing.RequestContext requestContext)
{
    var controllerContext = new ControllerContext();
    controllerContext.RequestContext = requestContext;
    var viewContext = new ViewContext(controllerContext, new WebFormView("dummy"),
        new ViewDataDictionary(), new TempDataDictionary(), TextWriter.Null);

    var helper = new HtmlHelper(viewContext, new ViewPage());
    var afToken = helper.Heartysoft().GetAntiForgeryToken(_settings.SaltGeneratorKey);
    return afToken;
}

What this method is doing is it's faking a view context and controller context, setting the controller context's request context to that of the current request and using the fake view context and controller context to create a new instance of the HtmlHelper class. Then, it calls our previously coded GetAntiForgeryToken helper to get the value of the salt string. This is a very useful technique of creating an instance of the HtmlHelper class and can be used to create helpers outside of view code. Over at the ASP.NET forums, Brad Wilson expressed concerns that passing in TextWriter.Null may cause problems, but I believe this is not so. TextWriter.Null provides a TextWriter instance that can be written to but not read from. I don't think any Html helpers should be reading from the TextWriter – they should only be writing to it. And even if there are such helpers, AntiForgeryToken() and our GetAntiForgeryToken() do not read from the TextWriter, so this is perfectly safe for our purposes.

The end result of all of this is that if there are encrypted hidden inputs, then they are decrypted and the entries with original names (i.e. wothout the prefix) are added to RouteData. This ensures the original entries are available for ModelBinding purposes. The last change you need to do is open up the global.asax.cs file and ensure the Application_Start looks like this:


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory));
}

Running the Page

We'll need to stop the dev server (if you're using IIS, simply restart it). We need to do this because we made changes to the global.asax.cs page. Right click the dev server's icon in your traybar and click stop:

stop-server

You can now simply hit ctrl + F5 in VS to run the page. Click the button and you should see this:

result

Run the page in another browser and click the button. You should see the same results. Viewing the source, you'll see that while pages processed in the same session always has the same encrypted value, a different session in a different browser produces a different encrypted value. Still, when either value is posted to the server, the decrypting controller factory correctly decrypts the values and we get our original value in RouteData. Neither the encryption key nor the encryption iv is ever passed to the client and thus the process is much more secure than our previous approach.

And that's all there is to enable an unobtrusive, easy to use, secure, reusable and salted hidden inputs in ASP.NET MVC. On top of that, it should perform better since we're using managed Rijndael encryption which does not call out to non-managed resources like TDES does.

Possible Questions

I would recommend you first read the "possible questions" section of the previous article. Other than those:

3. Is the data really secure?
The previous approach was pretty secure. As I mentioned, an attacker would need at least 2^56 attempts. This new approach makes it much much more harder for the attacker to succeed. So, it's even more secure. The previous approach didn't tackle CSRFs but relied on the developer using AntiForgeryToken on the pages and the associated ValidateAntiForgeryToken attribute on the controller action being posted to. This approach handles CSRFs itself.

8. How is the performance?
The performance is pretty good. The controller factory only creates a decrypter when needed and reuses it for decrypting all encrypted parameters. We do, however, need to create an encrypter for each call to Html.Heartyfoft().EncryptedHidden(). A way to negate this would be to use some sort of per request IOC container so that one instance is used for processing a whole request. In my opinion, helpers should not have state, but if you want, implementing that part should be easy.

Source Code

Download

kick it on DotNetKicks.com Shout it

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET MVC | ASP.NET | Security

Encrypted Hidden Inputs in ASP.NET MVC

by ashic 25. February 2010 02:27

I have written an update to this article that shows a better way of doing what is presented here. The core concepts are pretty much the same and I would request you to read this one before the improved version.

There are many situations where we need to persist some data during the user's session. There are a few options in achieving this:

1. Session: Using the Session object will persist the data in server resources. By default, they are persisted to server RAM. Using Session is very simple as to persist, you just go Session["key"] = "value, and to retrieve, var val = Session["key"] as [type]. There are a couple of problems in this approach, however. Firstly, it consumes server resources. If you anticipate large volumes of traffic, you can't store the data in the server's RAM cause it'll simply be too much of a resource hog. Second, if you're using a web farm setup where multiple physical machines (or virtual machines) are hosting your site, then data stored on one machine's RAM will not be available to the other machine. So, if requests from the same user go to different machines, complications are bound to arise. The workaround to this can be using a state server. That also needs setting it up and maintaining, load balancing etc. Furthermore, using a state server would result in a (however small) time delay where state information is persisted and retrieved during requests.

2. Cookies: Information can easily be persisted by using cookies. You can't rely on them though, as many users have them disabled – and they're very easy to disable.

3. Hidden Inputs: Using this method, you store the data as the value of hidden input controls. These values get posted during form submission and you can retrieve them easily. The data is stored in the html sent to the browser and thus does not consume any server resources. There's a slight amount of work required to store the value and retrieve it, but other than that, it's an ideal way to persist session specific data in a scalable way. We are going to explore this method in this article.

I don't discuss ViewState and ControlState here as the discussion is about ASP.NET MVC and not Webforms.

The Problem

Although hidden inputs sound great, there's one major flaw. The user can just go to the browser's "view source" option and actually see what value is persisted. This may not be a problem if you're simply storing a user's colour choice for example, but for sensitive data (for example, some id) that needs to be hidden from prying eyes (of the user and / or eavesdroppers), it can be a major issue.

The Solution

We try to solve the security issue by creating an Html Helper which will encrypt the data to be stored in the hidden input control. When the data is posted back, we need to ensure that consuming the data in the hidden input control is no more difficult than using a plain hidden input. To achieve this, we create a custom controller factory.

The Project

Let's create a default ASP.NET MVC 2 Web Application in VS 2010. Name it "HiddenEncryptDemo".

I installed MVC 2 RC 2 before installing VS 2010 RC. If you have some other combination, slight changes may be necessary.

The first thing I'm going to do is to create a very simple Computer.cs class in the Model folder:


public class Computer
    {
        public string Setting { get; set; }
    }

The reason I'm adding this class is to show that our approach will work not just on simple action parameters, but also on more complex types without a need for a separate model binder.

Next, add this to the Index.aspx view (in the /Views/Home folder):


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
        <%= Html.Hidden("computer.Setting", "hello world!") %>
        <input type="submit" value='submit' />
<% Html.EndForm(); %>

After that, add the following method to the Home controller:


        [HttpPost]
        public ActionResult Something(Computer computer)
        {
            ViewData["Message"] = computer.Setting;
            return View("Index");
        }

Nothing fancy, we're just setting the ViewData["Message"] to the computer.Setting value. Notice that in the code we added to Index.aspx, the form submitted to "Something" (the name of our action) and the name of the hidden input was "computer.Setting". The default model binder will find a value for "computer.Something" in the request parameters and upon seeing that it can set the Setting property of the computer parameter, it's going to set computer.Setting to the value it found (in our case, "hello world!"). If you run the project, you should see the index page with a submit button. Clicking the button will result in a post to the server where the Something action will get called. The Something action will then set the ViewData["Message"] and return the Index view. As such, you will see the words "hello world!" displayed on the page:

hello

On the initial Index page (or the page got after clicking the button), if you right click anywhere and select view source, you should see this (among other things):

hidden

Notice how the helper added a hidden input control, set its id to "computer_Setting" and name to "computer.Setting". The name is used as the key of the data in the post variables. Also notice how the value is "hello world!". The value is clearly visible to anyone looking at the source. Our goal is to come up with a way to hide that value from clear sight while at the same time ensuring everything works as smoothly and easily as it just did. A bonus would be if no change was required in the controller's code (i.e. no specific attributes needed on the consuming action or controller, no special model binding needed for the parameter etc.) – it would be unobtrusive.

The Encryption Provider

The first thing we'll need is an encrypting mechanism that's easy to use. Create a new folder in the project called "Helpers". [I know, I know, I should structure it better – but this is a demo :) ] Add a new interface called IEncryptString to the folder. The interface is very simple:


public interface IEncryptString
{
    string Encrypt(string value);
    string Decrypt(string value);
    string Prefix { get; }
}

Add an implementation of that interface called ConfigurationBasedEncryptionProvider:


public class ConfigurationBasedStringEncrypter : IEncryptString
{
    private static readonly ICryptoTransform _encrypter;
    private static readonly ICryptoTransform _decrypter;
    private static string _prefix;
    static ConfigurationBasedStringEncrypter()
    {
        //read settings from configuration
        var key = ConfigurationManager.AppSettings["EncryptionKey"];
        var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"];
        bool useHashing = true;
        if (string.Compare(useHashingString, "false", true) == 0)
        {
            useHashing = false;
        }

        var prefix = ConfigurationManager.AppSettings["EncryptionPrefix"];
        if (string.IsNullOrWhiteSpace(prefix))
        {
            _prefix = "encryptedHidden_";
        }
        byte[] keyArray = null;

        if (useHashing)
        {
            var hashmd5 = new MD5CryptoServiceProvider();
            keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
            hashmd5.Clear();
        }
        else
        {
            keyArray = UTF8Encoding.UTF8.GetBytes(key);
        }

        TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
        tdes.Key = keyArray;
        tdes.Mode = CipherMode.ECB;
        tdes.Padding = PaddingMode.PKCS7;

        _encrypter = tdes.CreateEncryptor();
        _decrypter = tdes.CreateDecryptor();

        tdes.Clear();
    }

    #region IEncryptionSettingsProvider Members

    public string Encrypt(string value)
    {
        var bytes = UTF8Encoding.UTF8.GetBytes(value);
        var encryptedBytes = _encrypter.TransformFinalBlock(bytes, 0, bytes.Length);
        var encrypted = Convert.ToBase64String(encryptedBytes);
        return encrypted;
    }

    public string Decrypt(string value)
    {
        var bytes = Convert.FromBase64String(value);
        var decryptedBytes = _decrypter.TransformFinalBlock(bytes, 0, bytes.Length);
        var decrypted = UTF8Encoding.UTF8.GetString(decryptedBytes);
        return decrypted;
    }

    public string Prefix
    {
        get { return _prefix; }
    }

    #endregion

}

You can see that in the class' static constructor, I'm reading in some configuration settings. While this goes against "convention over configuration", extensibility isn't really needed in terms of encryption keys. I though about using the machine key as the encryption key, but considering sever farm environments and other issues that could arise, I just chose to go with an app setting instead. Anyway, you can see that I'm using the configuration data to set up a couple of ICryptoTransforms that will be used for encrypting and decrypting. The class has two instance methods, Encrypt and Decrypt, which do pretty much what you'd expect. I'll explain the prefix part later.

The Html Helper

With the encryption provider out of the way, let's add our Html Helper. Create a new file called InputExtensions.cs in our Helpers folder and add the following code:


namespace HiddenEncryptDemo.Helpers
{
    using System;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;

    public static class InputExtensions
    {
        public static HeartysoftHtmlHelper Heartysoft(this HtmlHelper helper)
        {
            return new HeartysoftHtmlHelper(helper);
        }
    }

    public partial class HeartysoftHtmlHelper
    {
        private readonly HtmlHelper _helper;
        private static readonly IEncryptString _encrypter = new ConfigurationBasedStringEncrypter();

        public HeartysoftHtmlHelper(HtmlHelper helper)
        {
            _helper = helper;
        }
        public MvcHtmlString EncryptedHidden(string name, object value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            string strValue = value.ToString();
            var encryptedValue = _encrypter.Encrypt(strValue);
            var encodedValue = _helper.Encode(encryptedValue);
            var newName = string.Concat(_encrypter.Prefix, name);

            return _helper.Hidden(newName, encodedValue);
        }
    }
}

Here, I'm firstly creating a "Heartysoft" helper which in turn has an EncryptedHidden method. This approach ensures that my helpers don't conflict with other helpers and in the view, I can do this: Html.Heartysoft().HiddenEncrypt(…). The EncryptedHidden helper is pretty simple. It takes the name and value to be used and encrypts the value. It also adds the prefix to the name. Remember our EncryptionProvider had a Prefix property? Here's where it comes in. So, if you pass in "computer.Name", the hidden input would have a name of "encryptedHidden_computer.Name" and id of "encryptedHidden_computer_Name". Of course, if you specify a different prefix in configuration, that would have been used instead. The prefix is used to determine which inputs need decrypted down the line. At the end of the EncryptedHidden method, we're using the default Html helper's Hidden method to generate the <input> tag.

Configuration

Go to web.config and ensure that under <configuration>, the following entries are present:


<appSettings>
        <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/>
</appSettings>

The View

At this point, go to Index.aspx and change the html of the form to:


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
       <%= Html.Heartysoft().EncryptedHidden("computer.Setting", "hello world!") %>
       <%--<%= Html.Hidden("computer.Setting", "hello world!") %>--%>
       <input type="submit" value='submit' />
   <% Html.EndForm(); %>

I've just commented out the old helper and used our new helper. If you run the page and click the button, you should see this:

encrypted-res

Notice that the message has disappeared. Viewing the html source, you should see that the html tag now looks like this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="TA9p71fDeNMfjlCPGOtK9Q==" />

Notice that although the "hello world!" value has been encrypted, the name has changed to "encryptedHidden_computer.Setting". When the form is posted back after the button click, the input parameter is thus "encryptedHidden_computer.Setting". Since there's no longer a "computer.Setting" input parameter, the model binder doesn't assign the computer parameter's Setting property with any value. The ViewData["Message"] thus stays empty and we se a blank on the result page.

So, our final challenge is to ensure that the model binder finds the original value. This will be done through a custom controller factory.

The Controller Factory

Our controller factory will look for any encrypted input parameters, decrypt them and add them to RouteData using their original names so that they're available to the model binder. Add a class called DecryptingControllerFactory:


public class DecryptingControllerFactory : DefaultControllerFactory
{
    private static IEncryptString _decrypter = new ConfigurationBasedStringEncrypter();

    public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        var parameters = requestContext.HttpContext.Request.Params;
        var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_decrypter.Prefix)).ToList();
        foreach (var key in encryptedParamKeys)
        {
            var oldKey = key.Replace(_decrypter.Prefix, string.Empty);
            var oldValue = _decrypter.Decrypt(parameters[key]);
            requestContext.RouteData.Values[oldKey] =  oldValue;
        }

        return base.CreateController(requestContext, controllerName);
    }
}

As you can see, we're identifying encrypted input parameters by checking which keys start with our encryption prefix. For each match, we get the value by decrypting and the original name by removing the prefix. We're not really changing the generated controller in any way – the controller factory just acts as a point in the request-response cycle where we can add parameters that are to be consumed by the model binder.

The last change you need to do is open up the global.asax.cs file and ensure the Application_Start looks like this:


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory));
}

We're just registering our controller factory on that last line.

Running the Page

We'll need to stop the dev server (if you're using IIS, simply restart it). We need to do this because we made changes to the global.asax.cs page. Right click the dev server's icon in your traybar and click stop:

stop-server

You can now simply hit ctrl + F5 in VS to run the page. Click the button and you should see this:

result

Finally, view the html source in the browser, and you should see that the hidden input's value is encrypted:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="TA9p71fDeNMfjlCPGOtK9Q==" />

That means that our helper has encrypted the value for us and on postback, the controller factory is decrypting the value and adding it to the input parameters so that the model binder can correctly do its thing.

Possible Questions

1. Phil Haack already has a blog post stating how to add action method parameters here:
http://haacked.com/archive/2010/02/21/manipulating-action-method-parameters.aspx
Why didn't you use that method to add the input parameter?
The method described in that post uses an action filter. There are two downsides to that. First, you'd need to go about and decorating your controllers and actions with some [Decrypt] attribute. That's obtrusive. Second-and-more-important, action filters run after model binding. That would work for simple parameters for action methods, but for complex parameters (i.e. viewmodels) or parameters that need complex model binding, that simply won't work. My approach here adds the input parameter before the model binding stage and thus, the model binders can access the correct values without changing anything.

2. Why didn't you use a ModelBinder attribute for decryption?
That could have worked, but would have required you to use the model binder whenever you intended to use the Html.Heartysoft().EncryptedHidden() helper. That's obtrusive and forgetting to set an attribute could have caused nightmares. Furthermore, your other model binders would not have recognised the values. The approach outlined here will work with any and all existing model binders.

3. Is the data really secure?
The value sent to the browser is encrypted using a key. The key is never sent to the browser. As long as the key is secure, the data is as secure as the encryption is.

4. Your code structure is crappy.
It's a demo…deal with it ;) The code is pretty straight forward and you can use your our design patterns and abstractions to make it more your-framework-friendly.

5. I changed the key but the change isn't "happening". What gives?
The encryption key and whether to use hashing or not settings are read during the static constructor of our ConfigurationBasedEncryptionProvider. If you need to change the values, restart the server. I figured you didn't need to change these at runtime and thus put them in the static constructor so that they are read in only once.

6. Why a ControllerFactory? Why not in Begin_Request in global.asax.cs?
It seems that adding the parameters in Begin_Request doesn't seem to persist further down the line. Meaning, I tried that and the new parameters added seem to disappear. Hence the controller factory. Adding the parameters at that stage seems to allow the model binder access to the parameters without any problems. Perhaps a custom ActionInvoker would also have worked, but would have needed setting on each controller – or in a custom controller factory. If the latter, then why bother with the ActionInvoker when the factory can add the parameters itself?

7. Why do you add the parameters to RouteData and not Request[…] or Request.Params[…] or Request.Form[…] etc.?
Coz those are readonly.

Source Code

Download

kick it on DotNetKicks.com  Shout it

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis

I’m Back!!

by ashic 8. December 2009 01:10

After a few months absence, I’m back. I’ve redesigned the site and a few more updates will hopefully come in the next few weeks.

So much has happened since my last post –

  • VS 2010 and .net 4.0 beta
  • MVC 2.0 beta
  • Silverlight 4.0 beta
  • New Bing maps
  • Azure improvements

-and that’s just a high level view :)

I’ll hopefully put up some articles to cover these soon. Stay tuned!

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: Community

Speaking at Microsoft Day @ Dhaka- June 20, 2009

by ashic 12. June 2009 09:04

It’s finally happening…we’re having an MS dev (and IT) event here in Dhaka, Bangladesh. This is the first one ever and marks a significant landmark for the software industry here. It’s going to be held on June 20, at IDB Bhaban Auditorium. You can find details and register (mandatory) for the event here:

http://msdnbangladesh.net/content/msday.aspx

Seats are limited…only 200 in total. It’s open for devs, IT pros and students.

I’ll be doing a half hour session on Windows Azure, from 2:45pm to 3:15pm. It’s the first time I’m doing something like this, and I hope it goes well.

If you’re a dev / IT pro / student enthusiastic about MS technology, then register and be there. The MVPs from Bangladesh will be there and they’ll be doing sessions covering asp.net, MVC, silverlight 3, Win 7, IE8, Office 2007, VS 2010 Team System, Windows Live, MS Project, Sharepoint, Exchange Server, SQL 2008 – just be there…ok?

[The final agenda will be put up on the site on June 15.]

Shout it
Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET | Community

Finally Set Up My Own Site: BlogEngine.Net

by ashic 9. June 2009 08:12

I’ve been looking to set up my own site for quite some time now. The things I had in mind required a great deal of development and, due to my workload with completing graduation and a few client projects, I just couldn’t find the time. In the last few weeks, I’ve been looking at alternatives to doing the whole thing on my own. I looked at a few offerings:

1. Graffiti

2. Umbraco

3. Dnn

4. AxCMS

5. BlogEngine.Net

 

The things I was gunning for:

1. Easy to set up

2. Easy to administer

3. Easy to extend

4. Easy to change the look

5. Must support adding my own pages / running my own code on a few pages

6. Something I can work on in my free time – not something one shot (as in you upload and it’s fixed, unless you spend hours developing)

7. Not cause me any headaches

8. Cost

 

1. Graffiti

Well…I looked here and there and I found a cool post by Joe Stagner (http://misfitgeek.com/blog/community/my-move-to-graffiti/ ) and I thought it was the right place to begin. I downloaded the express edition. I installed it and my initial reaction was “hmmm…let’s see if it gets better”. Playing around, it seems a very good package that does what it’s touted to do – it publishes content. Now, I wanted to be able to create a gallery like page that I may need for tutorials. I wanted something that would allow me to code Asp.net and not just html. Wasn’t exactly the easiest thing to do. I spent a couple of hours wanting to find out how to do it. Couldn’t. It seems everything is a blog post –and if you’re ok with that, Graffiti is awesome. I’m looking for some extensibility – without getting into the labyrinths of the API. For this, I can’t support Graffiti. Very reluctantly, I moved on. Sorry Telligent. Sorry Joe.

2. Umbraco

I’ve been looking to give umbraco a run for quite some time. I nearly used it way back in ‘04 when my boss wanted me to look at a few CMSes. At that time, the priorities changed and the CMS stuff was left in the dust. Umbraco was a nightmare to install back then – that didn’t help its cause.

So I download umbraco, installation is a breeze. Although, on my dev Win7 machine, I needed to change a few settings in IIS. I remember having to use the classic pipeline and also having to have umbraco in the root. That means it didn’t work in a sub folder of Default Website, even when configured as an application. I was looking to use the CMS at the root of my site, so that wasn’t a (big) problem.

This time, my reaction was “Whoah…this is cool.” The admin tool is awesome. It worked well with SQL Server. I was happy. Then came the extensibility test. One of the things umbraco is famous for is supported developer created modules (user controls). Since I had this in the back of my head, I thought umbraco would be a sure shot winner here. Unfortunately, it was not so. Yes, umbraco is very extensible. Yes, it can be made to do anything I want. Thing is, I didn’t want to have to “make” it do anything. I didn’t want to have to learn an API just so I could run my own Asp.net code. From Joes blog post mentioned earlier: “It’s flexible, really flexible. I’m sort of in a hurry. Umbraco wants to be embraced.” Also, my blog was at http://weblogs.asp.net/ashicmahtab – that’s on Community Server. It can export BlogML. I wanted those posts on this site. I wanted feeds to be done automatically. The blog modules for umbraco seem to be left wanting in terms of features. Sorry Neils…it would have taken too much of my time to get to where I wanted to be.

3. Dnn

Disclaimer: I hate dnn. Yet I am forced to work with it from time to time. Possibly that’s a reason for my hatred. It’s freakin’ awesome and it’s a freakin’ nightmare. It’s overkill for most things. It has some really cool features, but there’s a lot of bloat. And talk about extensibility – I can do anything if I create a module for it. But creating a module for each and everything isn’t my cup of tea. It’s counter-intuitive. Still, I installed it. installed the blog module. Made a few blog posts. It was acceptable. But I wasn’t happy. It’s blogging features wasn’t exactly great, but it was the best I had till then. It may have even made it’s way to my site. Hey – there’s even a LiveWriter plugin.

4. AxCMS

This used to be a pricey product, but it’s free in toned down mode. Unfortunately, it was too toned down for my taste. Next.

5. BlogEngine.Net

I’d actually started on getting a blog ready using dnn, when –almost by accident and reasons unknown to me, I decided to install BlogEngine. Just for laughs. Hey, I want a site, not just a blog. Still, I downloaded, set the db to SQL 2005, changed the web.config. Ran the site – doesn’t look too good, but let’s see how we can change that, shall we? I looked at how I could theme it. The admin panel couldn’t do anything –it didn’t even allow me to edit the css. So, how on earth am I supposed to style this thing. And then it hit me – there’s a theme folder with folders for each theme. And in those folders, there’s a real life MasterPage and user controls for PostView and CommentView. There’s css and images and everything. I could work with the aspx code. I could plant in any user or server controls I wanted. That’s when I started giving this some serious consideration. I started looking around for how I could put my own code and pages into the system. Well, it supports user controls very easily. Just ERROR - UNABLE TO LOAD CONTROL : path type syntax is all that’s needed. That’s HUGE. It also supports adding pages from the admin panel. There’s one drawback to that approach – the url becomes something like “www.heartysoft.com/page/pageName.aspx”. Many people don’t seem to like the “page” section in the middle there. That wasn’t too big a downer for me. Next, I saw that you can derive your custom aspx page from a BlogEngine.Net type, and it’d simple carry out all the theming and Masterpage assignment. You could then code the page however way you wanted. That’s the knockout blow right there. I can code my own asp.net code and seamlessly integrate it into BlogEngine.Net. So, I put my site up. And no, I don’t like the theme I’m using. I’ll probably change it if / when I get some free time. And even if I don’t, it’s still useable. I can add sections and pages that hold my own code later on when I get the time. It supports feedburner, is completely open source, works with SQL Server without any hassles, theming is changing aspx markup – just like editing my own markup. Importing from the Community Server generated BlogML was smooth on my pc (although a little trickier for my remote server – I’ll save that for another post). All in all, I’ve got my basic  site up and running in less than half a day. And the best thing is that I can change it, add to it and work it as if it’s my own app – without knowing anything about the API. Granted, I can do some cool stuff with the API, but that should be optional. I should be able to use my own code when I want to and need to – without being forced to swallow a framework. And this is where BlogEngine.Net has excelled above and beyond the others I’ve tried.

Even after installing, I didn’t expect to be quite this happy with this thing, and yet I am. If you haven’t tried it yet, go ahead. Go to the site: http://dotnetblogengine.net/ , download it now, give it a shot. It’s awesome.

Shout it
Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET

FileUpload Control Doesn’t Give Full Path….HELP!!!!

by ashic 19. May 2009 15:44

I’ve answered this question about five times in the last few days at the forums. It’s getting tedious so I thought I’d make a blog post about it.

Issue:

The FileUpload control has a FileName property. In FireFox (FF) and some other standards compliant browsers, the filename only holds the name of the file. It does NOT hold the full path. In some versions of Internet Explorer (perhaps all versions, but at least up until v7), the FileName property holds a full path. Hence, people see discrepancies among the browsers in terms of the FileName property. A lot of you guys are asking how to get the full path from the file upload control.

What does the FileName property do?

The FileName property holds the name of the uploaded file. In FF, it holds only the file name. In IE7 and earlier (possibly IE8 too – I’ll refer to all of them as IE from here on) it holds THE FULL PATH TO THE FILE ON THE CLIENT’S MACHINE. Read that again, and again, and one more time. It’s the full path of the uploaded file on the client’s machine. NOT THE PATH OF THE UPLOADED FILE ON THE SERVER.

WHOAH…Hang on a minute…the client machine?

Yes. It holds the path to the uploaded file on the machine of the user using the website, not the server. It’s the path you see in the file upload controls text box. It’s the user’s local path to the file. The user may have an M drive with a pics folder. The FileName property will then hold something like M:/pics/pic1.png. The server may not even have an M drive with a pics folder.

That SUCKS!!!! What good is that?

This is a decision made by the creators of IE. It was done to enable easier automation of intranet apps where file operations across the network could be accomplished easier.

Hmmm…Why do them FF guys not support it?

Them FF guys think that letting the server know about the folder structure of a user’s machine is a serious breach of security. For example, if for some reason you had a pic with the path D:\My Racy Photos With X\pic112.jpg (which may be an official photo for whatever reason) and you upload that to the office server, you wouldn’t want your boss (or anyone else) to know that you have a folder called “My Racy Photos With X” on your pc. That’s private info and would be embarrassing if leaked. The worst case scenario would be a few blushes. Now think if you had a folder with some secure info in the name. Even if the contents were secure and not the folder name, you wouldn’t want to make it easier on anyone to get to them. Hence, this can be considered a security flaw.

Ok…err…So where does my uploaded file go to?

The uploaded file is uploaded to a temp location on the server. If you process it, fine. If not, it’s purged (read deleted).

Wait…how do I store the uploaded file then?

Simple. The FileUpload control has a SaveAs(string path) method. You can use that method to save the file to any path using any filename you want. Just make sure asp.net has permissions to write to that path. So, you can do something like this to save the uploaded file to the uploads folder in the root of your website:

fileUpload1.SaveAs(Server.MapPath(“~/uploads/”) + System.IO.Path.GetFilename(fileUpload1.FileName));

[The reason I’m doing a Path.GetFileName is to ensure that even in IE, I only pass the filename and not the entire client side path to the SaveAs method.]

If you wish to provide a different username, you can do that just by changing the parameter. If you wish to append a unique key to each upload, you can use something like Guid.NewGuid().ToString() –or any other technique you want.

Ok smarty pants…saving to the file system is old school. I wanna save it to a database as an array of bytes. If I don’t have the file location, how do I get the array of bytes? How do I use File.ReadAllBytes if I don’t have a path? Huh? Huh?

Glad you asked. The FileUpload control ha a FileBytes property that gives you an array of bytes with all the contents of the file. Just use that.

Now I want to do some processing on the uploaded file. How do I get a Stream without the path?

The FileUpload control has a FileContent property that gives you a stream to the file. You can use it like this:

Stream str = fileUpload1.FileContent;
StreamReader sr = new StreamReader(str);
string contentText = sr.ReadToEnd();

What about the normal <input type=file> file uploads? I don’t like the FileUpload control. Can I get the full filename from that?

The FileUpload control uses the <input type=’file’> tag behind the covers. You’ll get no added info about the path from using a basic <input type=’file’> tag. The same restrictions apply. Using the input tag, you’ll have to do a bit more work to get to the file bytes. You can get to each uploaded file in a postback by using Request.Files. This will give you every HttpFileUpload in the request, regardless of if you use a <input type=’file’> or a FileUpload control. If using the input tag, you can set the runat=’server’ and id attributes to access it from server side code. You can then access it’s PostedFile in postback. Each object in Request.Files and each PostedFile of each <input type=’file’ runat=’server> and also each PostedFile of each FileUpload is of type HttpFileUpload. That class also has a SaveAs method that does exactly what you’d expect it to. If you need to access the stream, then it has an Inputstream property. You can use it to read the contents using a StreamReader as I’ve shown above. If you need to access the bytes, the following code will help [please note that HttpFileUpload does not have any direct way to give you all the bytes in a byte array, like the FileUpload control does]:

HttpPostedFile file = Request.Files[0]; //you can use inputid.PostedFile too
Stream str = file.InputStream;
byte[] bytes = new byte[file.ContentLength];
str.Read(bytes, 0, file.ContentLength);

After that, bytes will hold the binary content of the file.

Hope that helps.

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis
Categories: .NET | ASP.NET

Powered by BlogEngine.NET 1.6.1.0
Theme by Ashic Mahtab

Need an expert?

Ashic Mahtab
ashic@live.com
(+44) 07879927393

Stats

Featured Ads

 

Donations

I maintain this site and create all content entirely in my own time just to help you guys out. If you find the stuff helpful, or cool or just like what you see, I'd appreciate you chipping in to help out with the hosting costs. It's easy to do so - just click the button below - no amount is too low :)