Tuesday, July 28, 2009

Handling 401 (Access Denied) errors with ASP.NET

Handling 401 (Access Denied) errors are easy to handle in ASP.NET. The reason is that it is a simple configuration that can be made at the IIS level, not at the code level.

While it is true you can get to the Request.Status or Request.StatusCode in your Global.asax file. You can also do a Server.Transfer() or Response.Redirect() on your master page. There are lots of ways to handle this. The problem is that if you want to distinguish between 401.1, 401.2, 401.3… 401.7 it is best to use the solution presented here.

Here is a good list of the HTTP Codes. For reference, here is what it says about the 401 codes.

401 - Access denied. IIS defines several different 401 errors that indicate a more specific cause of the error. These specific error codes are displayed in the browser but are not displayed in the IIS log:
401.1 - Logon failed.
401.2 - Logon failed due to server configuration.
401.3 - Unauthorized due to ACL on resource.
401.4 - Authorization failed by filter.
401.5 - Authorization failed by ISAPI/CGI application.
401.7 – Access denied by URL authorization policy on the Web server.

I recommend replacing 401.1 and 401.2 standard files in IIS with your own. This will cause the standard 401.1. and 401.2 pages to not be displayed and instead your custom files be shown to the end user. 401.1 is what the user will get if they click the Cancel button on the authentication prompt. 401.2 is what they will get if they actually have bad username and password for three attempts.

The first thing we need to do is create a new web site or create a virtual directory inside a web site. This is the location were we will save our custom access denied files. In theory you could do this in the same location as your web application, but I like to use the same 401.1 and 401.2 files for all my applications. That way there is less configuration when I deploy a new application to a server.

  1. Make sure Anonymous is enabled under the Directory Security tab.
  2. Navigate in Windows Explorer to the directory and files.
  3. Right-click the directory, choose Properties.
  4. Go to the Security tab and make sure the IIS_WPG group has Read permissions.

To setup your web site (your ASP.NET application) just do the following:

  1. Open up Internet Information Services (IIS) Manager.
  2. Expand Web Sites in the tree view
  3. Right-click on your website or virtual directory and choose the Properties menu item.
  4. Go to the Custom Errors tab as shown below.

    image

  5. Click on 401.1 line and click the Edit… button.
  6. You will see a window titled Edit Custom Error Properties.
  7. Select File from Message type drop down list.
  8. Use the Browse… button to select the access denied page that resides in the anonymous web site we set up.
  9. The window should look something like this.
    image
  10. Do the same thing for the 401.2 item.

Now when a user would normally get the generic 401.1 or 401.2 pages, they will now get your custom pages.

Monday, July 27, 2009

Search Connectors

Microsoft Enterprise Search Products / Solutions

  • Search Server 2008 Express
  • Search Server 2008
  • Office SharePoint Server 2007 (MOSS)
  • FAST ESP

Search Connectors (more info)

  • Indexed Search Connectors – allows  to index the contents of a target system. This is done by creating an internal representation of the content that can be used to return search results quickly. This is the most powerful connector to a target system. There are two main categories of Indexed Search Connectors.
    • Microsoft Indexing Connectors - There are many available for free for Download from Microsoft, and additional from MS Partners.
      • Protocol Handlers (more info) - for unstructured data sources.  Opens content sources in their native protocols and exploses documents and other item to be filtered. Implemented as COM objecs that implement the ISearchProtocol interface. The crawl process works much like the Google Mini. It requires a starting url, and crawls the pages using links on pages. The url determines what kind of protocol handler will be used to crawl a url.
      • Business Data Catalog Application Definition Files – for structured data sources
    • FAST Indexing Connectors – for both structured and unstructured data sources. They are available for purchase for any FAST ESP system.
  • Federated Search Connectors (more info) – The actual search is “out-sourced” to another search engine. Content is not crawled in this approach. Instead, you are enabled to display search results for additional content that is not crawled by your search server. With federation, the query can be performed over the local content index, or it can be forwarded to an external content repository where it is processed by that repository's search engine. The repository's search engine then returns the results to the search server. The search server formats and renders the results from the external repository within the same search results page as the results from the search server's own content index. If search engine does not adhere to standard interfaces, a custom interface may need to be developed that does adhere to a standard interface. Triggers can be used to determine when a search returns federated results. For example, keywords, always, and pattern matching. Anonymous, Common, and Per-User credentials are supported. These credentials can be passed by most any common protocol including Basic, Digest, NTLM, Forms, Cookies, and Kerberos. There is an API to create custom federated search Web Parts. This applies for MOSS as well. Lots of docs via SharePoint docs.
  • iFilters – allow indexing of a wide variety of documents and file types using a common interface across Search Server 2008, Office SharePoint Server 2007, Windows SharePoint Servies 3, Windows Desktop Search, Windows Vista, and SQL Server. Opens documents and other content source items in their native formats and filters these into chunks of text and properties. It can be part of the protocol handler component or it can be a separate component.

For a more in-depth review of whether to use a Federated or Crawled approach, click here.

How to fix a broken “Go to Calendar” link on a SharePoint Meeting Workspace

Here the scenario, you have a SharePoint Meeting Workspace. Near the top left of the page you will see a Go to Calendar link. You click the link and get a 404 error.

What this typically means is that there was a calendar that used to exist that does NOT exist anymore, the location has changed, or in general the url has been changed in some way.

Your first step is to figure out if the calendar that it is looking for still exists or not. You can look at the url that the Go to Calendar link points to, you will see that the name of the calendar (most likely) as the last item in the url. It may have %20 where there were spaces in the name.

Look on the Site Hierarchy on the left of the page. Look through the list of items for something that might be a matching calendar to the one you are looking for.

If the calendar no longer exists, create a new calendar with the same url as the one that the Go to Calendar link points to. The link should now work if you named it the same.

If the link does not work the url is somehow different. In this case, you can try to correct the url difference recreating the site in a different path that will make the url correct.

Also, you don’t mind making it so officially Microsoft won’t support you, you can always do a search and replace in the content database. The SQL statement would look something like this:

update [dbo].[AllUserData]
set   [nvarchar4] = '/myoldpath/MyMeeting'
where [nvarchar4] = '/mynewpath/MyMeeting''

Please note that if you have delete an event a calendar you will need to create a new event and re-link the event to a workspace. You can do that by following the instruction here and then scroll to the “Create or link to a Meeting Workspace site when you create an event. “

Wednesday, June 24, 2009

How to calculate dot pitch

Dot Pitch is the measure of the distance between each adjoining pixel on a color computer monitor (including LCD monitors), expressed in millimeters (mm). The lower the dot pitch the sharper the displayed image. Typically color monitors come with a dot pitch of 0.3 mm or lower.

Dot Pitch is related to resolution because resolution is a measure of the number of pixels horizontally and vertically. Given this relationship we can calculate the dot pitch of any monitor if we know the horizontal resolution, vertical resolution, and the diagonal distance. Technically, if we new vertical and horizontal measure in inches for example, we could do the same thing and get a more accurate measure of dot pitch. However, all marketing and technical specs use the horizontal dot pitch.

With a little math and conversion of units you can easily calculate the dot pitch yourself.

Let’s take the example of a monitor that is 1280 pixels horizontally by 1024 pixels vertically and has a viewable display (make sure you use the viewable measurement, not the advertised one since they differ slightly sometimes) of 20 inches.

Below is visual of the information we know. You’ll notice that we have a difference in the units here. We have pixels and inches. We do need to do some calculations to figure out what the 20 inches is in pixels.

 monitor

To calculate the diagonal in pixels we need to go back to our grade school math class. We can think of the problem as math problem where we have a right triangle and are solving for the hypotenuse (the diagonal). Below is a visual of the problem.

triangle

We need to solve for d. To do that we use the formula

d*d = x*x + y*y

We need to solve for d which means

d = square root (x*x + y*y)

For our example:  d = square root (1280 * 1280 + 1024 * 1024)

d = 1639.2 pixels

Now we know the diagonal in inches and pixels. This means we can calculate the ratio between them which is known as pixels per inch (ppi). This can be calculate very simply by dividing pixels by inches.

ppi = pixels / inches

In our example: ppi = 1639.2 / 20 = 81.96 pixels per inch.

This is a measure of dot pitch, but it is not really what marketing and technical specs will use. They use millimeters since they are smaller units of measure and more appropriate for small things like pixels.

To make our calculation compatible with marketing and technical specs we need to convert our answer to pixels per millimeter (dot pitch). It is a known conversion factor that 1 in is equal to 25.4 mm. Here is the formula to convert our answer to pixels to millimeter.

dot pitch = 25.4 / ppi

For our example: dot pitch = 25.4 / 81.96 = .31 pixels per mm (which is commonly known as dot pitch)

If you don’t want to do the math yourself, you can just use the JavaScript function below.

<script>
function calcDotPitch(hPixels, vPixels, diagonal)
{
var d = Math.sqrt(hPixels * hPixels + vPixels * vPixels);
var ppi = d/diagonal;
var dotPitch = 25.4 / ppi;
return dotPitch;
}
alert(calcDotPitch(1280, 1024, 20));
</script>

Tuesday, June 23, 2009

Using Beyond Compare for backups / mirroring

I love Beyond Compare. It is so useful. It allows you to compare contents of files in a very nice way. It can also compare entire directories. It can even compare and copy binary files.

In many ways, I prefer it to the built in Windows copy functionality because if there is an error in Windows copy, you have to try again and I never really know the state of the destination folder. When I use Beyond Compare, if there is an error all I have to do is look at the differences or just simply try again. I also get that warm-fuzzy when I see that there is no difference between the source and destination directories.

I also like to have the same warm-fuzzy when it comes to “backups". I don’t trust backup solutions in general. It isn’t that they aren’t good, it is that I don’t trust that I am configuring it right. What I want is to be able to see the files after a backup in the destination drive. Then if I can use a tool like Beyond Compare to verify the source and destination directories are the same, then I feel confident that what think I backed up is actually backed up.

One important point with this model is that I am not really talking about a true backup in the sense that most backup software provides. Most backup software saves different versions of a given file for a certain period of time. While, what I am talking about here could be modified to do so, it would be a substantially different solution. What I am talking about is really a way to snapshot my important data to another disk in the event that my disk fails. I am not worried about me changing a file and needing a previous version of a files. I use manual copies or version control for this type of stuff. I don’t need this for most files, thus I only need the most recent snapshot of my important data.

You can actually use Beyond Compare to do what I just described. However, I want an automated way of doing this. I also want to be notified of any errors. There is nothing worse than thinking your backup is running, and then when you need it most, you realize it was broken. So, always periodically check that your backup process is working.

Below are my scripts I have created to automate the process. The only software needed to do this is Beyond Compare and Microsoft LogParser and MS Windows XP (or greater I’m sure). LogParser is only needed if you want to see any errors that occur. If you like checking log files, then you don’t need LogParser.

This scripts below will need to be tweaked to match your environment. My environment is such that I have an external hard drive that I backup to. You can also use UNC pathnames as well if you want to use a Windows share.

You can download a zip file of the same basic thing, or you can follow the instructions below.

Setup

Assume E:\ is the volume that you are backing up to.

  1. Create E:\backupScript directory
  2. Create E:\backupScript\Logs directory
  3. Create E:\backup directory
  4. Create E:\backupScript\backupAll.bat text file.
  5. Create E:\backupScript\backupDirectory.bc text file.
  6. Create E:\backupScript\backupOne.bat text file.
  7. Create E:\backupScript\CheckForError.bat text file.
  8. Create E:\backupScript\CheckForErrors.sql text file.
  9. In Beyond Compare, create a Saved Session for each of the directories you want to backup. This would be the same thing you would use if you were to compare two directories, just that you save the configuration information. The name you give it will be used in the backupAll.bat file.
  10. Copy the content from each of the sections below into the appropriate text file you created.

backupAll.bat

This is the main file that you will run to do the backup. You can use Windows Scheduled Tasks to run this an a regular interval to automate the backup process. You need to modify this file to have a line for each of the Saved Sessions you created in the initial setup. If you are backup up a SQL Server database directory, then be sure to stop SQL Server first, otherwise the files will be locked and cannot be backed up. You could also schedule a backup of the SQL Server databases, and then backup the database backups. The choice is yours. The backup is running on my laptop where I don’t have much disk space to make backups of the databases just to back them up, but I no problem stopping SQL Server since I am the only one using it.


call backupOne.bat "Backup dev to E"
call backupOne.bat "Backup Inetpub to E"
call backupOne.bat "Backup Video to E"
rem Stop SQL Server so we can backup the database files
net stop MSSQLSERVER
call backupOne.bat "Backup Databases to E"

rem Check for errors
call CheckForErrors.bat

backupDirectory.bc

Below is a Beyond Compare script. It tells Beyond Compare to clear any read only flags in the destination directory so that we won’t get any errors when replacing any files. Then it makes the source and destination directory the same. There are other options, just check the Beyond Compare docs on how to modify the script to change the behavior of the sync command (see the last line), the rest of the script is to clear the read only flags.

# Backup Directory

# Turn on logging
log verbose %LOGS_PATH%\backup-%BACKUP_NAME%.log

# Load the deployment directory on the server and the backup directory on the server
load %BACKUP_NAME%

#Must expand all folders so that select will work
expand all

# Select all files in the backup directory on server
select right

# Clear the read only flag on the backup directory on server
attrib -r

# Assume yes to all confirmation messages
option confirm:yes-to-all

# Copy all files from local directory to backup directory on the server
sync mirror mirror:left->right

backupOne.bat

This file is very simple and doesn’t really need to be modified unless you changed the path to the Logs directory, or have installed Beyond Compare in a different path. This just tells Beyond Compare to run as quietly as possible so that it doesn’t disturb us. :)

SET LOGS_PATH=Logs
IF NOT EXIST %LOGS_PATH% md %LOGS_PATH%

set BACKUP_NAME=%1%
"C:\Program Files\Beyond Compare 2\bc2.exe" /silent @backupDirectory.bc

CheckForErrors.bat

This file is optional. You really only need it if you want to scan the log files for errors and then show them to you. It tells MS LogParser to query the logs we created using the query defined in the CheckForErrors.sql file. Adjust the path to LogParser if needed or a different version is used.

"C:\Program Files\Log Parser 2.2\LogParser" file:CheckForErrors.sql -i:TEXTLINE

CheckForErrors.sql

This file is optional like I said before. Adjust the path to your log files if you didn’t use the same path as I did.

select * into DATAGRID from E:\backupScript\Logs\*.log where Text like '%Script Error%'

Wednesday, June 17, 2009

Using GridView, Entity Framework, LINQ, and an ObjectDataSource to implement a GridView that sorts and filters.

I went in search of an elegant and flexible way to implement a GridView that supports sorting (and the ability to add custom paging if I need it later, though I won’t cover that here) and uses a Data Access Layer (DAL). The filtering I am looking for is the ability to add any number of controls on the page and have them filter the results that are shown in the GridView. Based on the values of these user controls, I want to be able to do a begins with, or a range, or choose from a list of values, etc. I don’t want to be limited to just one value.

I am a believer that like SQL, you don’t want your LINQ queries all over the place. I believe a Data Access Layer (DAL) is a good place to put all your LINQ queries.

At the present time, this means that the ObjectDataSource is probably the best choice because it can call the DAL to do the query and not embed it in the EntityDataSource or LinqDataSource.

It is possible to get pretty good filtering and little to no code to do this using the Dynamic Data Future, but even then I using the DynamicFilter I found it difficult to modify the query to do things like ranges, or a begins with search for example. If you decide to go down that road, you might also want to check out the following post on how to do this in your own project. It makes searching based on a DropDownList or an AutoComplete field very easy. I wanted more flexibility than that. You can also get much of that same functionality from VS 2008 SP1 (yes the SP1 is required to get this functionality since SP1 is essentially a feature release, not a bug fix release).

The hard part of this is writing the DAL method, but is actually much easier than it used to be now that we have LINQ. In this case, I am using LINQ to Entities to query the Entity Framework.

Here is my DAL:

public class DAL
{
private MyEntities ctx = new MyEntities();

public IQueryable GetPerson(string firstName, string lastName, bool hasChildren, int? age, string sortExpr)
{
// set a default sort order
if (string.IsNullOrEmpty(sortExpr))
{
sortExpr = "FName";
}

var people = from p in ctx.Person
select new
{
ID = p.ID,
FName = p.FName,
LName = p.LName,
Age = p.Age,
NumChildren = p.NumChildren
};

if (hasChildren)
people = people.Where(p => p.NumChildren > 0);

if (!string.IsNullOrEmpty(firstName))
people = people.Where(p => p.FName.StartsWith(firstName));

if (!string.IsNullOrEmpty(lastName))
people = people.Where(p => p.LName.StartsWith(lastName));

if (!age.HasValue)
people = people.Where(p => p.Age > age);

var sortedPeople = people.OrderBy(sortExpr)
.Select("new(ID, FName, LName, Age, NumChildren)");

return sortedPeople;
}
}

You may notice that the .OrderBy() method gives you a compiler error or is not in your Intellisense. You need to download it from Microsoft. Click here to download. In the zip file, look for the Dynamics.cs file. You can include it in your project or you can build the project that comes with and include the assembly it builds in your project. It is one file, so I like putting it in my project as source code.

This Dynamic class works very in scenarios like this because it actually supports the same syntax as the ObjectDataSource uses which is <ColumnName> <SortDirection>. If the sort direction is Ascending, then no direction is specifed by the ObjectDataSource. For example the syntax for sorting my FName in Ascending order, the sortExpression would be “FName” or “FName Descending” if you wanted to sort in Descending order.

You may also notice that I use Lambda expressions to do the additional where statements. Be sure to use the value returned by the Where() method since the Where() call doesn’t change (or even query the database). All the Where() does is adds another condition to the existing where clause in the generated sql. Each time Where() is called, the statement is ANDed to the existing where clause. The code is very optimized. I am quite impressed with the code generation.

For related details on sorting with the ObjectDataSource, check out my other post.

Below is the aspx code. The most important thing you get right is the SelectParameters/ControlParameters. The ControlID property needs to match the ID of the Controls you are using for Filtering. The Name property needs to match the parameter names in the DAL method you specified in the ObjectDataSource SelectMethod property.

First Name starts with: <asp:TextBox ID="FirstNameFilter" runat="server"></asp:TextBox><br />
Last Name Starts with: <asp:TextBox ID="LastNameFilter" runat="server"></asp:TextBox><br />
Has Children: <asp:CheckBox ID="cbHasChildren" runat="server" /><br />
<asp:Button ID="Button1" runat="server" Text="Apply Filter" />

<asp:GridView ID="GridView1" runat="server" AllowSorting="True"
AutoGenerateColumns="False" DataKeyNames="ID"
DataSourceID="dsPeople"
>
<Columns>
<asp:HyperLinkField DataNavigateUrlFormatString="PersonDetail.aspx?ID={0}" Text="View" DataNavigateUrlFields="ID" />

<asp:BoundField DataField="ID" HeaderText="ID" ReadOnly="True"
SortExpression="ID" Visible="False"/>
<asp:BoundField DataField="FName" HeaderText="First Name"
SortExpression="FName" />
<asp:BoundField DataField="LName" HeaderText="Last Name"
SortExpression="LName" />
<asp:BoundField DataField="NumChildren" HeaderText="Number of Children"
SortExpression="NumChildren" />
<asp:BoundField DataField="Age" HeaderText="Age"
SortExpression="Age" />
</Columns>
</asp:GridView>

<asp:ObjectDataSource ID="dsPeople" runat="server"
SelectMethod="GetPerson"
TypeName="MyNameSpace.DAL"
SortParameterName="sortExpr"
>
<SelectParameters>
<asp:ControlParameter ControlID="cbHasChildren" Name="hasChildren" Type="Boolean"/>
<asp:ControlParameter ControlID="FirstNameFilter" Name="firstName" Type="String"/>
<asp:ControlParameter ControlID="LastNameFilter" Name="lastName" Type="String"/>
</SelectParameters>

</asp:ObjectDataSource>

The context type MyDataContext does not belong to any registered model.

Are you getting the following error message at runtime in your ASP.NET application that is using LINQ to SQL?

The context type MyDataContext does not belong to any registered model.

If you are, it is very likely you have not registered you DataContext. To register you DataConext open your Global.asax.cs and add the following to the RegisterRoutes() method.

model.RegisterContext(typeof(MyDataContext), new ContextConfiguration() { ScaffoldAllTables = true });