Dec 1

We recently migrated from one online IEP vendor to another.  This is a program for collecting and managing Special Ed information, but it is an enterprise app and worthy of critique and comment.  They do a great job, but there are some small UI issues I wish could be fixed.

For instance, when you enter data onto a form and try to leave the form without saving they do a nice job of alerting you that you are about to lose the valuable data you just entered.  However, there is a catch:

SNAGHTML97e6ac4

Ouch, did you read that correctly?  “Hit CANCEL to Leave this page.”  The problem with that is I would say 99% of users are used to hitting Cancel when they realize they made a mistake and want to undo it.  Several times I’ve had to stop my knee-jerk reaction to hit cancel when I realized I updated something and didn’t hit Save.

Not only are the button actions inconsistent with what users are used to but I would go further and state they are actually opposite, which to me is one of the worst UI anti-patterns.  In addition the buttons are not very informative, OK and Cancel just don’t tell you what they are doing with your data.  Unfortunately I think it might be stuck as it is.  The software now boasts adoption of over half the state of California.  That’s a good sized client base and the current users are already conditioned to hitting OK to go back to the page and hit the Save button.  If the vendor were to reverse the impact of the buttons now I can just imagine the outcry there would be over users losing data because they are simply used to hitting OK.  You see, no matter how many emails, home page news alerts or training documents you give out half the users won’t even bother to read them.  They won’t know there is a change until they have already lost their data.  Not a good thing.

If I were to have to implement this change I would re-label the buttons all together.  This would not only alert the user that something functions differently then what they are used to but it would also make the dialog more usable, which is also a good UI pattern.  Unfortunately you cannot do this with plain JavaScript.  Since the vendor already makes use of jQuery the UI Dialog plug-in is a great tool to use.  It also standardizes the dialog boxes since JavaScript alerts are handled inconsistently between browsers.

Here’s a sample of what I would do using the jQuery UI Dialog plug-in.  I think it is easy to read and clear in informing the user on what they are about to do.  While this would be a change to what users are used to I think it would be one that is easily adapted to.

image

Oct 5

We have a particular web product that we use for all of our clients.  It is a pretty incredible system and has several hundred clients.  We are one of their largest ones so, between our implementation, and their normal customer needs its understandable why they cannot accomodate every one of our requests right away.

That being said, there was one slight “feature” that went slightly against one of our policies.  So, what to do?  Well, I put on my curiously tipped white hat and go to work.  One of the cool features of the system is they allow us, the admins for our group for all intensive purposes, to update portions of the page for our users.  They don’t escape HTML so it turns out I’m able to inject my own JavaScript code.

Ooohhh, that’s bad.  ;) I let the company know but I proceed on.

Turns out they, being the creative bunch they are, make use of jQuery.  This is definitely turning into a possibility with my toolbox already stocked and ready to go.

The particular portion of the page I choose to launch my JavaScript from is ideal because it is on the navigation bar, ensuring that it will be displayed on almost every screen.  Because we can only change content for our users it ensures that my changes will only affect our users and not those of their other clients.  However, with 5,000 users I had better make sure my code is well tested and clean.  I still have the ability to disrupt my 5,000 users if I make a bad mistake.  Point noted.

The vendor puts a character limit on the particular area of the page I’m changing.  That means I have to inject JavaScript that tells the browser to load a larger script from elsewhere.  No big, I put the script on our department website.  Hmmm, not so good.  The system is now throwing a warning that I’m loading JavaScript from a non-secure source.  Hmm.  Sure would be nice if I could load the script directly on my vendor’s server.

Well, I can. :)  The vendor also allows us to upload documents to a library that our users can download from.  So, I upload the script.  Uh, oh, didn’t work.  Turns out they block all but a few extensions.  So, instead of calling it myscript.js I change it to a text file called myscript.txt.  That uploads great and, guess what, the browser is happy to execute it.  Great!  On my way.

After a few tests it turns out I’m able to quite nicely make our little “issue” a thing of the past.  First I check to make sure the page in question is the one being viewed.  That way the bulk of the code (all 6 lines of it) runs only when necessary.  Then it cleans up after itself by removing my injected code from the page DOM.  Nice!

It’s not foolproof of course.  If the user isn’t running JavaScript then my trick won’t work, however,  half the system won’t work for them anyway since JavaScript it is required.  Also, if someone is sneaky enough they can load the original source and see where I injected my code.  But, if they are going to do that we have bigger problems then them simply disabling my script.

OK, that covers the techy issues, what about the people ones?  Well, as you may have guessed, no matter how many times we alerted our users to the changes we made, some users started calling the vendor’s Help Desk asking what happened to the report they were used to.  The vendor then called me after spending two days trying to solve the issue they weren’t seeing.  They didn’t really care for the changes.  They understood the necessity and gave me props for how I handled it but now their Help Desk had no idea if a problem call was because of their system or my code.  Granted, this totally makes sense.

So, they gave us a choice.  We can continue to develop our own “customizations” but we have to take over all Help Desk calls for our users for the entire system.  I understand that, but directly supporting 5,000 users is not something we are capable of doing.  I have to note also that the Help Desk is absolutely outstanding, one of the best  Ihave ever worked with.

So, after the vendor agreed to look into implementing our change we are allowed to keep the code and support the Help Desk for any questions regarding this one issue.  Once the vendor has solved the problem we will remove it and go on our way.

So, here is the moral of the story: Hacking your vendor’s site open doors to lots and lots of capabilities, however, it may aggravate your vendor a little or, quite possibly, cause an early termination to your contract.  I suggest you keep this little tool in your developer’s pocket for extreme cases.

It was fun and our vendor is great.  Once I put the change in place we started to come up with lists of additional “nice to have” features I could implement, but, in the end, we’ll just hand those over to our vendor and see if they make it in someday.

Jun 1

In every DBA’s career I think having to concatenate results happens at least a few times.  Probably more than we like to admit because we tend to live in table-land.  :)   However, there are those occasions, which are usually driven by some downstream requirement to format output.  Now, I know that formatting should be handled by whatever data viewing method you are using, but sometimes that just isn’t possible or practical.  Other times it may just be that we need to transform data from one system to another and that other system is not as normalized as the tables you are working with.

Like I said, I do it fairly infrequently, so I never remember the best way in my head.  I usually end up looking at how I’ve done it in the past.  I started thinking that there may be better ways then some of the convoluted strategies I’ve found in previous solutions.

Trusty Google sent me here:

http://www.projectdmx.com/tsql/rowconcatenate.aspx

It’s an incredible (though certainly not exhaustive) list of ways to deal with this depending on your need.  I like XML and chose to go with simplicity so, for this particular task, I went with Eugene Kogan’s “blackbox XML” method.  It’s only a few lines and if you are familiar with XML and SQL then it’s not that hard to understand.

I’ve definitely bookmarked this for later reference!

Apr 23

I am upgrading an older web app of ours as I referenced in my last blog post.  This was originally a straight html app with no dynamic content at all.  I created a .Net ASPX web app out of it and used LINQ to quickly and easily create a survey form that our users could fill out.  It worked great on my machine.

Unfortunately the happy ending got derailed when I deployed it to our web server.  Our web server is an ancient Windows 2000 server box with IIS 5.  this is because it’s where all our main apps our housed, everything works and there is great fear in changing it.  <sigh>

So, I either had to figure out how to get my .Net 3.5 app running on IIS5 with .Net 2 or I had to abandon LINQ and go back to data readers (yuck!).  I first tried to install .Net 3.5 on the web server but quickly found out that it requires Windows XP or Server 2003 as a minimum.  OK, so that’s ruled out.

I knew that the asp.net framework has always been 2.0 (until the new release of 4.0 that is) and .Net 3.0 and 3.5 just added extra features on top of 2.0 but never changed the underlying base classes.  So you can easily use .Net 3.5 apps on a .Net 2.0 web server.  In fact, this has caused a lot of confusion because there simply is no 3.0 or 3.5 selection in IIS for the .Net framework.

I knew if I could just reference the required .Net 3.5 dlls then this shoudl work.  Doing a quick search on Google lead me to this great article.  I was wondering if something like this was possible and, sure enough, it pointed me in the right direction.

 

Here is what I did and it worked like a charm.

I first set my build target for the web app in Visual Studio 2008 to .Net 2.0.  This caused VS 2008 to instantly remove any non-.Net 3.5 compatible references such as LINQ.  I did a build and received numerous errors, most pertaining to my code that made use of LINQ.

I copied the System.core and Linq.Data DLLs into my web app’s bin folder and referenced them.  After another attempt to build the solution the LINQ errors went away but it still didn’t understand my lambda expressions or my auto-properties.  This makes perfect sense.  These are compiler features and not referenced code.  Since, by default, asp.net compiles on the server it had better understand these.  I could change the autoproperties back to normal properties but there is no lambda equivalent for .Net 2.0.

So, I created a new project and moved all LINQ code into it and had it target .Net 3.5.  Having my data access classes in a separate project felt much cleaner and probably would having been an eventual refactoring later.  I removed this code from the web app and created a reference to the new project.

Ran a build and received the welcome success message.

I then deployed the web app to the web server.  Upon opening one of the new pages, which runs a LINQ query to obtain some data to populate a drop down list, I received the following error:
Could not load type ‘System.ComponentModel.INotifyPropertyChanging’ from assembly ‘System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′

After some googling it turns out that INotifyPropertyChanging wasn’t introduced until .Net 2.0 SP1.  Sure enough, our web server had 2.0 but no service packs.

I installed .Net 2.0 SP2 and everything worked great!

 

I am in the process of redesigning our entire department’s website and that is all built on MVC and several other current technologies.  I have another web server that is running 2003 for that.  I might miss out on some of the newer IIS 7 features but .Net 4 runs on it just fine so at least this is a major step forward.

Take care!

Apr 22

I’m adding some new features to an older ASP.Net Web Forms app we have.  Fortunately I can take advantage of .Net 3.5, though not MVC because it’s on a Windows 2000 server with IIS 5.  Anyway, this allows me to use LINQ, which has been incredible.  Creating the mapping and using LINQ queries is leaps and bounds more convenient and less prone to error than our older data readers and such we used in the past.

I am more in the TDD camp and like to manage my own data bindings so I don’t like to use the LINQ model designer.  Instead of dragging and dropping tables onto the designer and letting LINQ create the classes for me in the background I create my own concrete classes and then put attributes on the classes themselves.  I know, some may not like putting attributes in their code because they argue that it makes their objects dependent on the database framework rather than being completely database agnostic.  However, 100% of our apps make use of Microsoft SQL and I don’t see that ever changing in the near future.  So, rather than go through the overhead and complexity of a truly database agnostic ORM setup I’ll happily add a few attributes to my code that plainly show exactly what’s going on.

In this particular case we have parents and students that will be filling out a survey based on when they graduated from our programs.  Because we serve multiple districts, each with multiple schools, I ask the user to select which district and school they graduated from.  Thus, I have a SchoolDistrict class which contains a List of School classes.  In LINQ you can map a relationship using the Association attribute.  I got the details and examples from Microsoft’s MSDN article here: http://msdn.microsoft.com/en-us/library/bb386950.aspx

Here was my initial code for SchoolDistrict:

1: using System.Collections.Generic;
2: using System.Data.Linq;
3: using System.Data.Linq.Mapping;
4:
5: namespace App_Code.Entities
6: {
7: [Table(Name = " vw_Table_D_Current_SchoolDistricts ")]
8: public class SchoolDistrict
9: {
10: private EntitySet<School> Schools;
11:
12: [Column(IsPrimaryKey = true )]
13: public string districtCdsCode { get ; set ; }
14:
15: [Column]
16: public string name { get ; set ; }
17:
18: [Association(Storage = " Schools ", OtherKey = " districtCdsCode ")]
19: public List<School> schools
20: {
21: get { return new List<School>(Schools); }
22: set { Schools.Assign( value ); }
23: }
24: }
25: }

 

Here was my initial code for School:

1: usingSystem.Data.Linq.Mapping;
2:
3: namespaceApp_Code.Entities
4: {
5: [Table(Name = " vw_Table_D_Current_Schools")]
6: public classSchool
7: {
8: [Column(IsPrimaryKey = true)]
9: public string schoolCdsCode { get ; set ; }
10:
11: [Column]
12: public string name { get ; set ; }
13:
14: [Column]
15: public string districtCdsCode { get ; set ; }
16: }
17: }

 

Everything worked great and I was rolling.  Then I had ReSharper clean up a few things and I took some of its suggestions on optimizing my code.  I made a few other changes and, then all of a sudden, I got this when I ran the code:

System.Security.VerificationException: Operation could destabilize the runtime.

Ugh!  What’s that???!!

After a lot of Googling I had come up with nothing.  Most of the fixes I saw related to mapping IEnumerables to IQueryables, covariance vs contravariance, etc.  Nothing seemed to fit my particular scenario and none of the fixes worked.  I went over my code with a fine tooth comb making sure my mappings were still correct, making sure the capitalization in my attributes were not fouling things up, etc.  I came up with nothing.

 

So, I went back to square one.  I looked at the Microsoft example again and I found one modifier that was different.  ReSharper had noted that I could mark my Schools EntitySet in my SchoolDistrict class as readonly.  I removed this modifier and, voila, everything worked.  Sure enough, ReSharper again started suggesting that I mark this as readonly.  I did and my code broke again.  I had found the culprit.

I had not seen this mentioned anywhere on Google and I guess, in hindsight, most people wouldn’t mark this as readonly

I don’t always take ReSharper’s suggestions but this is the first time that ReSharper actually broke my code.  :(   Truth be told, ReSharper is a tool and it’s only as good as the one wielding it.  If I let ReSharper perform invalid operations that’s not ReSharper’s fault but my own.  However, I still didn’t like that I may not remember this key piece of information in the future.

So, I told ReSharper to ignore this in the future and wrote a comment as to why.  That cluttered up my code and it now looks like this:

1: // ReSharper disable FieldCanBeMadeReadOnly.Local
2: // If set to read only this causes an "Operation could destabilize the runtime" exception when the query is evaluated
3: privateEntitySet<School> Schools;
4: // ReSharper restore FieldCanBeMadeReadOnly.Local
5:

Wow, that is ugly!  I hate having to put comments in my code that are because of dependencies to frameworks.  Now I have a comment in my code that’s dependent on an IDE tool, not even something required for the app itself!!!  That’s just plain ugly and a major code smell if you ask me.  However, that’s just the way it is for now.  The app works and I’ll go on with my life.  It’s not perfect but I don’t want to spend a day figuring out a better way.  If someone posts a better suggestion or I find something in the future then I’ll revamp it, but for now, I’ll get the app out the door and life will go on.

I hope that this helps someone else out there that may run into this issue.

In the end ReSharper is one of the best tools I have in my toolbox, number 2 right behind Visual Studio itself.  It’s incredible and I can’t imagine coding without it.  It truly has helped make me a better developer than any other tool I’ve used to date (again, after VS).

Take care all and happy developing!

Feb 2

There are a lot of examples out there on how to pass a SelectList or MultiSelectList object to an Html.DropDownList or Html.ListBox helper.  However, I couldn’t find anything that was explaining the problem I was having.

In my app we have a Show object that represents a podcast.  Each show may have multiple Guest objects, which are accessed by the Show.Guests member.

In the Admin controller I have the following Action method:

public ActionResult EditShow(int id)
{
var show = _repository.Get<Show>(id);
show.Guests.Load();
var guestList = new MultiSelectList(_repository.List<Guest>().OrderBy(g => g.Name), "Id", "Name", show.Guests.Select(g => g.Id));
ViewData["GuestsSelectList"] = guestList;
return View(show);
}

In the Edit View I display the fields for the Show and a ListBox for the Guests.  You can select multiple Guests for a show.  Ideally, when you edit a Show the Guests that are already associated with the Show are pre-selected.  Makes sense, right?

Here is the code called in the View:

<%= Html.ListBox(" GuestListBox ", (MultiSelectList)ViewData[" GuestsSelectList "])%>

If you follow this you’ll notice that I am adding to the ViewData a MultiSelectList object, which contains an IEnumerable collection of all the Guests, that “Id” is the value field, “Name” is the text field, and an IEnumerable collection of the guest.Id values that are already associated with the show.  However, this never caused the ListBox to select the values upon rendering the page.

I used the debugger and, sure enough, my MultiSelectList object was successfully populated with the properly selected items.  What was the deal?  The breakdown must be happening with the Html.ListBox helper.  I spent a couple of hours scouring this myself and scouring the Internet.  I went home determined to re-write the Html.ListBox helper myself and just be done with it. 

However, the next morning I decided to download the MVC source.  Here’s a great and simple article from Steve Sanderson on how to attach it to your project so that you can step through it with the Visual Studio debugger.

One nice thing about Html.ListBox and Html.DropDownList helpers is that you don’t have to provide a collection of SelectItems explicitly.  If you have an item in the Model or ViewData that has the same name as the ListBox or DropDownList then it will find it automatically.  If you do explicitly provide the SelectItem collection you can opt to not provide the default values explicitly.  Again, as long as you have a object (or member of an object) that has the same name as the ListBox or DropDownList it will find it and use it.

Here’s the rub though.  If you explicitly define the SelectList collection and the default values (as I did in my code) the Html helper looks to the Model and ViewData first before your default items.  This normally never causes a problem, until you name your ListBox or DropDownBox the same name as one of your objects or members.

Notice that I called my DropDownList “Guests”? Notice how the Model is a Show object, which has a Guests member variable?  It turns out that the Html helper was grabbing this collection, rather than my values.  Technically these are the guests I want to default to, however, the Html helper was getting a collection of Guest objects not a collection of Guest Id values.  When it compared each guest Id in the full list with the actual Guest objects in the default list it never found a match.  Thus, nothing was ever selected.

What’s the fix?  Simply change the name of your ListBox or DropDownList.  When I changed the name value to “GuestListBox” all worked well, because, as you would expect there is no object or member in the Model or ViewData called “GuestListBox”.  Once it couldn’t find one it dropped through that code and used the default values I provided.

To find exactly where MVC is doing this check out the SelectInternal member in the SelectExtensions.cs file.  This is the internal method that both ListBox and DropDownList eventually call.  You can see commented code that states:

// If we haven’t already used ViewData to get the entire list of items then we need to
// use the ViewData-supplied value before using the parameter-supplied value.

In my opinion this is a bug.  It shouldn’t even check the ViewData or Model if you have already explicitely provided the default values.

So, I added my vote to the bug that was already submitted for this on CodePlex.  I haven’t check to see if this has been resolved in MVC v2.

Jan 13

Ever since virtual PC type systems have been out, long has been the dream to run any type of operating system you want, multiple operating systems at the same time, all working independently of each other.

However, only in the last year or two has this really become a reality with the latest hardware supporting virtual environments natively.  Now Windows 7 lets you boot off of a VHD as if it was a standard hard drive making full use of your actual hardware devices.

After attending Stephen Rose’s presentation on Virtualization 101 for Developers my eyes were suddenly open to the possibilities of what could be done.  I could move my entire development environment to a virtual PC image.  This would allow me to copy that image at any time allowing me to test beta products, new upgrades, etc without worrying about damaging my current environment.  Many times I have upgraded to new releases (or betas) that caused previous programs to stop running, Dlls required in legacy projects to be removed, etc.  In some rare cases (SQL Server 2008 RC2) I had to reload my entire environment because I could never get my machine back to its original state.

Now that Windows 7 allows you to boot from a VHD my wheels are suddenly turning.  I could move my entire computer use (not just development) to VHDs.  This would allow me to have a base image for general tasks like email, MS Office, etc that my wife and I could use for general day to day tasks.  I could have another environment for all my video editing work, which is especially useful since video compression codecs can sometimes cause havoc.  I could have a production development environment and as many beta environments as I want.  If I want to download a 30 day trial of any product I just create a copy of the appropriate VHD, boot off of that and try out the product.  After 30 days if I don’t like it I just delete the testing VHD and it will have never touched my production VHD.  If I do like the product I just delete the testing VHD, purchase the full copy and install it to my production VHD.  Very nice and clean.

I just purchased a new HP dv7 laptop with the Intel core i7.  So, this is the perfect time to get started.  While it is possible to wipe the drive clean and use VHDs without any core operating system at all I’ll leave the HP environment intact.  That way I can always fall back to that just in case.  If for some reason my sound stops working I doubt HP will be willing to care if I can’t reproduce it in the standard OS.

So, I’ll document how I set this up and how it works out in future blog posts.  Enjoy the ride!

Jul 9

I’m going to be doing a talk on Practical TDD at the Inland Empire .Net Users’ Group in the near future.  We’ve had TDD talks in the past but they have been more overviews with only a little code.  I plan to take the group through craeting a simple blog engine from scratch completely using TDD.  I hope that this will give a real great example of how to develop a project using TDD.

The presentation was supposed to be in August, but as that lands directly on my 8 year wedding anniversary and our son’s 3rd birthday I had to postpone it.  I don’t like sleeping on the couch. :)   James Johnson, the president, is still working on where to put my talk, so I’ll post an update when that happens.

Anyway, in the spirit of getting ready for this talk I’ll be writing a few blog posts on some great TDD resources I’ve found.

Recently Roy Osherove, the author of the Art of Unit Testing, started doing actual TDD code reviews on his blog.  These are great!  It’s one thing to read a blogger’s posts with their thoughts and opinions on topics but seeing Roy go through a live code review in a video is just spectacular.  It’s like getting a chance to sit in on a session with Roy.

While he admits that he’s a little harsh on the Nerd Dinner review, having done it at 2am, I think he is spot on in his findings.  Of course, Roy is a very experienced veteran in this area so we’d think nothnig less.  However, the points Roy makes are great and he really brings to light what good and bad examlpes of TDD he sees in the app.

I can’t wait to check out the rest of the videos.

He offers to review code that is sent in.  I’m not sure I’ll be brave enough to do that, but if I’m not that would indicate a smell in my code, wouldn’t it? :) Maybe when I get my talk together I’ll pass my code on to Roy to review prior to my presentation.  That would really give me a great resource to make sure my presentation is sound and a little more authority on this topic.

Jul 9

Here’s a great article on how to get a geographic location from an IP address within SQL Server:
http://www.sqlservercentral.com/articles/SQL+Server/67215/

The article is very easy to follow and gives great direction of setting up a user defined function in SQL to give you back a location based on the IP address.  Since SQL servers are very good at processing data quickly this seems like a natural way to get this information.  Once you have the function set up you can easily use it in asynchronous processes like analyzing logs, post processing of customer data, etc.  You can also set this up as a trigger within SQL or a service callable by outside apps, such as a webapp.

I’ve seen this used in a lot of ways that I don’t care for (thanks for letting me know about all the fictional hot girls that live in my area, but I don’t think my wife would approve :) ) but there are some legitimate ideas coming around.  For instance, let me power up my iPhone and see what Nerd Dinners are available in my area (work in progress).

Another scenario is blocking spam.  For instance, at my work we service Riverside County in southern California, USA.  We have methods to stop unauthorized users from creating accounts and blocking spam to our Wiki’s and such.  But why not use location based blocks as well?  I know my users are all from Riverside County, so why not block everyone from, say, outside of southern California?  While a user or two may be blocked while attempting to access their work from their vacation in Maui, I don’t think I’d get that much flack from blocking these edge cases.

Jun 3

On an old Web Forms app we had the request to allow users to download the data from a formatted SSRS report as an Excel spreadsheet.  They currently view the formatted report using the ReportViewerControl, however, when you use the export feature it exports it to Excel with all the formatting of the report, including groups.  This is fairly unusable.  What you really need is a simple spreadsheet with a cell for every value without any grouping.

So, I created a report that had no grouping or formatting and a column for every field of data. 

I could have forced the user to view this ugly report in the ReportViewer and then export it as an Excel spreadsheet but I wanted them to be able to click on a link and get the report directly.  That’s easy enough because you can get an SSRS report formatted as Excel by appending “rs:Format=Excel” to the end of the report url.  This doesn’t work for us, however, for two reasons:

1) The website is accessible to users outside our network and the SSRS server is not.

2) The report retrieves sensitive data filtered by a parameter.  It would be fairly easy for a user to change the parameters in the url to obtain data they shouldn’t be viewing.

In the ReportViewerControl we change the parameters on the server side so the user simply views the generated report with no option to change the parameters.  Now, I needed a way to let them download the Excel version with the same restrictions.

Below is the solution I used with the help of several different other sites on the web.

I created a hanlder that would create the url with the proper parameters and formatting, retrieve the report and then send it out to the user as an Excel document. This way when the user clicked the link for Excel version of the report their browser would open a dialogue to Open or Save the Excel file.  Works like a charm.

 

Here is the code:

1: Public Sub ProcessRequest( ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
2: context.Response.Clear()
3: context.Response.BufferOutput = True
4: context.Response.ClearContent()
5: context.Response.ClearHeaders()
6:
7: Dim uri As String = ” http://san-destiny/ReportServer?%2fSELPA+Reports%2fDistrict+Errors+Detail+CSV+-+CASEMIS&rs:Format=Excel&DistrictName= ” & Utility.GetDistrictNameForUser()
8:
9: Dim request As HttpWebRequest = HttpWebRequest.Create(uri)
10: request.Credentials = CredentialCache.DefaultNetworkCredentials
11:
12: Dim response As WebResponse = request.GetResponse()
13: Dim responseStream As System.IO.Stream = request.GetResponse.GetResponseStream()
14:
15: Dim buffer(4096) As Byte , blockSize As Integer
16: Dim tempStream As New MemoryStream
17: Do
18: blockSize = responseStream.Read(buffer, 0, 4096)
19: If blockSize > 0 Then tempStream.Write(buffer, 0, blockSize)
20: Loop While blockSize > 0
21:
22: context.Response.AddHeader(” Content-Type “, ” application/xls “)
23: context.Response.ContentType = ” application/xls
24: context.Response.AppendHeader(” Content-disposition “, ” attachment;filename=CASEMISErrorReport.xls “)
25: context.Response.BinaryWrite(tempStream.ToArray())
26:
27: context.Response.Flush()
28: context.Response. End ()
29: End Sub

 

The first few lines simply clear any headers or content that may be initially set.  This is mandatory when sending files because we are going to be setting the headers later.

Line 7 is the url of our report.  Notice that “rs:Format=Excel” is in the url letting SSRS know we want this report as an Excel doc.  Also notice that I set the DistrictName parameter by getting the logged in user’s district name via a utility method.

Line 8 sets up an HttpWebRequest object so that we can make a call to the SSRS server just as you would with a browser. 

Since the SSRS server is on our domain and user restrictions are in place Line 9 sets the credentials we need. Our webapp impersonates a user with the correct access so that we can have the proper security restrictions in place yet allow non-domain users access to the data.  This impersonation is set up in the web.config and DefaultNetworkCredentials looks their first to get the credential information.

Lines 12 & 13 simply grab the web response and sets up a Stream object to read it with.

Lines 15 – 20 sets up a temporary MemoryStream object which holds the bytes read from the response (remember SSRS is sending us an Excel file, which is a binary file, not plain text).

Lines 22 – 28 finally sets up the headers and content type, sends the bytes and closes the stream.

 

I probably didn’t need a MemoryStream object.  In the loop I probably could have written the bytes directly to the context.Response object.  But this seems a little easier to read and potentially debug if necessary.

 

In the end I’d rather use MVC.  In fact I created the exact same result (except as a CSV file) using MVC and LINQ.  It was a snap and I had it done in 5 minutes.  Unfortunately the current site is on a Windows 2000 server and makes heavy use of Web Forms and the SSRS viewer controls.  I know I could have simply had the link request the result from an MVC app on another server but I wanted to keep this all fairly consistent and coherent.

Look for me to change this all up next year when I’ve moved our department webpage entirely over to MVC.

Technorati Tags: ,,

« Previous Entries Next Entries »