I recently got woken up due to a production web application I am responsible for being down. Most of the reports indicated the users were not getting .net errors, it simply was not responding to their requests for data.
When I logged onto the site, and navigated to the problem area, I was greated with this error: "A potentially dangerous Request.Form value was detected from the client"; along with the recommendation to disable EnableEventValidation if the error were incorrect.
Bandaid
In an effort to just get the site back up and running I flipped the switch on that page variable. I'm not sure why the clients never saw the error, but as soon as I flipped that variable I started seeing the same problem that they were reporting; no errors, just no response to requests for data.
Since my development server was functioning perfectly I had to use Response.Write 's on the production server to start tracking down what was going on. I was shocked to learn that the Page.IsPostBack variable was always returning true. I got the site functioning again by finding an alternative variable I could use in its place.
With the problem under control efforts were redirected to figuring out what triggered my bad morning.
Problem
My co-worker discovered that windows updates had been applied by our hosting company to our production server. Through the good old fashioned method of uninstalling until things started working again, he discovered that KB2894842 and KB2894843 were the culprits. Apparently Microsoft had found a security flaw in pages that had EnableViewStateMac turned off and the fix had resulted in some rather odd results for our site.
EnableViewStateMac was turned off for our site because we were using javascript to submit data from one page to another. It was necessary to avoid the postback mac validation error "Validation of viewstate MAC failed". A better option might have been to use strictly .NET controls to handle postback, however it was an old site that originated in classic ASP and the javascript has always worked very well.
Solution
Later versions of .NET now have the ability to post to other pages on the site using the PostBackUrl attribute built into some controls. I was hoping to capture the javascript generated by such a control and use it to modify my stand alone javascript to enable it to perform a successful post to the next page.
I was disappointed to discover that using the PostBackUrl attribute not only generated additional .NET javascript, but also created a __PREVIOUSPAGE hidden field with a hash value for the next page to validate. However, I was pleasantly surprised to find out that as long as any control was rendered with that attribute then my original javascript was able to submit the page successfully with no errors. In fact my site was now able to function with EnableViewStateMac turned on giving me back that additional piece of security.
The compromise I finally settled on was adding a button with a PostBackUrl to my master page and hiding it with CSS.
I am still not sure why the Page.IsPostBack variable stopped functioning correctly and that is part of what prompted this post. Hopefully if I, or anyone else, runs into a similar problem in the future there will be a place to start looking due to this.
Thursday, January 16, 2014
Tuesday, December 10, 2013
Microsoft Windows Event Viewer Subscriptions
Most IT administrators love client server models that allow them to manage or report on various things from a centralized location. As part of my companies enhanced security policies, I was looking for a way to monitor and report on all user logon and logoff events on all the computers across our network.
Surprisingly this does not seem to be an overly popular thing to attempt as finding information on it was quite difficult.
At windowsecurity.com I found an article that explained how to setup Log Subscriptions, a feature which Microsoft has included in their operating systems beginning with Vista. In summary it had me:
1. open an elevated command prompt (run as administrator)
2. on the central aggregator machine
2a. run the command "winrm qc -q"
2b. run the command "wecutil qc /q"
2c. open up the event viewer and create a new subscription under "Subscriptions"
3. on each client machine being subscribed to
3a. run the command "winrm qc -q"
Note: There are patches available for windows 2003 and windows XP that must be installed before they are able to participate as a subscriber or subscribed to machine.
The account used to setup the subscription on the aggregator machine must be added to the "Event Log Readers" group on the client machine. Using a domain admin account avoids this requirement.
Some logs, the Security log in particular, require extra permissions to subscribe to it. Even though the machine is accessed by a domain admin account the log is still being read by the local "Network Service" built-in account, so that account must also be added to the "Event Log Readers" local group on every client machine. I was made aware of this by a post on Microsoft's Technet website.
WINDOWS XP and 2003 NOTE:
WinRM has really come into its own in Windows Vista and later. However, it can be installed on windows XP and 2003 as well by downloading the patch from Microsoft Here, and the KB for it is Here.
Once installed it should be noted that the new "server" may be listening on port 80, which means you would need to setup a separate subscription on the aggregator machine for all such installs and change the default port it queries to port 80.
Also, there is no "Event Log Readers" group on the older OSs. In order to allow the security logs to be read on these older machines the registry needs to be modified for windows 2003 or the service needs to be run as Local System for windows XP as detailed here.
Another note for older machines: there are new logs that older machines do not support. If you try and create a subscription for logs that the older machines can't handle, the older machines will likely throw error 0x6 and not return any data at all.
STAGE 2
Of course the log viewer is pretty limited in storage and is certainly designed with reporting speed in mind. Which meant I needed to find some way to save the data elsewhere. The logical choice for me was Microsoft SQL Server since that is what my company uses.
After several days of pain and suffering learning Power Shell for the first time, I came up with the following script that was able to extract the data I cared about most from the ForwardedEvents log, upload it to SQL, and delete the log.
This script will lose any events that come in between when the data is loaded into Power Shells memory and the next line where the log is truncated. It also doesn't recover any data lost due to SQL upload errors.
It is also important to note that this script must be run with elevated permissions in the task scheduler, otherwise it will fail to clear the event log on each run.
VMWARE NOTE:
I did notice an odd problem on windows 7 machines that were running VMWare player. When I tried to enable WinRM I got the error:
WinRM firewall exception will not work since one of the network connection types on this machine is set to Public
Unfortunately, on a domain connected machine, this setting can not be easily modified; fortunately I found a powershell script here that was able to do the trick for me:
Surprisingly this does not seem to be an overly popular thing to attempt as finding information on it was quite difficult.
At windowsecurity.com I found an article that explained how to setup Log Subscriptions, a feature which Microsoft has included in their operating systems beginning with Vista. In summary it had me:
1. open an elevated command prompt (run as administrator)
2. on the central aggregator machine
2a. run the command "winrm qc -q"
2b. run the command "wecutil qc /q"
2c. open up the event viewer and create a new subscription under "Subscriptions"
3. on each client machine being subscribed to
3a. run the command "winrm qc -q"
Note: There are patches available for windows 2003 and windows XP that must be installed before they are able to participate as a subscriber or subscribed to machine.
The account used to setup the subscription on the aggregator machine must be added to the "Event Log Readers" group on the client machine. Using a domain admin account avoids this requirement.
Some logs, the Security log in particular, require extra permissions to subscribe to it. Even though the machine is accessed by a domain admin account the log is still being read by the local "Network Service" built-in account, so that account must also be added to the "Event Log Readers" local group on every client machine. I was made aware of this by a post on Microsoft's Technet website.
WINDOWS XP and 2003 NOTE:
WinRM has really come into its own in Windows Vista and later. However, it can be installed on windows XP and 2003 as well by downloading the patch from Microsoft Here, and the KB for it is Here.
Once installed it should be noted that the new "server" may be listening on port 80, which means you would need to setup a separate subscription on the aggregator machine for all such installs and change the default port it queries to port 80.
Also, there is no "Event Log Readers" group on the older OSs. In order to allow the security logs to be read on these older machines the registry needs to be modified for windows 2003 or the service needs to be run as Local System for windows XP as detailed here.
Another note for older machines: there are new logs that older machines do not support. If you try and create a subscription for logs that the older machines can't handle, the older machines will likely throw error 0x6 and not return any data at all.
STAGE 2
Of course the log viewer is pretty limited in storage and is certainly designed with reporting speed in mind. Which meant I needed to find some way to save the data elsewhere. The logical choice for me was Microsoft SQL Server since that is what my company uses.
After several days of pain and suffering learning Power Shell for the first time, I came up with the following script that was able to extract the data I cared about most from the ForwardedEvents log, upload it to SQL, and delete the log.
$WriteTableName = "RawLogs"
$ColumnsForQuery = "Level, EntryDate, Source, EventID, TaskCategory, LogName, Computer, TargetName, Message"
$ParamNames = "@Level, @EntryDate, @Source, @EventID, @TaskCategory, @LogName, @Computer, @TargetName, @Message"
$WriteConnectionString = "server=servername;Trusted_Connection=Yes;Database=EventLogs; Connection Timeout=120"
$WriteConn = New-Object System.Data.SqlClient.SqlConnection
$WriteConn.ConnectionString = $WriteConnectionString
$WriteConn.Open() | Out-Null
[string]$SQLQuery = ("INSERT INTO {0} ({1}) VALUES ({2})" -f
$WriteTableName,
$ColumnsForQuery,
$ParamNames
)
#CREATE TABLE [dbo].[RawLogs] (
# [Level] [varchar] (100) NULL ,
# [EntryDate] [varchar] (100) NULL ,
# [Source] [varchar] (500) NULL ,
# [EventID] [varchar] (100) NULL ,
# [TaskCategory] [varchar] (500) NULL ,
# [LogName] [varchar] (500) NULL ,
# [Computer] [varchar] (500) NULL ,
# [TargetName] [varchar] (500) NULL ,
# [Message] [varchar] (max) NULL
#) ON [PRIMARY]
#GO
# $yesterday = (Get-Date) - (New-TimeSpan -day 1)
#Get-WinEvent -logname "ForwardedEvents" | where {$_.timecreated -ge $yesterday} |
#Format-Table TimeCreated, ID, ProviderName, LevelDisplayName, Message -AutoSize -Wrap | out-file C:\ForwardedEvents.txt
# $events = Get-WinEvent -logname "ForwardedEvents" -MaxEvents 5 | where {$_.timecreated -ge $yesterday}
#time calculation = miliseconds * seconds * minutes * hours = 1000*60*60*12 = 12 hours
$query = '*[System[TimeCreated[timediff(@SystemTime) <= 43200000]]]' #43200000]]]'
[xml]$xmlevents = wevtutil qe ForwardedEvents /q:$query /e:Events
#$xmlevents.Events.Event | %{ $_.System } | select Computer | export-csv 'C:\ForwardedEvents.txt' -NoTypeInformation
#$xmlevents.Events.Event | select @{Name="EventID"; Expression={$_.System.EventID}},@{Name="Computer"; Expression={$_.System.Computer}},@{Name="Message"; Expression={$_.RenderingInfo.Message}} | export-csv 'C:\ForwardedEvents.txt' -NoTypeInformation
#@{Name="TargetName"; Expression={ $_.EventData.InnerXml.substring($_.EventData.InnerXml.indexOf('TargetUserName'),$_.EventData.InnerXml.indexOf('TargetUserName')+20) }},
$DataImport = $xmlevents.Events.Event | select @{Name="Level"; Expression={$_.System.Level}},
@{Name="EntryDate"; Expression={$_.System.TimeCreated.SystemTime}},
@{Name="Source"; Expression={$_.System.Provider.Name}},
@{Name="EventID"; Expression={$_.System.EventID}},
@{Name="TaskCategory"; Expression={$_.RenderingInfo.Task}},
@{Name="LogName"; Expression={$_.RenderingInfo.Channel}},
@{Name="Computer"; Expression={$_.System.Computer}},
@{Name="TargetName"; Expression={ $_.EventData.InnerXml.substring($_.EventData.InnerXml.indexOf('>', $_.EventData.InnerXml.indexOf('TargetUserName'))+1,$_.EventData.InnerXml.indexOf('<', $_.EventData.InnerXml.indexOf('TargetUserName'))-($_.EventData.InnerXml.indexOf('>', $_.EventData.InnerXml.indexOf('TargetUserName'))+1)) }},
@{Name="Message"; Expression={$_.RenderingInfo.Message}}
wevtutil.exe cl ForwardedEvents # erase the event log
#$DataImport
#Exit
ForEach($Obj in $DataImport)
{
$writeCmd = new-object System.Data.SqlClient.SqlCommand
$writecmd.Connection = $WriteConn
If ($Obj -ne $Null)
{
If ($Obj.Level -ne $Null -and $Obj.Level.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@Level", $Obj.Level) | out-null }
else { $writeCmd.Parameters.AddWithValue("@Level", [DBNull]::Value) | out-null }
If ($Obj.EntryDate -ne $Null -and $Obj.EntryDate.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@EntryDate", $Obj.EntryDate) | out-null }
else { $writeCmd.Parameters.AddWithValue("@EntryDate", [DBNull]::Value) | out-null }
If ($Obj.Source -ne $Null -and $Obj.Source.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@Source", $Obj.Source) | out-null }
else { $writeCmd.Parameters.AddWithValue("@Source", [DBNull]::Value) | out-null }
If ($Obj.EventID -ne $Null -and $Obj.EventID.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@EventID", $Obj.EventID) | out-null }
else { $writeCmd.Parameters.AddWithValue("@EventID", [DBNull]::Value) | out-null }
If ($Obj.TaskCategory -ne $Null -and $Obj.TaskCategory.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@TaskCategory", $Obj.TaskCategory) | out-null }
else { $writeCmd.Parameters.AddWithValue("@TaskCategory", [DBNull]::Value) | out-null }
If ($Obj.LogName -ne $Null -and $Obj.LogName.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@LogName", $Obj.LogName) | out-null }
else { $writeCmd.Parameters.AddWithValue("@LogName", [DBNull]::Value) | out-null }
If ($Obj.Computer -ne $Null -and $Obj.Computer.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@Computer", $Obj.Computer) | out-null }
else { $writeCmd.Parameters.AddWithValue("@Computer", [DBNull]::Value) | out-null }
If ($Obj.TargetName -ne $Null -and $Obj.TargetName.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@TargetName", $Obj.TargetName) | out-null }
else { $writeCmd.Parameters.AddWithValue("@TargetName", [DBNull]::Value) | out-null }
If ($Obj.Message -ne $Null -and $Obj.Message.GetType().ToString() -ne "System.Xml.XmlElement") { $writeCmd.Parameters.AddWithValue("@Message", $Obj.Message) | out-null }
else { $writeCmd.Parameters.AddWithValue("@Message", [DBNull]::Value) | out-null }
$writecmd.CommandText = $SQLQuery
$Null = $writecmd.ExecuteNonQuery()
}
}
$WriteConn.close()
This script will lose any events that come in between when the data is loaded into Power Shells memory and the next line where the log is truncated. It also doesn't recover any data lost due to SQL upload errors.
It is also important to note that this script must be run with elevated permissions in the task scheduler, otherwise it will fail to clear the event log on each run.
VMWARE NOTE:
I did notice an odd problem on windows 7 machines that were running VMWare player. When I tried to enable WinRM I got the error:
WinRM firewall exception will not work since one of the network connection types on this machine is set to Public
Unfortunately, on a domain connected machine, this setting can not be easily modified; fortunately I found a powershell script here that was able to do the trick for me:
$nlm = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"))
$connections = $nlm.getnetworkconnections()
$connections |foreach {
if ($_.getnetwork().getcategory() -eq 0)
{
$_.getnetwork().setcategory(1)
}
}
Tuesday, October 8, 2013
WebResource.axd The resource cannot be found 404
Web Resources are a very nice features of .net giving the developer the capability to bundle a bunch of files into a single DLL for easy transport. However, it is not very forgiving when it comes to coding errors. There are a lot of sites out there that offer details on how to add a web reference, what is missed is just how precise each piece must be written. Unfortunately when it breaks the errors are simple 404's which are not helpful in the least.
Step 1: Include the file in your project, right click, to go properties, and set it as an Embedded Resource.
Step 2: add: [assembly: WebResource("DefaultNamespace.Folder1.Folder2.FileName.png", "image/png")]
This step has a couple of gotcha's. First the contentType (the second parameter) must be correct, WebResource.axd will serve up the file with this content type. So a content type of text/html used with an image file will result in a screen full of ascii.
Secondly the first parameter is the name of the file after it has been compiled. A reflector tool comes in very handy here for verification purposes, I use dotPeek. When compiled the file is named using the folder structure it is contained in prefixed by the Default namespace with periods as separators. The Default namespace can be found by right clicking on the project name and opening properties. Note: it is NOT the assembly name. Failure to perform this part correctly will simply result in the file never being found, again a 404 error.
Step 3: Use something like this:
this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "DefaultNamespace.Folder1.Folder2.FileName.png");
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), "DefaultNamespace.Folder1.Folder2.FileName.js");
Those lines allow you to get the URL for an image, or add a script reference at the beginning of the HTML output for the page. Notice how the filename referenced is IDENTICAL to the filename used in the [assembly: tag up above? If these two do not match and are not correct then it will result in a 404 error; although a URL or script reference may still be generated, it will not be the correct one.
Also, the GetType() call is very important as well. The type referenced must at the least be in the same project as the embedded resource, one person said it needed to be in the same folder as well, but I am fairly certain that is not the case. To play it safe I just use this.GetType() in the local file or Resource.cs file that generates the WebResource URL. I say playing it safe because I have been bitten before by renaming my Resource.cs file and having my typeof(PageName) reference a completely different, but still valid object. This resulted in no compile errors and a good looking URL, but always 404 errors.
Troubleshooting:
It can be very helpful to determine which /WebResource.axd is the one you are looking for in your HTML source code. To help with this someone over at Telerik has created a nice page that allows you to decrypt the URL and see a more friendly name.
Something to look for in the friendly name is your assembly name, if it does not reference the assembly that houses the embedded resource then you probably screwed up the Type when generating the URL.
Step 1: Include the file in your project, right click, to go properties, and set it as an Embedded Resource.
Step 2: add: [assembly: WebResource("DefaultNamespace.Folder1.Folder2.FileName.png", "image/png")]
This step has a couple of gotcha's. First the contentType (the second parameter) must be correct, WebResource.axd will serve up the file with this content type. So a content type of text/html used with an image file will result in a screen full of ascii.
Secondly the first parameter is the name of the file after it has been compiled. A reflector tool comes in very handy here for verification purposes, I use dotPeek. When compiled the file is named using the folder structure it is contained in prefixed by the Default namespace with periods as separators. The Default namespace can be found by right clicking on the project name and opening properties. Note: it is NOT the assembly name. Failure to perform this part correctly will simply result in the file never being found, again a 404 error.
Step 3: Use something like this:
this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "DefaultNamespace.Folder1.Folder2.FileName.png");
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), "DefaultNamespace.Folder1.Folder2.FileName.js");
Those lines allow you to get the URL for an image, or add a script reference at the beginning of the HTML output for the page. Notice how the filename referenced is IDENTICAL to the filename used in the [assembly: tag up above? If these two do not match and are not correct then it will result in a 404 error; although a URL or script reference may still be generated, it will not be the correct one.
Also, the GetType() call is very important as well. The type referenced must at the least be in the same project as the embedded resource, one person said it needed to be in the same folder as well, but I am fairly certain that is not the case. To play it safe I just use this.GetType() in the local file or Resource.cs file that generates the WebResource URL. I say playing it safe because I have been bitten before by renaming my Resource.cs file and having my typeof(PageName) reference a completely different, but still valid object. This resulted in no compile errors and a good looking URL, but always 404 errors.
Troubleshooting:
It can be very helpful to determine which /WebResource.axd is the one you are looking for in your HTML source code. To help with this someone over at Telerik has created a nice page that allows you to decrypt the URL and see a more friendly name.
Something to look for in the friendly name is your assembly name, if it does not reference the assembly that houses the embedded resource then you probably screwed up the Type when generating the URL.
Tuesday, October 1, 2013
Upgrading from Microsoft.Practices.EnterpriseLibrary v 3.0 to v 6.0
Obviously the versions I was jumping in between are quite different, this application had not been upgraded in some time. Taking one step at a time I decided to start by swapping out the DLLs. I ran into a minor issue when I only modified the references for one of the projects in my solution to the new version, somehow one of the other projects rolled back the files during compile time to the previous version. Re-updating the files and modifying the references for all the projects at once solved that issue.
But then I was presented with an odd error:
But then I was presented with an odd error:
The type or namespace name 'Practices' does not exist in the namespace 'Microsoft'
It was especially confusing since intellisense and object name highlighting was detecting the presence of the namespace correctly. After some web searching I ran into this thread, which implied that the target version of the framework I was building for needed to be newer or different then the one I was currently using. I had yet to convert the project over so I was still compiling for version 3.5 of the framework. I changed that to version 4.5 and was then able to build successfully.
------------
Unfortunately that was not the end of my problems. I was using asp:ScriptManager in my project ( any control that references ScriptManager, such as the replacement control from the AjaxControlToolkit, will also have this issue ) and as a result when I tried running the project under the .net 4.0 web engine I got this error:
I was unable to find a work around for that error as long as I was using the ScriptManager control. If I removed that control then the web application worked perfectly. Since my server only had windows 2003 on it upgrading IIS to .net 4.5 was not an option. I finally decided to roll back from EnterpriseLibrary v6.0 to v5.0.414 and build the project for .net 4.0 instead of 4.5.
This worked well until I started getting an odd error:
It turned out that one of my solutions was hanging onto an old version of one of my other projects DLLs, even though the primary project was referencing the sub project in my solution apparently it did not refresh that sub projects DLL in its cache. This was an issue since at one point I had the sub project referencing version 6.0.0.0 of the EnterpriseLibrary DLL. I removed and re-added the primary project's reference to the sub project, the cache refreshed itself and all was well.
------------
Unfortunately that was not the end of my problems. I was using asp:ScriptManager in my project ( any control that references ScriptManager, such as the replacement control from the AjaxControlToolkit, will also have this issue ) and as a result when I tried running the project under the .net 4.0 web engine I got this error:
Could not load type 'System.Runtime.CompilerServices.ExtensionAttribute'....
I was unable to find a work around for that error as long as I was using the ScriptManager control. If I removed that control then the web application worked perfectly. Since my server only had windows 2003 on it upgrading IIS to .net 4.5 was not an option. I finally decided to roll back from EnterpriseLibrary v6.0 to v5.0.414 and build the project for .net 4.0 instead of 4.5.
This worked well until I started getting an odd error:
Could not load file or assembly 'Microsoft.Practices.EnterpriseLibrary.Data, Version=6.0.0.0, Culture=neutral, PublicKeyToken= 31bf3856ad364e35' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference.
It turned out that one of my solutions was hanging onto an old version of one of my other projects DLLs, even though the primary project was referencing the sub project in my solution apparently it did not refresh that sub projects DLL in its cache. This was an issue since at one point I had the sub project referencing version 6.0.0.0 of the EnterpriseLibrary DLL. I removed and re-added the primary project's reference to the sub project, the cache refreshed itself and all was well.
Thursday, August 1, 2013
Organizing the Kindle Library with Kindle Collection Manager
While I was deployed to Afghanistan I did a lot of reading. With limited technological resources that was the best method of passing the time.
My girlfriend had been wonderful enough to purchase me an Amazon Kindle prior to me leaving country. I had taken the time to download every public domain free PDF book I could find, which was quite a few. However, while the Kindle is the best e-reader I have seen, it's organization and search features are some of the worst I have seen.
To help overcome this issue, I found a small program called Kindle Collection Manager; which helps organize the books on the Kindle. It is not a fancy or many featured program like Calibre, it is designed to be a super light weight method of modifying the library file directly on the kindle itself.
While the kindle can hold close to 3000 books, its OS is incapable of running at a decent speed with so much of its memory consumed. While the Kindle may be incapable of being filled to capacity with books, this program does allow be to group about 1000 books into manageable collections. I believe that should be enough books for me to carry around at any one time. The rest of my digital library can remain organized on my computer and I can swap out collections as desired.
My girlfriend had been wonderful enough to purchase me an Amazon Kindle prior to me leaving country. I had taken the time to download every public domain free PDF book I could find, which was quite a few. However, while the Kindle is the best e-reader I have seen, it's organization and search features are some of the worst I have seen.
To help overcome this issue, I found a small program called Kindle Collection Manager; which helps organize the books on the Kindle. It is not a fancy or many featured program like Calibre, it is designed to be a super light weight method of modifying the library file directly on the kindle itself.
While the kindle can hold close to 3000 books, its OS is incapable of running at a decent speed with so much of its memory consumed. While the Kindle may be incapable of being filled to capacity with books, this program does allow be to group about 1000 books into manageable collections. I believe that should be enough books for me to carry around at any one time. The rest of my digital library can remain organized on my computer and I can swap out collections as desired.
Monday, July 8, 2013
Uploading Large files with HTML 5
The task was to fix a silverlight based web uploader. The application was needed because so many companies are blocking port 21 for FTP, so we were looking for a web based solution to allow similar functionality through the firewall.
Since silverlight was end of life I started by upgrading the system to a free flash based plug in. It worked beautifully in IE, but FF and Chrome both had issues with files larger than 500MB. Most of my research on the Internet claimed this was a limitation/bug in flash; I thought it was odd that it worked correctly in IE though. I later discovered it had nothing to do with the browser type but rather random chance in the available memory space that .NET had to allocate which just happened to occur at the same time I was testing the IE browser.
When I was unable to quickly find a solution to the flash issue, I turned to HTML 5. Being new technology there were very few things built for it. I found two HTML 5 uploaders, but neither of them seemed to work for me, probably because I did not fully understand the technology at the time. So, I decided to roll my own solution using HTML 5, javascript, and chunking.
I got the entire solution working correctly, including having the file chunks uploaded into SQL Server and using varbinary(max) to combine them in the database. As I started testing larger files I noticed that the code combining the chunks ran exponentially slower the more chunks there were. I was expecting it to slow down at a linear rate, but with a 1GB file it took a couple of hours to combine 1024 1MB chunks; which was unacceptable to me.
Next I tried combining the chunks on the web server using an external command prompt "type" process. It worked perfectly for small files. However, the number of chunks ended up surpassing the parameter length that is allowed to be passed to a command line program, which brought that solution to a halt.
Then I tried combining the files in C#.NET, which ended up being hands down the fastest method yet. However I then ran into the issue of the file size overloading the byte array for uploading into SQL Server. This last discovery happened to be one of the most important ones I had made yet. As it turns out the byte array erroring out was the root of my flash issue as well. The error #2038 that flash had been returning to me was a generic error code that simply meant something had gone wrong on the server, but flash had no idea what the problem was. For most people across the web this error seemed to stem from a permissions issue, but for me the byte array problem was the source of my issue. Manually stepping through the .NET code when flash sent the request to it seems to be the only reliable method of tracking down errors like this.
So I used the OPENROWSET with BULK option in SQL Server. This was working perfectly manually, but when I tried to automate it I ran into the issue that my sql service used a local account that did not exist on the web server and so could not authenticate to grab the file. EXECUTE AS LOGIN did not work either to impersonate an Active Directory account because the local sql service did not have permissions to query the AD server.
Having .NET copy the newly combined file over to the database server was the key to solving the final problem. After the database upload was completed I then had .NET clean up all the chunks and the merged file giving me a finished product that just needed some GUI polishing.
In Summary:
Having .NET copy the newly combined file over to the database server was the key to solving the final problem. After the database upload was completed I then had .NET clean up all the chunks and the merged file giving me a finished product that just needed some GUI polishing.
In Summary:
- Using HTML5 I uploaded all the file chunks to the server.
- In .NET I sorted and combined the newly uploaded chunks.
- I moved the combined file to a location my SQL server could access.
- Using OPENROWSET with the BULK option I copied the file into the database.
Wednesday, May 8, 2013
Active Reports HTML viewer skinning
My company recently migrated from Microsoft's reporting services to Active Reports. I wrote a post here about my complaints and assessment of the product.
One of the big problems with the HTML viewer is the HTML code it generates and its heavy reliance on absolute positioning. It is some of the worst code I have seen a control generate, I suppose it is supposed to be for flexibility, however none of the flexibility has been made available to us yet.
In order to make this control work for my companies spreadsheet style reporting, I had to come up with a cross between the HtmlView and the RawHTML output. The raw HTML worked better in page flow usually, but it didn't have paging which is a pretty nice feature. So I generating the following CSS snippet in an attempt to make the HtmlView just a little closer to the RawHTML output.
.rptDiv and #controlTable and #rptFilter are my own personal divs wrapping the viewer output and my custom parameters, they should not be necessary for the CSS to function correctly.
There are three main features that this CSS changes. Overflow to remove the extra scrollbars that the viewer adds to the page. Left and top positioning to get rid of the dumb one inch white space margin that the viewer creates. And Z-index because moving the main panel one inch up and left causes an invisible portion of the viewer control to be placed on top of any controls in the nearby area; and so it needs to be stuff behind them.
One of the big problems with the HTML viewer is the HTML code it generates and its heavy reliance on absolute positioning. It is some of the worst code I have seen a control generate, I suppose it is supposed to be for flexibility, however none of the flexibility has been made available to us yet.
In order to make this control work for my companies spreadsheet style reporting, I had to come up with a cross between the HtmlView and the RawHTML output. The raw HTML worked better in page flow usually, but it didn't have paging which is a pretty nice feature. So I generating the following CSS snippet in an attempt to make the HtmlView just a little closer to the RawHTML output.
.rptDiv and #controlTable and #rptFilter are my own personal divs wrapping the viewer output and my custom parameters, they should not be necessary for the CSS to function correctly.
There are three main features that this CSS changes. Overflow to remove the extra scrollbars that the viewer adds to the page. Left and top positioning to get rid of the dumb one inch white space margin that the viewer creates. And Z-index because moving the main panel one inch up and left causes an invisible portion of the viewer control to be placed on top of any controls in the nearby area; and so it needs to be stuff behind them.
#viewer-layout-main-panel > div > div > div > div > span {
border:solid 1px;
border-color:white;
}
.rptDiv,
.viewer-layout-container,
.viewer-layout-container div {
overflow:visible !important;
z-index:2;
}
#controlTable,
.viewer-layout-main-panel {
border:none !important;
}
.viewer-layout-main-panel > div > div {
left:-1in;
top:-1in;
width:0px !important;
}
#rptFilter,
#viewer-layout-toolbar-panel {
z-index:3 !important;
position:relative;
}
Subscribe to:
Posts (Atom)