Using a shared page to retrieve and display a pdf file

We had a situtation recently where we needed to allow users to download a pdf file from our server but we did not want to show the URL in the browser window. Although there are many ways to do this we opted to use an aspx page to retrieve the file and present it to the user. Knowing we will probably use this method againe in the application we decided to make sure the page was reusable.

The code below is of a webform load event. There are two querystring parameters, obviously these parameters are relevant to our requirements and you may user different parameters. The t parameter is the token, which is passed to the backend server as an identifier e.g. eventid, certificateid or something similar. The r parameter is to identify which url should be called.

protected void Page_Load(object o, EventArgs e)
{
    var token = Request.QueryString["t"];
    var report = Request.QueryString["r"];
    var URL = string.Empty;

    switch (report.ToLower())
    {
        case "u1":
            URL = System.Configuration.ConfigurationManager.AppSettings["url1"];
            break;
        case "u2":
            URL = System.Configuration.ConfigurationManager.AppSettings["url2"];
            break;
    }

    var fullURL = string.Format("{0}{1}", URL, token);
    var client = new System.Net.WebClient();
    try
    {
        Byte[] buffer = client.DownloadData(fullURL);
        if (buffer != null)
        {
            Response.ContentType = "application/pdf";
            Response.AddHeader("content-length", buffer.Length.ToString());
            Response.BinaryWrite(buffer);
        }
    }
    catch (System.Net.WebException ex)
    {
        if (((System.Net.HttpWebResponse)ex.Response).StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            Server.Transfer("/404.aspx", false);
        }
    }
}

For us this page was called pdf.aspx, possible URLs this page will be called with are;

http://www.somedomain.com/pdf.aspx?t=1234&r=u1
http://www.somedomain.com/pdf.aspx?t=RY6N-UX6M&r=u2

It is possible to leave the token parameter blank if an identifier is not needed.

http://www.somedomain.com/pdf.aspx?t=&r=u2

The URLs of the backend server are stored in the web.config appSetting section, possible examples are.

<appSettings>
    <add key="url1" value="http://somebackendserver/{0}" />
    <add key="url2" value="http://anotherserver.addomain.net/certificates.aspx?ref={0}" />
</appSettings>

I have replaced the actual appSettings used with url1 and url2, your appSettings will have meaningful names.
The pdf file is downloaded to a byte array and presented to the user. If a WebException occurs the status of the response is checked and if a HttpStatusCode.NotFound status was returned from the backend server the applications 404 page is displayed.

There are a number of ways to extend this code and functionality relevant to any purpose.

Using hyphens in MVC urls for good SEO

Following the Convention over Configuration principle in an ASP.NET MVC application the controller and action names form part of the URL. So commonly a URL of http://www.example.com/account/forgotpassword would break down to;
Domain: example.com
Controller: account
Action: forgotpassword
It is good SEO practise to separate words in a URL with hyphens. One reason for doing this is due to Google and how it interprets the URL. Google will not see underscores as a word separator, so forgot_password will be seen as forgotpassword, there is no benefit in having underscores at all.

When it comes to an MVC application you cannot have hyphens in the action names, the code will not even compile.
hyphen-error

Fortunately there is a way to have hyphens in URLs by using the ActionName attribute on an action method. This method redefines the name used to access the action both from URL or direct code.

[AllowAnonymous]
[ActionName("forgot-password")]
public ActionResult ForgotPassword()
{
    return View("forgotpassword");
}

[AllowAnonymous]
[HttpPost]
[ActionName("forgot-password")]
public ActionResult ForgotPassword(ResetPasswordViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View("forgotpassword", model);
    }

    // other code here

    return View("forgotpassword", model);
}

You can see here both the HttpGet and HttpPost action methods have been decorated with the ActionName attribute. When calling this ForgotPassword action the url will be

http://www.example.com/account/forgot-password

You can no longer call the action without a hyphen. If calling the action from within code, again you will need to use the new redefined action name

// in a controller
RedirectToAction("forgot-password");

// from a link
@Html.ActionLink("Forgot Password","forgotten-password","account")

You will also notice in the top example that the action method signature still contains a method name of ForgotPassword which is the same as the view name. Normally due to the conventions built into MVC you would not need to specify the view name

public ActionResult ForgotPassword()
{
    return View();
}

as the action name has been redefined, when using View() method to display the view the engine will be looking for a view called forgot-password. That is fine of you view is called forgot-password but if not you will need to pass the name of the view as a parameter to the View() method.

[ActionName("forgot-password")]
AtionResult ForgotPassword()
{
    return View("ForgotPassword");
}

Another JQuery Autocomplete Example With ASP.NET Webforms

This is a quick run through of how to implement a jQuery autocomplete with webforms. The lookup data consists of about 600 items and changes very rarely so I want to load the data only once when the page loads rather than sending a query to the database as the user types in the input box.

On the Webform is a input box which will have autocomplete feature.

<div>
    <asp:Label ID="Label5" runat="server" Text="Depot Code:" AssociatedControlID="txtDepotCode" />
    <asp:TextBox ID="txtDepotCode" runat="server" ClientIDMode="Static" TabIndex="5" />
    <asp:TextBox ID="txtDepotName" runat="server" ClientIDMode="Static" TabIndex="6" />
</div>

In this javascript code a webservice is being called to retrieve the items via an ajax call. When the data is returned it is set as the source data for the autocomplete input box.

$(function () {
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "creditqueryservice.asmx/GetDepots",
        dataType: "json"
    }).done(function (response) {
        var data = $.map(response.d, function (item) {
            return {
                value: item.DepotCode,
                label: item.DepotCode + ' - ' + item.DepotName
            };
        });

        $("#txtDepotCode").autocomplete({
            source: data,
            minLength: 2,
            select: function(event, ui) { 
                // do something here.  ui parameter holds the values of the item selected
                $("#" + this.id).val(ui.item.value);
                $("#" + this.id + "name").val(ui.item.DepotName);
                return false;
            }
        });
    });
});

The depots are retrieved via a repository (_branchesRepo) variable in the class. Retrieve the data suitable to your application.

[WebMethod]
public List<Depot> GetDepots()
{
    var depots = new List<Depot>();
     if (depots == null || depots.Count == 0)
    {
        depots = _branchesRepo.GetBranches();
    }
    return depots;
}

To validate what the user has entered the autocomplete data can retrieved as in the code below.

var source = $("#txtDepotCode").autocomplete("option", "source");

Now you can compare the value entered with the values in the list. You would really only do this if the user typed into the input box without selecting an item from the matched list.

Validate at least one option is selected in radio button list on Webform

On a webform there is a radiobutton list. The user must select at least one of the options for the form to be valid.

<div>
	<asp:Label ID="Label37" runat="server" Text="Is Sales Item:" AssociatedControlID="rblSaleItem" />
	<asp:RadioButtonList ID="rblSaleItem" runat="server" ClientIDMode="Static" TabIndex="36"
		RepeatDirection="Horizontal" RepeatLayout="Flow" CssClass="checkbox-list">
		<asp:ListItem Text="Yes" Value="1"></asp:ListItem>
		<asp:ListItem Text="No" Value="0"></asp:ListItem>
	</asp:RadioButtonList>
	<asp:CustomValidator ID="IsSaleItemCustomValidator" runat="server" Text="Is Sales Item: selection is required"
		Display="Dynamic" ClientIDMode="Static" ClientValidationFunction="IsSalesItemValidator"
		OnServerValidate="IsSalesItemValidation" ValidationGroup="FORMGROUP" CssClass="field-validation-error"></asp:CustomValidator>
</div>

Here we have the javascript validation. The radiobutton list and validator are in the same div element, so from the validation control triggered we go up to the parent to find the radiobutton list. Each radio button in the list is tested to see if it is selected.

// Javascript client side validation
function IsSalesItemValidator(sender, args) {
    args.IsValid = false;

    $("#" + sender.id).parent().find(".checkbox-list").find(":radio").each(function () {
        if ($(this).is(":checked")) {
            args.IsValid = true;
            return;
        }
    });
}

On the server side the validation works in a similar way.

// C# Server side validation
protected void IsSalesItemValidation(object source, ServerValidateEventArgs args)
{
    // has a sale item check box been selected
    foreach (ListItem li in rblSaleItem.Items)
    {
        if (li.Selected)
        {
            args.IsValid = true;
            break;
        }
    }
}

I actually do not like the idea of iterating through the radiobutton list to find if one is checked. Normally the radiobutton lists are quite short on a page so it is probably not a big overhead, but it is too old school lazy coding for my liking. In .Net3.5 the Enumerable class was introduced in the System.Linq namespace. Unsing the Enumerable.Cast method we can construct linq queries to retrieve the selected item from the radiobutton list instead of iterating all the items.

protected void IsSalesItemValidation(object source, ServerValidateEventArgs args)
{
    var selectedItem = (from li in rblSaleItem.Items.Cast<ListItem>()
                        where li.Selected == true
                        select li).FirstOrDefault();
    /*
    // Alternatively if you prefer inline linq
    var selectedItem = rblSaleItem.Items.Cast<ListItem>()
                        .Where(item => item.Selected == true).FirstOrDefault();
    */
    
    args.IsValid = SelectedItem != null && SelectedItem.Selected;
}

Using the FirstOrDefault() method will return null if there are not selected items in the radiobutton list. If we were to use the First() method an InvalidOperationException would be thrown if there are no elements that meet the query conditions.
It is safe to use the Cast method in this situation because it is guaranteed all the items in the collection are of the ListItem type. If the Cast method was used on a collection containing different objects an InvalidCastException will be thrown. If the collection object itself is null an ArgumentNullException will be thrown.