Custom Error View and Elmah

I added Elmah you my MVC 4 web portal that I had been working on. I had Custom Error controller and view configured to handle the general public error messages, which worked perfectly.

I noticed that on a live deployment that I received an occasional exception

System.InvalidOperationExceptionThe view 'Error' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/Account/Error.cshtml ~/Views/Account/Error.vbhtml ~/Views/Shared/Error.cshtml ~/Views/Shared/Error.vbhtml ~/Views/Account/Error.aspx ~/Views/Account/Error.ascx ~/Views/Shared/Error.aspx ~/Views/Shared/Error.ascx

The problem occurs because Elmah adds the HandleErrorAttribute in the Global.asax.cs file

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }

This filter is executed when an un-handled exception is thrown. It sets the view as Error. The MVC runtime tries to render that view. But in this case, there is no such view. So it again throws another exception which is handled by ASP.NET runtime.

There are two solutions to this:

1) Add an Error.cshtml view in the Shared folder to display the exception thrown by the runtime.

2) Add/edit the key in web.comnfig appSettings

    <add key="elmah.mvc.disableHandleErrorFilter" value="true" />
This allows the exception to propagate to the configured Error controller using the existing routing rules.

The required anti-forgery cookie “__RequestVerificationToken” is not present

I have had a problem with the AntiforgeryToken exception on an MVC4 website.

Everything was correctly configured i.e.

view code:

using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    //some code
}

 

Controller code:

[HttpGet]
[ValidateAntiForgeryToken]
public ActionResult Index()
{
    //some code
}

What made things worse was that only random users had this issue.

The problem turned out to be a poorly configured load balanced server, and the web.config setting

<httpCookies requireSSL="true" />

one server had a wild card SSL installed and the second server did not.

The result being that some users were being served by the server with the SSL installed with provided an encrypted cookie containing the anti-forgery token. Other users are served by the other server which was unable to provided the encrypted cookie.

Hence the anti-forgery token exception is thrown.

ASP.NET MVC CurrencyBinder

Recently I have been working on an MVC 4 web portal. I had been bought on-board to help complete a live site. A problem was identified where a customer form would allow the user to enter a currency value, but the value would not persist.

The to test the binding I created a view model something like this

namespace TestWebApp.Models
{
	public class TestViewModel
	{
		[StringLength(30, MinimumLength = 3, ErrorMessage = "Invalid")]
		[Required(ErrorMessage = "Required")]
		public string StringData { get; set; }

		[Range(1, 9999, ErrorMessage = "Invalid value")]
		public int IntData { get; set; }

		[Required(ErrorMessage = "Required")]
		public decimal DecimalData { get; set; }

		[DataType(DataType.Currency)]
		[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:C2}")]
		[Required(ErrorMessage= "Required")]
		public decimal CurrencyData { get; set; }
	}
}

Usually MVC will infer the correct binding provider for each view model property and perform the correct validation for each data member. If, as in this case you are using something like jQuery client side validation, the user is provided with a really nice culture dependent currency formatted value

e.g.

£1,234.56 (en-GB)

$1,234.56 (en-US)

1 234,56 € (fr)

The problem is that the databinding tries to validate a numeric value, and throws an "invalid value" exception, this is beacuse the currency symbol is not recognised. The way to resolve this is to add a "custom" data binder for the currency data type.

#region Using
using System.Globalization;
using System.Web.Mvc;
#endregion
namespace TestWebApp.Helpers
{
    public class CurrencyBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            decimal value;
            var valueProvider = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (string.IsNullOrEmpty(valueProvider?.AttemptedValue))
                return null;
            if (bindingContext.ModelMetadata.DataTypeName == "Currency")
            {
                if (decimal.TryParse(valueProvider.AttemptedValue, NumberStyles.Currency, CultureInfo.CurrentCulture, out value))
                    return value;
            }
            else
            {
                if (decimal.TryParse(valueProvider.AttemptedValue, out value))
                    return value;
            }
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid Currency");
            return null;
        }
    }
}
You will need to tell your application to use your custom binding in the application start-up.
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
            ModelBinders.Binders.Add(typeof(decimal), new CurrencyBinder());
        }

How to work with Razor _layout menu items

While using Razor in an MVC4 application I was working on, I needed to 'enable' the menu item for the current page.

The solution turned out to be quite simple (when I thought about it)

in the _Layout.cshtml add the following script

<script>
        @functions 
        { 
            public string Selected(string PageTitle)
            {
                if (ViewBag.Title == PageTitle)
                    return "current";
                else
                    return "";
            } 
       } 
    </script>
Then call the function from  HTML
<li class="@Selected("Dashboard")">
                            <a href='@Url.Action("Index", "Dashboard")'>
                                <img src="~/img/computer.png" width="25" height="25">Dashboard</a>
</li>
<li class="@Selected("Reports")">
                            <a href='@Url.Action("Index", "Reports")'>
                                <img src="~/img/chart.png" width="25" height="25">Reports</a>
</li>
Finally in each view you will can define the page title
e.g. in Dashboard view
@{
    ViewBag.Title = "Dashboard";
 }